diff --git a/CLAUDE.md b/CLAUDE.md index d893b14..0624f58 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -70,7 +70,7 @@ Die `start_briscad.bat` ruft `setenv.bat` auf und startet BricsCAD. | Menue-Bereich | Modul | Befehle | |---|---|---| -| Kreisel-Module | `KreiselInsert` | KreiselInsert, KreiselConnect, KreiselEdit, ILS_Eckrad, EckradEdit | +| Kreisel-Module | `KreiselInsert` | KreiselInsert, KreiselConnect, KreiselEdit, ILS_Eckrad, EckradEdit, KREISEL_WEICHEN | | TRO | `Tro_Edit` | TRO_INSERT, TRO_EDIT | | Zusatzmodul/Foerderer/Kurven | `SSG_LIB_Commands` | ILS_BTMT_*, ILS_ETF_*, ILS_GK*_* | | VarioFoerderer | `VarioFoerderer` | FOERDERANLAGE, VARIOFOERDERER_EDIT, Vario_Kette_Merge | @@ -105,7 +105,7 @@ Detaillierte Dokumentation aller Funktionen und Befehle: siehe `Lisp/README.md`. | `ssg_blocktools.lsp` | SSG_BLOCK_COUNT, SSG_BLOCK_DELETE, SSG_ATTR_ERSETZEN | Block-Werkzeuge fuer das aktuelle Layout (Ursprung ACADSST `TOOLS.LSP`/`ssg.lsp`): Einfuegungen zaehlen (Pick, wcmatch-Muster oder Statistik aller Bloecke), alle Einfuegungen loeschen (Rueckfrage, gesperrte Layer bleiben), Attributwerte zeichnungsweit/in Auswahl suchen+ersetzen (Vorschau mit Anzahl, Teilstring oder Gesamtwert). Wirksamer Blockname (dynamische Bloecke). Reine Kerne `ssg-bt-name-passt-p`, `ssg-bt-zaehle-namen`, `ssg-bt-wert-neu` in `tests/test_unit.lsp` getestet | | `ssg_texttools.lsp` | SSG_TEXT_ERSETZEN, SSG_TEXT_PARALLEL, SSG_TEXT_HOEHE, SSG_TEXT_STIL | Text-Werkzeuge (Ursprung ACADSST `CHGTEXT`/`PARTEXT`/`ALTHOEHE`/`ALTSTIL`): Suchen/Ersetzen in TEXT, MTEXT und Block-Attributen (nutzt `ssg-bt-*`, daher NACH `ssg_blocktools` laden), Text parallel zu Linie/Polylinie/Bogen (lesbar ausgerichtet, Abstand), Hoehe/Stil aendern. Arbeitet in der XY-Ebene des Welt-KS. Reine Kerne `ssg-tt-lesbarer-winkel`, `ssg-tt-versetzt` getestet | | `ssg_stueckliste.lsp` | SSG_FAEHNCHEN_STUECKLISTE | Positions-Faehnchen + Stuecklistentabelle, komplett AUS DER ZEICHNUNG - es wird keine Datei eingelesen. Der Benutzer waehlt die Elemente aus (`ssg-sl-erkannte-elemente`: `ssget`-Auswahl, Enter ohne Auswahl = alle erkannten im aktuellen Layout; der reine Filter `ssg-sl-elemente-aus-ss` laesst nicht erkannte Objekte still wegfallen, man darf also grosszuegig ein Fenster aufziehen). `ssg-sl-klasse` klassifiziert Blocknamen (`KREISEL_*`/`ECKRAD_*` -> "ILS Kreisel/Eckrad", `VF_*`/`GF_*` -> "ILS Streckengruppe", `GR_*` -> "Rohrgeruest", `AP60*`/`AP110*`/`AP_110*`/`APG110*` -> je eigene Omniflo-Geraden-Gruppe; wirksamer Blockname ueber `ssg-bt-effname`, also auch dynamische Bloecke). Jedes einzelne Element bekommt ein Faehnchen (`ssg-sl-faehnchen`, Rechteck/Kreis/Automation-Rahmen + Text) an der unteren linken Ecke seiner Bounding-Box (aus `csv:get-bbox` - liefert Zentrum+Abmessungen, NICHT Eckpunkte, die Ecke wird im Code daraus berechnet); die Tabelle (`ssg-sl-tabelle-zeichnen`) gruppiert GROB je Elementklasse (keine Sivas-Artikelaufloesung wie `export_sivas.py`) zu einer Zeile mit Anzahl + Laengensumme aus dem `LAENGE`-Attribut (`ssg-sl-gruppieren`/reiner Kern `ssg-sl-gruppieren-werte`), mehrere gleichartige Elemente tragen daher dieselbe Positionsnummer. **Bewusst KEIN Datei-Import wie bei Walz/Bihler**: dort vergab ein externes System die Positionsnummern und lieferte sie als Datei zurueck, die das CAD nur abzeichnete - bei Walz (`MENU/POSITION.LSP`) eine `.pos`/`.zei` aus einem Clipper-Warenwirtschaftssystem mit Koordinaten-Zuordnung, bei Bihler ITB (`Walz_und_Bihler/SBCADBib/ITB-Module_ori.lsp`, `c:bea_pos`) eine `.pos` mit Zuordnung ueber den Entity-Handle. Diese Umwege sind hier nicht nachgebaut (Entscheidung 2026-10): Nummern und Mengen entstehen in dieser Funktion, die Stuecklisten-AUSGABE liefern EXPORTSIVAS/EXPORTCSV aus denselben Block-Attributen. Der alte `c:stueckl` (Laengen-Summierung ueber hartcodierte Walz-Profillayer wie `L-2`=ST, `A-7`=AP110) hat ebenfalls kein Aequivalent im dxfmakros-Datenmodell (Block-Attribute statt Linien-Layer). Braucht `csv:get-bbox`/`csv:get-attrib` aus `export.lsp` und `ssg-bt-effname` aus `ssg_blocktools.lsp` - muss daher NACH beiden geladen werden (siehe `ssg-ensure`-Reihenfolge in `SSG_LIB.mnl`/`ssg_load.lsp`). Reine Kerne `ssg-sl-klasse`, `ssg-sl-gruppieren-werte` in `tests/test_unit.lsp` getestet | -| `KreiselInsert.lsp` | KreiselInsert, KreiselConnect, KreiselRedraw, KreiselQuick, KreiselEdit, KreiselParams, KreiselLabelSetup, KreiselLabelPos, KreiselLabelHoehe, ILS_Eckrad, EckradEdit | ILS Kreisel und Eckrad komplett in AutoLISP. Eckrad: eigener Dialog `eckrad_edit.dcl` (`eckrad-dialog`, reiner Kern `eckrad-dialog-werte` getestet) beim Einfuegen (ILS_Eckrad, ohne GUI Konsolen-Rueckfall) und per Doppelklick (EckradEdit); kein Abstand/Typ, Drehung als freier Winkel statt fester Kreisel-Ausrichtung. `ils-eckrad-insert` behaelt eine vorhandene NUMMER (Edit -> gleicher Blockname `ECKRAD_n`). Kreiselname: `draw-module` setzt `Bezeichnung` nur noch bei leerem Wert auf die Vorgabe `KreiselN` (eigener Name aus KreiselEdit bleibt erhalten); Beschriftung `kreisel-label-text` = `KREISEL n: _` (ohne Namen bzw. bei Vorgabename wie bisher `KREISEL n: _`); da die Beschriftung als TEXT in der wiederverwendeten Blockdefinition `KREISEL_n_TYP_ABSTAND` steckt, setzt `kreisel-label-aktualisieren` sie dort per `entmod` neu (wirkt auf alle Referenzen der Definition) | +| `KreiselInsert.lsp` | KreiselInsert, KreiselConnect, KreiselRedraw, KreiselQuick, KreiselEdit, KreiselParams, KreiselLabelSetup, KreiselLabelPos, KreiselLabelHoehe, ILS_Eckrad, EckradEdit, KREISEL_WEICHEN | ILS Kreisel und Eckrad komplett in AutoLISP. **Beruehrpunkte/Weichen** (`kreisel-bp-*`, Befehl KREISEL_WEICHEN, Menue Kreisel-Module -> Weichenpunkte ermitteln): ermittelt aus den Blockparametern (Einfuegepunkt, Drehung, ABSTAND - nicht aus der Geometrie, Modell wie `draw-module`) die Beruehrpunkte aller `KREISEL_*`/`ECKRAD_*` GLEICHER Hoehe (`toleranz_hoehe`): Rad/Rad (`Kreis/Kreis`) und Rad/Laufprofil (`Kreis/Tangente`), nur auf der freien Radseite (aeusserer Halbkreis inkl. Flanken; Eckrad ganz frei), Toleranz `toleranz_beruehrung` (component_defaults.json, Sektion kreisel). Kapsel-Mindestabstand < 2r - tol = KOLLISION (Ueberschneidung), Kreisel Seite an Seite (Beruehrung nur an Flanke/Profil) = KOLLISION `Tangente/Tangente` - beide werden gemeldet, aber nie zur Weiche. Ausgabe: Konsolenliste + Kreis-Marker (Layer `kreisel_weiche`/`kreisel_kollision` aus layer.cfg, XDATA `SSG_KR_WEICHE`, jeder Lauf ersetzt die alten). Der Export schreibt dieselben Punkte als `kreisel_weichen.json` neben `export_raw.json` (`csv:kreisel-weichen-schreiben`, export.lsp); `export_csv.py` uebernimmt sie (`export_neighbors.load_lisp_kreisel_weichen`/`lisp_touch_switches`, Rueckfall auf `compute_kreisel_touch_switches` nur ohne Datei), `export_sivas.py` setzt daraus Merkmal 4403 "Anzahl Weichen ILS". Reine Kerne `kreisel-bp-element`/`-paar`/`-berechnen` in `tests/test_unit.lsp` (`tu-tests-kreisel-bp`, u.a. mit den 11 Einfuegungen aus `tests/testdata/schnittspunkte_kreisel.dxf` -> 10 Beruehrungen) getestet. Eckrad: eigener Dialog `eckrad_edit.dcl` (`eckrad-dialog`, reiner Kern `eckrad-dialog-werte` getestet) beim Einfuegen (ILS_Eckrad, ohne GUI Konsolen-Rueckfall) und per Doppelklick (EckradEdit); kein Abstand/Typ, Drehung als freier Winkel statt fester Kreisel-Ausrichtung. `ils-eckrad-insert` behaelt eine vorhandene NUMMER (Edit -> gleicher Blockname `ECKRAD_n`). Kreiselname: `draw-module` setzt `Bezeichnung` nur noch bei leerem Wert auf die Vorgabe `KreiselN` (eigener Name aus KreiselEdit bleibt erhalten); Beschriftung `kreisel-label-text` = `KREISEL n: _` (ohne Namen bzw. bei Vorgabename wie bisher `KREISEL n: _`); da die Beschriftung als TEXT in der wiederverwendeten Blockdefinition `KREISEL_n_TYP_ABSTAND` steckt, setzt `kreisel-label-aktualisieren` sie dort per `entmod` neu (wirkt auf alle Referenzen der Definition) | | `VarioFoerderer.lsp` (Wrapper fuer `vf_core.lsp`/`vf_standard.lsp`/`vf_etage.lsp`/`vf_linienzug.lsp`) | FOERDERANLAGE, VARIOFOERDERER_EDIT, VARIOFOERDERER_NACHRUESTEN, Vario_Kette_Merge, VF_SEKTION_RESTORE, VF_WIZARD_AN, VF_WIZARD_AUS | Vario-Foerderanlagen Generator (Typen Standard/Etage/Linienzug); Standard- UND Etage-Typ: Werteingabe per DCL-Dialog, inkl. der vorgelagerten Eingabemodus-Wahl 3D-Linie/Werte manuell (`vfc_eingabemodus`, `vfc-dialog-eingabemodus-waehlen`) - beide Modi laufen seitdem in denselben Basis-/Winkel-Dialog, der Konsolen-Pfad ist nur noch Rueckfall bei abgeschalteter GUI/anderer Sprache. Der Dialog-Ablauf ist zentral in vf_core (`vf-dialog-ablauf`/`vf-dialog-berechnen-einfuegen`, parametrisiert ueber Typ-Registry `*vf-typ-registry*` + Flag `horizontal-p`); `vfs-standard-dialog-ablauf`/`vfe-etage-dialog-ablauf` sind nur noch duenne Wrapper darauf. Beide nutzen dieselben Dialoge `vfs-dialog-eingabe-basis`/`vfs-dialog-winkel-verteilung` und den gemeinsamen L_GF/L_VF-Solve `vf-winkel-solve` (vf_core; berechne-alle-winkel/berechne-winkel-etage teilen ihn); Doppelklick auf VF_n-Block ruft VARIOFOERDERER_EDIT, das per SSG_VF_EDIT-XDATA-Marker ("standard"/"etage"/"linienzug") in den passenden Neuaufbau-Zweig dispatcht (`vfe-edit-ent` fuer Etage, `vfl-edit-ent` fuer Linienzug); Altbestand-Bloecke ohne Marker werden abgewiesen. Linienzug (Modus 1, `vf-linienzug-modus`) zeichnet jede interaktive Eingabe in einem Journal auf (`vfl-in-point`/`-string`/`-real`/`-int`) und schreibt es als XDATA auf den fertigen `VF_n`-Block; ein Abbruch (ESC) loescht dadurch nichts mehr, sondern wickelt die bis dahin gebaute Teil-Geometrie ebenso zu einem Block (`vfl-modus-abbruch-sichern`) - sie bleibt stehen und ist sofort per Doppelklick weiter editierbar/fortsetzbar. Der komplette Linienzug-Hauptstrang (Menue-Fragen von AS-Element bis Kettenende, inkl. GF-Bogen/Vario-Kurve/VF-Einheit-Fortsetzung) laeuft per Default ueber DCL-Dialoge statt Konsole (`*vfl-wizard-mode*`, Befehle VF_WIZARD_AN/VF_WIZARD_AUS): `vfl-in-string`/`-int`/`-real` zeigen bei aktivem Wizard-Modus generische Dialoge aus `dcl/vf_linienzug_wizard.dcl` (Zahl-Eingabe bzw. Mehrfachauswahl aus `vfl-menu`/`vfl-menu-int`) statt `getstring`/`getint`/`getreal` aufzurufen; die ORIGINALEN Konsolen-Fragen (princ + get*, de/en ueber ssg-text) bleiben unveraendert im Code (Fallback bei Fehlern via `vl-catch-all-apply`, siehe `doc/variofoerderer.md`). `vfl-edit-ent` liest das Journal, zeigt die Sektionen ueber einen DCL-Dialog (`vfl-dlg-position`, `dcl/vfl_edit.dcl`) an, kuerzt bei Bedarf auf eine gewaehlte Sektion (stummer Replay der behaltenen Eingaben, umgeht den Wizard-Zweig komplett) und baut danach interaktiv weiter. Linienzug Modus 2 (Pfad+Zielhoehe-Solver, `vf-linienzug-modus2`) nutzt dieselben Wrapper PLUS die neue Objektauswahl-Wrapper-Funktion `vfl-in-selection` (Pfad-Objekte als stabile Entity-Handles journalisiert, neue Journal-Art `"OBJS"`) und schreibt sein Journal unter dem eigenen XDATA-Marker `"linienzug2"`; Doppelklick baut hier NICHT abschnittsweise, sondern per vollem Reset 1:1 aus dem Journal neu auf (`vfl-edit-ent2`, dispatcht aus `vfl-edit-ent` per Marker) - Modus 3 (Vorwaerts-Nachbau) bleibt bewusst reine Konsole (im Code selbst als Legacy/"wird umgebaut" markiert). `Vario_Kette_Merge` (in vf_linienzug.lsp) fuehrt mehrere einzeln gebaute VF_n-Bloecke ab einem gewaehlten Start-Baustein ueber die reale KS_AUS->KS_EIN-Nachbarschaft zu einem Gesamt-Block zusammen (aggregierte Attribute, Luecken-Erkennung). Die interaktiven Editier-Pfade (`vfl-edit-ent` Sektions-Zweig, `vfl-edit-ent2`) rufen den Neuaufbau BEWUSST direkt auf, NICHT ueber `vl-catch-all-apply`: der alte Block ist beim Neuaufbau schon per `entdel` weg, und ein Catch faengt den Abbruch VOR `*error*` ab - der Abbruch-Wickler `vfl-modus-abbruch-sichern` kaeme nie zum Zug und die ganze Kette bliebe als lose Einzelteile ohne Block liegen (Fehlerbild "VF-Block aufgebrochen"). Nur der nicht-interaktive Batch-Konverter `vfl-konvertiere-ent` behaelt den Catch (die Batch-Schleife muss weiterlaufen) und meldet den betroffenen Block. Die Abbruch-Handler setzen zusaetzlich `*ssg-ils-dim*` zurueck, weil der Aufrufer, der das sonst tut, nach einem Abbruch nicht mehr erreicht wird. Recovery-Pfad fuer bereits aufgebrochene Ketten: `VF_SEKTION_RESTORE` (vf_linienzug.lsp) setzt das Ketten-Journal aus der XDATA der losen Objekte zusammen - `SSG_VF_EDIT_SEG` traegt pro Entity den Journal-Abschnitt DER ITERATION, in der es entstand (indiziert mit dem ersten Glied-Index darin; eine VF-Einheit mit eingebetteten Vario-Kurven erzeugt mehrere Glieder in einer Iteration), `SSG_VF_EDIT_PRE` auf den Entities des ersten Gliedes die Praeambel (Startpunkt/Starthoehe/AS). Die `.dbg`-Dateien sind reine AUSGABE und werden von keinem Befehl gelesen (dbgopen legt sie bei jedem Lauf neu an) - Wiederherstellungs-Daten gehoeren in die XDATA. `vf_konstanten.lsp` (von vf_core.lsp vor den drei Submodulen geladen) 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` (Replay-Kern `vsp-bau-journal` - nimmt auch ein unveraendert uebernommenes, aufgezeichnetes Journal, z.B. aus `dxf_anlage_export.py`). Damit traegt der fertige Block dasselbe `SSG_VF_EDIT`-Journal wie eine handgebaute Kette und bleibt per Doppelklick editier- und 2D/3D-konvertierbar - kein Journal-Konsument muss etwas von Specs wissen. Wichtige Regeln, die der Uebersetzer kennt: der Menue-Code haengt am Frame (erste Sektion 4 Optionen ohne GF-Bogen, danach 5), `hz` steht nur beim ERSTEN Segment im Journal, und `erst-hor` unterscheidet den horizontalen Erstkoerper einer VF-Einheit (Separator-/Endpunktfragen vorab) vom gewinkelten (keine Frage). Fehlerverhalten: fail fast - `vsp-pruefen` sammelt ALLE Spec-Fehler, gebaut wird dann nichts (nichts wird geraten). Ergebnis-Record je Kette mit `status` (`executed`/`warnung`/`desync`/`abbruch`/`spec-fehler`), Block/Handle/Einfuegepunkt/Attributen, `prompts` (muss 0 sein), `glieder_soll` vs. `glieder_ist` (Desync-Detektor, der auch bei aufgehender Queue greift) und den gesammelten `meldungen`. Headless-Rahmen `vsp-headless-an`/`-aus` (Save/Restore von `*vfl-headless*`, `*vfl-wizard-mode*`, `*ssg-gui-aus*` + zaehlende Eingabe-Stubs). Spec-Datei flach als JSON (`ssg-load-json` liest zeilenweise), erzeugt von `lib/vf_spec_export.py`. Gegenstueck/Referenz in Python: derselbe Emitter, gegen die 5 echten HundM-Ketten rundlaufgeprueft | | `Gefaellestrecke.lsp` | GEFAELLESTRECKE, GEFAELLESTRECKE_EDIT | Gefaelle-Foerderanlage (AUS -> Staustrecke skaliert -> Separator -> EIN); Modus 1 per DCL-Dialog (Einfuegehoehe, Element-Winkel 30/90); Doppelklick auf GF_n-Block ruft GEFAELLESTRECKE_EDIT (Dialog vorbelegt, Neuaufbau; die Einfuegehoehe kommt aus der aktuellen Z des Einfuegepunkts statt aus XDATA/HOEHE_VON_mm, damit ein in Z verschobener Block per OK direkt auf der neuen Hoehe neu entsteht - gleiches gilt fuer die 2D/3D-Umschaltung `gf-konvertiere-ent`; der GF-Linienzug-Rebuild nutzt weiter die absoluten Segmentpunkte aus XDATA) | @@ -195,7 +195,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). 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). Schreibt zusaetzlich immer `_zusammengefasst.csv`: Zeilen, die sich nur in Nr/Anzahl/IDs unterscheiden (v.a. Geruest, je Block eine Zeile), zu einer Zeile zusammengefasst (`items_zusammenfassen`, Anzahl addiert, IDs vereinigt); die Hauptdatei bleibt unveraendert. 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). Schreibt zusaetzlich immer `_zusammengefasst.csv`: Zeilen, die sich nur in Nr/Anzahl/IDs unterscheiden (v.a. Geruest, je Block eine Zeile), zu einer Zeile zusammengefasst (`items_zusammenfassen`, Anzahl addiert, IDs vereinigt); die Hauptdatei bleibt unveraendert. **ILS 2.0 Weiche (6216)**: eine Summenzeile (Anzahl 1), sobald `kreisel_weichen.json` (LISP-Export, siehe KreiselInsert.lsp) Beruehrpunkte enthaelt - je Beruehrpunkt eine Weichenkombination aus 1x aktiv (3371) + 1x passiv (3372), Zuordnung in `cfg/sivas.cfg` `[weichen]` ueber das synthetische Attribut `ANZAHL_WEICHEN`; dieselbe Zahl geht in Merkmal 4403 der Automation-Zeile. 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/KreiselInsert.lsp b/Lisp/KreiselInsert.lsp index 1039e58..cbb378a 100644 --- a/Lisp/KreiselInsert.lsp +++ b/Lisp/KreiselInsert.lsp @@ -1524,5 +1524,381 @@ (princ) ) + +;; ============================================================ +;; TEIL 5: BERUEHRPUNKTE / WEICHEN ZWISCHEN KREISELN UND ECKRAEDERN +;; ============================================================ +;; Ermittelt, wo sich Kreisel (KREISEL_n) und Eckraeder (ECKRAD_n) gleicher +;; Hoehe beruehren - dort sitzt eine ILS-Weiche. Gerechnet wird NUR aus den +;; Blockparametern (Einfuegepunkt, Drehung, ABSTAND), nicht aus der +;; gezeichneten Geometrie - dasselbe Modell wie draw-module: +;; Kreisel: Rad AN8 bei P+R(r,0), Rad SP8 bei P+R(r+abstand,0), Laufprofile +;; = die beiden Tangenten bei lokal y=+-r. Frei ist je Rad nur der +;; aeussere Halbkreis (die "drei freien Seiten": vorn + beide +;; Flanken), die innere Haelfte liegt zwischen den Laufprofilen. +;; Eckrad: Vollkreis um P, ganz frei, kein Laufprofil. +;; Paar-Logik: +;; - Mindestabstand der Achsen (Kapsel-Modell) < 2r - tol -> KOLLISION +;; (Rad ragt hinein, Laufprofile kreuzen sich) - keine Weiche. +;; - Sonst je Rad: Rad/Rad mit |Abstand - 2r| <= tol bzw. Rad/Laufprofil +;; mit |Lot - r| <= tol, Beruehrpunkt auf einer freien Radseite -> +;; BERUEHRUNG ("Kreis/Kreis" bzw. "Kreis/Tangente", Namen wie im Python- +;; Export export_neighbors.compute_kreisel_touch_switches). +;; - Beruehren sich nur Laufprofile (Kreisel liegen Seite an Seite) -> +;; KOLLISION "Tangente/Tangente". +;; Die reinen Kerne (kreisel-bp-element/-paar/-berechnen) laufen ohne +;; Zeichnung und sind in tests/test_unit.lsp getestet. + +(setq *kreisel-bp-app* "SSG_KR_WEICHE") + +;; --- 2D-Vektorhelfer (Punkte als (x y)) --- +(defun kreisel-bp-plus (a b) (list (+ (car a) (car b)) (+ (cadr a) (cadr b)))) +(defun kreisel-bp-minus (a b) (list (- (car a) (car b)) (- (cadr a) (cadr b)))) +(defun kreisel-bp-mal (a s) (list (* (car a) s) (* (cadr a) s))) +(defun kreisel-bp-dot (a b) (+ (* (car a) (car b)) (* (cadr a) (cadr b)))) +(defun kreisel-bp-abst (a b) (distance (list (car a) (cadr a)) (list (car b) (cadr b)))) +(defun kreisel-bp-mitte (a b) (kreisel-bp-mal (kreisel-bp-plus a b) 0.5)) + +;; Element-Record aus Blockparametern (reiner Kern). +;; typ "KREISEL" oder "ECKRAD" +;; p Einfuegepunkt (x y [z]) - Kreisel: AN-seitige Tangente, Eckrad: Mitte +;; z Hoehe (Einfuege-Z) +;; rot Drehung in Grad +;; abstand Achsabstand AN8->SP8 (Kreisel), beim Eckrad ignoriert +;; r Radradius +;; id/handle/name nur zur Ausgabe/Zuordnung +;; Rueckgabe: alist mit "raeder" ((label mitte freie-richtung|nil) ...), +;; "profile" ((p1 p2) (p1 p2)) und "achse" (a b) fuer den Kapsel-Abstand. +(defun kreisel-bp-element (typ p z rot abstand r id handle name / u n p2 a b) + (setq u (list (cos (* pi (/ rot 180.0))) (sin (* pi (/ rot 180.0)))) + n (list (- (cadr u)) (car u)) + p2 (list (car p) (cadr p))) + (if (= typ "KREISEL") + (progn + (setq a (kreisel-bp-plus p2 (kreisel-bp-mal u r)) + b (kreisel-bp-plus p2 (kreisel-bp-mal u (+ r abstand)))) + (list (cons "typ" typ) (cons "id" id) (cons "handle" handle) (cons "name" name) + (cons "z" z) (cons "r" r) + (cons "achse" (list a b)) + (cons "raeder" (list (list "AN8" a (kreisel-bp-mal u -1.0)) + (list "SP8" b u))) + (cons "profile" (list (list (kreisel-bp-plus a (kreisel-bp-mal n r)) + (kreisel-bp-plus b (kreisel-bp-mal n r))) + (list (kreisel-bp-minus a (kreisel-bp-mal n r)) + (kreisel-bp-minus b (kreisel-bp-mal n r))))))) + (list (cons "typ" typ) (cons "id" id) (cons "handle" handle) (cons "name" name) + (cons "z" z) (cons "r" r) + (cons "achse" (list p2 p2)) + (cons "raeder" (list (list "ECKRAD" p2 nil))) + (cons "profile" nil))) +) + +(defun kreisel-bp-get (key elem) (cdr (assoc key elem))) + +;; Naechstgelegene Punkte zweier Strecken [p1,q1] und [p2,q2] (Ericson, +;; Real-Time Collision Detection - wie export_neighbors._closest_pts_segments). +;; Rueckgabe (c1 c2). +(defun kreisel-bp-naechste-punkte (p1 q1 p2 q2 / eps d1 d2 rr a e f c b den s tt) + (setq eps 1e-9 + d1 (kreisel-bp-minus q1 p1) + d2 (kreisel-bp-minus q2 p2) + rr (kreisel-bp-minus p1 p2) + a (kreisel-bp-dot d1 d1) + e (kreisel-bp-dot d2 d2) + f (kreisel-bp-dot d2 rr)) + (cond + ((and (<= a eps) (<= e eps)) (setq s 0.0 tt 0.0)) + ((<= a eps) (setq s 0.0 tt (max 0.0 (min 1.0 (/ f e))))) + (t + (setq c (kreisel-bp-dot d1 rr)) + (if (<= e eps) + (setq tt 0.0 s (max 0.0 (min 1.0 (/ (- c) a)))) + (progn + (setq b (kreisel-bp-dot d1 d2) + den (- (* a e) (* b b))) + (setq s (if (> den eps) (max 0.0 (min 1.0 (/ (- (* b f) (* c e)) den))) 0.0)) + (setq tt (/ (+ (* b s) f) e)) + (cond + ((< tt 0.0) (setq tt 0.0 s (max 0.0 (min 1.0 (/ (- c) a))))) + ((> tt 1.0) (setq tt 1.0 s (max 0.0 (min 1.0 (/ (- b c) a))))) + ) + ) + )) + ) + (list (kreisel-bp-plus p1 (kreisel-bp-mal d1 s)) + (kreisel-bp-plus p2 (kreisel-bp-mal d2 tt))) +) + +;; Liegt Punkt pt auf der freien Seite des Rades (label mitte richtung)? +;; Richtung nil = Vollkreis (Eckrad). Die Flanken (90 Grad) zaehlen mit. +(defun kreisel-bp-frei-p (rad pt tol) + (or (null (caddr rad)) + (>= (kreisel-bp-dot (kreisel-bp-minus pt (cadr rad)) (caddr rad)) (- tol))) +) + +;; Liegt pt genau auf einer Flanke (90 Grad zur Achse) eines Kreiselrades? +;; Dort beginnt das eigene Laufprofil - eine Beruehrung NUR an der Flanke +;; bedeutet parallel aneinanderliegende Kreisel, keine Weiche. +(defun kreisel-bp-flanke-p (rad pt tol) + (and (caddr rad) + (<= (abs (kreisel-bp-dot (kreisel-bp-minus pt (cadr rad)) (caddr rad))) tol)) +) + +;; Lotfusspunkt von c auf die Strecke (p1 p2). Rueckgabe (t fusspunkt), t +;; unbegrenzt (0 = p1, 1 = p2). +(defun kreisel-bp-lot (c p1 p2 / d l2 tt) + (setq d (kreisel-bp-minus p2 p1) + l2 (kreisel-bp-dot d d)) + (setq tt (if (> l2 1e-12) (/ (kreisel-bp-dot (kreisel-bp-minus c p1) d) l2) 0.0)) + (list tt (kreisel-bp-plus p1 (kreisel-bp-mal d tt))) +) + +;; Ergebnis-Record (reiner Kern) +(defun kreisel-bp-treffer (status art ea eb pt rad-elem rad detail) + (list (cons "status" status) (cons "art" art) + (cons "a" ea) (cons "b" eb) + (cons "p" pt) + (cons "z" (/ (+ (kreisel-bp-get "z" ea) (kreisel-bp-get "z" eb)) 2.0)) + (cons "achse_von" rad-elem) + ;; Ausrichtung der Weiche: Radiale vom beruehrenden Rad zum Punkt + (cons "achse_rot" (if rad (* (/ 180.0 pi) (angle (cadr rad) pt)) 0.0)) + (cons "detail" detail)) +) + +;; Alle Beruehrungen eines Rades von ew mit Raedern/Profilen von eo. +;; Rueckgabe: Liste von (pt art rad detail). +(defun kreisel-bp-rad-kontakte (ew eo tol / r res d pt lot tt l) + (setq r (kreisel-bp-get "r" ew)) + (foreach rad (kreisel-bp-get "raeder" ew) + ;; Rad/Rad + (foreach rad2 (kreisel-bp-get "raeder" eo) + (setq d (kreisel-bp-abst (cadr rad) (cadr rad2))) + (if (<= (abs (- d (+ r (kreisel-bp-get "r" eo)))) tol) + (progn + (setq pt (kreisel-bp-mitte (cadr rad) (cadr rad2))) + ;; Beide Kreiselraeder nur an der Flanke = Kreisel Seite an Seite + (if (and (kreisel-bp-frei-p rad pt tol) (kreisel-bp-frei-p rad2 pt tol) + (not (and (kreisel-bp-flanke-p rad pt tol) + (kreisel-bp-flanke-p rad2 pt tol)))) + (setq res (cons (list pt "Kreis/Kreis" rad + (strcat (car rad) "/" (car rad2))) + res)))))) + ;; Rad/Laufprofil + (foreach pr (kreisel-bp-get "profile" eo) + (setq lot (kreisel-bp-lot (cadr rad) (car pr) (cadr pr)) + tt (car lot) + pt (cadr lot) + l (kreisel-bp-abst (car pr) (cadr pr))) + (if (and (> l 0.0) + (>= tt (- (/ tol l))) (<= tt (+ 1.0 (/ tol l))) + (<= (abs (- (kreisel-bp-abst (cadr rad) pt) r)) tol) + (kreisel-bp-frei-p rad pt tol) + ;; Flanke an Laufprofil = Profile liegen parallel aufeinander + (not (kreisel-bp-flanke-p rad pt tol))) + (setq res (cons (list pt "Kreis/Tangente" rad (strcat (car rad) "/Profil")) res)))) + ) + (reverse res) +) + +;; Ein Paar pruefen (reiner Kern). Rueckgabe: Liste von Treffer-Records +;; (leer = kein Kontakt). Unterschiedliche Hoehe wird VORHER ausgefiltert. +(defun kreisel-bp-paar (ea eb tol / ac bc cp dmin soll res kont pt doppelt) + (setq ac (kreisel-bp-get "achse" ea) + bc (kreisel-bp-get "achse" eb) + cp (kreisel-bp-naechste-punkte (car ac) (cadr ac) (car bc) (cadr bc)) + dmin (kreisel-bp-abst (car cp) (cadr cp)) + soll (+ (kreisel-bp-get "r" ea) (kreisel-bp-get "r" eb))) + (cond + ;; Weit auseinander - nichts + ((> dmin (+ soll tol)) nil) + ;; Ueberschneidung - Kollision, keine Weiche + ((< dmin (- soll tol)) + (list (kreisel-bp-treffer "kollision" "Ueberschneidung" ea eb + (kreisel-bp-mitte (car cp) (cadr cp)) (kreisel-bp-get "id" ea) nil + (strcat "Achsabstand " (rtos dmin 2 1))))) + ;; Beruehrung innerhalb Toleranz - Kontaktpunkte aufzaehlen + (t + (foreach k (append + (mapcar '(lambda (x) (cons ea x)) (kreisel-bp-rad-kontakte ea eb tol)) + (mapcar '(lambda (x) (cons eb x)) (kreisel-bp-rad-kontakte eb ea tol))) + ;; k = (elem pt art rad detail); Dubletten (gleicher Punkt) verwerfen + (setq pt (cadr k) doppelt nil) + (foreach t2 res + (if (<= (kreisel-bp-abst pt (kreisel-bp-get "p" t2)) (+ (* 2.0 tol) 1.0)) + (setq doppelt T))) + (if (not doppelt) + (setq res (append res + (list (kreisel-bp-treffer "beruehrung" (nth 2 k) ea eb pt + (kreisel-bp-get "id" (car k)) (nth 3 k) (nth 4 k))))))) + (if res + res + ;; Nur Laufprofile liegen aneinander (Seite an Seite) - keine Weiche + (list (kreisel-bp-treffer "kollision" "Tangente/Tangente" ea eb + (kreisel-bp-mitte (car cp) (cadr cp)) (kreisel-bp-get "id" ea) nil + "Laufprofile liegen aneinander")))) + ) +) + +;; Alle Paare auswerten (reiner Kern). +;; elemente = Liste von kreisel-bp-element-Records +;; Rueckgabe: (beruehrungen kollisionen) +(defun kreisel-bp-berechnen (elemente tol tol-z / rest ea ber kol) + (setq rest elemente) + (while rest + (setq ea (car rest) rest (cdr rest)) + (foreach eb rest + (if (<= (abs (- (kreisel-bp-get "z" ea) (kreisel-bp-get "z" eb))) tol-z) + (foreach tr (kreisel-bp-paar ea eb tol) + (if (= (kreisel-bp-get "status" tr) "kollision") + (setq kol (cons tr kol)) + (setq ber (cons tr ber))))))) + (list (reverse ber) (reverse kol)) +) + +;; ABSTAND eines Kreisels: Attribut, sonst letzter Token des Blocknamens +;; KREISEL_n_TYP_ABSTAND (reiner Kern). +(defun kreisel-bp-abstand (attr-wert bname / pos) + (cond + ((and attr-wert (> (atof attr-wert) 0.0)) (atof attr-wert)) + ((setq pos (vl-string-position (ascii "_") bname 0 T)) + (atof (substr bname (+ pos 2)))) + (t 0.0)) +) + +;; Toleranzen aus data/json/component_defaults.json (Sektion kreisel) +(defun kreisel-bp-toleranz () (float (ssg-cfg-or "kreisel" "toleranz_beruehrung" 1.0))) +(defun kreisel-bp-toleranz-z () (float (ssg-cfg-or "kreisel" "toleranz_hoehe" 1.0))) + +;; Alle KREISEL_n/ECKRAD_n im Modellbereich als Element-Records sammeln. +;; Bloecke auf gefrorenen Layern bleiben aussen vor (wie im Export). +(defun kreisel-bp-elemente-sammeln ( / ss i ent ed bname typ attribs p r res) + (setq r (/ *kreisel-durchmesser* 2.0)) + (if (setq ss (ssget "_X" '((0 . "INSERT") (2 . "KREISEL_*,ECKRAD_*") (410 . "Model")))) + (progn + (setq i 0) + (while (setq ent (ssname ss i)) + (setq ed (entget ent) + bname (strcase (cdr (assoc 2 ed))) + typ (if (wcmatch bname "KREISEL_*") "KREISEL" "ECKRAD")) + (if (not (and (car (atoms-family 1 '("SSG-ENT-LAYER-GEFROREN-P"))) + (ssg-ent-layer-gefroren-p ent))) + (progn + (setq attribs (ssg-attrib-read ent) + p (cdr (assoc 10 ed))) + (setq res (cons (kreisel-bp-element typ p (if (caddr p) (caddr p) 0.0) + (* (/ 180.0 pi) (cdr (assoc 50 ed))) + (if (= typ "KREISEL") + (kreisel-bp-abstand (cdr (assoc "ABSTAND" attribs)) bname) + 0.0) + r + (cond ((cdr (assoc "ID" attribs))) ("")) + (cdr (assoc 5 ed)) + (cond ((cdr (assoc "Bezeichnung" attribs)) + (if (= (cdr (assoc "Bezeichnung" attribs)) "") + bname (cdr (assoc "Bezeichnung" attribs)))) + (bname))) + res)))) + (setq i (1+ i)) + ) + ) + ) + (reverse res) +) + +;; Einstieg fuer Befehl UND Export: (beruehrungen kollisionen) der Zeichnung +(defun kreisel-bp-ermitteln () + (kreisel-bp-berechnen (kreisel-bp-elemente-sammeln) + (kreisel-bp-toleranz) (kreisel-bp-toleranz-z)) +) + +;; Anzeigetext eines Elements: "Kreisel3 (ID 0007)" bzw. ohne ID mit Handle +(defun kreisel-bp-elem-text (e) + (strcat (kreisel-bp-get "name" e) + (if (/= (kreisel-bp-get "id" e) "") + (strcat " (ID " (kreisel-bp-get "id" e) ")") + (strcat " (#" (kreisel-bp-get "handle" e) ")"))) +) + +;; Alte Marker (Kreise mit XDATA *kreisel-bp-app*) entfernen. Rueckgabe: Anzahl +(defun kreisel-bp-marker-loeschen ( / ss i n) + (setq n 0) + (if (setq ss (ssget "_X" (list '(0 . "CIRCLE") (list -3 (list *kreisel-bp-app*))))) + (progn + (setq i 0) + (while (< i (sslength ss)) + (entdel (ssname ss i)) + (setq i (1+ i) n (1+ n))))) + n +) + +;; Marker-Kreis fuer einen Treffer zeichnen +(defun kreisel-bp-marker (tr lay radius) + (if (not (tblsearch "APPID" *kreisel-bp-app*)) (regapp *kreisel-bp-app*)) + (entmake (list '(0 . "CIRCLE") (cons 8 lay) + (list 10 (car (kreisel-bp-get "p" tr)) (cadr (kreisel-bp-get "p" tr)) + (kreisel-bp-get "z" tr)) + (cons 40 radius) + (list -3 (list *kreisel-bp-app* + (cons 1000 (kreisel-bp-get "status" tr)) + (cons 1000 (kreisel-bp-get "art" tr)))))) +) + +;; Layer fuer Marker: cfg/layer.cfg [allgemein] key, sonst Rueckfall +(defun kreisel-bp-layer (key fallback farbe / lay) + (if (null (setq lay (ssg-layer-anlegen key nil))) + (progn (ssg-make-layer fallback farbe nil) (setq lay fallback))) + lay +) + +;; Konsolenzeile fuer einen Treffer +(defun kreisel-bp-zeile (key nr tr) + (princ (ssg-textf key + (list nr + (kreisel-bp-elem-text (kreisel-bp-get "a" tr)) + (kreisel-bp-elem-text (kreisel-bp-get "b" tr)) + (strcat (kreisel-bp-get "art" tr) ", " (kreisel-bp-get "detail" tr)) + (rtos (car (kreisel-bp-get "p" tr)) 2 1) + (rtos (cadr (kreisel-bp-get "p" tr)) 2 1) + (rtos (kreisel-bp-get "z" tr) 2 1)))) +) + +;; -------------------------------------------- +;; KREISEL_WEICHEN: Beruehrpunkte (Weichen) und Kollisionen aller Kreisel/ +;; Eckraeder ermitteln, in der Konsole auflisten und als Kreise markieren. +;; Ein erneuter Aufruf ersetzt die Marker des vorigen Laufs. +;; -------------------------------------------- +(defun c:KREISEL_WEICHEN ( / erg ber kol nr lay-w lay-k radius) + (ssg-start (ssg-text "kreisel-bp-start") nil) + (setq erg (kreisel-bp-ermitteln) + ber (car erg) + kol (cadr erg)) + (dbgmsg (strcat "KREISEL_WEICHEN: " (itoa (length ber)) " Beruehrungen, " + (itoa (length kol)) " Kollisionen")) + (kreisel-bp-marker-loeschen) + (princ (ssg-textf "kreisel-bp-summe" + (list (itoa (length ber)) (itoa (length kol)) (rtos (kreisel-bp-toleranz) 2 1)))) + (setq radius (float (ssg-cfg-or "kreisel" "weiche_marker_radius" 50.0))) + (if ber + (setq lay-w (kreisel-bp-layer "kreisel_weiche" "ZZ_Kreisel_Weichen" "4"))) + (if kol + (setq lay-k (kreisel-bp-layer "kreisel_kollision" "ZZ_Kreisel_Kollision" "1"))) + (setq nr 0) + (foreach tr ber + (setq nr (1+ nr)) + (kreisel-bp-zeile "kreisel-bp-zeile" (itoa nr) tr) + (kreisel-bp-marker tr lay-w radius)) + (setq nr 0) + (foreach tr kol + (setq nr (1+ nr)) + (kreisel-bp-zeile "kreisel-bp-kollision" (itoa nr) tr) + (kreisel-bp-marker tr lay-k (* 2.0 radius))) + (if (or ber kol) + (princ (ssg-textf "kreisel-bp-marker" + (list (itoa (+ (length ber) (length kol))) + (cond (lay-w) (lay-k)))))) + (ssg-end) + (princ) +) + (princ "\n>>> KreiselInsert geladen") (princ) diff --git a/Lisp/README.md b/Lisp/README.md index 41a89f2..14846ce 100644 --- a/Lisp/README.md +++ b/Lisp/README.md @@ -686,6 +686,7 @@ AutoLISP-Implementierung fuer ILS Kreisel und Eckrad. Enthaelt alle Befehle fuer | `c:KreiselLabelHoehe` | Label-Hoehe aendern | | `c:ILS_Eckrad` | Eckrad an Tangentenpunkt einfuegen, Parameter per DCL-Dialog (`eckrad_edit.dcl`; ohne GUI Konsolen-Rueckfall) | | `c:EckradEdit` | Eckrad ueber DCL-Dialog bearbeiten (Bezeichnung, Hoehe, Drehung, Drehrichtung, Geruest; kein Abstand/Typ), Neuaufbau mit gleicher Nummer | +| `c:KREISEL_WEICHEN` | Beruehrpunkte (= ILS-Weichen) aller Kreisel/Eckraeder gleicher Hoehe ermitteln: Rad/Rad und Rad/Laufprofil auf der freien Radseite, Toleranz `toleranz_beruehrung`/`toleranz_hoehe` (component_defaults.json). Ueberschneidungen und Seite-an-Seite-Kreisel werden als Kollision gemeldet. Konsolenliste + Kreis-Marker (Layer `kreisel_weiche`/`kreisel_kollision`), ein neuer Lauf ersetzt die Marker. Derselbe Kern (`kreisel-bp-ermitteln`) liefert beim Export `kreisel_weichen.json` fuer `export_csv.py`/`export_sivas.py` | --- diff --git a/Lisp/export.lsp b/Lisp/export.lsp index 93e8200..197f25b 100644 --- a/Lisp/export.lsp +++ b/Lisp/export.lsp @@ -149,6 +149,7 @@ (setq *export-filenames* (list (cons "raw_json_datei" "export_raw.json") + (cons "kreisel_weichen_datei" "kreisel_weichen.json") (cons "sivas_script" "export_sivas.py") (cons "sivas_csv" "sivas.csv") (cons "csv_script" "export_csv.py") @@ -2745,6 +2746,74 @@ ) ) +;; --- Kreisel-Beruehrpunkte (Weichen) fuer den Python-Export ------------- +;; Schreibt /kreisel_weichen.json aus kreisel-bp-ermitteln +;; (KreiselInsert.lsp) - derselbe Kern wie der Befehl KREISEL_WEICHEN, nur +;; ohne Marker. Gelesen von export_neighbors.load_lisp_touch_switches +;; (export_csv.py) und export_sivas.py (Merkmal 4403). Partner werden ueber +;; ihr ID-Attribut (= teileid im Python-Export) UND den Handle genannt. +;; Eine alte Datei wird vorher geloescht, damit Python nie veraltete Punkte +;; liest, falls KreiselInsert nicht geladen ist (dann rechnet Python selbst). +;; Rueckgabe: Pfad der geschriebenen Datei oder nil. +(defun csv:kreisel-weichen-json-eintrag (tr / ea eb) + (setq ea (cdr (assoc "a" tr)) + eb (cdr (assoc "b" tr))) + (strcat + " {\"a\":\"" (csv:json-escape (cdr (assoc "id" ea))) "\"" + ",\"b\":\"" (csv:json-escape (cdr (assoc "id" eb))) "\"" + ",\"a_handle\":\"" (csv:json-escape (cdr (assoc "handle" ea))) "\"" + ",\"b_handle\":\"" (csv:json-escape (cdr (assoc "handle" eb))) "\"" + ",\"a_typ\":\"" (cdr (assoc "typ" ea)) "\"" + ",\"b_typ\":\"" (cdr (assoc "typ" eb)) "\"" + ",\"x\":" (rtos (car (cdr (assoc "p" tr))) 2 4) + ",\"y\":" (rtos (cadr (cdr (assoc "p" tr))) 2 4) + ",\"z\":" (rtos (cdr (assoc "z" tr)) 2 4) + ",\"art\":\"" (csv:json-escape (cdr (assoc "art" tr))) "\"" + ",\"detail\":\"" (csv:json-escape (cdr (assoc "detail" tr))) "\"" + ",\"achse_von\":\"" (csv:json-escape (cdr (assoc "achse_von" tr))) "\"" + ",\"achse_rot_grad\":" (rtos (cdr (assoc "achse_rot" tr)) 2 4) + "}") +) + +(defun csv:kreisel-weichen-json-liste (fh treffer / first) + (setq first T) + (foreach tr treffer + (if (not first) (write-line "," fh)) + (princ (csv:kreisel-weichen-json-eintrag tr) fh) + (setq first nil)) + (if treffer (write-line "" fh)) +) + +(defun csv:kreisel-weichen-schreiben (label out-dir / pfad erg fh) + (setq pfad (strcat out-dir "/" (export:filename "kreisel_weichen_datei"))) + (if (findfile pfad) (vl-file-delete pfad)) + (if (car (atoms-family 1 '("KREISEL-BP-ERMITTELN"))) + (progn + (setq erg (kreisel-bp-ermitteln)) + (if (setq fh (open pfad "w")) + (progn + (write-line "{" fh) + (write-line (strcat " \"toleranz_mm\": " (rtos (kreisel-bp-toleranz) 2 4) ",") fh) + (write-line " \"beruehrungen\": [" fh) + (csv:kreisel-weichen-json-liste fh (car erg)) + (write-line " ]," fh) + (write-line " \"kollisionen\": [" fh) + (csv:kreisel-weichen-json-liste fh (cadr erg)) + (write-line " ]" fh) + (write-line "}" fh) + (close fh) + (princ (ssg-textf "exp-kreisel-weichen" + (list label (itoa (length (car erg))) (itoa (length (cadr erg))) pfad))) + (if (cadr erg) + (princ (ssg-textf "exp-kreisel-kollision" (list label (itoa (length (cadr erg))))))) + pfad + ) + (progn (princ (ssg-textf "exp-file-open-error" (list label pfad))) nil) + ) + ) + ) +) + ;; py-name = Python-Skript-Name (z.B. "export_csv.py") ;; csv-name = Ergebnis-CSV-Name (z.B. "export.csv") ;; include-bbox = T -> Bounding-Box je Block ermitteln und mit exportieren @@ -2819,6 +2888,10 @@ (list (itoa *csv-bbox-fail-count*) (cond (*csv-bbox-fail-first*) ("?")))))) + ;; Kreisel-Beruehrpunkte (Weichen) neben die Raw-Datei schreiben - + ;; die Python-Skripte uebernehmen sie statt selbst zu rechnen. + (csv:kreisel-weichen-schreiben label out-dir) + ;; Python-Export-Skript aufrufen (ausgelagert, siehe oben). (csv:python-aufrufen label out-dir out-pfad py-name csv-name) ) diff --git a/cfg/layer.cfg b/cfg/layer.cfg index 102c316..3593504 100644 --- a/cfg/layer.cfg +++ b/cfg/layer.cfg @@ -152,6 +152,12 @@ kreisel_pinbereich = pinbereich, 3 # Beschriftungstext des Kreisels (Laenge/Bezeichnung). kreisel_beschriftung = S_KRS_BESCHR, 7 +# Marker der Kreisel-Beruehrpunkte (Weichen) und -Kollisionen, gezeichnet +# vom Befehl KREISEL_WEICHEN (Lisp/KreiselInsert.lsp, kreisel-bp-*). Reine +# Hilfsgeometrie, jeder Aufruf ersetzt die Marker des vorigen Laufs. +kreisel_weiche = ZZ_Kreisel_Weichen, 4 +kreisel_kollision = ZZ_Kreisel_Kollision, 1 + # Beschriftungstext der Vario-Foerderanlage (Hoehen-/Sektionslabel). vf_beschriftung = VF_Beschriftung, 7 diff --git a/cfg/sivas.cfg b/cfg/sivas.cfg index 7e4a203..aa11925 100644 --- a/cfg/sivas.cfg +++ b/cfg/sivas.cfg @@ -107,3 +107,11 @@ Farbe= FARBE # ASCII-Attributwert (GERUEST_TYP) -> Sivas-Klartext mit Umlauten/Sonder- # zeichen (Attribute selbst bleiben ASCII, siehe CLAUDE.md-Konvention). Schoenenberger Geruest= Schönenberger-Gerüst + +[weichen] +# ILS 2.0 Weiche (6216): eine Summenzeile, Werte aus den Kreisel-Beruehr- +# punkten des LISP-Exports (kreisel_weichen.json). Ein Beruehrpunkt ist eine +# Weichenkombination = 1x aktiv + 1x passiv, darum lesen beide Merkmale +# dieselbe Zahl ANZAHL_WEICHEN (= Anzahl Beruehrpunkte, siehe export_sivas.py). +3371= ANZAHL_WEICHEN : 0 +3372= ANZAHL_WEICHEN : 0 diff --git a/data/json/component_defaults.json b/data/json/component_defaults.json index d35a647..6fb983a 100644 --- a/data/json/component_defaults.json +++ b/data/json/component_defaults.json @@ -12,7 +12,10 @@ "color_pin": 3, "toleranz_kollinear": 0.01, "toleranz_winkel": 1.0, - "toleranz_min_distanz": 0.001 + "toleranz_min_distanz": 0.001, + "toleranz_beruehrung": 1.0, + "toleranz_hoehe": 1.0, + "weiche_marker_radius": 50.0 }, "eckrad": { "default_hoehe": 2000.0 diff --git a/dcl/eckrad_edit.dcl b/dcl/eckrad_edit.dcl new file mode 100644 index 0000000..a73a6bd --- /dev/null +++ b/dcl/eckrad_edit.dcl @@ -0,0 +1,54 @@ +// ============================================================ +// eckrad_edit.dcl - Eckrad Parameter einfuegen/bearbeiten +// Wie kreisel_edit.dcl, aber ohne Abstand und Typ (ein Eckrad +// hat keine Laenge und keine Kreiselart). Die Drehung ist ein +// freier Winkel (aus dem Richtungsklick), daher edit_box statt +// der festen Kreisel-Ausrichtungen. +// ============================================================ + +eckrad_edit : dialog { + label = "Eckrad"; + + : column { + + : edit_box { + key = "name"; + label = "Bezeichnung:"; + edit_width = 30; + } + + : row { + : edit_box { + key = "hoehe"; + label = "Hoehe (mm):"; + edit_width = 10; + } + : edit_box { + key = "drehung"; + label = "Drehung (Grad):"; + edit_width = 10; + } + } + + : popup_list { + key = "drehrichtung"; + label = "Motordrehrichtung:"; + width = 30; + } + + : toggle { + key = "geruest_einzelmodul"; + label = "Geruest fuer Einzelmodul"; + } + + : popup_list { + key = "geruest_typ"; + label = "Geruestoption:"; + width = 30; + } + + } + + ok_cancel; + errtile; +} diff --git a/lang/de_DE.json b/lang/de_DE.json index 0e87048..a20a215 100644 --- a/lang/de_DE.json +++ b/lang/de_DE.json @@ -1108,6 +1108,13 @@ "sl-erk-fahrstrecke": "Omniflo Fahrstrecke", "sl-erk-ap60": "Omniflo Gerade AP60", "sl-erk-ap110": "Omniflo Gerade AP110", - "sl-erk-apg110": "Omniflo Gerade APG110" + "sl-erk-apg110": "Omniflo Gerade APG110", + "kreisel-bp-start": "Kreisel-Weichenpunkte", + "kreisel-bp-summe": "\n=== Kreisel-Weichenpunkte: %1 Beruehrung(en), %2 Kollision(en) (Toleranz %3 mm) ===", + "kreisel-bp-zeile": "\n Weiche %1: %2 <-> %3 [%4] bei X=%5 Y=%6 Z=%7", + "kreisel-bp-kollision": "\n KOLLISION %1: %2 <-> %3 [%4] bei X=%5 Y=%6 Z=%7", + "kreisel-bp-marker": "\n%1 Marker gezeichnet (Layer %2).", + "exp-kreisel-weichen": "\n[%1] Kreisel-Weichenpunkte: %2 Beruehrung(en), %3 Kollision(en) -> %4", + "exp-kreisel-kollision": "\n[%1] WARNUNG: %2 Kreisel-Kollision(en) - Befehl KREISEL_WEICHEN zeigt sie an." } } diff --git a/lang/en_GB.json b/lang/en_GB.json index 9208ce6..49b5a18 100644 --- a/lang/en_GB.json +++ b/lang/en_GB.json @@ -1108,6 +1108,13 @@ "sl-erk-fahrstrecke": "Omniflo track", "sl-erk-ap60": "Omniflo straight AP60", "sl-erk-ap110": "Omniflo straight AP110", - "sl-erk-apg110": "Omniflo straight APG110" + "sl-erk-apg110": "Omniflo straight APG110", + "kreisel-bp-start": "Kreisel switch points", + "kreisel-bp-summe": "\n=== Kreisel switch points: %1 contact(s), %2 collision(s) (tolerance %3 mm) ===", + "kreisel-bp-zeile": "\n Switch %1: %2 <-> %3 [%4] at X=%5 Y=%6 Z=%7", + "kreisel-bp-kollision": "\n COLLISION %1: %2 <-> %3 [%4] at X=%5 Y=%6 Z=%7", + "kreisel-bp-marker": "\n%1 markers drawn (layer %2).", + "exp-kreisel-weichen": "\n[%1] Kreisel switch points: %2 contact(s), %3 collision(s) -> %4", + "exp-kreisel-kollision": "\n[%1] WARNING: %2 Kreisel collision(s) - command KREISEL_WEICHEN shows them." } } diff --git a/lib/export_csv.py b/lib/export_csv.py index 8d1bb90..c2b539d 100644 --- a/lib/export_csv.py +++ b/lib/export_csv.py @@ -40,6 +40,8 @@ from export_neighbors import ( compute_sensor_zuordnung, compute_scanner_nearest_separator, compute_kreisel_touch_switches, + lisp_touch_switches, + load_lisp_kreisel_weichen, compute_sep_kette, compute_kreisel_umlauf, SENSOR_TEILEARTEN, @@ -952,7 +954,9 @@ def pruefe_ases_in_beiden_containern(items, dbg=None): return auffaellig -def process_blocks(blocks, lookup, dbg=None): +def process_blocks(blocks, lookup, dbg=None, lisp_weichen=None): + # lisp_weichen = Inhalt von kreisel_weichen.json (load_lisp_kreisel_weichen) + # oder None - dann werden die Kreisel-Beruehrpunkte hier selbst gerechnet. # 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. @@ -1535,9 +1539,15 @@ def process_blocks(blocks, lookup, dbg=None): # compute_kreisel_umlauf (damit die Weiche als Verzweigungspunkt in # BEIDEN beruehrenden Kreisel-Umlaeufen auftaucht) als auch in # build_kreisel_weiche_items (das die tatsaechlichen Zeilen erzeugt). - touch_switches = compute_kreisel_touch_switches( - items, NEIGHBOR_TOLERANCE_MM, durchmesser_mm=KREISEL_DURCHMESSER_MM, - element_box_mm=ELEMENT_BOX_MM, dbg=dbg) + # Quelle ist der LISP-Export (kreisel_weichen.json: Kreisel UND Eckrad, + # nur gleiche Hoehe, Kollisionen getrennt); fehlt die Datei, rechnet + # compute_kreisel_touch_switches wie bisher (nur Kreisel, reine XY-Pruefung). + if lisp_weichen is not None: + touch_switches = lisp_touch_switches(lisp_weichen, element_box_mm=ELEMENT_BOX_MM, dbg=dbg) + else: + touch_switches = compute_kreisel_touch_switches( + items, NEIGHBOR_TOLERANCE_MM, durchmesser_mm=KREISEL_DURCHMESSER_MM, + element_box_mm=ELEMENT_BOX_MM, dbg=dbg) # Umlauf-Reihenfolge an jedem Kreisel: freie Separatoren, BTMT-Stationen # UND die AS/ES-Enden andockender Ketten (alle zusammen KREISEL_UMLAUF_ @@ -1693,7 +1703,13 @@ def main(): dbg_file = None dbg = None - items = process_blocks(blocks, lookup, dbg=dbg) + lisp_weichen = load_lisp_kreisel_weichen(raw_json_path) + if lisp_weichen is not None: + print(f"[export_csv] Kreisel-Weichen aus LISP: " + f"{len(lisp_weichen['beruehrungen'])} Beruehrung(en), " + f"{len(lisp_weichen['kollisionen'])} Kollision(en).") + + items = process_blocks(blocks, lookup, dbg=dbg, lisp_weichen=lisp_weichen) if dbg_file: dbg_file.close() diff --git a/lib/export_neighbors.py b/lib/export_neighbors.py index 81d3e44..23de13d 100644 --- a/lib/export_neighbors.py +++ b/lib/export_neighbors.py @@ -47,6 +47,7 @@ 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 json import math import os @@ -1091,6 +1092,84 @@ def compute_kreisel_touch_switches(items, tolerance_mm, durchmesser_mm=800.0, return switches +# Dateiname der von LISP (csv:kreisel-weichen-schreiben, export.lsp) neben +# export_raw.json geschriebenen Kreisel-Beruehrpunkte. +LISP_KREISEL_WEICHEN_DATEI = "kreisel_weichen.json" + + +def load_lisp_kreisel_weichen(raw_json_path): + """Liest kreisel_weichen.json aus dem Ordner von raw_json_path. + + Die Datei schreibt der LISP-Export (KreiselInsert.lsp kreisel-bp-*): + Kreisel UND Eckraeder, nur gleiche Hoehe, mehrere Punkte je Paar moeglich, + Ueberschneidungen getrennt als "kollisionen". Rueckgabe: dict mit + "beruehrungen"/"kollisionen" oder None, wenn die Datei fehlt/unlesbar ist + (dann rechnet der Aufrufer selbst - compute_kreisel_touch_switches).""" + pfad = os.path.join(os.path.dirname(os.path.abspath(raw_json_path)), + LISP_KREISEL_WEICHEN_DATEI) + if not os.path.exists(pfad): + return None + try: + with open(pfad, encoding="utf-8", errors="replace") as f: + daten = json.load(f) + except (OSError, ValueError): + return None + if not isinstance(daten, dict): + return None + daten.setdefault("beruehrungen", []) + daten.setdefault("kollisionen", []) + return daten + + +def lisp_touch_switches(lisp_daten, element_box_mm=None, dbg=None): + """Baut aus den LISP-Beruehrpunkten (load_lisp_kreisel_weichen) dieselben + Switch-dicts wie compute_kreisel_touch_switches - damit laufen + compute_kreisel_umlauf und build_kreisel_weiche_items unveraendert. + + Partner-ID = ID-Attribut (= teileid), ohne ID der Handle. Mehrere Punkte + eines Paares bekommen eindeutige Schluessel (WEICHE#a#b, WEICHE#a#b#2, ...). + Die Box wird an achse_rot_grad (Radiale des beruehrenden Rades) ausgerichtet. + Kollisionen werden NICHT zu Weichen, nur ins Debuglog geschrieben.""" + if element_box_mm is None: + element_box_mm = load_element_box_mm() + box_l, box_b, box_h = element_box_mm + switches = [] + keys = set() + if dbg: + dbg("") + dbg("=== Kreisel-Beruehrpunkte -> ILS-Weichen (aus LISP kreisel_weichen.json) ===") + for b in lisp_daten.get("beruehrungen", []): + id_a = b.get("a") or b.get("a_handle", "") + id_b = b.get("b") or b.get("b_handle", "") + key = f"WEICHE#{id_a}#{id_b}" + n = 1 + while key in keys: + n += 1 + key = f"WEICHE#{id_a}#{id_b}#{n}" + keys.add(key) + dx, dy, dz = _oriented_box_aabb(math.radians(float(b.get("achse_rot_grad", 0.0))), + box_l, box_b, box_h) + sw = { + "key": key, + "x": float(b.get("x", 0.0)), "y": float(b.get("y", 0.0)), "z": float(b.get("z", 0.0)), + "dx": dx, "dy": dy, "dz": dz, + "nachbarn": [id_a, id_b], + "achse_von": b.get("achse_von", "") or id_a, + "beruehrung": b.get("art", ""), + } + switches.append(sw) + if dbg: + dbg(f" {id_a} <-> {id_b}: {sw['beruehrung']} ({b.get('detail', '')}) " + f"bei ({sw['x']:.0f}, {sw['y']:.0f}, {sw['z']:.0f})") + if dbg: + for k in lisp_daten.get("kollisionen", []): + dbg(f" KOLLISION {k.get('a', '')} <-> {k.get('b', '')}: {k.get('art', '')} " + f"({k.get('detail', '')}) bei ({float(k.get('x', 0)):.0f}, " + f"{float(k.get('y', 0)):.0f})") + dbg(f"Erzeugte ILS-Weichen: {len(switches)}") + return switches + + # --------------------------------------------------------------------------- # Kreisel <-> Strecken-Enden: verfeinerte Kollisionsbox fuer compute_neighbor_ # ids (die "Nachbarn"-Spalte einer GF/VF-Strecke soll nur an ihrem echten AS-/ diff --git a/lib/export_sivas.py b/lib/export_sivas.py index 3dd2ac5..0a7c277 100644 --- a/lib/export_sivas.py +++ b/lib/export_sivas.py @@ -63,6 +63,7 @@ from export_blockpatterns import (load_patterns, load_export_cfg, cfg_path_from_ matches_any, csv_quote, safe_float, safe_int) from merkmalstamm import lade_merkmalstamm, bezeichnung_fuer import sivas_mapping +from export_neighbors import load_lisp_kreisel_weichen BLOCKPATTERNS = load_patterns() @@ -297,6 +298,7 @@ def build_variofoerderer_details(block): SIVAS_MAPPING_KREISEL = sivas_mapping.load_section_mapping("kreisel") SIVAS_MAPPING_ECKRAD = sivas_mapping.load_section_mapping("eckrad") SIVAS_MAPPING_GERUEST = sivas_mapping.load_section_mapping("geruest") +SIVAS_MAPPING_WEICHEN = sivas_mapping.load_section_mapping("weichen") def build_kreisel_details(block): @@ -414,7 +416,9 @@ ILS_SIVASNR = { } -def process_blocks(blocks): +def process_blocks(blocks, anzahl_weichen_ils=0): + # anzahl_weichen_ils = Anzahl Kreisel-Beruehrpunkte aus dem LISP-Export + # (kreisel_weichen.json, siehe main) -> Merkmal 4403 der Automation-Zeile. """ Verarbeitet alle Bloecke. @@ -448,7 +452,7 @@ def process_blocks(blocks): "sep_kreisel_ohne_pin": 0, "anzahl_verbindungskreisel": 0, "laengen_kreisel": 0.0, - "anzahl_weichen_ils": 0, + "anzahl_weichen_ils": anzahl_weichen_ils, "anzahl_eckraeder": 0, "anzahl_gefaellestrecken": 0, "anzahl_variofoerderer": 0, @@ -826,6 +830,30 @@ def process_blocks(blocks): }) nr += 1 + # ILS 2.0 Weiche (6216): EINE Summenzeile fuer alle Kreisel-Beruehrpunkte + # (kreisel_weichen.json des LISP-Exports, Anzahl = anzahl_weichen_ils). + # Ein Beruehrpunkt = eine Weichenkombination aus 1x aktiv (3371) + 1x + # passiv (3372); die Zuordnung Merkmal -> Wert steht in sivas.cfg + # [weichen], beide lesen ANZAHL_WEICHEN. Anzahl-Spalte 1 wie bei der + # Automation-Zeile - die Gesamtanzahlen stecken in den Merkmalen. + if counters["anzahl_weichen_ils"] > 0: + weiche_sivasnr, weiche_teileart = ILS_SIVASNR["ils_weiche"] + items.append({ + "nr": nr, + "teileart": weiche_teileart, + "sivasnr": weiche_sivasnr, + "sivasnr_quoted": True, + "bezeichnung": "", + "dispgruppe": 20, + "anzahl": 1, + "laenge_mm": "", + "details": sivas_mapping.anwenden( + SIVAS_MAPPING_WEICHEN, + {"ANZAHL_WEICHEN": str(counters["anzahl_weichen_ils"])}, + TRANSFORMS), + }) + nr += 1 + # ILS Automation Summierungszeile (nur wenn ILS-Elemente vorhanden) has_ils = (counters["anzahl_kreisel"] > 0 or counters["anzahl_eckraeder"] > 0 @@ -986,7 +1014,14 @@ def main(): blocks = load_json(raw_json_path) print(f"[export_sivas] {len(blocks)} Bloecke geladen.") - items, _ = process_blocks(blocks) + # Kreisel-Beruehrpunkte (= ILS-Weichen) zaehlt der LISP-Export + # (kreisel_weichen.json neben der Raw-Datei); fehlt sie, bleibt 4403 = 0. + lisp_weichen = load_lisp_kreisel_weichen(raw_json_path) + anzahl_weichen = len(lisp_weichen["beruehrungen"]) if lisp_weichen else 0 + if lisp_weichen is not None: + print(f"[export_sivas] Kreisel-Weichen aus LISP: {anzahl_weichen}") + + items, _ = process_blocks(blocks, anzahl_weichen_ils=anzahl_weichen) 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 745d3c3..c5ffbed 100644 --- a/menu/SSG_LIB.cui +++ b/menu/SSG_LIB.cui @@ -44,865 +44,883 @@ ^C^C(ssg-ensure "KreiselInsert") ILS_Eckrad - + + + Eckrad bearbeiten + ^C^C(ssg-ensure "KreiselInsert") EckradEdit + + + + + Weichenpunkte ermitteln + ^C^C(ssg-ensure "KreiselInsert") KREISEL_WEICHEN + + + BTMT-Beladung ^C^C(ssg-ensure "SSG_LIB_Commands") ILS_BTMT_Beladung - + BTMT-Entladung ^C^C(ssg-ensure "SSG_LIB_Commands") ILS_BTMT_Entladung - + Scanner einfuegen ^C^C(ssg-ensure "SSG_LIB_Commands") ILS_SCANNER_INSERT - + Separator einfuegen ^C^C(ssg-ensure "SSG_LIB_Commands") ILS_SEPARATOR_INSERT - + Werte manuell ^C^C(ssg-ensure "VarioFoerderer") StandardVarioFoerderer - + aus 3D-Linie ^C^C(ssg-ensure "VarioFoerderer") StandardVarioFoerderer3D - + Werte manuell ^C^C(ssg-ensure "VarioFoerderer") EtageVarioFoerderer - + aus 3D-Linie ^C^C(ssg-ensure "VarioFoerderer") EtageVarioFoerderer3D - + Werte manuell ^C^C(ssg-ensure "VarioFoerderer") LinienzugVarioFoerdererManuell - + aus 3D Linie mit Zielhoehe ^C^C(ssg-ensure "VarioFoerderer") LinienzugVarioFoerdererZielhoehe - + 3D Objekte stueckweise (in Arbeit) ^C^C(ssg-ensure "VarioFoerderer") LinienzugVarioFoerdererStueckweise - + Vario_Kette_Merge ^C^C(ssg-ensure "VarioFoerderer") Vario_Kette_Merge - + VarioFoerderer nachruesten (Marker) ^C^C(ssg-ensure "VarioFoerderer") VARIOFOERDERER_NACHRUESTEN - + einfach ^C^C(ssg-ensure "Gefaellestrecke") GEFAELLESTRECKE_EINFACH - + aus 3D-Linie ^C^C(ssg-ensure "Gefaellestrecke") GEFAELLESTRECKE_3DLINIE - + aus Linienzug (LINE + ARC) ^C^C(ssg-ensure "Gefaellestrecke") GEFAELLESTRECKE_LINIENZUG - + QC Linie mit GF (automatische Auf-Ab-Berechnung) ^C^C(ssg-ensure "VarioFoerderer") QcLinieMitGf - + QC Linie mit GF und U-Form ^C^C(ssg-ensure "VarioFoerderer") QcLinieMitGfUForm - + Fahrstrecke ^C^C(ssg-ensure "GeruestInsert") OMNI_FAHRSTRECKE - + Fahrstrecken beschriften ^C^C(ssg-ensure "GeruestInsert") OMNI_FAHRSTRECKE_BESCHRIFTEN - + 90 ^C^C(ssg-ensure "OmniModulInsert") OMNI_APB_630_90 - + 67.5 ^C^C(ssg-ensure "OmniModulInsert") OMNI_APB_550_675 - + 45 ^C^C(ssg-ensure "OmniModulInsert") OMNI_APB_630_45 - + 22.5 ^C^C(ssg-ensure "OmniModulInsert") OMNI_APB_550_225 - + 180 ^C^C(ssg-ensure "OmniModulInsert") OMNI_APB_650_180 - + 90 ^C^C(ssg-ensure "OmniModulInsert") OMNI_APB60_90 - + AP 60 ^C^C(ssg-ensure "OmniModulInsert") OMNI_AP60 - + AP 110 ^C^C(ssg-ensure "OmniModulInsert") OMNI_AP110 - + APG 110 ^C^C(ssg-ensure "OmniModulInsert") OMNI_APG110 - + Einfach 90 ^C^C(ssg-ensure "OmniModulInsert") OMNI_W90_Einfach - + Doppel 90 ^C^C(ssg-ensure "OmniModulInsert") OMNI_W90_Doppel - + Dreiwege 90 ^C^C(ssg-ensure "OmniModulInsert") OMNI_W90_Dreiwege - + Einfach 45 ^C^C(ssg-ensure "OmniModulInsert") OMNI_W45_Einfach - + Doppel 45 ^C^C(ssg-ensure "OmniModulInsert") OMNI_W45_Doppel - + Dreiwege 45 ^C^C(ssg-ensure "OmniModulInsert") OMNI_W45_Dreiwege - + Einfach Parallel ^C^C(ssg-ensure "OmniModulInsert") OMNI_WP_Einfach - + Doppel Parallel ^C^C(ssg-ensure "OmniModulInsert") OMNI_WP_Doppel - + Dreiwege Parallel ^C^C(ssg-ensure "OmniModulInsert") OMNI_WP_Dreiwege - + Einfach 22.5 ^C^C(ssg-ensure "OmniModulInsert") OMNI_WK_Einfach - + Doppel 22.5 ^C^C(ssg-ensure "OmniModulInsert") OMNI_WK_Doppel - + Dreiwege 22.5 ^C^C(ssg-ensure "OmniModulInsert") OMNI_WK_Dreiwege - + Delta 1600/800 ^C^C(ssg-ensure "OmniModulInsert") OMNI_WKomb_Delta_1600 - + Delta 1400/700 ^C^C(ssg-ensure "OmniModulInsert") OMNI_WKomb_Delta_1400 - + Star 1400/1400 ^C^C(ssg-ensure "OmniModulInsert") OMNI_WKomb_Star - + TEF Antriebst. rechts ^C^C(ssg-ensure "OmniModulInsert") OMNI_TEF_AntriebR - + TEF Umlenkspannst. rechts ^C^C(ssg-ensure "OmniModulInsert") OMNI_TEF_UmlenkR - + TEF Antriebst. links ^C^C(ssg-ensure "OmniModulInsert") OMNI_TEF_AntriebL - + TEF Umlenkspannst. links ^C^C(ssg-ensure "OmniModulInsert") OMNI_TEF_UmlenkL - + TEF Gerade auf Linie ^C^C(ssg-ensure "TEFInsert") TEF_FOERDERER_LINIE - + TEF Gerade ^C^C(ssg-ensure "OmniModulInsert") OMNI_TEF_Gerade - + Stopper AP ^C^C(ssg-ensure "OmniModulInsert") OMNI_STOPPER - + Scanner ^C^C(ssg-ensure "OmniModulInsert") OMNI_SCANNER - + Bürstenbremse ^C^C(ssg-ensure "OmniModulInsert") OMNI_BUERSTENBREMSE - + Schwenkschiene Schiebetür L ^C^C(ssg-ensure "OmniModulInsert") OMNI_SCHW_400_L - + Schwenkschiene Schiebetür R ^C^C(ssg-ensure "OmniModulInsert") OMNI_SCHW_400_R - + Schwenkschiene Schiebetür L mit Sensorik ^C^C(ssg-ensure "OmniModulInsert") OMNI_SCHW_400_L_SENSORIK - + Schwenkschiene Schiebetür R mit Sensorik ^C^C(ssg-ensure "OmniModulInsert") OMNI_SCHW_400_R_SENSORIK - + KettenFoerderer ^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 - + Rohr-Gerüst-Traeger ^C^C(ssg-ensure "GeruestInsert") GERUEST_TRAEGER - + Säulen ^C^C(ssg-ensure "GeruestInsert") GERUEST_SAEULEN - + Rohrabhänger ^C^C(ssg-ensure "GeruestInsert") GERUEST_ROHRABHAENGER - + Wandbefestigung ^C^C(ssg-ensure "GeruestInsert") GERUEST_WANDBEFESTIGUNG - + Gatterträger ^C^C(ssg-ensure "GeruestInsert") GERUEST - + Traeger beschriften ^C^C(ssg-ensure "GeruestInsert") GERUEST_TRAEGER_BESCHRIFTEN - + 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 - + Bloecke zaehlen ^C^C(ssg-ensure "ssg_blocktools") SSG_BLOCK_COUNT - + Bloecke loeschen ^C^C(ssg-ensure "ssg_blocktools") SSG_BLOCK_DELETE - + Attribute ersetzen ^C^C(ssg-ensure "ssg_blocktools") SSG_ATTR_ERSETZEN - + Text ersetzen ^C^C(ssg-ensure "ssg_texttools") SSG_TEXT_ERSETZEN - + Text parallel beschriften ^C^C(ssg-ensure "ssg_texttools") SSG_TEXT_PARALLEL - + Texthoehe aendern ^C^C(ssg-ensure "ssg_texttools") SSG_TEXT_HOEHE - + Textstil aendern ^C^C(ssg-ensure "ssg_texttools") SSG_TEXT_STIL - + Faehnchen + Stueckliste aus Zeichnung ^C^C(ssg-ensure "ssg_stueckliste") SSG_FAEHNCHEN_STUECKLISTE - + Din Formate ^C^C(ssg-ensure "DinFormate") DIN_FORMATE - + 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) - + Dematic/Arvato Flowpuffer (Vorlage) ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_dematic_arvato.lsp")) (c:TEST_DEMATIC_ARVATO) - + ILS 3.0 Showroom (Vorlage) ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_showroom_ils.lsp")) (c:TEST_SHOWROOM_ILS) - + KS 2D/3D pruefen (CSV) ^C^C(load (strcat (getenv "DXFMAKRO") "/Lisp/ks_pruefung.lsp")) KS_PRUEFEN - + Kreisel bearbeiten (ssg-ensure "KreiselInsert") KreiselEdit - + + + Eckrad bearbeiten + (ssg-ensure "KreiselInsert") EckradEdit + + + 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 @@ -918,20 +936,20 @@ POP1 SSG_LIB - - - - + + + - - + + + ILS - - - + + + Kreisel-Module @@ -959,70 +977,68 @@ + + Eckrad bearbeiten + + + + + + + Weichenpunkte ermitteln + + + + - + Zusatzmodul BTMT-Beladung - + BTMT-Entladung - + Scanner einfuegen - + Separator einfuegen - + - + Foerderer - + Vario_Kette_Merge - + VarioFoerderer nachruesten (Marker) - - - - - - Standard - - Werte manuell - - - - - - aus 3D-Linie - - + - Etage + Standard Werte manuell @@ -1037,7 +1053,7 @@ - Streckengruppe + Etage Werte manuell @@ -1045,902 +1061,923 @@ - aus 3D Linie mit Zielhoehe + aus 3D-Linie + + + Streckengruppe + + Werte manuell + + + + + + aus 3D Linie mit Zielhoehe + + + + 3D Objekte stueckweise (in Arbeit) - + - + Gefaellestrecke einfach - - - - - - aus 3D-Linie - - - - - - aus Linienzug (LINE + ARC) - - - - - - QC Linie mit GF (automatische Auf-Ab-Berechnung) - QC Linie mit GF und U-Form + aus 3D-Linie - - - Omniflo - Fahrstrecke + aus Linienzug (LINE + ARC) + + + + + + QC Linie mit GF (automatische Auf-Ab-Berechnung) - Fahrstrecken beschriften + QC Linie mit GF und U-Form + + + Omniflo + + Fahrstrecke + + + + + + Fahrstrecken beschriften + + + + - - - - - - - + + + + + + + ~KettenFoerderer - + Ladeschleifen - + - - + + Element bearbeiten - + - + Boegen APB110 90 - - - - - - 67.5 - - - - - - 45 - - - - - - 22.5 - 180 + 67.5 - - - Boegen APB60 - 90 + 45 + + + + + + 22.5 + + 180 + + + + - + + Boegen APB60 + + 90 + + + + + + Aluprofil Gerade AP 60 - + AP 110 - + APG 110 - + - + Weichen Einfach 90 - + Doppel 90 - + Dreiwege 90 - + Einfach 45 - + Doppel 45 - + Dreiwege 45 - + Einfach Parallel - + Doppel Parallel - + Dreiwege Parallel - + Einfach 22.5 - + Doppel 22.5 - + Dreiwege 22.5 - + Delta 1600/800 - + Delta 1400/700 - + Star 1400/1400 - - - - - - - TEF Elemente - - TEF Antriebst. rechts - - - - - - TEF Umlenkspannst. rechts - + + + TEF Elemente - TEF Antriebst. links + TEF Antriebst. rechts - TEF Umlenkspannst. links + TEF Umlenkspannst. rechts - TEF Gerade auf Linie + TEF Antriebst. links - TEF Gerade + TEF Umlenkspannst. links - - - Anbaugruppen + - Stopper AP + TEF Gerade auf Linie - Scanner + TEF Gerade + + + Anbaugruppen + + Stopper AP + + + + + + Scanner + + + + Bürstenbremse - + - + Spez. Streckenelemente - + - + Türlösungen - + - + Schwenkschienen Schwenkschiene Schiebetür L - + Schwenkschiene Schiebetür R - + Schwenkschiene Schiebetür L mit Sensorik - + Schwenkschiene Schiebetür R mit Sensorik - + - + Verbinder ~TV W-W kpl - - - - - - ~TV W-AP110 kpl - - - - - - ~TV W-AP60 oben kpl. - - - - - - ~TV W-AP110-W kpl - ~TV W-AP oben kpl + ~TV W-AP110 kpl - ~TV 80 + ~TV W-AP60 oben kpl. - ~TV 50 + ~TV W-AP110-W kpl - ~Endplatte Al-Profil AP schraubbar + ~TV W-AP oben kpl - ~Anschlag AP-Ende fuer Spulenzug + ~TV 80 - ~TV-A fuer AP110/AP60 kpl + ~TV 50 - ~TV-A fuer AP60/ST + ~Endplatte Al-Profil AP schraubbar - ~TV-A fuer AP110/ST kpl + ~Anschlag AP-Ende fuer Spulenzug - ~TV-SW II fuer SW-SW + ~TV-A fuer AP110/AP60 kpl - ~TV-SW III fuer SW-AP110-SW + ~TV-A fuer AP60/ST - ~TV-SW III fuer SW-AP110 + ~TV-A fuer AP110/ST kpl - ~TV-SW IV fuer Anschluss SW-AP60 + ~TV-SW II fuer SW-SW - ~TV-SW IV fuer SW-ST + ~TV-SW III fuer SW-AP110-SW - ~Muffe + ~TV-SW III fuer SW-AP110 - - - Boegen fuer Weichen - ~APB 110 R400/45 140 nur fuer Delta + ~TV-SW IV fuer Anschluss SW-AP60 + + + + + + ~TV-SW IV fuer SW-ST - ~APB 110 R550/45 194 nur fuer Delta + ~Muffe + + + Boegen fuer Weichen - ~APBW 110 R550/22,5 360x248,5 - - - - - - ~APBW 110 R550/22,5 410x298,5 + ~APB 110 R400/45 140 nur fuer Delta - ~APBW 110 R550/22,5 410x78,5 + ~APB 110 R550/45 194 nur fuer Delta - ~APBW 110 R550/22,5 410x98,5 + ~APBW 110 R550/22,5 360x248,5 - ~APBW 110 R550/67,5 360x598,5 + ~APBW 110 R550/22,5 410x298,5 - ~APBW 110 R400/67,5 260x398,5 + ~APBW 110 R550/22,5 410x78,5 - ~APBW 110 R550/45 77x160 + ~APBW 110 R550/22,5 410x98,5 - ~APBW 110 R550/67,5 125x83,5 + ~APBW 110 R550/67,5 360x598,5 - ~APBW 110 R750/90 200x290 + ~APBW 110 R400/67,5 260x398,5 - ~APBW 110 R650/90 300x190 + ~APBW 110 R550/45 77x160 - ~APBW 110 R550/67,5 208x347 + ~APBW 110 R550/67,5 125x83,5 - ~APBW 110 R550/67,5 58x497 + ~APBW 110 R750/90 200x290 - - - Gerüst - Rohr-Gerüst-Traeger + ~APBW 110 R650/90 300x190 + + + + + + ~APBW 110 R550/67,5 208x347 + + + + + + ~APBW 110 R550/67,5 58x497 + + + Gerüst - Säulen - - - - - - Rohrabhänger - - - - - - Wandbefestigung + Rohr-Gerüst-Traeger - Gatterträger + Säulen - - - - Funktionen - Traeger beschriften + Rohrabhänger + + + + + + Wandbefestigung - - - SPS + + Gatterträger + + + + - + + + Funktionen + + Traeger beschriften + + + + + + + SPS + + Eingang - + Ausgang - + SPS Schliessen - + - + TRO Marker einfuegen - + Marker bearbeiten - + Bearbeiten (Auto) - + - + Connections Verbindung einfuegen - + Verbindung bearbeiten - + Bearbeiten (Auto) - - - - - - Export - - Export Sivas - - - - - - Export CSV - - + - Konfiguration + Export - Umschalten 2D und 3D + Export Sivas - Alle Bloecke auf 2D + Export CSV + + + Konfiguration - Alle Bloecke auf 3D - - - - - - Sprache + Umschalten 2D und 3D - Planquadrate an/aus + Alle Bloecke auf 2D - CSV-Export + Alle Bloecke auf 3D - - - Werkzeuge - Bloecke zaehlen + Sprache + + + + + + Planquadrate an/aus - Bloecke loeschen + CSV-Export + + + Werkzeuge + + Bloecke zaehlen + + + + + + Bloecke loeschen + + + + Attribute ersetzen - + Text ersetzen - + Text parallel beschriften - + Texthoehe aendern - + Textstil aendern - + Faehnchen + Stueckliste aus Zeichnung - + Din Formate - + - + Tests alle Tests ausfuehren und exportieren - + Tests ausfuehren und stehen lassen - + Export CSV/Sivas ALL - + Kreisel - + Foerderer - + Foerderer-Linienzug - + Omniflo - + Omniflo Strecke - + Gefaellestrecke - + Mubea Gesamtmodell - + HundM05 Kreisel-Abschnitt - + Dematic/Arvato Flowpuffer (Vorlage) - + ILS 3.0 Showroom (Vorlage) - + KS 2D/3D pruefen (CSV) - + - + POP501 SSG_LIB Edit Context Kreisel bearbeiten - - - - - - Omniflo Element bearbeiten - - - - - - Gefaellestrecke bearbeiten - - - - - - VarioFoerderer bearbeiten - TRO-Marker bearbeiten + Eckrad bearbeiten - Verbindung 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 ead1030..8eee998 100644 --- a/menu/SSG_LIB.mnu +++ b/menu/SSG_LIB.mnu @@ -8,7 +8,9 @@ [Verbinden]^C^C(ssg-ensure "KreiselInsert") KreiselConnect [Bearbeiten]^C^C(ssg-ensure "KreiselInsert") KreiselEdit [Eckrad]^C^C(ssg-ensure "KreiselInsert") ILS_Eckrad -[<-Eckrad bearbeiten]^C^C(ssg-ensure "KreiselInsert") EckradEdit +[Eckrad bearbeiten]^C^C(ssg-ensure "KreiselInsert") EckradEdit +[--] +[<-Weichenpunkte ermitteln]^C^C(ssg-ensure "KreiselInsert") KREISEL_WEICHEN [->Zusatzmodul] [BTMT-Beladung]^C^C(ssg-ensure "SSG_LIB_Commands") ILS_BTMT_Beladung [BTMT-Entladung]^C^C(ssg-ensure "SSG_LIB_Commands") ILS_BTMT_Entladung diff --git a/tests/test_export_sivas_eckrad.py b/tests/test_export_sivas_eckrad.py new file mode 100644 index 0000000..fcd3a57 --- /dev/null +++ b/tests/test_export_sivas_eckrad.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +test_export_sivas_eckrad.py - Sivas-Merkmale des ILS Eckrads (6210), +am Kreisel ausgerichtet (cfg/sivas.cfg [eckrad]). +Laeuft komplett ohne BricsCAD (reine Python-Logik). +""" + +import os +import sys + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "lib")) + +import export_sivas # noqa: E402 + + +def _eckrad(nr, hoehe="2000", einzelmodul="1", typ="Schoenenberger Geruest"): + return {"block_name": f"ECKRAD_{nr}", "x": 0.0, "y": 0.0, "z": float(hoehe), + "rotation": 0.0, + "attribs": {"ID": f"000{nr}", "Bezeichnung": f"Eckrad {nr}", + "NUMMER": str(nr), "HOEHE": hoehe, + "GERUEST_EINZELMODUL": einzelmodul, "GERUEST_TYP": typ}} + + +def test_eckrad_details_wie_kreisel(): + details = export_sivas.build_eckrad_details(_eckrad(1)) + assert details == {"2950": "2.0", "3381": "1", "3992": "Ja", + "3993": export_sivas.geruest_typ_anzeige("Schoenenberger Geruest")} + + +def test_eckrad_ohne_geruest_ist_nein(): + details = export_sivas.build_eckrad_details(_eckrad(1, einzelmodul="0")) + assert details["3992"] == "Nein" + + +def test_eckrad_hoehe_in_metern(): + assert export_sivas.build_eckrad_details(_eckrad(1, hoehe="4820"))["2950"] == "4.82" + + +def _kreisel(nr, montagehoehe): + return {"block_name": f"KREISEL_{nr}", "x": 0.0, "y": 0.0, "z": 0.0, + "rotation": 0.0, + "attribs": {"ID": f"000{nr}", "NUMMER": str(nr), "KREISELART": "STANDARD", + "ABSTAND": "2300", "MONTAGEHOEHE_m": montagehoehe}} + + +def test_kreisel_verschiedene_hoehen_getrennte_zeilen(): + assert (export_sivas.kreisel_signature(_kreisel(1, "4.820")) + != export_sivas.kreisel_signature(_kreisel(2, "2.000"))) + # numerischer Vergleich: gleiche Hoehe, andere Schreibweise -> eine Zeile + assert (export_sivas.kreisel_signature(_kreisel(1, "4.820")) + == export_sivas.kreisel_signature(_kreisel(2, "4.82"))) + + +def test_eckrad_verschiedene_hoehen_getrennte_zeilen(): + assert (export_sivas.eckrad_signature(_eckrad(1, hoehe="2000")) + != export_sivas.eckrad_signature(_eckrad(2, hoehe="2500"))) + assert (export_sivas.eckrad_signature(_eckrad(1, hoehe="2000")) + == export_sivas.eckrad_signature(_eckrad(2, hoehe="2000"))) diff --git a/tests/test_kreisel_weichen_json.py b/tests/test_kreisel_weichen_json.py new file mode 100644 index 0000000..b44557f --- /dev/null +++ b/tests/test_kreisel_weichen_json.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +test_kreisel_weichen_json.py - Uebernahme der LISP-Kreisel-Beruehrpunkte. + +Der LISP-Export (export.lsp csv:kreisel-weichen-schreiben, Kern +KreiselInsert.lsp kreisel-bp-*) schreibt kreisel_weichen.json neben +export_raw.json. export_csv.py/export_sivas.py lesen sie statt selbst zu +rechnen; fehlt sie, bleibt es beim alten Python-Rueckfall. +Laeuft ohne BricsCAD. +""" + +import json +import os +import sys + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "lib")) + +from export_neighbors import ( # noqa: E402 + lisp_touch_switches, + load_lisp_kreisel_weichen, +) +import export_sivas # noqa: E402 + +BOX = (250.0, 100.0, 30.0) + +BEISPIEL = { + "toleranz_mm": 1.0, + "beruehrungen": [ + # Rad an Laufprofil, Radiale senkrecht (90 Grad) + {"a": "0001", "b": "0002", "a_handle": "2A", "b_handle": "2B", + "a_typ": "KREISEL", "b_typ": "KREISEL", "x": 22503.49, "y": -2821.76, + "z": 5000.0, "art": "Kreis/Tangente", "detail": "AN8/Profil", + "achse_von": "0002", "achse_rot_grad": 270.0}, + # Zweiter Punkt desselben Paares -> eigener Schluessel + {"a": "0001", "b": "0002", "a_handle": "2A", "b_handle": "2B", + "a_typ": "KREISEL", "b_typ": "KREISEL", "x": 23000.0, "y": -2821.76, + "z": 5000.0, "art": "Kreis/Kreis", "detail": "SP8/AN8", + "achse_von": "0001", "achse_rot_grad": 0.0}, + # Eckrad ohne ID -> Handle + {"a": "", "b": "0003", "a_handle": "3F", "b_handle": "3C", + "a_typ": "ECKRAD", "b_typ": "KREISEL", "x": 1.0, "y": 2.0, "z": 0.0, + "art": "Kreis/Kreis", "detail": "ECKRAD/AN8", + "achse_von": "", "achse_rot_grad": 0.0}, + ], + "kollisionen": [ + {"a": "0004", "b": "0005", "x": 0.0, "y": 0.0, "z": 0.0, + "art": "Ueberschneidung", "detail": "Achsabstand 500.0", + "achse_von": "0004", "achse_rot_grad": 0.0}, + ], +} + + +def _schreibe(tmp_path, daten): + raw = tmp_path / "export_raw.json" + raw.write_text("[]", encoding="utf-8") + (tmp_path / "kreisel_weichen.json").write_text(json.dumps(daten), encoding="utf-8") + return str(raw) + + +def test_datei_fehlt_liefert_none(tmp_path): + raw = tmp_path / "export_raw.json" + raw.write_text("[]", encoding="utf-8") + assert load_lisp_kreisel_weichen(str(raw)) is None + + +def test_kaputte_datei_liefert_none(tmp_path): + raw = tmp_path / "export_raw.json" + raw.write_text("[]", encoding="utf-8") + (tmp_path / "kreisel_weichen.json").write_text("{kaputt", encoding="utf-8") + assert load_lisp_kreisel_weichen(str(raw)) is None + + +def test_switches_aus_lisp(tmp_path): + daten = load_lisp_kreisel_weichen(_schreibe(tmp_path, BEISPIEL)) + sw = lisp_touch_switches(daten, element_box_mm=BOX) + # Kollisionen werden keine Weichen + assert len(sw) == 3 + assert [s["key"] for s in sw] == ["WEICHE#0001#0002", "WEICHE#0001#0002#2", + "WEICHE#3F#0003"] + assert sw[0]["nachbarn"] == ["0001", "0002"] + assert sw[0]["beruehrung"] == "Kreis/Tangente" + assert sw[0]["achse_von"] == "0002" + assert (sw[0]["x"], sw[0]["y"], sw[0]["z"]) == (22503.49, -2821.76, 5000.0) + # Radiale 270 Grad: Laengsseite (250) quer zur Radialen -> entlang X + assert abs(sw[0]["dx"] - 250.0) < 1e-6 and abs(sw[0]["dy"] - 100.0) < 1e-6 + # Radiale 0 Grad: Laengsseite entlang Y + assert abs(sw[1]["dx"] - 100.0) < 1e-6 and abs(sw[1]["dy"] - 250.0) < 1e-6 + # Ohne achse_von -> erster Partner + assert sw[2]["achse_von"] == "3F" + + +def test_sivas_4403_zaehlt_beruehrungen(): + _, counters = export_sivas.process_blocks([], anzahl_weichen_ils=10) + assert counters["anzahl_weichen_ils"] == 10 + _, counters = export_sivas.process_blocks([]) + assert counters["anzahl_weichen_ils"] == 0 + + +def test_sivas_6216_summenzeile(): + """Eine Zeile 6216, je Beruehrpunkt 1x aktiv (3371) + 1x passiv (3372).""" + items, _ = export_sivas.process_blocks([], anzahl_weichen_ils=10) + weichen = [i for i in items if i["sivasnr"] == "6216"] + assert len(weichen) == 1 + w = weichen[0] + assert w["teileart"] == "ILS 2.0 Weiche" + assert w["anzahl"] == 1 + assert w["details"] == {"3371": "10", "3372": "10"} + # CSV-Zeile laesst sich formatieren + assert ';"6216";' in export_sivas.format_csv_line(w) + + +def test_sivas_6216_ohne_weichen_keine_zeile(): + items, _ = export_sivas.process_blocks([]) + assert not [i for i in items if i["sivasnr"] == "6216"] diff --git a/tests/test_unit.lsp b/tests/test_unit.lsp index 0b17433..1b4c047 100644 --- a/tests/test_unit.lsp +++ b/tests/test_unit.lsp @@ -756,6 +756,99 @@ ) +;; ------------------------------------------------------------ +;; TESTS: KreiselInsert.lsp - Beruehrpunkte/Weichen (kreisel-bp-*) +;; ------------------------------------------------------------ +;; Element-Record kurz: (typ x y z rot abstand id) +(defun tu-bp-el (typ x y z rot abstand id) + (kreisel-bp-element typ (list x y) z rot abstand 400.0 id id id) +) + +(defun tu-bp-arten (treffer) + (mapcar '(lambda (tr) (cdr (assoc "art" tr))) treffer) +) + +(defun tu-tests-kreisel-bp ( / _els _erg _ber _kol) + (princ "\n[KreiselInsert/Beruehrpunkte]") + + ;; Hilfs-Kerne + (tu-eqf "kreisel-bp-abstand/attribut" 7715.0 (kreisel-bp-abstand "7715" "KREISEL_1_STANDARD_7715")) + (tu-eqf "kreisel-bp-abstand/blockname" 3469.0 (kreisel-bp-abstand nil "KREISEL_3_STANDARD_3469")) + (tu-eqf "kreisel-bp-naechste-punkte/kreuz" + '((5.0 0.0) (5.0 0.0)) + (kreisel-bp-naechste-punkte '(0.0 0.0) '(10.0 0.0) '(5.0 -5.0) '(5.0 5.0))) + + ;; Die 11 Einfuegungen aus tests/testdata/schnittspunkte_kreisel.dxf + (setq _els + (list + (tu-bp-el "ECKRAD" 22405.42028816571 -4021.762548616969 5000.0 270.0 0.0 "E1") + (tu-bp-el "KREISEL" 20971.14247926632 -3221.762548616969 5000.0 0.0 7715.0 "K1") + (tu-bp-el "KREISEL" 22503.49142858727 -2821.762548616969 5000.0 90.0 9020.0 "K2") + (tu-bp-el "KREISEL" 27253.08134883916 -3621.762548616969 5000.0 270.0 3469.0 "K3") + (tu-bp-el "KREISEL" 22503.49142858727 6998.075803812235 5000.0 90.0 4746.0 "K4") + (tu-bp-el "KREISEL" 22405.42028816571 -4421.762548616969 5000.0 270.0 6147.0 "K5") + (tu-bp-el "KREISEL" 26853.08134883916 -6582.442351476258 5000.0 180.0 3248.0 "K6") + (tu-bp-el "KREISEL" 32541.84792939903 -5752.219206460449 2000.0 270.0 9200.0 "K7") + (tu-bp-el "KREISEL" 32941.84792939903 -7662.266486416901 2000.0 0.0 3637.0 "K8a") + (tu-bp-el "KREISEL" 32941.84792939903 -11185.64734962363 2000.0 0.0 3637.0 "K8b") + (tu-bp-el "ECKRAD" 20571.14247926632 -3221.762548616969 5000.0 270.0 0.0 "E1b"))) + (setq _erg (kreisel-bp-berechnen _els 1.0 1.0) + _ber (car _erg) + _kol (cadr _erg)) + (tu-eq "kreisel-bp/dxf-beruehrungen" 10 (length _ber)) + (tu-eq "kreisel-bp/dxf-kollisionen" 0 (length _kol)) + (tu-eq "kreisel-bp/dxf-kreis-kreis" 3 + (length (vl-remove-if-not '(lambda (a) (= a "Kreis/Kreis")) (tu-bp-arten _ber)))) + (tu-eq "kreisel-bp/dxf-paare" + '(("E1" "K1") ("E1" "K5") ("K1" "K2") ("K1" "K3") ("K1" "E1b") ("K2" "K4") + ("K3" "K6") ("K5" "K6") ("K7" "K8a") ("K7" "K8b")) + (mapcar '(lambda (tr) (list (cdr (assoc "id" (cdr (assoc "a" tr)))) + (cdr (assoc "id" (cdr (assoc "b" tr)))))) + _ber)) + ;; K2 (AN8) steht auf dem oberen Laufprofil von K1 + (tu-eqf "kreisel-bp/dxf-punkt-k1-k2" + (list 22503.49142858727 -2821.762548616969) + (cdr (assoc "p" (nth 2 _ber)))) + (tu-eq "kreisel-bp/dxf-achse-von-k1-k2" "K2" (cdr (assoc "achse_von" (nth 2 _ber)))) + + ;; Unterschiedliche Hoehe -> kein Kontakt + (tu-eq "kreisel-bp/hoehe" + '(nil nil) + (kreisel-bp-berechnen (list (tu-bp-el "ECKRAD" 0.0 0.0 0.0 0.0 0.0 "A") + (tu-bp-el "ECKRAD" 800.0 0.0 500.0 0.0 0.0 "B")) + 1.0 1.0)) + ;; Zwei Eckraeder Rad an Rad -> eine Weiche in der Mitte + (setq _ber (car (kreisel-bp-berechnen (list (tu-bp-el "ECKRAD" 0.0 0.0 0.0 0.0 0.0 "A") + (tu-bp-el "ECKRAD" 800.0 0.0 0.0 0.0 0.0 "B")) + 1.0 1.0))) + (tu-eq "kreisel-bp/eckraeder-art" '("Kreis/Kreis") (tu-bp-arten _ber)) + (tu-eqf "kreisel-bp/eckraeder-punkt" '(400.0 0.0) (cdr (assoc "p" (car _ber)))) + ;; Spalt groesser als Toleranz -> nichts + (tu-eq "kreisel-bp/spalt" + '(nil nil) + (kreisel-bp-berechnen (list (tu-bp-el "ECKRAD" 0.0 0.0 0.0 0.0 0.0 "A") + (tu-bp-el "ECKRAD" 805.0 0.0 0.0 0.0 0.0 "B")) + 1.0 1.0)) + ;; Laufprofile kreuzen sich -> Kollision, keine Weiche + (setq _erg (kreisel-bp-berechnen (list (tu-bp-el "KREISEL" 0.0 0.0 0.0 0.0 2000.0 "A") + (tu-bp-el "KREISEL" 1000.0 -1500.0 0.0 90.0 2000.0 "B")) + 1.0 1.0)) + (tu-nil "kreisel-bp/kreuzung-keine-weiche" (car _erg)) + (tu-eq "kreisel-bp/kreuzung-kollision" '("Ueberschneidung") (tu-bp-arten (cadr _erg))) + ;; Kreisel Seite an Seite (Profile aufeinander) -> Kollision, keine Weiche + (setq _erg (kreisel-bp-berechnen (list (tu-bp-el "KREISEL" 0.0 0.0 0.0 0.0 2000.0 "A") + (tu-bp-el "KREISEL" 500.0 800.0 0.0 0.0 2000.0 "B")) + 1.0 1.0)) + (tu-nil "kreisel-bp/parallel-keine-weiche" (car _erg)) + (tu-eq "kreisel-bp/parallel-kollision" '("Tangente/Tangente") (tu-bp-arten (cadr _erg))) + ;; Eckrad an der Flanke eines Kreiselrades -> Weiche (freie Seite) + (setq _ber (car (kreisel-bp-berechnen (list (tu-bp-el "KREISEL" 0.0 0.0 0.0 0.0 2000.0 "A") + (tu-bp-el "ECKRAD" 400.0 800.0 0.0 0.0 0.0 "B")) + 1.0 1.0))) + (tu-eq "kreisel-bp/flanke-eckrad" 1 (length _ber)) +) + + ;; ------------------------------------------------------------ ;; TESTS: ssg_ks_insert.lsp - Sepliste-Serialisierung ;; ------------------------------------------------------------ @@ -2046,7 +2139,7 @@ (foreach fn '("tu-tests-core" "tu-tests-lang" "tu-tests-id" "tu-tests-vfcore" "tu-tests-etage" - "tu-tests-export" "tu-tests-hoehen" "tu-tests-gf" "tu-tests-omni" "tu-tests-kreisel" + "tu-tests-export" "tu-tests-hoehen" "tu-tests-gf" "tu-tests-omni" "tu-tests-kreisel" "tu-tests-kreisel-bp" "tu-tests-sepliste" "tu-tests-json" "tu-tests-geruest" "tu-tests-sepscan" "tu-tests-vfl-segs" "tu-tests-vfl-abbruch" "tu-tests-werkzeuge")