diff --git a/CLAUDE.md b/CLAUDE.md index 7efc238..9d5d154 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -74,7 +74,7 @@ Die `start_briscad.bat` ruft `setenv.bat` auf und startet BricsCAD. | Zusatzmodul/Foerderer/Kurven | `SSG_LIB_Commands` | ILS_BTMT_*, ILS_ETF_*, ILS_GK*_* | | VarioFoerderer | `VarioFoerderer` | FOERDERANLAGE, VARIOFOERDERER_EDIT, Vario_Kette_Merge | | Gefaellestrecke | `Gefaellestrecke` | GEFAELLESTRECKE, GEFAELLESTRECKE_EDIT | -| Omniflo (komplett) | `OmniModulInsert` | OMNI_APB_*, OMNI_W*_*, OMNI_AP*, OMNI_TEF_*, OMNI_TV_*, OMNI_EDIT | +| Omniflo (komplett) | `OmniModulInsert` | OMNI_APB_*, OMNI_W*_*, OMNI_AP*, OMNI_TEF_*, OMNI_TV_*, OMNI_LADESCHLEIFE_*, OMNI_EDIT | | Geruest | `GeruestInsert` | GERUEST (ein Dialog fuer alle Bereiche) | | Connections | `Connection_Edit` + `SSG_LIB_Commands` | CONNECTION_INSERT, CONNECTION_EDIT, ILS_EINGANG_INSERT, ILS_AUSGANG_INSERT | | Export | `export` | EXPORTSIVAS, EXPORTCSV, CALLPYTHON | @@ -98,13 +98,13 @@ Detaillierte Dokumentation aller Funktionen und Befehle: siehe `Lisp/README.md`. | `ssg_lang.lsp` | - | Sprachumschaltung Deutsch/Englisch (ssg-text, ssg-textf, *ssg-lang*) | | `ssg_dbg.lsp` | - | Debug-Logging (dbgopen, dbgmsg, dbgflush, dbgclose) | | `ssg_dialog.lsp` | - | DCL-Dialog-Vorlagen und Hilfsfunktionen | -| `ssg_layer.lsp` | - | Layer-Verwaltung | +| `ssg_layer.lsp` | - | Layer-Verwaltung. `ssg-layer-visible-p` (aus ODER gefroren), `ssg-layer-gefroren-p`/`ssg-ent-layer-gefroren-p` (NUR gefroren, fuer den Export) | | `KreiselInsert.lsp` | KreiselInsert, KreiselConnect, KreiselRedraw, KreiselQuick, KreiselEdit, KreiselParams, KreiselLabelSetup, KreiselLabelPos, KreiselLabelHoehe, ILS_Eckrad | ILS Kreisel und Eckrad komplett in AutoLISP | | `VarioFoerderer.lsp` (Wrapper fuer `vf_core.lsp`/`vf_standard.lsp`/`vf_etage.lsp`/`vf_linienzug.lsp`) | FOERDERANLAGE, VARIOFOERDERER_EDIT, VARIOFOERDERER_NACHRUESTEN, Vario_Kette_Merge, VF_SEKTION_RESTORE, VF_WIZARD_AN, VF_WIZARD_AUS | Vario-Foerderanlagen Generator (Typen Standard/Etage/Linienzug); Standard- UND Etage-Typ: Werteingabe per DCL-Dialog. Der Dialog-Ablauf ist zentral in vf_core (`vf-dialog-ablauf`/`vf-dialog-berechnen-einfuegen`, parametrisiert ueber Typ-Registry `*vf-typ-registry*` + Flag `horizontal-p`); `vfs-standard-dialog-ablauf`/`vfe-etage-dialog-ablauf` sind nur noch duenne Wrapper darauf. Beide nutzen dieselben Dialoge `vfs-dialog-eingabe-basis`/`vfs-dialog-winkel-verteilung` und den gemeinsamen L_GF/L_VF-Solve `vf-winkel-solve` (vf_core; berechne-alle-winkel/berechne-winkel-etage teilen ihn); Doppelklick auf VF_n-Block ruft VARIOFOERDERER_EDIT, das per SSG_VF_EDIT-XDATA-Marker ("standard"/"etage"/"linienzug") in den passenden Neuaufbau-Zweig dispatcht (`vfe-edit-ent` fuer Etage, `vfl-edit-ent` fuer Linienzug); Altbestand-Bloecke ohne Marker werden abgewiesen. Linienzug (Modus 1, `vf-linienzug-modus`) zeichnet jede interaktive Eingabe in einem Journal auf (`vfl-in-point`/`-string`/`-real`/`-int`) und schreibt es als XDATA auf den fertigen `VF_n`-Block; ein Abbruch (ESC) loescht dadurch nichts mehr, sondern wickelt die bis dahin gebaute Teil-Geometrie ebenso zu einem Block (`vfl-modus-abbruch-sichern`) - sie bleibt stehen und ist sofort per Doppelklick weiter editierbar/fortsetzbar. Der komplette Linienzug-Hauptstrang (Menue-Fragen von AS-Element bis Kettenende, inkl. GF-Bogen/Vario-Kurve/VF-Einheit-Fortsetzung) laeuft per Default ueber DCL-Dialoge statt Konsole (`*vfl-wizard-mode*`, Befehle VF_WIZARD_AN/VF_WIZARD_AUS): `vfl-in-string`/`-int`/`-real` zeigen bei aktivem Wizard-Modus generische Dialoge aus `dcl/vf_linienzug_wizard.dcl` (Zahl-Eingabe bzw. Mehrfachauswahl aus `vfl-menu`/`vfl-menu-int`) statt `getstring`/`getint`/`getreal` aufzurufen; die ORIGINALEN Konsolen-Fragen (princ + get*, de/en ueber ssg-text) bleiben unveraendert im Code (Fallback bei Fehlern via `vl-catch-all-apply`, siehe `doc/variofoerderer.md`). `vfl-edit-ent` liest das Journal, zeigt die Sektionen ueber einen DCL-Dialog (`vfl-dlg-position`, `dcl/vfl_edit.dcl`) an, kuerzt bei Bedarf auf eine gewaehlte Sektion (stummer Replay der behaltenen Eingaben, umgeht den Wizard-Zweig komplett) und baut danach interaktiv weiter. Linienzug Modus 2 (Pfad+Zielhoehe-Solver, `vf-linienzug-modus2`) nutzt dieselben Wrapper PLUS die neue Objektauswahl-Wrapper-Funktion `vfl-in-selection` (Pfad-Objekte als stabile Entity-Handles journalisiert, neue Journal-Art `"OBJS"`) und schreibt sein Journal unter dem eigenen XDATA-Marker `"linienzug2"`; Doppelklick baut hier NICHT abschnittsweise, sondern per vollem Reset 1:1 aus dem Journal neu auf (`vfl-edit-ent2`, dispatcht aus `vfl-edit-ent` per Marker) - Modus 3 (Vorwaerts-Nachbau) bleibt bewusst reine Konsole (im Code selbst als Legacy/"wird umgebaut" markiert). `Vario_Kette_Merge` (in vf_linienzug.lsp) fuehrt mehrere einzeln gebaute VF_n-Bloecke ab einem gewaehlten Start-Baustein ueber die reale KS_AUS->KS_EIN-Nachbarschaft zu einem Gesamt-Block zusammen (aggregierte Attribute, Luecken-Erkennung). Die interaktiven Editier-Pfade (`vfl-edit-ent` Sektions-Zweig, `vfl-edit-ent2`) rufen den Neuaufbau BEWUSST direkt auf, NICHT ueber `vl-catch-all-apply`: der alte Block ist beim Neuaufbau schon per `entdel` weg, und ein Catch faengt den Abbruch VOR `*error*` ab - der Abbruch-Wickler `vfl-modus-abbruch-sichern` kaeme nie zum Zug und die ganze Kette bliebe als lose Einzelteile ohne Block liegen (Fehlerbild "VF-Block aufgebrochen"). Nur der nicht-interaktive Batch-Konverter `vfl-konvertiere-ent` behaelt den Catch (die Batch-Schleife muss weiterlaufen) und meldet den betroffenen Block. Die Abbruch-Handler setzen zusaetzlich `*ssg-ils-dim*` zurueck, weil der Aufrufer, der das sonst tut, nach einem Abbruch nicht mehr erreicht wird. Recovery-Pfad fuer bereits aufgebrochene Ketten: `VF_SEKTION_RESTORE` (vf_linienzug.lsp) setzt das Ketten-Journal aus der XDATA der losen Objekte zusammen - `SSG_VF_EDIT_SEG` traegt pro Entity den Journal-Abschnitt DER ITERATION, in der es entstand (indiziert mit dem ersten Glied-Index darin; eine VF-Einheit mit eingebetteten Vario-Kurven erzeugt mehrere Glieder in einer Iteration), `SSG_VF_EDIT_PRE` auf den Entities des ersten Gliedes die Praeambel (Startpunkt/Starthoehe/AS). Die `.dbg`-Dateien sind reine AUSGABE und werden von keinem Befehl gelesen (dbgopen legt sie bei jedem Lauf neu an) - Wiederherstellungs-Daten gehoeren in die XDATA. `vf_konstanten.lsp` (von vf_core.lsp vor den drei Submodulen geladen) sammelt alle Laengen-/Winkel-/Toleranz-Magic-Numbers aus dem VarioFoerderer-Cluster als dokumentierte `*vfk-*`-Konstanten. Die Migration ist abgeschlossen: vf_core/vf_standard/vf_etage/vf_linienzug referenzieren durchgaengig die zentralen Konstanten statt roher Literale (Winkel, Stations-/Separator-Laengen, Mindestlaengen, AS/ES-Fallback-Versaetze, Text-/Label-Masse) - Ausnahmen sind in `vf_konstanten.lsp` selbst dokumentiert (Werte mit fuehrender Config-Anbindung via `ssg-cfg-or`, itoa-/Integer-Kontexte, Blocknamen-Strings, die 2000.0-mm/m-Umrechnung). `*vfk-gf-bogen-winkel*` (30/60/90, GF-Bogen + Vario-Kurve) und `*vfk-as-es-winkel*` ("30"/"90", AS/ES; bewusst STRING, da 2-wertig und im Journal als STR gefuehrt) sind Beispiele; vf_linienzug hat fuer beide einen Fallback, falls vf_konstanten nicht geladen wurde. **Nicht-interaktiver Betrieb**: `*vfl-headless*` (+ `vfl-headless-p`/`-abbruch`/`-notausgang`, Diagnose `*vfl-headless-fehler*`, optionaler Antwort-Hook `*vfl-headless-antwort-fn*`) verbietet jede Live-Eingabe - eine erschoepfte Replay-Queue ist damit ein harter Abbruch MIT Fundstelle (Art der Eingabe, Glied- und Eingabe-Nummer) statt eines stillen Rueckfalls auf `getpoint`/`getreal`; eingebaut in `vfl-in-value`/`-value-p`/`-selection`/`-abstand`. Die Bau-Pfade melden abgewiesene Sektionen ueber `vfl-meldung` (sammelt nach `*vfl-meldungen*` + `dbgmsg`, `alert` nur bei erlaubter GUI, sonst `princ`) statt per blockierendem `alert`; reine Interaktiv-Alerts (fehlendes DCL, "nicht editierbar", "kein Journal") bleiben `alert`. Genutzt von `tests/test_hm_recformat.lsp` (Replay der echten Aufzeichnungen) und von `vf_spec.lsp`; Fahrplan in `doc/TODO-plan-vf-interactive.md`. Details/Entscheidungsbaum: `doc/variofoerderer.md` | | `vf_spec.lsp` (von vf_core.lsp NACH vf_linienzug.lsp geladen) | VF_SPEC_BAU | Linienzug-Ketten aus **Eingabedaten** bauen, ohne Dialog und ohne Konsolenfrage (Stufe 1 aus `doc/TODO-plan-vf-interactive.md`). Eine **Spec** beschreibt eine Kette in Domaenenwerten (`"links"`, `"winkel"`, `"aussen"`, ja/nein) statt in Menue-Codes; `vsp-journal` uebersetzt sie in ein Eingabe-Journal, `vsp-bau-aus-spec` spielt es per `vfl-journal-replay-start` durch den UNVERAENDERTEN `vf-linienzug-modus`. Damit traegt der fertige Block dasselbe `SSG_VF_EDIT`-Journal wie eine handgebaute Kette und bleibt per Doppelklick editier- und 2D/3D-konvertierbar - kein Journal-Konsument muss etwas von Specs wissen. Wichtige Regeln, die der Uebersetzer kennt: der Menue-Code haengt am Frame (erste Sektion 4 Optionen ohne GF-Bogen, danach 5), `hz` steht nur beim ERSTEN Segment im Journal, und `erst-hor` unterscheidet den horizontalen Erstkoerper einer VF-Einheit (Separator-/Endpunktfragen vorab) vom gewinkelten (keine Frage). Fehlerverhalten: fail fast - `vsp-pruefen` sammelt ALLE Spec-Fehler, gebaut wird dann nichts (nichts wird geraten). Ergebnis-Record je Kette mit `status` (`executed`/`warnung`/`desync`/`abbruch`/`spec-fehler`), Block/Handle/Einfuegepunkt/Attributen, `prompts` (muss 0 sein), `glieder_soll` vs. `glieder_ist` (Desync-Detektor, der auch bei aufgehender Queue greift) und den gesammelten `meldungen`. Headless-Rahmen `vsp-headless-an`/`-aus` (Save/Restore von `*vfl-headless*`, `*vfl-wizard-mode*`, `*ssg-gui-aus*` + zaehlende Eingabe-Stubs). Spec-Datei flach als JSON (`ssg-load-json` liest zeilenweise), erzeugt von `lib/vf_spec_export.py`. Gegenstueck/Referenz in Python: derselbe Emitter, gegen die 5 echten HundM-Ketten rundlaufgeprueft | | `Gefaellestrecke.lsp` | GEFAELLESTRECKE, GEFAELLESTRECKE_EDIT | Gefaelle-Foerderanlage (AUS -> Staustrecke skaliert -> Separator -> EIN); Modus 1 per DCL-Dialog (Einfuegehoehe, Element-Winkel 30/90); Doppelklick auf GF_n-Block ruft GEFAELLESTRECKE_EDIT (Dialog vorbelegt, Neuaufbau) | -| `export.lsp` | EXPORTSIVAS, EXPORTCSV, OMNI_UPDATE_ATTRIBS | JSON-Sammlung und Python-Export (Omniflo-Merkmale/Sum-Zeile via lib/export_csv.py) | -| `OmniModulInsert.lsp` | OMNI_LOAD, OMNI_APB_*, OMNI_W*_*, OMNI_AP60/AP110, OMNI_TEF_*, OMNI_TV_*, OMNI_APBW_*, OMNI_EDIT | Omniflo-Komponenten: Boegen, Weichen, Verbinder, Transferwagen, Edit-Dialog | +| `export.lsp` | EXPORTSIVAS, EXPORTCSV, OMNI_UPDATE_ATTRIBS, CSV_EXPORT_KONFIG | JSON-Sammlung und Python-Export (Omniflo-Merkmale/Sum-Zeile via lib/export_csv.py). **Gefrorene Layer**: `csv:collect-export-blocks` laesst Bloecke auf gefrorenen Layern aus (ausgeschaltete Layer zaehlen NICHT), Schalter `*csv-gefrorene-ausschliessen*` (Standard an, Toggle im Dialog CSV_EXPORT_KONFIG). Die Hilfsobjekte eines gefrorenen VF_n/GF_n (Separator-/Element-Kopien, Einheiten-/AS-ES-Marker) liegen auf eigenen Layern und werden darum ueber `*csv-ausschluss-enames*` mit ausgelassen (gesetzt in `csv:export-vorbereiten` per `csv:hilfskopien-ausschluss`); erzeugt und nummeriert werden sie trotzdem, damit IDs stabil bleiben. DrawPlanquadrate nutzt dieselbe Sammlung und folgt dem Filter | +| `OmniModulInsert.lsp` | OMNI_LOAD, OMNI_APB_*, OMNI_W*_*, OMNI_AP60/AP110, OMNI_TEF_*, OMNI_TV_*, OMNI_APBW_*, OMNI_LADESCHLEIFE_AP110, OMNI_EDIT | Omniflo-Komponenten: Boegen, Weichen, Verbinder, Transferwagen, Ladeschleifen (`data/json/omniflo_ladeschleifen.json`, per `ssg-json-laden`, da die Beschreibungen Kommas enthalten; Blockdatei darf `.dwg` statt `.dxf` sein), Edit-Dialog | | `SSG_LIB_Commands.lsp` | ILS_BTMT_Beladung, ILS_BTMT_Entladung, ILS_SCANNER_INSERT, ILS_SEPARATOR_INSERT, ILS_EINGANG_INSERT, ILS_AUSGANG_INSERT, ILS_ETF_*, ILS_GK*_*, SSG_BLOCKEDIT, SSG_DIM_SWITCH, SSG_SPRACHE | BTMT Be-/Entladung (`ils-insert-station`: wiederholtes Einfuegen von `BTMT-Beladung` bzw. `SC_Entladung`, Drehung interaktiv, Layer aus `LAYER`-Attribut, Dimension via `ssg-ils-block-laden`) + Sensoren (`ils-insert-sensor`) + Eingang-/Ausgang-Symbol (frei platzierbar wie BTMT Be-/Entladung, ueber `ils-insert-station` mit den Rohnamen `Eingang`/`Ausgang`, Bloecke `Eingang_.dwg`/`Ausgang_.dwg` in data/ils) + Dummy-Befehle fuer Menue + Doppelklick/Kontextmenue-Dispatcher + Konfiguration (2D/3D, Sprache) | | `Tro_Edit.lsp` | TRO_INSERT, TRO_EDIT | TRO-Marker (Block `TRO_SYM_`) fuer sps_skel (lib/tro_annotate.py): TRO_INSERT fuegt eine bestehende Blockdefinition an einem gewaehlten Punkt ein und setzt ID/TYPE (bricht ab, wenn der Typ noch keine Blockdefinition in der Zeichnung hat - die entsteht nur ueber tro_annotate.py); TRO_EDIT aendert ID/TYPE eines bestehenden Markers per Dialog. Typliste aus `tro_types.lsp` (von `tro_annotate.py --emit-lisp` nach DXFM_LISP geschrieben). Der Dispatcher SSG_BLOCKEDIT leitet Blocknamen `TRO_*` zu TRO_EDIT. | | `GeruestInsert.lsp` | GERUEST | Geruest-Baugruppen aus `data/json/geruest.json` konfigurieren und einfuegen. **Ein Dialog fuer alle Bereiche**: oben ein Dropdown `bereich` (Rohrabhaenger / Saeulen / Wandbefestigung, Reihenfolge = Dateireihenfolge, Anzeigename aus dem ASCII-Feld `titel`). DCL hat **kein Reiter-Tile**, und da sich die Teilezeilen je Bereich unterscheiden, schliesst ein Bereichswechsel den Dialog (`done_dialog 4`) und die Schleife in `c:GERUEST` baut ihn neu auf; der Stand jedes Bereichs bleibt in `*geruest-zustand*` erhalten, ein versehentlicher Wechsel verwirft also nichts. 1"- und 2"-Saeulen liegen in EINER Baugruppe `Saeulen` mit gemeinsamer Anbauteil-Liste; **`teileart` und `dummy` duerfen je Geometrie-Variante ueberschrieben werden** (`geruest-variante-wert`, Variante gewinnt vor Baugruppe) - so liefert `R 1"` weiterhin TEILEART `Geruest Saeule 1 Zoll` und die `R 2"`-Varianten `Geruest Saeule 2 Zoll`, die Groessen bleiben im Export also unterscheidbar. **Die Dialogdefinition wird zur Laufzeit erzeugt** (`geruest-dcl-schreiben` -> temporaere Datei per `vl-filename-mktemp`, nach `unload_dialog` wieder geloescht) - es gibt KEINE `dcl/geruest.dcl`. Grund: je Anbauteil wird ein eigenes Mengenfeld gebraucht, und eine DCL-Liste (`list_box`) kann keine Eingabefelder enthalten; DCL kann weder Dateien einbinden noch Tiles zur Laufzeit erzeugen. Inhalt: Basis-Variante aus `geometrie_varianten` (Dropdown), Laenge (gegen `attribute.Laenge` min/max geprueft, Retry-Schleife), Farbe (`attribute.Teilefarbe`, Freitext bei `frei_option`) und **zwei Befestigungslisten** ("oben"/"unten"), die BEIDE alle `anbaugruppen` enthalten - dasselbe Teil kann oben, unten oder gleichzeitig an beiden Positionen sitzen, je mit eigener Stueckzahl (vorbelegt aus `menge`). Baugruppen ohne `attribute`-Block (Wandbefestigung) bekommen Laenge/Farbe gesperrt, Baugruppen ohne Anbauteile bekommen gar keine Spalten (ein leerer DCL-Cluster ist unzulaessig). Im generierten Label wird `"` zu `''` ersetzt (`geruest-dcl-label`) - fuenf Artikelnamen enthalten ein Anfuehrungszeichen, das das DCL-Label sonst vorzeitig beendet und die Datei zerstoert. **Konfigurationen**: gepflegte Bibliothek je Baugruppe unter `konfigurationen` in `geruest.json` (schreibgeschuetzt) plus eigene in `cfg/geruest_user.json` (gitignored, `geruest-user-schreiben`; getrennte Datei, damit Benutzerdaten und versionierte Bibliothek nicht kollidieren). Eigene Eintraege sind in der Liste mit `*` markiert und nur diese loeschbar. Konfigurationen verweisen ueber den ANZEIGENAMEN auf Variante und Anbauteile; ein unbekannter Name wird gemeldet und uebersprungen, statt die Konfiguration scheitern zu lassen. Speichern/Loeschen laufen ueber `done_dialog 2`/`3` (Dialog schliessen, Aktion, neu zeigen) - `getstring` funktioniert aus einem `action_tile`-Rueckruf heraus nicht. Der Block wird - wie KREISEL_n/VF_n - SELBST zusammengebaut: Dummy-DWG aus `data/geruest` liefert nur Geometrie, das Attribut-Schema kommt aus `*geruest-attrib-defs*` (ID, ARTINR, LAENGE, BEZEICHNUNG, TEILEART, TEILEFARBE, LAYER) und wird per `ssg-attrib-merge` mit den Dummy-Tags zusammengefuehrt - sonst verschluckt `ssg-attrib-set-on` stillschweigend jeden Tag, den die Dummy-DWG nicht als ATTDEF kennt. Blockname `GR__` (Kollisionszaehler wie in OmniModulInsert). **Das `GR_`-Praefix ist nicht kosmetisch**: `SSG_BLOCKEDIT` haelt JEDEN Block mit TEILEART-Attribut fuer ein Omniflo-Element, deshalb steht in `SSG_LIB_Commands.lsp` ein `GR_*`-Zweig VOR dem Omniflo-Zweig. ARTINR listet jede Sivas-ID so oft, wie die gewaehlte Stueckzahl es vorgibt - erst Basis, dann Befestigung oben, dann unten; bei einer benannten Gruppe (`teile`) wird das ganze Set n-mal wiederholt, die Mengen INNERHALB des Sets bleiben dabei erhalten (`geruest-artikelnummern-n`). BEZEICHNUNG nennt Position und Menge je Teil ("oben 2x ...") und verkettet mit `" + "`, NICHT mit Komma - die Artikelbezeichnungen enthalten selbst Kommas ("ROHR R 1\" 33,7 x 2,6"). Zuordnung Menue -> JSON laeuft ueber das ASCII-Feld `id`, weil die aeusseren JSON-Schluessel Umlaute bzw. Anfuehrungszeichen tragen. CSV-/Sivas-Export: `pattern_geruest = GR_*` (cfg/export.cfg), `csv:collect-export-blocks`/`ssg-id-collect-blocks` sammeln GR_-Bloecke wie BTMT eigenstaendig; `*geruest-attrib-defs*` traegt zusaetzlich ein leeres `ID`-ATTDEF (Voraussetzung fuer `ssg-id-check-all`, das nur bestehende ATTRIBs beschreiben kann). ARTINR/BEZEICHNUNG/TEILEART/LAENGE landen in export_csv.py/export_sivas.py in festen Spalten, TEILEFARBE/LAYER (+ ARTINR bei export_csv.py) im Merkmale-/details-JSON. Noch NICHT enthalten: Edit-Pfad (GERUEST_EDIT) | @@ -120,6 +120,7 @@ Detaillierte Dokumentation aller Funktionen und Befehle: siehe `Lisp/README.md`. | `variofoerderer.dcl` | FOERDERANLAGE Standardfall + Werteingabe (vf_standard.lsp, `vfs-dialog-eingabe-basis`/`vfs-dialog-winkel-verteilung`) | | `omniflo_boegen.dcl` | OMNI_APB_* (OmniModulInsert.lsp) | | `omniflo_weichen.dcl` | OMNI_W*_* (OmniModulInsert.lsp) | +| `omniflo_ladeschleifen.dcl` | OMNI_LADESCHLEIFE_* (OmniModulInsert.lsp, `omni:ladeschleifen-dialog`) | | `sprache.dcl` | SSG_SPRACHE (SSG_LIB_Commands.lsp) | | `tro_insert.dcl` | TRO_INSERT (Tro_Edit.lsp) | | `tro_edit.dcl` | TRO_EDIT (Tro_Edit.lsp) | @@ -182,7 +183,7 @@ 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` | -| `export_sivas.py` | Erzeugt Sivas-Export-CSV mit Summierungszeilen (ILS Automation, Omniflo Sum). Aufruf: `python export_sivas.py ` | +| `export_sivas.py` | Erzeugt Sivas-Export-CSV mit Summierungszeilen (ILS Automation, Omniflo Sum). Omniflo-Elemente werden ueber das TEILEART-Attribut (`Omniflo *`) erkannt, ihre Sivas-Nummern kommen - wie beim Geruest - NUR aus dem kommagetrennten `ARTINR`-Attribut (eine Zeile je Nummer, TEF-Teile `0_B*` als `Omniflo TEF`); KEIN Katalog-Lookup mehr (den nutzt nur noch `export_csv.py` fuer die Merkmale). Aufruf: `python export_sivas.py ` | | `export_csv.py` | Erzeugt einfache Item-Liste als CSV (ohne Summierung). Aufruf: `python export_csv.py ` | | `split_block_library.py` | Teilt `ils_library.dxf` in einzelne DXF-Dateien pro Block auf (mit Abhaengigkeiten). Export-Prefixe: Vario_, Staustrecke_, _3D_AS_, _3D_ES_ | | `vf_journal_export.py` | Liest die Eingabe-Journale der `VF_n`-Bloecke (XDATA-App `SSG_VF_EDIT`, Marker `"linienzug"`) aus einer Zeichnung und schreibt sie als Frage-Antwort-Protokoll im Schema von `tests/testdata/linienzug_tests.json` (Gegenstueck zu `vfl-entry->string`; kein ezdxf, reine Gruppencode-Lesung). Optional `--csv ` fuer Strecken-ID und Erwartungswerte je Kette. Aufruf: `python vf_journal_export.py [--csv ...] [--kein-startpunkt-fix]` — erzeugt `tests/testdata/hm_recformat.json` aus `data/polylines.dxf` (die Aufzeichnung, aus der `tests/testdata/hundm05.json` als Spec abgeleitet wird) | diff --git a/Lisp/OmniModulInsert.lsp b/Lisp/OmniModulInsert.lsp index be18217..7b43477 100644 --- a/Lisp/OmniModulInsert.lsp +++ b/Lisp/OmniModulInsert.lsp @@ -964,6 +964,11 @@ ) (progn (setq dxf-pfad (strcat omniflo-pfad "/" sivasnr-str ".dxf")) + ;; Fallback: manche Elemente (z.B. Ladeschleifen) liegen nur als DWG vor + (if (and (not (findfile dxf-pfad)) + (findfile (strcat omniflo-pfad "/" sivasnr-str ".dwg"))) + (setq dxf-pfad (strcat omniflo-pfad "/" sivasnr-str ".dwg")) + ) (if (not (findfile dxf-pfad)) (progn (princ (ssg-textf "omni-fehler-dxf-nicht-gefunden" (list dxf-pfad))) @@ -1230,7 +1235,7 @@ typ eintrag winkel weichentyp dlg-result new-eintrag new-hoehe new-drehung basePoint sivasnr-str dxf-pfad data-pfad angleDeg blockEnt - layer-name) + layer-name such) ;; Implied Selection pruefen (setq ss (ssget "I")) @@ -1307,6 +1312,20 @@ ) ) + ;; Zuletzt in Ladeschleifen suchen (ARTINR, dann Blockname) + (if (null eintrag) + (progn + (if (null *OMNI-LADESCHLEIFEN*) (omni:load-ladeschleifen)) + (foreach such (list artinr bname) + (if (and such (null eintrag)) + (setq eintrag (omni:get-by-sivasid *OMNI-LADESCHLEIFEN* + (if (/= (atoi such) 0) (atoi such) such))) + ) + ) + (if eintrag (setq typ "ladeschleife")) + ) + ) + (if (null eintrag) (progn (ssg-emsg (ssg-text "omni-edit-kein-element")) @@ -1317,12 +1336,15 @@ ;; 4. Dialog oeffnen mit vorhandenen Werten (setq dlg-result nil) - (if (= typ "bogen") - (progn + (cond + ((= typ "bogen") (setq winkel (cdr (assoc "KurvenWinkel" eintrag))) (setq dlg-result (omni:bogen-dialog winkel hoehe drehung nil)) ) - (progn + ((= typ "ladeschleife") + (setq dlg-result (omni:ladeschleifen-dialog nil hoehe)) + ) + (T (setq winkel (cdr (assoc "KurvenWinkel" eintrag))) (setq weichentyp (cdr (assoc "WeichenTyp" eintrag))) (setq dlg-result (omni:weichen-dialog winkel weichentyp hoehe drehung nil)) @@ -1343,8 +1365,12 @@ (setq new-drehung (rtos rotation 2 1)) (setq sivasnr-str (omni:sivasid-to-str (omni:val new-eintrag "Sivasnr"))) - ;; 6. DXF-Datei pruefen + ;; 6. DXF-Datei pruefen (Fallback DWG wie in omni:insert-dxf) (setq dxf-pfad (strcat (getenv "DXFM_OMNIFLO") "/" sivasnr-str ".dxf")) + (if (and (not (findfile dxf-pfad)) + (findfile (strcat (getenv "DXFM_OMNIFLO") "/" sivasnr-str ".dwg"))) + (setq dxf-pfad (strcat (getenv "DXFM_OMNIFLO") "/" sivasnr-str ".dwg")) + ) (if (not (findfile dxf-pfad)) (progn (ssg-emsg (ssg-textf "omni-edit-dxf-nicht-gefunden" (list dxf-pfad))) @@ -1446,6 +1472,119 @@ (princ) ) + +;; --- Omniflo / Ladeschleifen --- +;; Datenbasis: data/json/omniflo_ladeschleifen.json. Die Beschreibungen +;; enthalten Kommas, daher der rekursive Parser ssg-json-laden statt des +;; zeilenbasierten ssg-load-json (siehe ssg_core.lsp). + +;; Ladeschleifen-Daten laden (in *OMNI-LADESCHLEIFEN*) +(defun omni:load-ladeschleifen ( / data-pfad datei) + (setq data-pfad (getenv "DXFM_DATA")) + (if (null data-pfad) + (progn (princ (ssg-text "omni-fehler-dxfm-data")) nil) + (progn + (setq datei (strcat data-pfad "/json/omniflo_ladeschleifen.json")) + (setq *OMNI-LADESCHLEIFEN* (ssg-json-laden datei)) + (if *OMNI-LADESCHLEIFEN* + (princ (strcat "\n[OMNI] " (itoa (length *OMNI-LADESCHLEIFEN*)) " Ladeschleifen geladen.")) + (princ (strcat "\n[OMNI] WARNUNG: Keine Ladeschleifen geladen aus " datei)) + ) + *OMNI-LADESCHLEIFEN* + ) + ) +) + + +;; Zahl als Ganzzahl-String fuer die Dialoganzeige ("" wenn kein Zahlwert) +(defun omni:zahl-str (v) + (if (numberp v) (itoa (fix v)) "") +) + + +;; Detail-Felder im Ladeschleifen-Dialog aktualisieren (action_tile callback) +(defun omni:ladeschleife-dlg-update (idx-str / eintrag) + (setq eintrag (nth (atoi idx-str) *OMNI-DLG-LADESCHLEIFEN*)) + (if eintrag + (progn + (set_tile "val_sivasnr" (omni:sivasid-to-str (omni:val eintrag "Sivasnr"))) + (set_tile "val_profiltyp" (omni:sivasid-to-str (omni:val eintrag "ProfilTyp"))) + (set_tile "val_laenge" (omni:zahl-str (omni:val eintrag "Laenge"))) + ) + ) +) + + +;; Ladeschleifen-Auswahl-Dialog oeffnen +;; profiltyp = Praefix zum Filtern des ProfilTyp, z.B. "AP 110" (nil = alle) +;; init-hoehe = Vorbelegung Hoehe (nil = Default aus cfg) +;; Rueckgabe: (eintrag hoehe nil) wie omni:bogen-dialog, oder nil bei Abbruch +(defun omni:ladeschleifen-dialog (profiltyp init-hoehe / liste dat ergebnis dlg-hoehe profil) + (if (null *OMNI-LADESCHLEIFEN*) (omni:load-ladeschleifen)) + (if (null init-hoehe) (setq init-hoehe (ssg-cfg-or "omniflo" "default_hoehe" "2000"))) + + (setq liste *OMNI-LADESCHLEIFEN*) + (if profiltyp + (setq liste + (vl-remove-if-not + '(lambda (e) + (setq profil (cdr (assoc "ProfilTyp" e))) + (and (= (type profil) 'STR) (= (vl-string-search profiltyp profil) 0)) + ) + liste + ) + ) + ) + + (if (null liste) + (progn + (alert (ssg-textf "omni-ladeschleife-keine-gefunden" + (list (if profiltyp (strcat " (" profiltyp ")") "")))) + nil + ) + (progn + (setq *OMNI-DLG-LADESCHLEIFEN* liste) + (setq dat (load_dialog (strcat (getenv "DXFM_DCL") "/omniflo_ladeschleifen.dcl"))) + (if (not (new_dialog "omniflo_ladeschleifen" dat)) + (progn (alert (ssg-text "omni-ladeschleife-dialog-ladefehler")) nil) + (progn + (start_list "ladeschleife") + (mapcar '(lambda (e) (add_list (omni:sivasid-to-str (omni:val e "Beschreibung")))) liste) + (end_list) + (set_tile "ladeschleife" "0") + (set_tile "val_hoehe" init-hoehe) + (omni:ladeschleife-dlg-update "0") + + (action_tile "ladeschleife" "(omni:ladeschleife-dlg-update $value)") + (action_tile "accept" + (strcat + "(setq ergebnis (atoi (get_tile \"ladeschleife\")))" + "(setq dlg-hoehe (get_tile \"val_hoehe\"))" + "(done_dialog 1)")) + (action_tile "cancel" "(done_dialog 0)") + + (start_dialog) + (unload_dialog dat) + + (if ergebnis + (list (nth ergebnis *OMNI-DLG-LADESCHLEIFEN*) dlg-hoehe nil) + ) + ) + ) + ) + ) +) + + +(defun c:OMNI_LADESCHLEIFE_AP110 ( / dlg-result) + (setq dlg-result (omni:ladeschleifen-dialog "AP 110" nil)) + (if dlg-result + (omni:insert-from-entry dlg-result) + (princ (ssg-text "omni-abbruch-ladeschleife")) + ) + (princ) +) + ;; --- Omniflo / Verbinder --- (defun c:OMNI_TV_W_W () (princ "\n[DUMMY] TV W-W kpl") diff --git a/Lisp/README.md b/Lisp/README.md index ed1672d..0db3524 100644 --- a/Lisp/README.md +++ b/Lisp/README.md @@ -572,6 +572,7 @@ AutoLISP-Implementierung fuer alle Omniflo-Komponenten: Boegen, Weichen, Verbind | Boegen | `OMNI_APB_630_90`, `OMNI_APB_550_675`, `OMNI_APB_630_45`, `OMNI_APB_550_225`, `OMNI_APB_650_180` | Omniflo-Boegen ueber DCL-Dialog einfuegen | | Weichen | `OMNI_W90_*`, `OMNI_W45_*`, `OMNI_WP_*`, `OMNI_WK_*`, `OMNI_WKomb_*` | Omniflo-Weichen (Einfach/Doppel/Dreiwege, Delta, Stern) | | Geraden | `OMNI_AP60`, `OMNI_AP110`, `OMNI_APG110` | Omniflo-Geradenstuecke | +| Ladeschleifen | `OMNI_LADESCHLEIFE_AP110` | Ladeschleife aus `omniflo_ladeschleifen.json` ueber DCL-Dialog (`dcl/omniflo_ladeschleifen.dcl`) einfuegen; Daten per `ssg-json-laden` (Beschreibungen enthalten Kommas). Blockdatei `.dwg` unter `DXFM_OMNIFLO` (`omni:insert-dxf` faellt auf `.dwg` zurueck, wenn keine `.dxf` existiert). Per `OMNI_EDIT` editierbar | | TEF ⚠️ | `OMNI_TEF_UmlenkR/L`, `OMNI_TEF_AntriebL/R`, `OMNI_TEF_Gerade`, `OMNI_KettenFoerderer` | **Dummy** (noch nicht implementiert, im Menue ausgegraut) | | Transferwagen ⚠️ | `OMNI_TV_*` | **Dummy** (noch nicht implementiert, im Menue ausgegraut) | | Bogenweichen ⚠️ | `OMNI_APBW_*` | **Dummy** (noch nicht implementiert, im Menue ausgegraut) | diff --git a/Lisp/export.lsp b/Lisp/export.lsp index f5ac8bd..d2cd335 100644 --- a/Lisp/export.lsp +++ b/Lisp/export.lsp @@ -104,6 +104,32 @@ (if (car (atoms-family 1 '("DBG-SCHALTER-SET"))) (dbg-schalter-set "csv_leave_proxies" (if an 1 0)))) +;; --- Bloecke auf gefrorenen Layern vom Export ausschliessen --- +;; Standard an (T). Nur GEFRORENE Layer zaehlen (ssg-ent-layer-gefroren-p, +;; ssg_layer.lsp) - ausgeschaltete Layer werden weiter exportiert. Bedienbar +;; im Dialog CSV_EXPORT_KONFIG. Wirkt in csv:collect-export-blocks auf den +;; Block selbst UND - ueber *csv-ausschluss-enames* - auf die Hilfsobjekte +;; (Separator-/Element-Kopien, Einheiten-/AS-ES-Marker) eines gefrorenen +;; VF_n/GF_n-Wrappers, die auf eigenen Layern liegen. +(if (not (boundp '*csv-gefrorene-ausschliessen*)) (setq *csv-gefrorene-ausschliessen* T)) +(defun csv:gefrorene-ausschliessen-p () (if *csv-gefrorene-ausschliessen* T nil)) +(defun csv:gefrorene-ausschliessen-setzen (an) + (setq *csv-gefrorene-ausschliessen* (if an T nil))) + +;; Hilfsobjekte (car eines Records), deren Wrapper (nth 1) gefroren-fn +;; erfuellt. records = Record-Listen der Form (hilfsobjekt wrapper ...), wie +;; sie csv:sep-proxies-erzeugen, csv:element-proxies-erzeugen, +;; csv:asmark-aktualisieren und csv:ketten-verarbeiten liefern. +;; gefroren-fn = Praedikat auf den Wrapper. Reine Listenlogik, file-frei +;; testbar (tests/test_unit.lsp). +(defun csv:hilfskopien-ausschluss (records gefroren-fn / result) + (foreach rec records + (if (and (car rec) (nth 1 rec) (apply gefroren-fn (list (nth 1 rec)))) + (setq result (cons (car rec) result)))) + (reverse result) +) +(setq *csv-ausschluss-enames* nil) + ;; --- Feste Datei-/Skriptnamen dieses Moduls (keine Config, direkt im Code) --- (setq *export-filenames* @@ -2463,8 +2489,13 @@ ;; GeruestInsert.lsp). Wie BTMT eigenstaendig und von keinem anderen ;; Element mitgezaehlt - export_csv.py UND export_sivas.py fuehren sie ;; beide als eigene TeileArt-Zeilen. +;; Bloecke auf GEFRORENEN Layern (und die Hilfsobjekte gefrorener VF_n/GF_n) +;; werden ausgelassen, solange *csv-gefrorene-ausschliessen* an ist (Standard, +;; Dialog CSV_EXPORT_KONFIG); ausgeschaltete Layer zaehlen nicht als gefroren. ;; Gibt Auswahlsatz zurueck oder nil. -(defun csv:collect-export-blocks ( / ss-all ss-out i ename ed bname ist-nested-element) +(defun csv:collect-export-blocks ( / ss-all ss-out i ename ed bname ist-nested-element + gefroren n-gefroren) + (setq n-gefroren 0) (setq ss-all (ssget "X" (list (cons 0 "INSERT")))) (if (null ss-all) (setq ss-out nil) (progn @@ -2486,7 +2517,16 @@ (setq ist-nested-element (and (not (csv:export-alle-elemente-p)) (wcmatch bname (csv:pattern-nested-element)))) + ;; Gefrorener Layer (Schalter *csv-gefrorene-ausschliessen*): der + ;; Block selbst liegt darauf, oder er ist Hilfsobjekt eines + ;; gefrorenen VF_n/GF_n (*csv-ausschluss-enames*, csv:export-vorbereiten). + (setq gefroren + (and (csv:gefrorene-ausschliessen-p) + (or (ssg-ent-layer-gefroren-p ename) + (member ename *csv-ausschluss-enames*)))) + (if gefroren (setq n-gefroren (1+ n-gefroren))) (if (and (not ist-nested-element) + (not gefroren) (or (wcmatch bname (export:pattern "pattern_kreisel" "KR_*,KREISEL_*,ECKRAD_*")) (wcmatch (strcase bname) (export:pattern "pattern_gerade" "AP110*,AP_110*")) @@ -2514,6 +2554,8 @@ (if (= (sslength ss-out) 0) (setq ss-out nil)) ) ) + (if (> n-gefroren 0) + (princ (ssg-textf "exp-gefroren-ausgelassen" (list (itoa n-gefroren))))) ss-out ) @@ -2555,6 +2597,7 @@ ;; Kopien - Separatoren UND AS/ES-Schleuselemente (der Aufrufer entfernt sie am ;; Ende wieder). label dient nur den Meldungen. (defun csv:export-vorbereiten (label / sep-proxies asmarks element-proxies unitmarks) + (setq *csv-ausschluss-enames* nil) ;; Baustein-Geometrie der Stellvertreter (Separator-Kopie, AS-/ES-Marker) ;; wird beim Erzeugen neu gemerkt - Stand eines frueheren Laufs verwerfen. (setq *csv-baustein-by-handle* nil) @@ -2580,6 +2623,15 @@ ;; alle neuen Export-Objekte jeder Kette von AS nach ES aufsteigend ;; nummerieren - VOR ssg-id-check-all, siehe csv:ketten-verarbeiten. (setq unitmarks (csv:ketten-verarbeiten sep-proxies element-proxies asmarks)) + ;; Hilfsobjekte gefrorener Wrapper merken: sie liegen auf eigenen Layern, + ;; der Wrapper-Layer allein haelt sie daher nicht aus dem Export heraus + ;; (Auswertung in csv:collect-export-blocks). Erzeugt werden sie trotzdem, + ;; damit IDs und AS/ES-Marker ueber Laeufe stabil bleiben. + (setq *csv-ausschluss-enames* + (if (csv:gefrorene-ausschliessen-p) + (csv:hilfskopien-ausschluss + (append sep-proxies asmarks element-proxies unitmarks) + 'ssg-ent-layer-gefroren-p))) ;; Vor dem Export: IDs aller Bloecke sicherstellen und Duplikate korrigieren. ;; Erst hier - also erst nachdem ALLE Kopien aus allen Wrapper-Bloecken ;; stehen - wird ueberhaupt eine ID vergeben. ssg-id-check-all ermittelt @@ -2786,6 +2838,7 @@ (csv:sep-proxies-loeschen sep-proxies) (csv:hilfskopien-loeschen element-proxies "exp-art-element") (csv:hilfskopien-loeschen unitmarks "exp-art-einheit") + (setq *csv-ausschluss-enames* nil) (princ) ) @@ -2831,6 +2884,7 @@ (if eintrag (setq idx eintrag)) (set_tile "modus" (itoa idx)) (set_tile "hilfskopien_behalten" (if (csv:hilfskopien-behalten-p) "1" "0")) + (set_tile "gefrorene_ausschliessen" (if (csv:gefrorene-ausschliessen-p) "1" "0")) ) (defun csv:export-konfig-accept ( / idx modus) @@ -2838,6 +2892,7 @@ (setq modus (car (nth idx *csv-export-konfig-modi*))) (if modus (csv:export-modus-setzen modus)) (csv:hilfskopien-behalten-setzen (= (get_tile "hilfskopien_behalten") "1")) + (csv:gefrorene-ausschliessen-setzen (= (get_tile "gefrorene_ausschliessen") "1")) ) (defun c:CSV_EXPORT_KONFIG () diff --git a/Lisp/ssg_layer.lsp b/Lisp/ssg_layer.lsp index 968d354..dde4c9a 100644 --- a/Lisp/ssg_layer.lsp +++ b/Lisp/ssg_layer.lsp @@ -56,6 +56,24 @@ ) ) +;; Layer gefroren? (nur Frieren, NICHT Ausschalten - anders als +;; ssg-layer-visible-p). Rueckgabe: T wenn der Layer existiert und gefroren +;; ist (Bit 0 in Gruppe 70), sonst nil. Genutzt vom Export +;; (csv:collect-export-blocks, export.lsp). +(defun ssg-layer-gefroren-p (lay-name / entry) + (setq entry (if lay-name (tblsearch "LAYER" lay-name))) + (if entry + (= (logand (cdr (assoc 70 entry)) 1) 1) + ) +) + +;; Liegt die Entity ename auf einem gefrorenen Layer (Gruppe 8)? +(defun ssg-ent-layer-gefroren-p (ename) + (if ename + (ssg-layer-gefroren-p (cdr (assoc 8 (entget ename)))) + ) +) + ;; Pruefen ob Objekt-Layer der aktive Layer ist (Schutz vor Ausschalten) ;; lay-name = zu pruefender Layer; ist-gruppe = T wenn Gruppen-Layer ;; Rueckgabe: T wenn sicher (nicht aktiver Layer), nil wenn Problem diff --git a/README.md b/README.md index ab3bca9..9f9bdfc 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,9 @@ SSG_LIB │ │ ├── Antriebst. links → OMNI_TEF_AntriebL │ │ ├── Antriebst. rechts → OMNI_TEF_AntriebR │ │ └── TEF Gerade → OMNI_TEF_Gerade -│ └── KettenFoerderer → OMNI_KettenFoerderer +│ ├── KettenFoerderer → OMNI_KettenFoerderer +│ └── Ladeschleifen +│ └── AP 110 → OMNI_LADESCHLEIFE_AP110 ├── Konfiguration │ ├── Umschalten 2D und 3D → SSG_DIM_SWITCH │ └── Sprache → SSG_SPRACHE @@ -100,6 +102,7 @@ Einfuegen von Omniflo-Standardkomponenten (Boegen, Geraden, Weichen, TEF-Element | `OMNI_WKomb_Delta_1400/1600, OMNI_WKomb_Star` | Weichenkombinationen einfuegen | | `OMNI_TEF_*` | TEF-Elemente einfuegen (Umlenk-/Antriebsstationen, Gerade) | | `OMNI_KettenFoerderer` | Kettenfoerderer einfuegen | +| `OMNI_LADESCHLEIFE_AP110` | Ladeschleife (AP 110) ueber DCL-Dialog einfuegen | ### VarioFoerderer.lsp - Foerderanlage Generator (v24.4) diff --git a/data/omniflo/2D/824331125.dwg b/data/omniflo/2D/824331125.dwg new file mode 100644 index 0000000..ac56f1d Binary files /dev/null and b/data/omniflo/2D/824331125.dwg differ diff --git a/data/omniflo/2D/824604001.dwg b/data/omniflo/2D/824604001.dwg new file mode 100644 index 0000000..5b691cb Binary files /dev/null and b/data/omniflo/2D/824604001.dwg differ diff --git a/dcl/csv_export_konfig.dcl b/dcl/csv_export_konfig.dcl index 5281a55..9fc4fab 100644 --- a/dcl/csv_export_konfig.dcl +++ b/dcl/csv_export_konfig.dcl @@ -2,7 +2,8 @@ // csv_export_konfig.dcl - CSV-Export Konfiguration (c:CSV_EXPORT_KONFIG, // export.lsp). Steuert *csv-export-modus* (Combobox mit drei sich // gegenseitig ausschliessenden Eintraegen) sowie den Debug-Schalter -// csv_leave_proxies (Hilfskopien nach dem Export stehen lassen). +// csv_leave_proxies (Hilfskopien nach dem Export stehen lassen) und +// *csv-gefrorene-ausschliessen* (gefrorene Layer nicht exportieren). // // popup_list statt radio_column: set_tile/get_tile auf einer // boxed_radio_column liefern/erwarten den KEY des gewaehlten radio_button @@ -23,6 +24,11 @@ csv_export_konfig : dialog { width = 55; } + : toggle { + key = "gefrorene_ausschliessen"; + label = "Bloecke auf gefrorenen Layern nicht exportieren"; + } + spacer; : boxed_column { diff --git a/dcl/omniflo_ladeschleifen.dcl b/dcl/omniflo_ladeschleifen.dcl new file mode 100644 index 0000000..d0129ad --- /dev/null +++ b/dcl/omniflo_ladeschleifen.dcl @@ -0,0 +1,47 @@ +// ============================================================ +// omniflo_ladeschleifen.dcl - Ladeschleifen-Auswahl Dialog fuer Omniflo +// Zeigt die Ladeschleifen aus omniflo_ladeschleifen.json mit Details an. +// ============================================================ + +omniflo_ladeschleifen : dialog { + label = "Omniflo Ladeschleife Auswahl"; + + : row { + : popup_list { + key = "ladeschleife"; + label = "Ladeschleife:"; + width = 50; + } + : edit_box { + key = "val_sivasnr"; + label = "SivasNr:"; + width = 22; + is_enabled = false; + } + } + + : row { + : edit_box { + key = "val_profiltyp"; + label = "Profiltyp:"; + width = 16; + is_enabled = false; + } + : edit_box { + key = "val_laenge"; + label = "Laenge:"; + width = 16; + is_enabled = false; + } + } + + : row { + : edit_box { + key = "val_hoehe"; + label = "Hoehe (mm):"; + edit_width = 10; + } + } + + ok_cancel; +} diff --git a/lang/de_DE.json b/lang/de_DE.json index b9457cc..585a0d9 100644 --- a/lang/de_DE.json +++ b/lang/de_DE.json @@ -478,6 +478,9 @@ "kreisel-warn-core-fehlt": "\n[KreiselInsert] WARNUNG: ssg_core.lsp nicht gefunden - Standardwerte.", "omni-abbruch": "\nAbgebrochen.", "omni-abbruch-bogen": "\n[BOGEN] Abgebrochen.", + "omni-abbruch-ladeschleife": "\n[LADESCHLEIFE] Abgebrochen.", + "omni-ladeschleife-dialog-ladefehler": "Ladeschleifen-Dialog konnte nicht geladen werden.", + "omni-ladeschleife-keine-gefunden": "Keine Ladeschleifen%1 gefunden.", "omni-abbruch-omni": "\n[OMNI] Abgebrochen.", "omni-abbruch-weiche": "\n[WEICHE] Abgebrochen.", "omni-bogen-dialog-ladefehler": "Bogen-Dialog konnte nicht geladen werden.", @@ -717,6 +720,7 @@ "exp-unitmark-ids": "\n[EXPORT] %1 Einheiten-Proxy(s) mit ID versehen.", "exp-kette-luecke": "\n[EXPORT] WARNUNG: Kette '%1' reisst ab (kein K1 auf dem letzten K2) - %2 Baustein(e) als eigene Teilkette weitergefuehrt, Einheitengrenzen dort pruefen.", "exp-hilfskopien-behalten": "\n[EXPORT] csv_leave_proxies=1: %1 %2 bleiben in der Zeichnung (Abschalten: (dbg-schalter-off \"csv_leave_proxies\"))", + "exp-gefroren-ausgelassen": "\n[EXPORT] %1 Bloecke auf gefrorenen Layern nicht exportiert (abschaltbar in CSV_EXPORT_KONFIG).", "exp-art-separator": "Separator-Kopie(n)", "exp-art-element": "Element-Kopie(n)", "exp-art-einheit": "Einheiten-Proxy(s)", diff --git a/lang/en_GB.json b/lang/en_GB.json index 7877a4f..4eebc3a 100644 --- a/lang/en_GB.json +++ b/lang/en_GB.json @@ -478,6 +478,9 @@ "kreisel-warn-core-fehlt": "\n[KreiselInsert] WARNING: ssg_core.lsp not found - using defaults.", "omni-abbruch": "\nCancelled.", "omni-abbruch-bogen": "\n[ARC] Cancelled.", + "omni-abbruch-ladeschleife": "\n[LOADING LOOP] Cancelled.", + "omni-ladeschleife-dialog-ladefehler": "Loading loop dialog could not be loaded.", + "omni-ladeschleife-keine-gefunden": "No loading loops%1 found.", "omni-abbruch-omni": "\n[OMNI] Cancelled.", "omni-abbruch-weiche": "\n[SWITCH] Cancelled.", "omni-bogen-dialog-ladefehler": "Arc dialog could not be loaded.", @@ -717,6 +720,7 @@ "exp-unitmark-ids": "\n[EXPORT] %1 unit proxy(s) given an ID.", "exp-kette-luecke": "\n[EXPORT] WARNING: chain '%1' is broken (no K1 at the last K2) - %2 element(s) continued as a separate sub-chain, check the unit boundaries there.", "exp-hilfskopien-behalten": "\n[EXPORT] csv_leave_proxies=1: %1 %2 remain in the drawing (switch off: (dbg-schalter-off \"csv_leave_proxies\"))", + "exp-gefroren-ausgelassen": "\n[EXPORT] %1 blocks on frozen layers not exported (can be switched off in CSV_EXPORT_KONFIG).", "exp-art-separator": "separator copy(ies)", "exp-art-element": "element copy(ies)", "exp-art-einheit": "unit proxy(ies)", diff --git a/lib/export_sivas.py b/lib/export_sivas.py index 22ad293..6e425bb 100644 --- a/lib/export_sivas.py +++ b/lib/export_sivas.py @@ -14,7 +14,8 @@ Neues CSV-Format: - IDs: kommaseparierte Liste der ID-Attribute aller Bloecke der Zeile (eigene Spalte, nicht mehr Teil des details-JSON). - - Omniflo Bogen/Weiche: gruppiert nach SivasNummer, Anzahl summiert + - Omniflo (TEILEART "Omniflo ..."): je Nummer aus dem ARTINR-Attribut + (kommagetrennt) eine Zeile, Anzahl summiert; TEF-Teile als "Omniflo TEF" - Omniflo Gerade: gruppiert nach ARTINR-Attribut, Laenge[mm] summiert - ILS 2.0 Kreisel: gruppiert nach Kreiselart+Abstand+Anzahl Separatoren/ Scanner/Rampen, Anzahl summiert @@ -34,7 +35,8 @@ Aufruf: Argumente: export_raw.json - JSON-Datei mit allen INSERT-Entities aus der Zeichnung - data_dir - Pfad zum data/-Verzeichnis (mit json/ Unterordner) + data_dir - Pfad zum data/-Verzeichnis (derzeit ungenutzt, bleibt + fuer den Aufrufer export.lsp erhalten) output.csv - Ziel-CSV-Datei (reine Merkmalnummern, fuer den Sivas-Import) @@ -71,47 +73,11 @@ def load_json(path): return json.load(f) -def build_lookup(boegen, weichen): - """Erzeugt ein Dict: SivasNr (als 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 - - -def classify_weiche(eintrag): - """Bestimmt den Weichen-Subtyp fuer die ILS-Automation-Zaehlung.""" - wt = eintrag.get("WeichenTyp", "") - profil = eintrag.get("ProfilTyp", "") - if "WEICHENKOERPER" in profil.upper(): - return "weichenkoerper" - if wt == "Einzelweiche": - return "einzelweiche" - if wt in ("Doppelweiche", "Dreiwegeweiche"): - return "doppelweiche" - if wt in ("Deltaweiche", "Dreifachweiche") or "DELTA" in profil.upper(): - return "deltaweiche" - if wt == "Sternweiche" or "STAR" in profil.upper(): - return "sternweiche" - return "einzelweiche" - - def get_id(block): """Liest das eindeutige ID-Attribut eines Blocks (von ssg-id-check-all vergeben).""" return block.get("attribs", {}).get("ID", "") -def format_sivasnr(sivasnr): - """ - Wandelt einen "+"-separierten Sivasnr-Wert (Rohformat aus den JSON-Katalogen, - dort weiterhin benoetigt fuer Dateinamen-Referenzen) in eine reine - kommaseparierte Liste fuer die Ausgabe (CSV/Block-Attribut) um. - """ - return ", ".join(p.strip() for p in str(sivasnr).split("+") if p.strip()) - - # --- Sivas-Merkmalnummern: Anzeigetexte, die nicht 1:1 aus dem Attribut # uebernommen werden koennen (Attribute bleiben ASCII, siehe CLAUDE.md- # Konvention "Alle Texte in Dateien ohne Umlaute" - Sivas erwartet aber die @@ -218,42 +184,6 @@ def gefaellestrecke_signature(block): ) -def expand_tef_components(sivasnr_parts, sivasnr_tef): - """Ersetzt "0_BG*"-Platzhalter-Teile in sivasnr_parts durch die dahinter - liegenden echten TEF-Sivasnummern (Katalogfeld SivasnrTEF). - - "0_BG*"-Werte im Sivasnr-Rohwert (z.B. "834372404+0_BG071090") sind KEINE - echten Sivasnummern, sondern reine Blocknamen-Platzhalter fuer eine TEF- - Baugruppe aus mehreren realen Teilen - "0_BG071090" steht z.B. fuer die - drei echten Teile "0_B10071"+"0_B10090"+"0_B10090" (siehe SivasnrTEF im - Katalog, omniflo_weichen.json/omniflo_boegen.json). Ohne diese Aufloesung - wuerde "0_BG071090" selbst als (nicht existente) Sivasnummer gezaehlt und - die echten Teile (u.a. 0_B10071) fehlten in der Summierung. - Reale TEF-Einzelteile, die bereits direkt im Sivasnr-Rohwert stehen - (Praefix "0_B", aber nicht "0_BG", z.B. "821104026+0_B10071"), sind schon - echte Sivasnummern und werden unveraendert uebernommen. - Bei mehreren "0_BG*"-Platzhaltern im selben Rohwert (TEF beidseitig) wird - SivasnrTEF zu gleichen Teilen aufgeteilt - der Katalog listet die Teile - je Seite hintereinander in SivasnrTEF. - """ - bg_count = sum(1 for p in sivasnr_parts if p.startswith("0_BG")) - if bg_count == 0: - return list(sivasnr_parts) - - tef_parts = [p.strip() for p in str(sivasnr_tef or "").split("+") if p.strip()] - chunk_size = len(tef_parts) // bg_count if bg_count else 0 - - expanded = [] - tef_index = 0 - for part in sivasnr_parts: - if part.startswith("0_BG"): - expanded.extend(tef_parts[tef_index:tef_index + chunk_size]) - tef_index += chunk_size - else: - expanded.append(part) - return expanded - - def parse_float_list(csv_str): """Kommagetrennten Attribut-String (z.B. L_GF_m) in Liste von floats.""" out = [] @@ -443,17 +373,21 @@ ILS_SIVASNR = { } -def process_blocks(blocks, lookup): +def process_blocks(blocks): """ Verarbeitet alle Bloecke. + Omniflo-Elemente werden ueber das TEILEART-Attribut ("Omniflo ...") + erkannt, ihre Sivas-Nummern kommen - wie beim Geruest - ausschliesslich + aus dem ARTINR-Attribut (kommagetrennt, TEF-Teile dort bereits + aufgeloest). Ein Katalog-Lookup ueber den Blocknamen findet nicht statt. + Rueckgabe: items - Liste der CSV-Zeilen als Dicts counters - Zaehler fuer ILS-Automation-Summierungszeile """ - # Gruppen: sivasnr_str -> {anzahl, ids} - bogen_groups = {} # sivasnr -> dict - weiche_groups = {} # sivasnr -> dict + # Omniflo: artinr -> {teileart, anzahl, ids} + omniflo_groups = {} # Gerade: artinr_str -> {anzahl, laenge_mm_total, ids} gerade_groups = {} kreisel_list = [] # Liste von blocks @@ -467,14 +401,6 @@ def process_blocks(blocks, lookup): geruest_list = [] # Liste von GR_-Geruest-Baugruppenbloecken, je einer Zeile counters = { - "anzahl_boegen": 0, - "anzahl_weichenkoerper": 0, - "anzahl_einzelweichen": 0, - "anzahl_deltaweichen": 0, - "anzahl_doppel_dreiwegeweichen": 0, - "anzahl_sternweichen": 0, - "gesamtlaenge_ap110": 0.0, - "gesamtlaenge_ap60": 0.0, "anzahl_kreisel": 0, "anzahl_kreisel_pinband": 0, "sep_kreisel_pin": 0, @@ -544,76 +470,9 @@ def process_blocks(blocks, lookup): counters["anzahl_geruest"] += 1 continue - # --- Omniflo Bogen oder Weiche (erkannt via Katalog) --- - if bname in lookup: - typ, eintrag = lookup[bname] - sivasnr_raw = eintrag.get("Sivasnr", bname) - sivasnr_parts = [p.strip() for p in str(sivasnr_raw).split("+")] - has_tef = any(p.startswith("0_B") for p in sivasnr_parts) - block_id = get_id(block) - - if typ == "bogen": - if has_tef and len(sivasnr_parts) > 1: - # Multi-Komponenten: jede Komponente separat gruppieren - # ("0_BG*"-Platzhalter werden ueber SivasnrTEF in ihre - # echten Einzelteile aufgeloest, siehe expand_tef_components) - expanded_parts = expand_tef_components(sivasnr_parts, eintrag.get("SivasnrTEF")) - for i, comp_sivasnr in enumerate(expanded_parts): - comp_id = f"{block_id}.t{i+1}" - if comp_sivasnr not in bogen_groups: - bogen_groups[comp_sivasnr] = {"anzahl": 0, "ids": []} - bogen_groups[comp_sivasnr]["anzahl"] += 1 - bogen_groups[comp_sivasnr]["ids"].append(comp_id) - else: - # Normal: alle als eine Komponente - sivasnr = format_sivasnr(sivasnr_raw) - if sivasnr not in bogen_groups: - bogen_groups[sivasnr] = {"anzahl": 0, "ids": []} - bogen_groups[sivasnr]["anzahl"] += 1 - bogen_groups[sivasnr]["ids"].append(block_id) - counters["anzahl_boegen"] += 1 - - elif typ == "weiche": - subtyp = classify_weiche(eintrag) - if has_tef and len(sivasnr_parts) > 1: - # Multi-Komponenten: jede Komponente separat gruppieren - # ("0_BG*"-Platzhalter werden ueber SivasnrTEF in ihre - # echten Einzelteile aufgeloest, siehe expand_tef_components) - expanded_parts = expand_tef_components(sivasnr_parts, eintrag.get("SivasnrTEF")) - for i, comp_sivasnr in enumerate(expanded_parts): - comp_id = f"{block_id}.t{i+1}" - if comp_sivasnr.startswith("0_B"): - # TEF-Komponente (Bogen) in bogen_groups - if comp_sivasnr not in bogen_groups: - bogen_groups[comp_sivasnr] = {"anzahl": 0, "ids": []} - bogen_groups[comp_sivasnr]["anzahl"] += 1 - bogen_groups[comp_sivasnr]["ids"].append(comp_id) - else: - # Weichen-Komponente in weiche_groups - if comp_sivasnr not in weiche_groups: - weiche_groups[comp_sivasnr] = {"anzahl": 0, "ids": []} - weiche_groups[comp_sivasnr]["anzahl"] += 1 - weiche_groups[comp_sivasnr]["ids"].append(comp_id) - else: - # Normal: alle als eine Komponente - sivasnr = format_sivasnr(sivasnr_raw) - if sivasnr not in weiche_groups: - weiche_groups[sivasnr] = {"anzahl": 0, "ids": []} - weiche_groups[sivasnr]["anzahl"] += 1 - weiche_groups[sivasnr]["ids"].append(block_id) - if subtyp == "einzelweiche": - counters["anzahl_einzelweichen"] += 1 - elif subtyp in ("doppelweiche", "dreiwegeweiche"): - counters["anzahl_doppel_dreiwegeweichen"] += 1 - elif subtyp == "deltaweiche": - counters["anzahl_deltaweichen"] += 1 - elif subtyp == "sternweiche": - counters["anzahl_sternweichen"] += 1 - elif subtyp == "weichenkoerper": - counters["anzahl_weichenkoerper"] += 1 - continue - # --- Omniflo Gerade (AP110) --- + # VOR dem allgemeinen Omniflo-Zweig: Geraden tragen ggf. ebenfalls + # TEILEART "Omniflo ...", brauchen aber die Laengensumme. if matches_any(bname, BLOCKPATTERNS.get("pattern_gerade", [])): attribs = block.get("attribs", {}) artinr = attribs.get("ARTINR", "") @@ -628,7 +487,33 @@ def process_blocks(blocks, lookup): gerade_groups[key]["anzahl"] += 1 gerade_groups[key]["laenge_mm"] += laenge gerade_groups[key]["ids"].append(get_id(block)) - counters["gesamtlaenge_ap110"] += laenge + continue + + # --- Omniflo-Element (Bogen, Weiche, Ladeschleife, TEF, ...) --- + # Erkennung ueber TEILEART, Sivas-Nummern aus ARTINR - wie beim + # Geruest eine Zeile je Nummer, hier aber ueber ALLE Bloecke + # gruppiert. Die DWGs fuehren TEF-Teile bereits aufgeloest im ARTINR + # (z.B. "834372002, 0_B10090, 0_B10090"). TEF-Teile ("0_B*") laufen + # unter "Omniflo TEF", damit dasselbe Teil aus Bogen und Weiche EINE + # Zeile bleibt. + attribs = block.get("attribs", {}) + teileart = attribs.get("TEILEART", "") + if teileart.startswith("Omniflo"): + artinr_list = [a.strip() for a in attribs.get("ARTINR", "").split(",") if a.strip()] + block_id = get_id(block) + if not artinr_list: + print(f"[export_sivas] WARNUNG: Omniflo-Block {bname} (ID {block_id}) " + f"ohne ARTINR - nicht exportiert.", file=sys.stderr) + continue + for i, artinr in enumerate(artinr_list): + comp_id = f"{block_id}.t{i+1}" if len(artinr_list) > 1 else block_id + if artinr not in omniflo_groups: + omniflo_groups[artinr] = { + "teileart": "Omniflo TEF" if artinr.startswith("0_B") else teileart, + "anzahl": 0, "ids": [], + } + omniflo_groups[artinr]["anzahl"] += 1 + omniflo_groups[artinr]["ids"].append(comp_id) continue # --- ILS Eckrad (vor Kreisel pruefen: pattern_kreisel schliesst @@ -695,27 +580,10 @@ def process_blocks(blocks, lookup): items = [] nr = 1 - for sivasnr, g in bogen_groups.items(): - teileart = "TEF Bogen" if str(sivasnr).startswith("0_B") else "Omniflo Bogen" + for sivasnr, g in omniflo_groups.items(): items.append({ "nr": nr, - "teileart": teileart, - "sivasnr": sivasnr, - "sivasnr_quoted": False, - "bezeichnung": "", - "dispgruppe": 10, - "anzahl": g["anzahl"], - "laenge_mm": "", - "ids": g["ids"], - "details": {}, - }) - nr += 1 - - for sivasnr, g in weiche_groups.items(): - teileart = "TEF Weiche" if str(sivasnr).startswith("0_B") else "Omniflo Weiche" - items.append({ - "nr": nr, - "teileart": teileart, + "teileart": g["teileart"], "sivasnr": sivasnr, "sivasnr_quoted": False, "bezeichnung": "", @@ -842,7 +710,7 @@ def process_blocks(blocks, lookup): # ILS_SIVASNR - JEDE Artikelnummer bekommt ihre EIGENE Sivas-Zeile (Sivas # kennt nur eine Sivasnr je Zeile), gleiche Artikelnummern INNERHALB # derselben Baugruppe werden dabei zu einer Zeile mit anzahl>1 - # zusammengefasst (wie bogen_groups/weiche_groups oben). Die volle + # zusammengefasst (wie omniflo_groups oben). Die volle # LAENGE der Baugruppe steht auf JEDER so entstandenen Zeile - Sivas hat # kein Konzept einer "gemeinsamen" Laenge ueber mehrere Zeilen hinweg. for block in geruest_list: @@ -1026,22 +894,15 @@ def main(): sys.exit(1) raw_json_path = sys.argv[1] - data_dir = sys.argv[2] + # sys.argv[2] (data_dir) wird derzeit nicht gebraucht - Omniflo-Nummern + # kommen aus ARTINR, nicht mehr aus dem Katalog. Argument bleibt fuer + # den Aufrufer (export.lsp) bestehen. output_csv = sys.argv[3] blocks = load_json(raw_json_path) - boegen_path = os.path.join(data_dir, "json", "omniflo_boegen.json") - weichen_path = os.path.join(data_dir, "json", "omniflo_weichen.json") + print(f"[export_sivas] {len(blocks)} Bloecke geladen.") - boegen = load_json(boegen_path) if os.path.exists(boegen_path) else [] - weichen = load_json(weichen_path) if os.path.exists(weichen_path) else [] - - lookup = build_lookup(boegen, weichen) - - print(f"[export_sivas] {len(blocks)} Bloecke geladen, " - f"{len(boegen)} Boegen, {len(weichen)} Weichen im Katalog.") - - items, _ = process_blocks(blocks, lookup) + items, _ = process_blocks(blocks) header = "Nr;TeileArt;SivasNummer;Bezeichnung;Dispogruppe;Anzahl;Laenge[mm];IDs;details" with open(output_csv, "w", encoding="utf-8") as f: diff --git a/menu/SSG_LIB.cui b/menu/SSG_LIB.cui index e2db2fd..03285bb 100644 --- a/menu/SSG_LIB.cui +++ b/menu/SSG_LIB.cui @@ -332,415 +332,421 @@ ^C^C(ssg-ensure "OmniModulInsert") OMNI_KettenFoerderer - + + + Ladeschleifen + ^C^C(ssg-ensure "OmniModulInsert") OMNI_LADESCHLEIFE_AP110 + + + TV W-W kpl ^C^C(ssg-ensure "OmniModulInsert") OMNI_TV_W_W - + TV W-AP110 kpl ^C^C(ssg-ensure "OmniModulInsert") OMNI_TV_W_AP110 - + TV W-AP60 oben kpl. ^C^C(ssg-ensure "OmniModulInsert") OMNI_TV_W_AP60 - + TV W-AP110-W kpl ^C^C(ssg-ensure "OmniModulInsert") OMNI_TV_W_AP110_W - + TV W-AP oben kpl ^C^C(ssg-ensure "OmniModulInsert") OMNI_TV_W_AP - + TV 80 ^C^C(ssg-ensure "OmniModulInsert") OMNI_TV_80 - + TV 50 ^C^C(ssg-ensure "OmniModulInsert") OMNI_TV_50 - + Endplatte Al-Profil AP schraubbar ^C^C(ssg-ensure "OmniModulInsert") OMNI_TV_ENDPLATTE - + Anschlag AP-Ende fuer Spulenzug ^C^C(ssg-ensure "OmniModulInsert") OMNI_TV_ANSCHLAG - + TV-A fuer AP110/AP60 kpl ^C^C(ssg-ensure "OmniModulInsert") OMNI_TV_A_AP110_AP60 - + TV-A fuer AP60/ST ^C^C(ssg-ensure "OmniModulInsert") OMNI_TV_A_AP60_ST - + TV-A fuer AP110/ST kpl ^C^C(ssg-ensure "OmniModulInsert") OMNI_TV_A_AP110_ST - + TV-SW II fuer SW-SW ^C^C(ssg-ensure "OmniModulInsert") OMNI_TV_SW_II_SW_SW - + TV-SW III fuer SW-AP110-SW ^C^C(ssg-ensure "OmniModulInsert") OMNI_TV_SW_III_AP110_SW - + TV-SW III fuer SW-AP110 ^C^C(ssg-ensure "OmniModulInsert") OMNI_TV_SW_III_AP110 - + TV-SW IV fuer Anschluss SW-AP60 ^C^C(ssg-ensure "OmniModulInsert") OMNI_TV_SW_IV_AP60 - + TV-SW IV fuer SW-ST ^C^C(ssg-ensure "OmniModulInsert") OMNI_TV_SW_IV_ST - + Muffe ^C^C(ssg-ensure "OmniModulInsert") OMNI_TV_MUFFE - + APB 110 R400/45 140 nur fuer Delta ^C^C(ssg-ensure "OmniModulInsert") OMNI_APBW_R400_45_140 - + APB 110 R550/45 194 nur fuer Delta ^C^C(ssg-ensure "OmniModulInsert") OMNI_APBW_R550_45_194 - + APBW 110 R550/22,5 360x248,5 ^C^C(ssg-ensure "OmniModulInsert") OMNI_APBW_R550_225_360 - + APBW 110 R550/22,5 410x298,5 ^C^C(ssg-ensure "OmniModulInsert") OMNI_APBW_R550_225_410A - + APBW 110 R550/22,5 410x78,5 ^C^C(ssg-ensure "OmniModulInsert") OMNI_APBW_R550_225_410B - + APBW 110 R550/22,5 410x98,5 ^C^C(ssg-ensure "OmniModulInsert") OMNI_APBW_R550_225_410C - + APBW 110 R550/67,5 360x598,5 ^C^C(ssg-ensure "OmniModulInsert") OMNI_APBW_R550_675_360 - + APBW 110 R400/67,5 260x398,5 ^C^C(ssg-ensure "OmniModulInsert") OMNI_APBW_R400_675_260 - + APBW 110 R550/45 77x160 ^C^C(ssg-ensure "OmniModulInsert") OMNI_APBW_R550_45_77 - + APBW 110 R550/67,5 125x83,5 ^C^C(ssg-ensure "OmniModulInsert") OMNI_APBW_R550_675_125 - + APBW 110 R750/90 200x290 ^C^C(ssg-ensure "OmniModulInsert") OMNI_APBW_R750_90_200 - + APBW 110 R650/90 300x190 ^C^C(ssg-ensure "OmniModulInsert") OMNI_APBW_R650_90_300 - + APBW 110 R550/67,5 208x347 ^C^C(ssg-ensure "OmniModulInsert") OMNI_APBW_R550_675_208 - + APBW 110 R550/67,5 58x497 ^C^C(ssg-ensure "OmniModulInsert") OMNI_APBW_R550_675_58 - + Element bearbeiten ^C^C(ssg-ensure "OmniModulInsert") OMNI_EDIT - + Geruest... ^C^C(ssg-ensure "GeruestInsert") GERUEST - + Marker einfuegen ^C^C(ssg-ensure "Tro_Edit") TRO_INSERT - + Marker bearbeiten ^C^C(ssg-ensure "Tro_Edit") TRO_EDIT - + Bearbeiten (Auto) ^C^C(ssg-ensure "SSG_LIB_Commands") SSG_BLOCKEDIT - + Verbindung einfuegen ^C^C(ssg-ensure "Connection_Edit") CONNECTION_INSERT - + Verbindung bearbeiten ^C^C(ssg-ensure "Connection_Edit") CONNECTION_EDIT - + Eingang ^C^C(ssg-ensure "SSG_LIB_Commands") ILS_EINGANG_INSERT - + Ausgang ^C^C(ssg-ensure "SSG_LIB_Commands") ILS_AUSGANG_INSERT - + SPS Schliessen ^C^C - + Export Sivas ^C^C(ssg-ensure "export") EXPORTSIVAS - + Export CSV ^C^C(ssg-ensure "export") EXPORTCSV - + Umschalten 2D und 3D ^C^C(ssg-ensure "SSG_LIB_Commands") SSG_DIM_SWITCH - + Alle Bloecke auf 2D ^C^C(ssg-ensure "SSG_LIB_Commands") SSG_DIM_ALL_2D - + Alle Bloecke auf 3D ^C^C(ssg-ensure "SSG_LIB_Commands") SSG_DIM_ALL_3D - + Sprache ^C^C(ssg-ensure "SSG_LIB_Commands") SSG_SPRACHE - + Planquadrate an/aus ^C^C(ssg-ensure "DrawPlanquadrate") PLANQUADRATE_TOGGLE - + CSV-Export ^C^C(ssg-ensure "export") CSV_EXPORT_KONFIG - + alle Tests ausfuehren und exportieren ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_run_all.lsp")) (c:SSG_RUN_ALL_TESTS_EXPORT) - + Tests ausfuehren und stehen lassen ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_run_all.lsp")) (c:SSG_RUN_ALL_TESTS_OFFEN) - + Export CSV/Sivas ALL ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_export_all.lsp")) (c:TEST_EXPORT_ALL) - + Kreisel ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_kreisel.lsp")) (c:TEST_KREISEL) - + Foerderer ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_foerderer.lsp")) (c:TEST_FOERDERER) - + Foerderer-Linienzug ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_linienzug.lsp")) (c:TEST_LINIENZUG) (c:TEST_LINIENZUG_STUFE2) - + Omniflo ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_omniflo.lsp")) (c:TEST_OMNIFLO) - + Omniflo Strecke ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_omniflo_strecke.lsp")) (c:TEST_OMNIFLO_STRECKE) - + Gefaellestrecke ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_gefaellestrecke.lsp")) (c:TEST_GEFAELLESTRECKE) - + Mubea Gesamtmodell ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_mubea.lsp")) (c:TEST_MUBEA) - + HundM05 Kreisel-Abschnitt ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_hundm05.lsp")) (c:TEST_HUNDM05) - + KS 2D/3D pruefen (CSV) ^C^C(load (strcat (getenv "DXFMAKRO") "/Lisp/ks_pruefung.lsp")) KS_PRUEFEN - + 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 - + TRO-Marker bearbeiten (ssg-ensure "Tro_Edit") TRO_EDIT - + Verbindung bearbeiten (ssg-ensure "Connection_Edit") CONNECTION_EDIT - + SSG Bearbeiten (Auto) (ssg-ensure "SSG_LIB_Commands") SSG_BLOCKEDIT @@ -760,13 +766,13 @@ Geruest... - + - - - - + + + + ILS @@ -922,12 +928,18 @@ - - + + Ladeschleifen + + + + + + Element bearbeiten - + @@ -1161,443 +1173,443 @@ - + Verbinder ~TV W-W kpl - - - - - - ~TV W-AP110 kpl - ~TV W-AP60 oben kpl. + ~TV W-AP110 kpl - ~TV W-AP110-W kpl + ~TV W-AP60 oben kpl. - ~TV W-AP oben kpl + ~TV W-AP110-W kpl - ~TV 80 + ~TV W-AP oben kpl - ~TV 50 + ~TV 80 - ~Endplatte Al-Profil AP schraubbar + ~TV 50 - ~Anschlag AP-Ende fuer Spulenzug + ~Endplatte Al-Profil AP schraubbar - ~TV-A fuer AP110/AP60 kpl + ~Anschlag AP-Ende fuer Spulenzug - ~TV-A fuer AP60/ST + ~TV-A fuer AP110/AP60 kpl - ~TV-A fuer AP110/ST kpl + ~TV-A fuer AP60/ST - ~TV-SW II fuer SW-SW + ~TV-A fuer AP110/ST kpl - ~TV-SW III fuer SW-AP110-SW + ~TV-SW II fuer SW-SW - ~TV-SW III fuer SW-AP110 + ~TV-SW III fuer SW-AP110-SW - ~TV-SW IV fuer Anschluss SW-AP60 + ~TV-SW III fuer SW-AP110 - ~TV-SW IV fuer SW-ST + ~TV-SW IV fuer Anschluss SW-AP60 - ~Muffe + ~TV-SW IV fuer SW-ST + + ~Muffe + + + + - + Boegen fuer Weichen ~APB 110 R400/45 140 nur fuer Delta - - - - - - ~APB 110 R550/45 194 nur fuer Delta - ~APBW 110 R550/22,5 360x248,5 + ~APB 110 R550/45 194 nur fuer Delta - ~APBW 110 R550/22,5 410x298,5 + ~APBW 110 R550/22,5 360x248,5 - ~APBW 110 R550/22,5 410x78,5 + ~APBW 110 R550/22,5 410x298,5 - ~APBW 110 R550/22,5 410x98,5 + ~APBW 110 R550/22,5 410x78,5 - ~APBW 110 R550/67,5 360x598,5 + ~APBW 110 R550/22,5 410x98,5 - ~APBW 110 R400/67,5 260x398,5 + ~APBW 110 R550/67,5 360x598,5 - ~APBW 110 R550/45 77x160 + ~APBW 110 R400/67,5 260x398,5 - ~APBW 110 R550/67,5 125x83,5 + ~APBW 110 R550/45 77x160 - ~APBW 110 R750/90 200x290 + ~APBW 110 R550/67,5 125x83,5 - ~APBW 110 R650/90 300x190 + ~APBW 110 R750/90 200x290 - ~APBW 110 R550/67,5 208x347 + ~APBW 110 R650/90 300x190 - ~APBW 110 R550/67,5 58x497 + ~APBW 110 R550/67,5 208x347 - - - SPS - - - Eingang + ~APBW 110 R550/67,5 58x497 - + + + + SPS + + - Ausgang + Eingang - SPS Schliessen + Ausgang + + SPS Schliessen + + + + - + TRO Marker einfuegen - - - - - - Marker bearbeiten - Bearbeiten (Auto) + Marker bearbeiten + + Bearbeiten (Auto) + + + + - + Connections Verbindung einfuegen - - - - - - Verbindung bearbeiten + + Verbindung bearbeiten + + + + Bearbeiten (Auto) - + - + Export Export Sivas - + Export CSV - + - + Konfiguration Umschalten 2D und 3D - - - - - - Alle Bloecke auf 2D - Alle Bloecke auf 3D + Alle Bloecke auf 2D - Sprache + Alle Bloecke auf 3D - Planquadrate an/aus + Sprache - CSV-Export + Planquadrate an/aus + + CSV-Export + + + + - + Tests alle Tests ausfuehren und exportieren - - - - - - Tests ausfuehren und stehen lassen - Export CSV/Sivas ALL + Tests ausfuehren und stehen lassen + + Export CSV/Sivas ALL + + + + Kreisel - + Foerderer - + Foerderer-Linienzug - + Omniflo - + Omniflo Strecke - + Gefaellestrecke - + Mubea Gesamtmodell - + HundM05 Kreisel-Abschnitt - + KS 2D/3D pruefen (CSV) - + - + POP501 SSG_LIB Edit Context Kreisel bearbeiten - + Omniflo Element bearbeiten - + Gefaellestrecke bearbeiten - + VarioFoerderer bearbeiten - + TRO-Marker bearbeiten - + Verbindung bearbeiten - + SSG Bearbeiten (Auto) - + diff --git a/menu/SSG_LIB.mnu b/menu/SSG_LIB.mnu index 7d8945f..cd6e675 100644 --- a/menu/SSG_LIB.mnu +++ b/menu/SSG_LIB.mnu @@ -75,6 +75,7 @@ [Scanner]^C^C(ssg-ensure "OmniModulInsert") OMNI_SCANNER [<-Bürstenbremse]^C^C(ssg-ensure "OmniModulInsert") OMNI_BUERSTENBREMSE [~KettenFoerderer]^C^C(ssg-ensure "OmniModulInsert") OMNI_KettenFoerderer +[Ladeschleifen]^C^C(ssg-ensure "OmniModulInsert") OMNI_LADESCHLEIFE_AP110 [->Verbinder] [~TV W-W kpl]^C^C(ssg-ensure "OmniModulInsert") OMNI_TV_W_W [~TV W-AP110 kpl]^C^C(ssg-ensure "OmniModulInsert") OMNI_TV_W_AP110 diff --git a/tests/test_export_eingang_ausgang.py b/tests/test_export_eingang_ausgang.py index 816daf3..0d94552 100644 --- a/tests/test_export_eingang_ausgang.py +++ b/tests/test_export_eingang_ausgang.py @@ -78,7 +78,7 @@ class TestSivasSchliesstAus: _block("Eingang_2D", "0050"), _block("Ausgang_3D", "0051"), ] - items, counters = export_sivas.process_blocks(blocks, lookup={}) + items, counters = export_sivas.process_blocks(blocks) arten = {it.get("teileart", "") for it in items} assert "Eingang" not in arten diff --git a/tests/test_export_sivas_omniflo.py b/tests/test_export_sivas_omniflo.py new file mode 100644 index 0000000..3c545ff --- /dev/null +++ b/tests/test_export_sivas_omniflo.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +test_export_sivas_omniflo.py - Omniflo-Zeilen im Sivas-Export. +Laeuft komplett ohne BricsCAD (reine Python-Logik). + +Omniflo-Elemente werden ueber das TEILEART-Attribut erkannt, ihre Sivas- +Nummern kommen - wie beim Geruest - ausschliesslich aus dem ARTINR-Attribut +(kommagetrennt, TEF-Teile dort bereits aufgeloest). Kein Katalog-Lookup. +""" + +import os +import sys + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "lib")) + +import export_sivas # noqa: E402 + + +def _block(block_name, teileid, teileart=None, artinr=None, **extra): + attribs = {"ID": teileid} + if teileart is not None: + attribs["TEILEART"] = teileart + if artinr is not None: + attribs["ARTINR"] = artinr + attribs.update(extra) + return {"block_name": block_name, "x": 0.0, "y": 0.0, "z": 0.0, + "rotation": 0.0, "attribs": attribs} + + +def _zeilen(blocks): + items, _ = export_sivas.process_blocks(blocks) + return {it["sivasnr"]: it for it in items} + + +def test_einfache_teile_je_nummer_eine_zeile(): + zeilen = _zeilen([ + _block("834372027", "0001", "Omniflo Einzelweiche", "834372027"), + _block("834372027", "0002", "Omniflo Einzelweiche", "834372027"), + _block("821104033", "0003", "Omniflo Bogen", "821104033"), + ]) + assert zeilen["834372027"]["teileart"] == "Omniflo Einzelweiche" + assert zeilen["834372027"]["anzahl"] == 2 + assert zeilen["834372027"]["ids"] == ["0001", "0002"] + assert zeilen["821104033"]["teileart"] == "Omniflo Bogen" + assert zeilen["821104033"]["anzahl"] == 1 + + +def test_mehrteilige_weiche_mit_tef(): + zeilen = _zeilen([ + _block("834372002+0_BG090090", "0010", "Omniflo Einzelweiche", + "834372002, 0_B10090, 0_B10090"), + ]) + assert zeilen["834372002"]["anzahl"] == 1 + assert zeilen["834372002"]["ids"] == ["0010.t1"] + assert zeilen["0_B10090"]["teileart"] == "Omniflo TEF" + assert zeilen["0_B10090"]["anzahl"] == 2 + assert zeilen["0_B10090"]["ids"] == ["0010.t2", "0010.t3"] + + +def test_tef_teil_aus_bogen_und_weiche_in_einer_zeile(): + items, _ = export_sivas.process_blocks([ + _block("821104025+0_B10090", "0020", "Omniflo Bogen", "821104025, 0_B10090"), + _block("834372002+0_BG090090", "0021", "Omniflo Einzelweiche", + "834372002, 0_B10090, 0_B10090"), + ]) + tef = [it for it in items if it["sivasnr"] == "0_B10090"] + assert len(tef) == 1 + assert tef[0]["anzahl"] == 3 + + +def test_ladeschleife_wird_exportiert(): + zeilen = _zeilen([ + _block("824331125", "0030", "Omniflo Ladeschleife", "824331125"), + ]) + assert zeilen["824331125"]["teileart"] == "Omniflo Ladeschleife" + + +def test_omniflo_ohne_artinr_warnt_und_fehlt(capsys): + items, _ = export_sivas.process_blocks([ + _block("834372027", "0040", "Omniflo Einzelweiche", ""), + ]) + assert items == [] + assert "ohne ARTINR" in capsys.readouterr().err + + +def test_block_ohne_teileart_wird_ignoriert(): + items, _ = export_sivas.process_blocks([ + _block("Scanner", "0050", artinr=""), + _block("827064041", "0051", artinr="827064041"), + ]) + assert items == [] + + +def test_geruest_unveraendert(): + zeilen = _zeilen([ + _block("GR_Saeulen_20260928112449", "0060", "Geruest Saeule 2 Zoll", + "821006051,821004201,825002022,825002022", LAENGE="2500"), + ]) + assert zeilen["825002022"]["anzahl"] == 2 + assert zeilen["825002022"]["teileart"] == "Geruest Saeule 2 Zoll" + assert zeilen["825002022"]["ids"] == ["0060"] + assert zeilen["825002022"]["dispgruppe"] == 20 diff --git a/tests/test_unit.lsp b/tests/test_unit.lsp index 3672d95..4f61f54 100644 --- a/tests/test_unit.lsp +++ b/tests/test_unit.lsp @@ -503,6 +503,29 @@ (tu-eq "csv:export-modus-setzen/ungueltig-ignoriert" "separator_track" *csv-export-modus*) (csv:export-modus-setzen _modus-alt) + + ;; --- Gefrorene Layer: Schalter + Hilfskopien-Ausschluss (file-frei) --- + ;; Records (hilfsobjekt wrapper ...) mit Symbolen als Stand-ins fuer + ;; Entities; das Praedikat ersetzt ssg-ent-layer-gefroren-p. + (setq _gef-alt (csv:gefrorene-ausschliessen-p)) + (csv:gefrorene-ausschliessen-setzen T) + (tu-true "csv:gefrorene-ausschliessen/an" (csv:gefrorene-ausschliessen-p)) + (csv:gefrorene-ausschliessen-setzen nil) + (tu-nil "csv:gefrorene-ausschliessen/aus" (csv:gefrorene-ausschliessen-p)) + (csv:gefrorene-ausschliessen-setzen _gef-alt) + + (setq _gef-recs '((sep1 vf1 "SEP") (sep2 vf2 "SEP") (mark1 vf1 "AS") (unit1 gf1 "E" 1))) + (tu-eq "csv:hilfskopien-ausschluss/wrapper-vf1-gefroren" '(sep1 mark1) + (csv:hilfskopien-ausschluss _gef-recs (function (lambda (w) (eq w 'vf1))))) + (tu-eq "csv:hilfskopien-ausschluss/zwei-wrapper" '(sep2 unit1) + (csv:hilfskopien-ausschluss _gef-recs + (function (lambda (w) (member w '(vf2 gf1)))))) + (tu-nil "csv:hilfskopien-ausschluss/nichts-gefroren" + (csv:hilfskopien-ausschluss _gef-recs (function (lambda (w) nil)))) + (tu-nil "csv:hilfskopien-ausschluss/leere-liste" + (csv:hilfskopien-ausschluss nil (function (lambda (w) T)))) + (tu-nil "csv:hilfskopien-ausschluss/record-ohne-wrapper" + (csv:hilfskopien-ausschluss '((sep1)) (function (lambda (w) T)))) )