From b8e8d0b0a0149f122dad27b1a2ed1c9cdfdbd8ee Mon Sep 17 00:00:00 2001 From: Yuelin Wang Date: Fri, 23 Oct 2026 15:34:27 +0200 Subject: [PATCH] feat: Kettenfoerderer (Schraegfoerderer Stahl) als parametrische 3D-Baugruppe Neues Modul KettenFoerderer.lsp: baut aus KF_UM_, KF_LAENGE und KF_AN_ (je KS_AUS auf KS_EIN) plus der Draufsicht KF_2D einen Block KF_. Die Bausteine werden aufgerichtet eingebaut (Rz*Rx(90)*Rz), die Hoehe laeuft in Z; KF_2D liegt deckungsgleich mit der Kette. Der Einbauwinkel jedes Folgebausteins wird aus der Richtung des KS_AUS seines Vorgaengers gemessen, KF_LAENGE ueber die Einbauhoehe skaliert. - Dialog (DCL-Vorlage, Typ als Radiobuttons), Hoehe per Zahl oder aus zwei Punkten, Doppelklick auf KF_n oeffnet KF_EDIT mit Neuaufbau - eigenstaendiges Attribut-Schema, Auswahllisten mit Sivas-Optionstexten in data/json/kettenfoerderer.json - Sivas-Export: Merkmale 350, 353-357, 359-361, 391, baugleiche Foerderer werden zu einer Zeile zusammengefasst (Artikelnummer 6120) - ssg_ks_insert.lsp: neue Primitiven ks-xy-winkel, ks-block-messen, insert-block-ks-skaliert und insert-block-ks-matrix (freie 3x3-Rotation); ensure-block-loaded nimmt Bloecke ohne Dimensionssuffix unveraendert - Menueeintrag KettenFoerderer aktiviert, toter Dummy OMNI_KettenFoerderer entfernt; KF_* im Export-Pattern, in der ID-Vergabe und im Dispatcher SSG_BLOCKEDIT (vor dem Omniflo-Zweig) - Unit-Tests tu-tests-kf fuer die reinen Rechenkerne; Modul wird in der MNL vorgeladen, dbg-Schalter kf-zeit zur Laufzeitmessung Co-Authored-By: Claude Sonnet 5.5 --- CLAUDE.md | 7 +- Lisp/KettenFoerderer.lsp | 1160 ++++++++++++++++++++++++++++++++ Lisp/OmniModulInsert.lsp | 10 +- Lisp/README.md | 11 +- Lisp/SSG_LIB_Commands.lsp | 18 + Lisp/export.lsp | 6 + Lisp/ssg_dbg.lsp | 6 +- Lisp/ssg_id.lsp | 4 + Lisp/ssg_ks_insert.lsp | 287 +++++++- cfg/export.cfg | 10 + cfg/layer.cfg | 7 + cfg/sivas.cfg | 46 ++ data/json/kettenfoerderer.json | 67 ++ data/omniflo/2D/KF_2D.dwg | Bin 0 -> 11200 bytes data/omniflo/2D/KF_AN_26.dwg | Bin 0 -> 45086 bytes data/omniflo/2D/KF_AN_30.dwg | Bin 0 -> 40543 bytes data/omniflo/2D/KF_LAENGE.dwg | Bin 0 -> 12408 bytes data/omniflo/2D/KF_UM_26.dwg | Bin 0 -> 29984 bytes data/omniflo/2D/KF_UM_30.dwg | Bin 0 -> 30113 bytes dcl/kettenfoerderer.dcl | 104 +++ lang/de_DE.json | 52 +- lang/en_GB.json | 52 +- lib/export_sivas.py | 115 ++++ menu/SSG_LIB.cui | 4 +- menu/SSG_LIB.mnl | 5 + menu/SSG_LIB.mnu | 2 +- tests/test_unit.lsp | 254 ++++++- tests/test_unit.scr | 1 + 28 files changed, 2207 insertions(+), 21 deletions(-) create mode 100644 Lisp/KettenFoerderer.lsp create mode 100644 data/json/kettenfoerderer.json create mode 100644 data/omniflo/2D/KF_2D.dwg create mode 100644 data/omniflo/2D/KF_AN_26.dwg create mode 100644 data/omniflo/2D/KF_AN_30.dwg create mode 100644 data/omniflo/2D/KF_LAENGE.dwg create mode 100644 data/omniflo/2D/KF_UM_26.dwg create mode 100644 data/omniflo/2D/KF_UM_30.dwg create mode 100644 dcl/kettenfoerderer.dcl diff --git a/CLAUDE.md b/CLAUDE.md index 2b0b848..c13e307 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -75,13 +75,14 @@ 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 | +| Kettenfoerderer | `KettenFoerderer` | KETTENFOERDERER (Menue: Omniflo -> KettenFoerderer), KF_EDIT (Doppelklick auf KF_n). KF_KS_PRUEFEN ist ein reiner Diagnosebefehl und bewusst NICHT im Menue | | Omniflo (komplett) | `OmniModulInsert` + `GeruestInsert` | OMNI_APB_*, OMNI_W*_*, OMNI_AP*, OMNI_TEF_*, OMNI_TV_*, OMNI_LADESCHLEIFE_*, OMNI_EDIT; OMNI_FAHRSTRECKE / OMNI_FAHRSTRECKE_BESCHRIFTEN (GeruestInsert, Linien-Logik wie GERUEST_TRAEGER) | | Geruest | `GeruestInsert` | GERUEST_SAEULEN, GERUEST_ROHRABHAENGER, GERUEST_WANDBEFESTIGUNG (je eigener Dialog aus der Vorlage geruest.dcl, keine Umschaltung), GERUEST_TRAEGER (eigener Dialog), GERUEST_TRAEGER_BESCHRIFTEN (Untermenue Geruest -> Funktionen -> Traeger beschriften; alle Traeger der Zeichnung neu beschriften); Gattertraeger noch Platzhalter auf GERUEST (zeigt nur einen Hinweis) | | Connections | `Connection_Edit` + `SSG_LIB_Commands` | CONNECTION_INSERT, CONNECTION_EDIT, ILS_EINGANG_INSERT, ILS_AUSGANG_INSERT | | Export | `export` | EXPORTSIVAS, EXPORTCSV, CALLPYTHON | | Werkzeuge | `ssg_blocktools`, `ssg_texttools`, `ssg_stueckliste`, `DinFormate` | SSG_BLOCK_COUNT, SSG_BLOCK_DELETE, SSG_ATTR_ERSETZEN, SSG_TEXT_ERSETZEN, SSG_TEXT_PARALLEL, SSG_TEXT_HOEHE, SSG_TEXT_STIL, SSG_FAEHNCHEN_STUECKLISTE, DIN_FORMATE | | Konfiguration | `SSG_LIB_Commands` + `DrawPlanquadrate` | SSG_DIM_SWITCH, SSG_SPRACHE, PLANQUADRATE_TOGGLE (DRAW_PLANQUADRATE/DELETE_PLANQUADRATE) | -| Doppelklick/Kontextmenue | `SSG_LIB_Commands` | SSG_BLOCKEDIT (dispatcht Kreisel->KreiselEdit, Eckrad->EckradEdit, Omniflo->OMNI_EDIT, GF_*->GEFAELLESTRECKE_EDIT, VF_*->VARIOFOERDERER_EDIT) | +| Doppelklick/Kontextmenue | `SSG_LIB_Commands` | SSG_BLOCKEDIT (dispatcht Kreisel->KreiselEdit, Eckrad->EckradEdit, Omniflo->OMNI_EDIT, GF_*->GEFAELLESTRECKE_EDIT, VF_*->VARIOFOERDERER_EDIT, KF_*->KF_EDIT) | ### Entfernte Legacy-Dateien @@ -96,7 +97,7 @@ Detaillierte Dokumentation aller Funktionen und Befehle: siehe `Lisp/README.md`. | Datei | Befehle | Beschreibung | |---|---|---| | `ssg_core.lsp` | - | Kernel: ssg-start/ssg-end, ssg-cfg-or, ssg-attrib-read/write, Layer/Block-Ops. Zentrale Helfer: `ssg-lisp-verzeichnis`/`ssg-lisp-datei-pfad` (Lisp-Pfad-Ermittlung), `ssg-rot-matrix-zy` (4x4 Rz(hz)*Ry(vert)-Rotationsmatrix, clusteruebergreifend), GUI-Aus-Schalter `*ssg-gui-aus*` + `ssg-gui-aus`/`ssg-gui-an`/`ssg-gui-p` (fuer automatische Tests: schaltet DCL-/Wizard-Dialoge ab, Module fallen auf Konsolen-/getXXX-Pfad zurueck; der Linienzug hat zusaetzlich `*vfl-headless*`, das die Konsolen-Rueckfrage selbst verbietet - siehe VarioFoerderer-Zeile). **Layer-Zuordnung**: `ssg-layer-cfg-load`/`-reset` (liest `cfg/layer.cfg` lazy), `ssg-layer-cfg-eintrag` (reine Aufloesung "Layername, Farbe" -> `(layer . farbe)`, in `tests/test_unit.lsp` getestet), `ssg-layer-eintrag` (Suchreihenfolge `[ils_]` -> `[allgemein]`), `ssg-layer-anlegen` (Layer mit cfg-Farbe anlegen, optional aktivieren) und `ssg-layer-setzen` (Blockreferenz auf den Baugruppen-Layer legen) - fuer ZUSAMMENGEBAUTE Baugruppen; `ssg-block-layer-vorgabe` (LAYER-Attribut -> `ssg-ils-block-ebene`) fuer nur EINGEFUEGTE Bloecke. Alle liefern nil, wenn nichts konfiguriert ist, und lassen den Layer dann unangetastet. **JSON**: drei Parser nebeneinander, bewusst nicht zusammengelegt - `ssg-load-json`/`ssg-parse-json-array` (flaches Array von Objekten) und `ssg-cfg-parse-nested` (genau 1 Ebene) sind ZEILENbasiert und koennen weder echte Verschachtelung noch mehrzeilige Arrays, maskierte Anfuehrungszeichen (`\"`) oder Kommas in Strings lesen; ihre Konsumenten (Konfiguration, Sprache, VF-Spec) sind auf dieses Verhalten eingestellt und bleiben unangetastet. Fuer alles andere `ssg-json-laden`/`ssg-json-parse`/`ssg-json-get` (rekursiver Descent-Parser ueber einen Positionszeiger): Objekt -> Alist, Array -> Liste, true -> T, false/null -> nil (beide nil, "Feld fehlt" ist also nicht von "Feld ist false" unterscheidbar), `\uXXXX` nur fuer ASCII. In `tests/test_unit.lsp` (`tu-tests-json`) gegen die echte Geruest-Datendatei getestet. Dazu `ssg-join` (Liste + Trennzeichen -> String). **DCL-Vorlagen**: `ssg-dcl-vorlage-fuellen` (Vorlage lesen, Markerzeilen `//@NAME@` durch erzeugte Zeilen ersetzen - reiner Kern `ssg-dcl-zeilen-ersetzen`, getestet - und per `ssg-dcl-temp-schreiben` als Temp-Datei schreiben) fuer Dialoge mit fester Grundstruktur und einzelnen datenabhaengigen Bloecken. `ssg-lastent-ohne-attribute`: Startmarke fuer "alles Neue seit Marke per entnext einsammeln" - statt nacktem `(entlast)` verwenden, sonst laeuft `entnext` in die ATTRIBs/SEQEND eines vorherigen Attribut-Blocks und `_.-BLOCK` entfernt sie dort (realer Bug: per Strg+C/V kopierter GR_-Block verlor beim naechsten GERUEST seine Attribute; Test `tests/test_lastent.lsp`) | -| `ssg_ks_insert.lsp` | - | Gemeinsame KS-Extraktion + Block-Einfuegeprimitiven (von VarioFoerderer UND Gefaellestrecke genutzt, frueher in beiden dupliziert): `vec-length`, `ks-line-axis`, `ks-normalize-name`, `ks-relativize`/`-absolutize`, `ensure-block-loaded`, `extract-ks-from-block[-raw]` (Cache `*ks-cache*`), `insert-block-by-ks` (nur Rz), `insert-inclined-scaled-block`/`insert-rotated-block-with-ks` (Rz*Ry). MNL laedt es als Core-Modul VOR Gefaellestrecke/VarioFoerderer; vf_core/Gefaellestrecke haben einen guarded Nachlade-Load fuers isolierte Test-Laden | +| `ssg_ks_insert.lsp` | - | Gemeinsame KS-Extraktion + Block-Einfuegeprimitiven (von VarioFoerderer UND Gefaellestrecke genutzt, frueher in beiden dupliziert): `vec-length`, `ks-line-axis`, `ks-normalize-name`, `ks-relativize`/`-absolutize`, `ensure-block-loaded`, `extract-ks-from-block[-raw]` (Cache `*ks-cache*`), `insert-block-by-ks` (nur Rz), `insert-inclined-scaled-block`/`insert-rotated-block-with-ks` (Rz*Ry). MNL laedt es als Core-Modul VOR Gefaellestrecke/VarioFoerderer; vf_core/Gefaellestrecke haben einen guarded Nachlade-Load fuers isolierte Test-Laden Fuer aufgerichtete/frei gedrehte Baugruppen: `ks-xy-winkel` (Richtung eines KS), `ks-block-messen`/`ks-versatz`, `insert-block-ks-skaliert` (Rz + X-Skalierung), `insert-block-ks-matrix` (freie 3x3-Rotationsmatrix, `ks-rot-x/-y/-z`). `ensure-block-loaded` nimmt einen Block OHNE Dimensionssuffix unveraendert, wenn nur der Rohname in der Zeichnung steht (Omniflo-Bloecke aus data/omniflo//). | | `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 | @@ -114,6 +115,7 @@ Detaillierte Dokumentation aller Funktionen und Befehle: siehe `Lisp/README.md`. | `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_SAEULEN, GERUEST_ROHRABHAENGER, GERUEST_WANDBEFESTIGUNG, GERUEST_TRAEGER, GERUEST_TRAEGER_BESCHRIFTEN, OMNI_FAHRSTRECKE, OMNI_FAHRSTRECKE_BESCHRIFTEN | Geruest-Baugruppen aus `data/json/geruest.json` konfigurieren und einfuegen. **Ein Dialog je Bereich**: Rohrabhaenger / Saeulen / Wandbefestigung oeffnen NUR ueber ihren eigenen Menuebefehl (`geruest-starten id`, Dialogtitel aus dem ASCII-Feld `titel`); eine Umschaltung im Dialog gibt es nicht (DCL hat kein Reiter-Tile), `GERUEST` ohne Bereich zeigt nur einen Hinweis. Der Dialogstand beginnt je Aufruf frisch. 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; fuer `dummy` ueber `geruest-dummy-wahl`: fehlt die DWG der Variante, Meldung + Rueckfall auf den Baugruppen-Dummy; ist der Dummy zugleich eine Teile-DWG, wird er per `geruest-ohne-pfad` nicht doppelt eingefuegt) - 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. **Das Layout steht als Vorlage in `dcl/geruest.dcl`** (bzw. `dcl/geruest_traeger.dcl`); nur die datenabhaengigen Bloecke - ein `radio_button` je Variante (Key `v`, `geruest-radio-key`/`-index`), Mengenfeld + Ankreuzfeld je Anbauteil - werden zur Laufzeit an Markerzeilen `//@NAME@` eingesetzt (`geruest-dcl-ersetzungen` -> `ssg-dcl-vorlage-fuellen` in ssg_core, Temp-Datei, nach `unload_dialog` geloescht). Grund: DCL kann Tiles weder zur Laufzeit erzeugen noch ausblenden, Radiobutton-Labels lassen sich per `set_tile` nicht aendern, und eine `list_box` kann keine Eingabefelder enthalten. Varianten bewusst als Radiobuttons statt `list_box`: BricsCAD zeichnet die Listenspalte nur halb so breit, und ein `tabs`-Wert passt nur schrift-/skalierungsabhaengig. Inhalt: Basis-Variante aus `geometrie_varianten` (`list_box` mit Einfachauswahl, Hoehe nach Variantenzahl, `geruest-dcl-listenhoehe`), **Winkel Platte** (0/90 Grad, `*geruest-winkel-werte*`): Ausrichtung der Anbauteile zur Basis - die Teile-DWGs aus oben/unten/sonstiges werden um diesen Winkel um Z gedreht in den Block eingefuegt, Basis und Dummy bleiben ungedreht (`geruest-teile-dwgs-rot`; in Konfigurationen als echter Wert `winkel_platte`, kein Block-Attribut), Laenge (gegen `attribute.Laenge` min/max geprueft, Retry-Schleife), Farbe (`attribute.Teilefarbe`, Freitext bei `frei_option`) und **eine Befestigungsspalte** "Befestigung oben und unten" mit allen `anbaugruppen.befestigung` - die Anzahl ist die GESAMTanzahl je Teil (oben und unten zusammen; Tile-Keys `b`/`bm`, Zustand `*geruest-dlg-bef*`). Unter jedem Teil (auch in Sonstiges) zeigt eine Textzeile "Art.-Nr. ..." die Artikelnummer(n) (`geruest-artnr-anzeige`: Einzelteil -> `sivas_id`, Gruppe -> eindeutige Nummern der Teile; DCL kennt keine Schriftgroesse, darum normale Groesse in eigener Zeile). `anbaugruppen` ist ein Objekt mit zwei Listen: `befestigung` (Befestigungsspalte) und `sonstiges` (Teile, die weder oben noch unten sitzen, z.B. die Saeulen-Haltearme) - letztere bekommen eine eigene Zeile darunter (nur wenn nicht leer), in ARTINR/BEZEICHNUNG nach "unten" als "sonstiges" (`geruest-anbau-liste`; ein altes flaches Array gilt als `befestigung`). **Zwei Mengenfelder im JSON, bewusst verschieden benannt**: `stueckzahl` = wie oft ein Teil TATSAECHLICH geschrieben wird (je Variante bzw. je Satz/Anbauteil, fehlt = 1), `anzahl_vorgabe` = NUR die Vorbelegung des Anzahl-Feldes im Dialog (Anbaugruppen, fehlt = 1); ein Anbauteil wird Anzahl x `stueckzahl` mal geschrieben, bei einer Gruppe (`teile`) zaehlt die Anzahl die Saetze (`geruest-stueckzahl`/`geruest-anzahl-vorgabe`). In Konfigurationen (`befestigung`/`sonstiges`, dazu `winkel_platte`) heisst der gespeicherte Dialogwert `anzahl`. Aeltere Konfigurationen mit getrennten `oben`/`unten`-Listen laden weiter: deren Mengen werden zur Gesamtanzahl addiert (`geruest-konfig-befestigung`), Sonstiges-Teile darin still in die Sonstiges-Zeile umgehaengt (`geruest-konfig-sonstiges`); beim naechsten Speichern steht alles im neuen Format. 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. **Geaenderte Konfiguration**: beim Anwenden wird der Dialogstand als Vergleichsbasis gemerkt (`*geruest-dlg-konfig-basis*`); jede Eingabe vergleicht den aktuellen Record damit (`geruest-dlg-aenderung-pruefen`, Vergleich statt Flag - Zuruecknehmen hebt die Markierung wieder auf) und kennzeichnet den Listeneintrag mit "(geaendert)" plus Statuszeile `kstatus`. Speichern bietet bei einer gewaehlten EIGENEN Konfiguration deren Namen als Vorgabe an (Enter = ueberschreiben, `geruest-konfig-name-vorgabe`); Bibliotheks-Konfigurationen brauchen einen neuen Namen. 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. **Teile-Geometrie**: jede ausgewaehlte Artikelnummer (Basis/oben/unten inkl. aufgeloester Gruppen), fuer die `data/geruest/.dwg` existiert, wird zusaetzlich einmal bei 0,0,0 mit in den Block eingefuegt (`geruest-teile-dwgs`, dedupliziert, fehlende DWGs still uebergangen) - reine visuelle Rueckmeldung, die Attribut-Logik bleibt davon unberuehrt. **TEILEFARBE** ist eine kommagetrennte Liste parallel zu ARTINR (z.B. `,,RAL7030,,RAL7030`): Teile mit `"farbig": true` in `geruest.json` (je Artikel; eine Gruppe vererbt es an ihre Teile, fehlend = false) bekommen die im Dialog gewaehlte Baugruppenfarbe, alle anderen einen leeren Eintrag. ARTINR und TEILEFARBE entstehen aus demselben Durchlauf (`geruest-teile-paare`/`-paare-n`, `geruest-farbliste`), sind also garantiert gleich lang. `export_sivas.py` schreibt daraus je Artikelnummer das Merkmal `{"108": ""}` in `details` (Nummer aus `cfg/export.cfg` `[Sivas] merkmal_teilefarbe`). 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 Anzahl x `stueckzahl` es vorgeben - erst Basis, dann Befestigung, dann Sonstiges; 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 ("Befestigung 4x ...", "sonstiges 1x ...") 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. **Attribut-Keys in `geruest.json` sind GROSS** (`LAENGE`, `TEILEFARBE`, ...) = die Block-Attribut-Tags; `ssg-json-get` unterscheidet Gross/Klein. **Rohr-Geruest-Traeger** (`GERUEST_TRAEGER`): Traeger-Baugruppen mit Linien-Logik liegen in einer EIGENEN Datei `data/json/geruest_traeger.json` (gleicher Aufbau wie geruest.json, eigener Lader/Cache `geruest-traeger-daten`, Suche `geruest-baugruppe id daten`; gemeinsamer Lader `geruest-json-laden-cache`) - sie tauchen darum nicht im Bereichs-Dropdown von GERUEST auf; dort kann spaeter auch der Gattertraeger hin. eigener Dialog aus der Vorlage `dcl/geruest_traeger.dcl` (Variante als Radiobuttons, JEDE Variante - auch die mit Muffe - mit eigenem Button, Beschriftung "Name (Artikelnummern)" ueber `geruest-varianten-label`; Varianten mit `"disabled": true` erscheinen in allen drei Geruest-Dialogen ausgegraut und nicht waehlbar (`geruest-variante-gesperrt-p`, `geruest-varianten-sperren` per `mode_tile`; Vorauswahl ueber `geruest-variante-waehlbar`/`-erste-freie-variante`; eine Konfiguration mit gesperrter Variante laesst die Auswahl unveraendert und meldet `gr-konfig-variante-gesperrt`) - bleiben aber in den Daten, damit Konfigurationen und Neubeschriften sie weiter ueber den Namen finden; eine Muffen-Checkbox gab es zwischenzeitlich, sie wurde wieder entfernt, weil die Artikelnummer der Muffen-Variante dann im Dialog nicht sichtbar war; Farbe, je eine Checkbox + Feld fuer `LAENGENABZUGANFANG`/`LAENGENABZUGENDE`, Vorgabe `vorgabe`/`aktiv` aus dem attribute-Block, je Variante ueberschreibbar via `geruest-attr-feld`); danach Einfuege-Schleife bis ESC/Enter: Startpunkt, dann Endpunkt ODER getippte Laenge (+ Richtung). Aktive Abzuege ruecken die Linie beidseitig ein (Strecke 1000, Abzug Anfang 20/Ende 35 -> Start 20 mm hinter dem Klick, Linie 945 mm); `LAENGE` = Netto-Laenge, gegen `LAENGE.min/max` geprueft. KEINE Dummy-DWG: die Geometrie ist eine per `entmake` erzeugte LINE (0,0,0)-(L,0,0) auf dem `layer` der Variante, eingefuegt mit dem Richtungswinkel (ANGBASE/ANGDIR/AUNITS dafuer kurz auf 0). **Querstreben + Muffe** als verschachtelte Dummy-DWGs im selben Block, reine Grafik ohne Export: `querstrebe.dummy` (`data/geruest/dummy_Strebe_30x4.dwg`, Basispunkt Strebenmitte, 30 mm laengs X) an jeder Strebenposition, NUR bei Varianten mit ausdruecklichem `"mit_querstrebe": true` (z.B. nicht beim `Rohr 1"`); fehlt die DWG, Rueckfall auf je eine 25-mm-Querlinie an Strebenanfang/-ende (`laenge`/`querlinie`, Standard 30/25 nur im Code). `muffe.dummy` (`dummy_Muffe_1zoll.dwg`, Basispunkt Muffenmitte) am Linienende, NUR bei Varianten mit ausdruecklichem `"mit_muffe": true` (nicht aus dem Namen geraten; bei "mit 2 Muffen" trotzdem nur eine am Ende); `querstrebe`/`muffe` per Variante ueberschreibbar (`geruest-variante-wert`). Dummys werden ohne Drehung eingefuegt (Traeger laeuft im Block entlang +X), ab dem zweiten Mal per Blockname (`geruest-dummy-einfuegen`). Verteilungs-Konfiguration `querstrebe` (`abstand`/`rand_min`/`rand_max`, Variante vor Baugruppe), Plan `geruest-traeger-streben-plan` -> (rand positionen): der Abstand zwischen zwei Streben ist IMMER genau `abstand`, beide Raender gleich (netto = k*abstand + 2*rand); gewaehlt wird die Strebenzahl, deren Rand am naechsten an `rand_min`..`rand_max` liegt (Gleichstand -> weniger Streben). Mit 500/100..250 geht das nur auf, wenn netto mod 500 = 0 oder in [200, 500) liegt (z.B. 1300 -> 150|500|500|150); sonst (1001..1199, unter 200 mm) liegt der Rand ausserhalb (1100 -> 300|500|300) - `geruest-traeger-rand-pruefen` zeigt dann eine Messagebox (ohne GUI: Konsole), der Traeger wird trotzdem eingefuegt. Schema `*geruest-traeger-attrib-defs*` = `*geruest-attrib-defs*` + die zwei Abzug-Tags; gemeinsamer Block-Schluss mit den anderen Bereichen in `geruest-block-abschliessen`. Reine Berechnung (`geruest-traeger-laenge`/`-start`/`-pruefen`, `geruest-mm-zahl`) in `tests/test_unit.lsp`. **Traeger-Beschriftung** (Checkbox im Dialog, Standard an; z.B. `RT 80/80 L=2650`): EIGENES TEXT-Objekt neben dem Block, abgeleitet NUR aus dem fertigen Block (Einfuegepunkt, DXF 50, Attribute `LAENGE` + `VARIANTE`) - darum nutzen Einfuegen und `GERUEST_TRAEGER_BESCHRIFTEN` dieselbe Funktion `geruest-traeger-beschriften`, und ein von Hand verschobener/gedrehter Traeger wird beim Neubeschriften korrekt erfasst. Verknuepfung Text -> Block per XDATA `SSG_GR_BESCHR` (1005 = Block-Handle): Neubeschriften ersetzt statt verdoppelt, der Sammelbefehl entfernt verwaiste Texte. JSON `beschriftung` (je FELD Variante vor Baugruppe, `geruest-obj-feld`): Inhalt `format` (`%1` Text, `%2` Laenge auf ganze mm) + `text` (fehlt -> Variantenname) und `layer` (je Variante verschieden, darum in JEDER Variante gepflegt - keine Baugruppen-Vorgabe; fehlt er doch, aktueller Layer); Formatierung NUR `stil`/`ausrichtung`/`hoehe` - alles andere regelt der Textstil ausserhalb, es wird kein Stil angelegt (fehlt er: Meldung, aktueller TEXTSTYLE). `ausrichtung` = Codes von TEXT "Ausrichten" (T/M/B + L/C/R), Bezugspunkt auf der Traegerlinie, L/R in LESErichtung. Textwinkel entlang des Traegers, DIN: fuer 90 < w <= 270 Grad um 180 gedreht (`geruest-text-winkel`). **Omniflo-Fahrstrecke** (`OMNI_FAHRSTRECKE`, Daten `data/json/omniflo_fahrstrecke.json`, Vorlage `dcl/fahrstrecke.dcl`): dieselbe Linien-Logik wie der Traeger, Code geteilt - `geruest-linien-starten id daten dcl-datei dlg-name` (Dialog `geruest-traeger-dialog` mit Vorlage + Dialogname als Argument, Einfuegen `geruest-traeger-einfuegen`), Beschriftung `geruest-traeger-beschriften blockEnt bg` mit der Baugruppe als Argument, Sammelbefehle je Typ ueber `geruest-linien-beschriften id daten` (Bloecke `GR__*`; verwaiste Texte werden typunabhaengig entfernt). Unterschiede stehen nur in den Daten: keine Querstreben (kein `mit_querstrebe`), Muffen-Dummy wie beim Traeger ueber den `muffe`-Block der Baugruppe (nur Varianten mit `"mit_muffe": true`), `farbig` nur bei ST/STL, Muffen-Varianten nur fuer ST/STL. Der Dialogstand bleibt je Baugruppe erhalten (`*geruest-tr-id*`; Wechsel Traeger <-> Fahrstrecke setzt ihn zurueck). Meldungstexte `gr-tr-*` nennen den Bauteilnamen aus `titel` als Platzhalter. In der Stueckliste eigene Klasse `fahrstrecke` (`GR_Fahrstrecke_*` vor dem allgemeinen `GR_*`). Noch NICHT enthalten: Edit-Pfad (GERUEST_EDIT) | +| `KettenFoerderer.lsp` | KETTENFOERDERER, KF_EDIT, KF_KS_PRUEFEN | Kettenfoerderer/Schraegfoerderer Stahl: baut aus `KF_UM_` -> `KF_LAENGE` -> `KF_AN_` (je KS_AUS auf KS_EIN) plus der Draufsicht `KF_2D` EINEN Block `KF_`. Daten `data/json/kettenfoerderer.json`, Dialog `dcl/kettenfoerderer.dcl`. **Geometrie**: die Bausteine sind in der XY-Ebene gezeichnet (Hoehe = Y), werden aber AUFGERICHTET eingebaut `Rz(basis)*Rx(90)*Rz(eigen)` (`kf-matrix-aufrecht`) - die Hoehe laeuft in **Z**; dafuer `insert-block-ks-matrix` (freie 3x3-Rotation) in ssg_ks_insert.lsp. Die Neigung (26/30 Grad) ist KEIN Rechenparameter: sie steckt in den Varianten-Bloecken, der Einbauwinkel jedes Folgebausteins wird zur Laufzeit aus der RICHTUNG des KS_AUS seines Vorgaengers gemessen (`ks-xy-winkel`/`kf-hz-folge`) - keine gepflegte Winkeltabelle. `KF_LAENGE` wird in X skaliert, die Einbauhoehe H bestimmt seine Laenge ueber die Hoehenbilanz (`kf-laenge-berechnen`). `KF_2D` liegt DECKUNGSGLEICH mit der Kette (KS_EIN/KS_AUS auf denen von Umlenkung/Antrieb), ist flach gezeichnet, nur um die Steigung geneigt und auf die Schraeglaenge skaliert. **Attribut-Schema** `*kf-attrib-defs-liste*` ist EIGENSTAENDIG und bewusst NICHT von `ssg-strecke-attrib-defs` abgeleitet (sonst ~24 geerbte VF-Tags am Block); die Reihenfolge folgt dem Sivas-Merkmalstamm. FOERDERERTYP = Bauart im Klartext, WINKEL = nackte Zahl 26/30 (der Klartext mit Gradzeichen entsteht erst ueber `[kf_typ_anzeige]` in cfg/sivas.cfg, damit Attributwerte ASCII bleiben); kein TEILEART (Festwert `KF_TEILEART` im Export). Alle Auswahllisten stehen mit ihren Sivas-Optionstexten in der JSON. **Das `KF_`-Praefix ist wie `GR_` nicht kosmetisch**: `SSG_BLOCKEDIT` haelt JEDEN Block mit TEILEART-Attribut fuer ein Omniflo-Element, darum steht ein `KF_*`-Zweig VOR dem Omniflo-Zweig (Doppelklick -> KF_EDIT). **Sivas-Export** (`[kettenfoerderer]` in cfg/sivas.cfg, `build_kettenfoerderer_details` in export_sivas.py): baugleiche Foerderer werden zu EINER Zeile zusammengefasst (`kettenfoerderer_signature`), die Gruppengroesse traegt Merkmal 353 (entsteht erst aus der Gruppierung, daher Code); Farbe (354) und Antriebsmotor (355) sind ZWEI Merkmale, und jedes merkmalbildende Attribut steht auch in der Signatur, sonst verschmelzen unterschiedlich ausgefuehrte Foerderer. Reine Kerne in `tests/test_unit.lsp` (`tu-tests-kf`); das Modul wird in der MNL vorgeladen, Laufzeit-Messung ueber den dbg-Schalter `kf-zeit` | | `menu-inventory.lsp` | MenuInventory | BricsCAD Diagnostik (Menuegruppen, Plugins, Startdateien) | | `DinFormate.lsp` | DIN_FORMATE | Ersatz fuer das alte ACADSST-Makro `DINFORM.LSP`/`.DCL` (SSG2-Menue "DIN-FORMATE..."): fuegt einen DIN-Rahmen (A0-A3, A4 quer/hoch) und optional einen Schriftkopf (KOPF11, Sprachen DE/EN/FR/IT) per Dialog (`dcl/dinformate.dcl`) ein. Block-DWGs liegen flach in `cfg/rahmen/` (kopiert aus `\\infor-ln\SSG_Tools\ACADSST\KOEPFE` - nur die von DINFORM.LSP tatsaechlich referenzierten Dateien: `A0`/`A1`/`A2`/`A3`/`A4Q`/`A4H.DWG` + `Kopf11`/`Kopf11e`/`Kopf11f`/`Kopf11i.dwg`). Rahmen wird im Zeichenmassstab-Faktor eingefuegt, der Kopf im Faktor/100 (Kopf-DWG ist 100x groesser als die Rahmen-DWGs angelegt) - unveraendert aus dem Original-Massstabsschema uebernommen. Blockdefinitionen werden einmalig per `_.-INSERT ... _X 0` nachgeladen (`dinformate-block-laden`), falls noch nicht in der Zeichnung vorhanden. Lazy geladen ueber `(ssg-ensure "DinFormate")`, Menue: Werkzeuge > Din Formate. | | `DrawPlanquadrate.lsp` | DRAW_PLANQUADRATE, DELETE_PLANQUADRATE, PLANQUADRATE_TOGGLE | Zeichnet auf Z=0 ein Planquadrat-Linienraster (senkrecht/waagerecht), im Abstand/Zaehlschema der aktiven `[Planquadrate]`-Konfiguration aus `cfg/export.cfg` (dieselbe Quelle wie `lib/export_planquadrat.py` fuer den CSV-Export). Bereich kommt NICHT aus den Zeichnungs-Extents, sondern aus derselben Blockmenge wie EXPORTCSV (`csv:collect-export-blocks`, export.lsp; siehe `plq-export-bbox`): Ursprung (Index 1) UND obere Grenze kommen BEIDE aus der tatsaechlichen Bounding-Box jedes Blocks (`csv:get-bbox`, wie `bbox_columns()`/`planquadrat_column()`/`_bbox_min()` in `lib/export_csv.py` bzw. `lib/export_planquadrat.py`), NICHT aus dem reinen Einfuegepunkt - bei einem gedrehten/exzentrisch eingefuegten Block (z.B. ein um 90 Grad gedrehter Kreisel) kann das Bounding-Box-Zentrum weit vom Einfuegepunkt abweichen; Ursprung und Zellzuordnung muessen auf derselben Referenz beruhen, sonst driften sie auseinander (realer Bug, behoben). Reine Hilfsgeometrie (Kreisel-Umfahrungsboegen als eigene Linien, Texte) zaehlt bewusst nicht mit, sonst weicht das Raster vom CSV-Export ab. `x_origin`/`y_origin` ueberschreiben den Ursprung, Fallback auf EXTMIN/EXTMAX falls keine Export-Bloecke vorhanden sind bzw. `csv:get-bbox` fuer einen Block scheitert. Spalten-/Zeilenzahl wird IMMER aufgerundet auf volle Rasterschritte, damit alle senkrechten Linien auf der letzten waagerechten enden und umgekehrt - das Raster bildet immer ein geschlossenes Rechteck. Zaehlung (alphabetisch/numerisch je Achse) als Text am linken/unteren Rand. Das fertige Raster (Linien+Texte) wird per `_.-BLOCK`/`_.INSERT` zu einem einzigen Block `ZZ_PLANQUADRATE` bei (0,0,0) zusammengefasst. Layer aus `cfg/layer.cfg` (`[allgemein]` Key `planquadrate`, Fallback `ZZ_Planquadrate`). DRAW_PLANQUADRATE entfernt vor dem Neuaufbau zuerst einen evtl. vorhandenen Block (`plq-block-entfernen`: INSERTs loeschen + `_-PURGE _B`), DELETE_PLANQUADRATE ruft nur diese Entfernung auf. PLANQUADRATE_TOGGLE ("Planquadrate an/aus", Menue: Konfiguration) prueft per `tblsearch`/`ssget "X"`, ob der Block existiert, und dispatcht auf Zeichnen bzw. Loeschen. Lazy geladen ueber `(ssg-ensure "DrawPlanquadrate")`, nicht in der Core-Ladereihenfolge. Die Planquadrat-SPALTE im CSV-Export (`export_csv.py::planquadrat_column`/`resolve_origins`, `lib/export_planquadrat.py::compute_planquadrat`/`_bbox_min`) rechnet ebenfalls durchgaengig auf Bounding-Box-Basis (nicht nur Einfuegepunkt) und liefert eine SPANNE ("A/1-J/1") statt nur einer Zelle, wenn ein Element mehr als eine Zelle ueberdeckt (Kreisel, lange VF-/GF-Ketten). | @@ -135,6 +137,7 @@ Detaillierte Dokumentation aller Funktionen und Befehle: siehe `Lisp/README.md`. | `geruest.dcl` | VORLAGE fuer GERUEST_SAEULEN/_ROHRABHAENGER/_WANDBEFESTIGUNG (GeruestInsert.lsp, `geruest-dialog`); Platzhalter `//@TITEL@`/`//@VARIANTEN@`/`//@BEFESTIGUNG@`/`//@SONSTIGES@` werden per `ssg-dcl-vorlage-fuellen` gefuellt, nicht direkt ladbar | | `geruest_traeger.dcl` | VORLAGE fuer GERUEST_TRAEGER (GeruestInsert.lsp, `geruest-traeger-dialog`), Platzhalter `//@VARIANTEN@` | | `fahrstrecke.dcl` | VORLAGE fuer OMNI_FAHRSTRECKE (GeruestInsert.lsp, `geruest-traeger-dialog`), gleiche Tile-Keys wie `geruest_traeger.dcl`, Platzhalter `//@VARIANTEN@` | +| `kettenfoerderer.dcl` | KETTENFOERDERER/KF_EDIT (KettenFoerderer.lsp, `kf-dialog`); VORLAGE mit Markerzeile `//@VARIANTEN@` (Typ als Radiobuttons, gefuellt per `ssg-dcl-vorlage-fuellen`), die uebrigen Auswahllisten sind `popup_list`-Tiles, die per `start_list`/`add_list` aus `data/json/kettenfoerderer.json` gefuellt werden. Der Knopf `hoehe_messen` schliesst den Dialog per `done_dialog 2` (Hoehe aus 2 Punkten messen, danach erneut zeigen) | | `dinformate.dcl` | DIN_FORMATE (DinFormate.lsp) | | `vfl_edit.dcl` | Linienzug-Editieren/Fortsetzen (vf_linienzug.lsp, `vfl-dlg-position`, aufgerufen aus `vfl-edit-ent`) | | `vf_linienzug_wizard.dcl` | Linienzug-Assistent (Modus 1, 2 UND 3): zwei generische Einzel-Dialoge (Zahl-Eingabe, Mehrfachauswahl; `vflw-zahl`/`vflw-wahl`, aufgerufen aus `vfl-in-real`/`vfl-in-string`/`vfl-in-int` bei aktivem `*vfl-wizard-mode*`) PLUS sieben Gruppen-Dialoge, die mehrere immer unmittelbar aufeinanderfolgende Fragen in einem Bildschirm zusammenfassen (Kettenstart, AS-Element, GF-Bogen, ES-Element, Vario-Kurve, Gefaelle festlegen, Ziel-Hoehe, Horizontales Stueck - inkl. Punktwahl-Button mit X/Y/Z-Anzeige; `vflw-gruppe-*-impl` in vf_linienzug.lsp fuellen dafuer vorab eine kleine Pending-Queue `*vflw-pending*`, die einzelnen `vfl-in-*`-Aufrufe bleiben dabei unveraendert). Das AS-Element am Kettenanfang von Modus 1 nutzt die Variante `vflw_as_achse` (Parameter `mit-achse` von `vflw-gruppe-as-impl`): zusaetzlich zu Ja/Nein, Winkel und Seite liegt dort die **Zufuehrungsachse** (2. Punkt, Punktwahl-Button) im selben Fenster, und die Seite ist auch beim 30-Grad-Element waehlbar. Der ES-Fall und die Modi 2/3 bleiben beim unveraenderten `vflw_as` PLUS drei Segment-Gruppen-Dialoge fuer die segmentweise Klassifizierung in Modus 2/3 (`vflw_seg_linie_m2` = Typ+GF-Neigung, `vflw_seg_linie_m3` = 4-fach-Typ+kontextabh. Wertfeld, `vflw_seg_bogen` = Typ+Variante; `vflw-seg-*-impl`). Beide Modi heben vor der Frage das aktuelle Pfad-Segment (LINE/ARC) per `vfl-seg-highlight` (`redraw`) in der Zeichnung hervor und zeigen "Segment i/n ..." im Dialogkopf. Details: `doc/variofoerderer.md` | diff --git a/Lisp/KettenFoerderer.lsp b/Lisp/KettenFoerderer.lsp new file mode 100644 index 0000000..12d17df --- /dev/null +++ b/Lisp/KettenFoerderer.lsp @@ -0,0 +1,1160 @@ +;; ============================================================ +;; KETTENFOERDERER.LSP - Schraegfoerderer Stahl (Omniflo) +;; ============================================================ +;; Baut aus drei vorhandenen Bloecken eine Kette zusammen und fasst sie zu +;; EINEM Block KF_ zusammen - dasselbe Muster wie KREISEL_n/VF_n/GF_n: +;; +;; KF_UM_ (Umlenkstation, unten) +;; KS_AUS --> KS_EIN +;; KF_LAENGE (Zwischenstueck, in X SKALIERT - wie die Staustrecke +;; beim VarioFoerderer) +;; KS_AUS --> KS_EIN +;; KF_AN_ (Antriebsstation, oben) +;; +;; GEOMETRIE - wichtigste Entwurfsentscheidung: +;; Die Neigung (26 bzw. 30 Grad) ist KEIN Rechenparameter. Sie steckt fest in +;; der Geometrie der Varianten-Bloecke (KF_UM_26 gegen KF_UM_30). Der +;; Einbauwinkel jedes Folgebausteins wird darum zur Laufzeit aus der RICHTUNG +;; des KS_AUS seines Vorgaengers gemessen (ks-xy-winkel/ks-block-messen in +;; ssg_ks_insert.lsp) - es gibt hier keine gepflegte Winkeltabelle, die beim +;; Austausch einer DWG veralten koennte. Der JSON-Wert "winkel" dient nur der +;; Variantenwahl, der Anzeige und dem Attribut KF_WINKEL. +;; +;; Die Bloecke sind eine 2D-SEITENANSICHT: die Steigung liegt in der +;; XY-Zeichenebene, die Hoehenachse ist Y (nicht Z wie bei VF/GF). Die +;; Neigung ist damit eine reine Rz-Drehung - deshalb genuegt +;; insert-block-by-ks/insert-block-ks-skaliert, die Rz*Ry-Primitiven des +;; VarioFoerderers werden hier nicht gebraucht. +;; +;; Die Einbauhoehe H (Abstand KS_EIN der Umlenkung bis KS_AUS des Antriebs auf +;; der Y-Achse) bestimmt die Laenge des Zwischenstuecks ueber die +;; Hoehenbilanz, siehe kf-laenge-berechnen. +;; +;; Abhaengigkeiten: ssg_core, ssg_lang, ssg_layer, ssg_id, ssg_ks_insert, +;; OmniModulInsert (nur fuer omni:omniflo-datei-finden - die KF-DWGs liegen +;; wie alle Omniflo-Bauteile in data/omniflo//). +;; ============================================================ + +(vl-load-com) + +;; modelspace/doc werden von den KS-Einfuegeprimitiven als GLOBALE Variablen +;; erwartet (gesetzt von vf_core.lsp bzw. Gefaellestrecke.lsp). Beim isolierten +;; Laden dieses Moduls - etwa in einem Testskript - ist noch keines davon +;; geladen, darum hier guarded nachziehen (gleiches Muster wie TEFInsert.lsp). +(if (null doc) (setq doc (vla-get-ActiveDocument (vlax-get-acad-object)))) +(if (null modelspace) (setq modelspace (vla-get-ModelSpace doc))) + +;; Einfuegeprimitiven guarded nachladen (die MNL laedt ssg_ks_insert als +;; Kernmodul, beim isolierten Test-Laden fehlt es aber). Geprueft wird ueber +;; atoms-family, nicht ueber (null ) - ein noch nie definiertes +;; Funktionssymbol ist zwar nil, ein definiertes liefert aber eine USUBR, und +;; die Abfrage waere damit vom Ladezustand abhaengig statt von der Existenz. +(if (not (car (atoms-family 1 '("INSERT-BLOCK-KS-SKALIERT")))) + (if (findfile (ssg-lisp-datei-pfad "ssg_ks_insert.lsp")) + (load (ssg-lisp-datei-pfad "ssg_ks_insert.lsp")))) + +;; ------------------------------------------------------------ +;; LAUFZEIT-PROTOKOLL (Schalter "kf-zeit") +;; ------------------------------------------------------------ +;; Schreibt Zwischenzeiten ueber ssg_dbg (dbgmsg) statt per princ ins +;; BricsCAD-Fenster - die Zeilen landen damit in einer .dbg-Datei im +;; DXFM_LOG-Verzeichnis und sind ueber den Schalter abschaltbar, koennen +;; also dauerhaft im Code bleiben (siehe Debug-Abschnitt in CLAUDE.md). +;; Einschalten: (dbg-schalter-setzen "kf-zeit" 1), dann KETTENFOERDERER. +;; Zweck: klaeren, wo die spuerbare Verzoegerung beim ersten Menueklick +;; entsteht (Lazy-Laden des Moduls / JSON lesen / Dialogaufbau). +;; Eigener Dateihandle statt dbgmsg: ssg_dbg fuehrt mit *dbg-path* genau EINE +;; global offene Datei - die Zeilen wuerden sonst stillschweigend in einem +;; fremden Protokoll landen (gleiche Ueberlegung wie bei ssg-sepliste-dbg). +(if (not (boundp '*kf-zeit-pfad*)) (setq *kf-zeit-pfad* nil)) + +(defun kf-zeit (was t0 / dir fh) + (if (and t0 + (car (atoms-family 1 '("DBG-SCHALTER-P"))) + (dbg-schalter-p "kf-zeit")) + (progn + (if (null *kf-zeit-pfad*) + (progn + (setq dir (getenv "DXFM_LOG")) + (if (or (null dir) (= dir "")) (setq dir ".")) + (setq *kf-zeit-pfad* (strcat dir "\\kf_zeit.dbg")))) + (setq fh (open *kf-zeit-pfad* "a")) + (if fh + (progn + (write-line (strcat was ": " (itoa (fix (- (getvar "MILLISECS") t0))) + " ms seit Befehlsstart") fh) + (close fh))))) + (princ)) + +;; ============================================================ +;; DATEN (data/json/kettenfoerderer.json) +;; ============================================================ +;; Verschachteltes JSON -> ssg-json-laden (rekursiver Parser), NICHT der +;; zeilenbasierte ssg-load-json: die Datei hat verschachtelte Objekte und +;; Kommas in den Beschreibungstexten. +(if (not (boundp '*kf-daten*)) (setq *kf-daten* nil)) + +(defun kf-daten-datei ( / basis) + (setq basis (getenv "DXFM_DATA")) + (if (or (null basis) (= basis "")) + (setq basis (strcat (getenv "DXFMAKRO") "/data"))) + (strcat basis "/json/kettenfoerderer.json")) + +;; Geladene Daten (Cache). kf-daten-reset erzwingt das Neulesen nach einer +;; Aenderung an der JSON, ohne BricsCAD neu zu starten. +(defun kf-daten ( / datei roh) + (if (null *kf-daten*) + (progn + (setq datei (kf-daten-datei)) + (if (findfile datei) + (progn + (setq roh (ssg-json-laden datei)) + (setq *kf-daten* (ssg-json-get roh "kettenfoerderer"))) + (princ (ssg-textf "kf-fehler-daten-fehlt" (list datei)))))) + *kf-daten*) + +(defun kf-daten-reset ( ) (setq *kf-daten* nil) (princ)) + +;; ============================================================ +;; REINE RECHENKERNE (ohne Zeichnung/Datei testbar - tests/test_unit.lsp) +;; ============================================================ + +;; JSON-Wert als Zahl. Der Parser liefert Zahlen je nach Schreibweise als +;; REAL/INT oder (bei gequoteten Werten) als String - beides zulassen. +(defun kf-zahl (v) + (cond + ((null v) nil) + ((numberp v) (float v)) + ((= (type v) 'STR) (atof v)) + (t nil))) + +;; Laenge des skalierbaren Zwischenstuecks aus der Hoehenbilanz. +;; H = dy-um + [ L*sin(hz) + dy-nativ*cos(hz) ] + dy-an +;; aufgeloest nach L. Der Term dy-nativ*cos(hz) faellt weg, wenn das +;; Zwischenstueck waagerecht gezeichnet ist (Normalfall) - er steht hier, +;; damit auch ein bereits geneigt gezeichnetes KF_LAENGE richtig gerechnet +;; wird. dy-nativ skaliert BEWUSST nicht mit: skaliert wird nur in X. +;; h-soll gewuenschte Einbauhoehe H (mm) +;; dy-um Y-Versatz der eingebauten Umlenkstation +;; dy-an Y-Versatz der eingebauten Antriebsstation +;; dy-nativ Y-Anteil des KS_AUS-KS_EIN-Versatzes von KF_LAENGE (Blockmass) +;; hz Einbauwinkel des Zwischenstuecks (Grad) +;; Rueckgabe: Laenge entlang der Blockachse (mm), oder nil wenn die Neigung +;; keinen Hoehengewinn liefert (sin(hz)=0 - dann ist H nicht erreichbar). +(defun kf-laenge-berechnen (h-soll dy-um dy-an dy-nativ hz / rad sh ch rest) + (setq rad (* (float hz) (/ pi 180.0))) + (setq sh (sin rad) ch (cos rad)) + (if (< (abs sh) 1e-9) + nil + (progn + (setq rest (- (float h-soll) (float dy-um) (float dy-an) + (* (float dy-nativ) ch))) + (/ rest sh)))) + +;; X-Massstab fuer KF_LAENGE: gewuenschte Laenge / native Blocklaenge. +;; nil, wenn der Block keine messbare X-Laenge hat. +(defun kf-skala-berechnen (laenge dx-nativ) + (if (or (null laenge) (null dx-nativ) (< (abs dx-nativ) 1e-9)) + nil + (/ (float laenge) (float dx-nativ)))) + +;; Errechnete Laenge gegen die Grenzen aus der JSON pruefen. +;; Rueckgabe: nil = in Ordnung, sonst der Sprach-Key der Meldung. +(defun kf-laenge-pruefen (laenge lmin lmax) + (cond + ((null laenge) "kf-fehler-winkel-null") + ((<= laenge 0.0) "kf-fehler-hoehe-zu-klein") + ((and lmin (< laenge (float lmin))) "kf-fehler-laenge-unter-min") + ((and lmax (> laenge (float lmax))) "kf-fehler-laenge-ueber-max") + (t nil))) + +;; Y-Anteil des KS_AUS-KS_EIN-Versatzes nach Einbau mit Rz(hz) und X-Massstab. +;; In der 2D-Seitenansicht ist Y die Hoehenachse. +(defun kf-dy-eingebaut (dx-nativ dy-nativ hz skala / rad) + (setq rad (* (float hz) (/ pi 180.0))) + (+ (* (* (float skala) (float dx-nativ)) (sin rad)) + (* (float dy-nativ) (cos rad)))) + +;; Einbauwinkel (Rz, Grad) eines Folgebausteins: sein KS_EIN soll in dieselbe +;; Richtung zeigen wie das KS_AUS seines Vorgaengers. +;; hz = Weltrichtung(KS_AUS Vorgaenger) - Eigenrichtung(KS_EIN Folgebaustein) +;; Fehlt die Eigenrichtung des Folgebausteins, wird er wie sein Vorgaenger +;; ausgerichtet; fehlt die Vorgaengerrichtung, ist keine Aussage moeglich (nil). +(defun kf-hz-folge (phi-aus beta-ein) + (cond + ((null phi-aus) nil) + ((null beta-ein) (ks-winkel-normalisieren phi-aus)) + (t (ks-winkel-normalisieren (- phi-aus beta-ein))))) + +;; Variante zu einem Winkel suchen. Rueckgabe: Varianten-Objekt oder nil. +(defun kf-variante-finden (winkel varianten / treffer w) + (setq treffer nil) + (foreach v varianten + (setq w (kf-zahl (ssg-json-get v "winkel"))) + (if (and (null treffer) w (< (abs (- w (float winkel))) 0.01)) + (setq treffer v))) + treffer) + +;; ------------------------------------------------------------ +;; ATTRIBUT-SCHEMA +;; ------------------------------------------------------------ +;; EIGENSTAENDIGE Liste - bewusst NICHT vom Strecken-Schema des +;; VarioFoerderers (`ssg-strecke-attrib-defs`) abgeleitet. Anfangs war sie +;; das, was dem Block rund zwei Dutzend geerbte Tags eintrug, die der +;; Kettenfoerderer gar nicht kennt (SEITE_AS/ES, GF_Bogen_*, ANZAHL_VF, +;; MOTORSEITE, VF_Bogen_*, ANZAHL_SEPARATOR ...). Sie standen leer am Block, +;; waren im Attributeditor nur Ballast und haetten im Export Werte +;; vorgetaeuscht, die es hier nicht gibt. Eine explizite Liste ist zudem +;; leichter zu lesen als eine Erbung mit Ausnahmeliste. +;; +;; Was die Tags bedeuten: +;; ID - vergibt ssg-id-generate (Voraussetzung fuer den Export) +;; Bezeichnung - Blockname KF_, CSV-Spalte +;; ARTINR - Sivas-Artikelnummer, CSV-Spalte "SivasNummer" +;; FOERDERERTYP - Bauart im Klartext ("Schraegfoerderer Stahl"), +;; Merkmal 350. Der Wert kommt aus der JSON +;; (Feld "foerderertyp"), nicht aus dem Code - so +;; bleibt diese Datei frei von Sonderzeichen und der +;; Text ist an einer Stelle pflegbar. +;; WINKEL - Neigungswinkel als nackte Zahl ("26"/"30"). +;; Den Sivas-Klartext mit Gradzeichen erzeugt erst die +;; Tabelle [kf_typ_anzeige] (cfg/sivas.cfg, Merkmal +;; 359) - Attributwerte bleiben so ASCII. +;; STANDARD_STEUERUNG - Merkmal 361, "1"/"0" ueber Transform ja_nein. +;; Steht direkt hinter WINKEL, wie im Merkmalstamm. +;; H_EINBAUHOEHE_mm - Merkmal 360 +;; L_KF_mm - Laenge des skalierten Zwischenstuecks, CSV-Spalte +;; FARBE - Merkmal 354 +;; ANTRIEBSMOTOR - Merkmal 355 (eigenes Merkmal, nicht Teil von 354) +;; ANTRIEBSART - Merkmal 357, FEST "1.1 kW" (Wert aus der JSON, +;; im Dialog ein Dropdown mit EINER, zwingend gewaehlten Option) +;; MOVIMOT - Merkmal 356, "1"/"0" ueber Transform ja_nein +;; VERBINDUNGSELEMENTE - Merkmal 391 +;; LAYER - Ziel-Layer der Baugruppe +;; +;; KEINE TEILEART: sie ist fuer jeden Kettenfoerderer gleich und steht darum +;; als Festwert im Export (KF_TEILEART in export_sivas.py) statt an jedem +;; Block. Die Erkennung laeuft ohnehin ueber den Blocknamen (pattern_ +;; kettenfoerderer), nicht ueber TEILEART. +(setq *kf-attrib-defs-liste* + '(("ID" "") + ("Bezeichnung" "") + ("ARTINR" "") + ("FOERDERERTYP" "Schraegfoerderer Stahl") + ("WINKEL" "26") + ;; Reihenfolge wie im Sivas-Merkmalstamm: 361 folgt direkt auf den Typ. + ("STANDARD_STEUERUNG" "0") + ("H_EINBAUHOEHE_mm" "0") + ("L_KF_mm" "0") + ("FARBE" "RAL 7035") + ("ANTRIEBSMOTOR" "Europa 50 Hz") + ;; Merkmal 357 - beim Kettenfoerderer fest, im Dialog nur Anzeige. + ("ANTRIEBSART" "1.1 kW") + ("MOVIMOT" "0") + ("VERBINDUNGSELEMENTE" "Nein") + ("LAYER" "F-3"))) + +(defun kf-attrib-defs ( ) *kf-attrib-defs-liste*) + +;; Antriebsart (Merkmal 357). Beim Kettenfoerderer gibt es genau EINE Option +;; ("1.1 kW"), die im Dialog zwingend gewaehlt ist. Sie steht als Liste in der +;; JSON (wie alle anderen Auswahlen), nicht im Code. Rueckgabe: der "wert" des +;; ERSTEN Eintrags - bei einer Ein-Eintrag-Liste also immer derselbe Wert. +(defun kf-antriebsart (daten) + (kf-auswahl-wert (ssg-json-get daten "antriebsart") 0 "1.1 kW")) + +;; Position eines Tags in einer ATTDEF-Liste (0-basiert), nil wenn nicht +;; enthalten. Die REIHENFOLGE ist nicht beliebig: sie bestimmt, in welcher +;; Folge die Attribute im Blockeditor und in den Exportzeilen erscheinen, und +;; folgt darum dem Sivas-Merkmalstamm. Reiner Kern, in test_unit geprueft. +(defun kf-tag-index (tag defs / i treffer) + (setq i 0 treffer nil) + (foreach d defs + (if (and (null treffer) (= (car d) tag)) (setq treffer i)) + (setq i (1+ i))) + treffer) + +;; ============================================================ +;; BLOCK-DATEIEN (data/omniflo//) +;; ============================================================ +;; Die KF-Bloecke liegen - anders als die ILS-Bloecke in data/ils (flach, mit +;; Dimensionssuffix _2D/_3D) - in den Omniflo-Ordnern ohne Suffix. Die +;; KS-Primitiven in ssg_ks_insert.lsp suchen ueber ssg-ils-block-laden aber in +;; data/ils. Darum wird die Blockdefinition HIER vorab aus dem Omniflo-Ordner +;; in die Zeichnung geladen; ensure-block-loaded findet sie dann per +;; tblsearch bereits vor und laesst sie unangetastet. +(defun kf-block-laden (blockname / pfad omniflo-pfad temp-obj) + (if (tblsearch "BLOCK" blockname) + blockname + (progn + (setq omniflo-pfad (getenv "DXFM_OMNIFLO")) + (setq pfad + (if (and omniflo-pfad + (car (atoms-family 1 '("OMNI:OMNIFLO-DATEI-FINDEN")))) + (omni:omniflo-datei-finden blockname omniflo-pfad) + nil)) + ;; Rueckfall ohne OmniModulInsert/DXFM_OMNIFLO: direkt im Datenordner + (if (null pfad) + (setq pfad (kf-block-datei-direkt blockname))) + (if pfad + (progn + ;; vla-InsertBlock mit DATEIPFAD definiert den Block unter seinem + ;; Dateinamen (gleiches Vorgehen wie dinformate-block-laden). + (setq temp-obj (vla-InsertBlock modelspace + (vlax-3D-point '(0 0 0)) pfad 1.0 1.0 1.0 0)) + (if (and temp-obj (not (vlax-erased-p temp-obj))) + (vla-Delete temp-obj)) + (if (tblsearch "BLOCK" blockname) blockname nil)) + (progn + (princ (ssg-textf "kf-fehler-block-datei" (list blockname))) + nil))))) + +;; Blockdatei ohne DXFM_OMNIFLO suchen: data/omniflo// in der aktuellen +;; Dimension, dann in der anderen (der 3D-Datenbestand ist unvollstaendig). +(defun kf-block-datei-direkt (blockname / basis dim kandidaten treffer) + (setq basis (getenv "DXFM_DATA")) + (if (or (null basis) (= basis "")) + (setq basis (strcat (getenv "DXFMAKRO") "/data"))) + (setq dim (if (car (atoms-family 1 '("SSG-ILS-DIM-AKTUELL"))) + (ssg-ils-dim-aktuell) "2D")) + (setq kandidaten + (list (strcat basis "/omniflo/" dim "/" blockname ".dwg") + (strcat basis "/omniflo/" dim "/" blockname ".dxf") + (strcat basis "/omniflo/" (if (= dim "2D") "3D" "2D") "/" blockname ".dwg") + (strcat basis "/omniflo/" (if (= dim "2D") "3D" "2D") "/" blockname ".dxf"))) + (setq treffer nil) + (foreach k kandidaten + (if (and (null treffer) (findfile k)) (setq treffer (findfile k)))) + treffer) + +;; ============================================================ +;; AUFRICHTUNG: die Baugruppe steht im Modell +;; ============================================================ +;; Die Bausteine sind in der XY-Ebene gezeichnet (X = Fahrtrichtung, +;; Y = Hoehe) - eine Seitenansicht. Eingebaut werden sie AUFGERICHTET: um +;; 90 Grad um die Fahrtrichtungsachse gekippt, sodass die Hoehe nach Z +;; wandert. Von oben sieht man dann die Draufsicht KF_2D, von der Seite die +;; Kette; beide bilden EIN Objekt ohne Versatz. +;; +;; Gesamtdrehung je Baustein: Rz(basis) * Rx(90) * Rz(eigen) +;; Rz(eigen) richtet den Baustein in seiner EIGENEN Zeichenebene aus +;; (der aus den KS gemessene Verkettungswinkel, s. kf-hz-folge) +;; Rx(90) richtet die Ebene auf - Y_lokal wird zu Z_welt +;; Rz(basis) dreht die fertige Baugruppe in der Draufsicht +;; Die Reihenfolge ist nicht beliebig: Rz(eigen) muss VOR dem Aufrichten +;; wirken (es ist eine Drehung in der Blockebene), Rz(basis) danach (es ist +;; eine Drehung im Modell). +(defun kf-matrix-aufrecht (basis-hz eigen-hz) + (mat3-mul-mat3 (ks-rot-z basis-hz) + (mat3-mul-mat3 (ks-rot-x 90.0) (ks-rot-z eigen-hz)))) + +;; ============================================================ +;; GEOMETRIE EINER VARIANTE MESSEN +;; ============================================================ +;; Misst alle drei Bloecke EINMAL und leitet daraus die Einbauwinkel und die +;; Hoehenbeitraege ab. Ergebnis ist ein Record (Assoc-Liste), den der Dialog +;; fuer die Laengenvorschau und der Bau fuer die eigentliche Verkettung nutzt - +;; gemessen wird also genau einmal je Variante. +;; +;; Felder: "hz-um" "hz-l" "hz-an" Einbauwinkel (Grad, relativ zur Basisdrehung) +;; "dy-um" "dy-an" Hoehenbeitrag der beiden Stationen +;; "dx-l" "dy-l" nativer KS-Versatz von KF_LAENGE +;; "fehler" Sprach-Key, falls etwas fehlt (sonst nil) +(defun kf-geometrie-messen (variante / b-um b-l b-an ks-um ks-l ks-an + v-um v-l v-an beta-aus-um beta-ein-l beta-aus-l + beta-ein-an hz-um hz-l hz-an dy-um dy-an fehler) + (setq b-um (ssg-json-get variante "block_umlenkung") + b-l (ssg-json-get variante "block_laenge") + b-an (ssg-json-get variante "block_antrieb")) + (setq fehler nil) + (if (not (and (kf-block-laden b-um) (kf-block-laden b-l) (kf-block-laden b-an))) + (setq fehler "kf-fehler-block-datei") + (progn + (setq ks-um (ks-block-messen b-um) + ks-l (ks-block-messen b-l) + ks-an (ks-block-messen b-an)) + (setq v-um (ks-versatz ks-um) + v-l (ks-versatz ks-l) + v-an (ks-versatz ks-an)) + (if (not (and v-um v-l v-an)) + ;; Welcher Baustein klemmt, gehoert ins Protokoll: die Meldung allein + ;; ("in mindestens einem Baustein fehlt ein KS") hat bei einem realen + ;; Fehlschlag nicht gereicht, um die Ursache zu finden - sie lag nicht + ;; an den Bloecken, sondern an einem falsch aufgeloesten Blocknamen + ;; (siehe ensure-block-loaded in ssg_ks_insert.lsp). + (progn + (foreach paar (list (cons b-um v-um) (cons b-l v-l) (cons b-an v-an)) + (if (null (cdr paar)) + (princ (ssg-textf "kf-pruef-ks-fehlt-kurz" (list (car paar)))))) + (setq fehler "kf-fehler-ks-fehlt")) + (progn + ;; Eigenrichtungen der KS in Blockkoordinaten + (setq beta-aus-um (ks-xy-winkel (cadr (assoc "KS_AUS" ks-um))) + beta-ein-l (ks-xy-winkel (cadr (assoc "KS_EIN" ks-l))) + beta-aus-l (ks-xy-winkel (cadr (assoc "KS_AUS" ks-l))) + beta-ein-an (ks-xy-winkel (cadr (assoc "KS_EIN" ks-an)))) + ;; Die Umlenkstation gibt die Basisrichtung vor (hz 0); jeder + ;; Folgebaustein richtet sich am KS_AUS seines Vorgaengers aus. + (setq hz-um 0.0) + (setq hz-l (cond ((kf-hz-folge (if beta-aus-um (+ beta-aus-um hz-um) nil) + beta-ein-l)) (0.0))) + (setq hz-an (cond ((kf-hz-folge (if beta-aus-l (+ beta-aus-l hz-l) nil) + beta-ein-an)) (hz-l))) + ;; Hoehenbeitrag der beiden Stationen (Massstab 1) + (setq dy-um (kf-dy-eingebaut (car v-um) (cadr v-um) hz-um 1.0)) + (setq dy-an (kf-dy-eingebaut (car v-an) (cadr v-an) hz-an 1.0)))))) + (list (cons "fehler" fehler) + (cons "hz-um" (cond (hz-um) (0.0))) + (cons "hz-l" (cond (hz-l) (0.0))) + (cons "hz-an" (cond (hz-an) (0.0))) + (cons "dy-um" (cond (dy-um) (0.0))) + (cons "dy-an" (cond (dy-an) (0.0))) + (cons "dx-l" (cond ((car v-l)) (0.0))) + (cons "dy-l" (cond ((cadr v-l)) (0.0))))) + +;; Laenge + Massstab fuer eine Hoehe aus einem gemessenen Geometrie-Record. +;; Rueckgabe: (laenge skala fehler-key) - laenge/skala nil bei Fehler. +(defun kf-laenge-fuer-hoehe (geo hoehe lmin lmax / laenge skala key) + (if (cdr (assoc "fehler" geo)) + (list nil nil (cdr (assoc "fehler" geo))) + (progn + (setq laenge (kf-laenge-berechnen hoehe + (cdr (assoc "dy-um" geo)) + (cdr (assoc "dy-an" geo)) + (cdr (assoc "dy-l" geo)) + (cdr (assoc "hz-l" geo)))) + (setq key (kf-laenge-pruefen laenge lmin lmax)) + (if key + (list nil nil key) + (progn + (setq skala (kf-skala-berechnen laenge (cdr (assoc "dx-l" geo)))) + (if (null skala) + (list nil nil "kf-fehler-ks-fehlt") + (list laenge skala nil))))))) + +;; ============================================================ +;; KETTE BAUEN (aufgerichtet, 3D) +;; ============================================================ +;; Fuegt die drei Bausteine ab startpunkt verkettet ein - AUFGERICHTET, die +;; Hoehe laeuft also in Z (siehe kf-matrix-aufrecht). basis-hz dreht die +;; fertige Baugruppe in der Draufsicht. +;; Rueckgabe: Weltpunkt des KS_AUS der Antriebsstation (Kettenende). +(defun kf-kette-bauen (variante geo skala startpunkt basis-hz / pt) + (setq pt startpunkt) + (setq pt (insert-block-ks-matrix + (ssg-json-get variante "block_umlenkung") + pt + (kf-matrix-aufrecht basis-hz (cdr (assoc "hz-um" geo))) + 1.0)) + (setq pt (insert-block-ks-matrix + (ssg-json-get variante "block_laenge") + pt + (kf-matrix-aufrecht basis-hz (cdr (assoc "hz-l" geo))) + skala)) + (setq pt (insert-block-ks-matrix + (ssg-json-get variante "block_antrieb") + pt + (kf-matrix-aufrecht basis-hz (cdr (assoc "hz-an" geo))) + 1.0)) + pt) + +;; ------------------------------------------------------------ +;; DRAUFSICHT (KF_2D) - deckungsgleich mit der Kette +;; ------------------------------------------------------------ +;; KF_2D gehoert in DIESELBE Lage wie die Kette, nicht daneben: sein KS_EIN +;; faellt mit KS_EIN der Umlenkung zusammen, sein KS_AUS mit KS_AUS des +;; Antriebs. Dadurch ist die Baugruppe EIN Koerper - von oben sieht man die +;; Draufsicht, von der Seite die Kette. +;; +;; Zwischenzeitlich lagen beide Ansichten 1000 mm uebereinander (versatz_y). +;; Das war eine Hilfskonstruktion, solange die Kette flach in der XY-Ebene +;; lag; seit sie aufgerichtet eingebaut wird, ist sie unnoetig. +;; +;; KF_2D ist selbst flach gezeichnet und wird darum NICHT aufgerichtet, +;; sondern nur um die Steigung GENEIGT (Rz(basis) * Ry(-winkel)) und auf die +;; SCHRAEGLAENGE skaliert. In der Draufsicht erscheint es dadurch genau mit +;; der Grundrisslaenge - die Verkuerzung uebernimmt die Projektion, sie muss +;; nicht gerechnet werden. + +;; Neigungswinkel und Schraeglaenge aus Start- und Endpunkt der Kette. +;; Rueckgabe: (schraeglaenge winkel-grad), oder nil bei Laenge 0. +;; Reiner Rechenkern - der eigentliche Pruefpunkt der 3D-Umstellung. +(defun kf-neigung-aus-punkten (p1 p2 basis-hz / rad dx dy dz horiz laenge) + (setq rad (* (float basis-hz) (/ pi 180.0))) + (setq dx (- (car p2) (car p1)) + dy (- (cadr p2) (cadr p1)) + dz (- (cond ((caddr p2)) (0.0)) (cond ((caddr p1)) (0.0)))) + ;; Horizontalanteil auf die Kettenrichtung projiziert (nicht die blosse + ;; X-Differenz) - sonst stimmt es bei gedreht eingefuegter Kette nicht. + (setq horiz (+ (* dx (cos rad)) (* dy (sin rad)))) + (setq laenge (sqrt (+ (* horiz horiz) (* dz dz)))) + (if (< laenge 1e-6) + nil + (list laenge (/ (* 180.0 (atan dz horiz)) pi)))) + +;; Draufsicht einfuegen - deckungsgleich mit der Kette. +;; kette-start/kette-ende = KS_EIN der Umlenkung bzw. KS_AUS des Antriebs. +;; Rueckgabe: die verwendete Schraeglaenge (mm), sonst nil. +(defun kf-ansicht-2d-bauen (daten kette-start kette-ende basis-hz / + cfg bname masse laenge winkel ks ein-dx skala R) + (setq cfg (ssg-json-get daten "ansicht_2d")) + (setq bname (if cfg (ssg-json-get cfg "block"))) + (if (or (null bname) (= bname "")) + nil + (progn + (setq masse (kf-neigung-aus-punkten kette-start kette-ende basis-hz)) + (if (null masse) + (progn (princ (ssg-text "kf-2d-laenge-null")) nil) + (progn + (setq laenge (car masse) winkel (cadr masse)) + (if (null (kf-block-laden bname)) + (progn (princ (ssg-textf "kf-fehler-block-datei" (list bname))) nil) + (progn + (setq ks (ks-block-messen bname)) + (setq ein-dx (car (cond ((ks-versatz ks)) ('(0.0 0.0 0.0))))) + (setq skala (kf-skala-berechnen laenge ein-dx)) + (if (null skala) + (progn (princ (ssg-textf "kf-2d-ks-fehlt" (list bname))) nil) + (progn + ;; Ry(-winkel) hebt die flache Scheibe um die Steigung an; + ;; das Vorzeichen folgt der Rechte-Hand-Regel (positives dz + ;; = Drehung gegen den Uhrzeigersinn um die Y-Achse). + (setq R (mat3-mul-mat3 (ks-rot-z basis-hz) + (ks-rot-y (- winkel)))) + (insert-block-ks-matrix bname kette-start R skala) + (princ (ssg-textf "kf-2d-gebaut" + (list bname (rtos laenge 2 0) (rtos winkel 2 1)))) + laenge))))))))) + +;; ============================================================ +;; BLOCK ABSCHLIESSEN +;; ============================================================ +;; Sammelt alle seit last-ent erzeugten Objekte, legt die ATTDEFs an, fasst +;; alles zu KF_ zusammen und beschriftet den fertigen Block. +;; Vorbild: gf-block-erstellen (Gefaellestrecke.lsp). +(defun kf-block-erstellen (variante geo hoehe laenge verbindung + startpunkt last-ent kf-nummer + farbe antriebsmotor movimot steuerung / + kf-bname kf-ss ent kf-insert winkel-str) + (setq kf-bname (strcat "KF_" (itoa kf-nummer))) + ;; Winkel als nackte Ganzzahl ("26"/"30") - bewusst ueber itoa/fix statt + ;; rtos: rtos haengt an DIMZIN/LUPREC/LUNITS der Zeichnung und liefert je + ;; nach Zeichnungsprofil unterschiedliche Schreibweisen. Dieser Wert landet + ;; als Attribut WINKEL am Block und wird vom Sivas-Export als Schluessel der + ;; Tabelle [kf_typ_anzeige] verwendet - er muss profilunabhaengig sein + ;; (gleiche Ueberlegung wie bei ssg-sepliste-fmt-koord). + (setq winkel-str (itoa (fix (+ 0.5 (kf-zahl (ssg-json-get variante "winkel")))))) + ;; ATTDEFs (unsichtbar, am Startpunkt) nach dem KF-Schema + (foreach def (kf-attrib-defs) + (entmake + (list '(0 . "ATTDEF") + (cons 10 startpunkt) + (cons 11 startpunkt) + '(40 . 50.0) + (cons 1 (cadr def)) + (cons 2 (car def)) + (cons 3 (car def)) + '(70 . 1) + '(72 . 0) + '(74 . 0)))) + ;; Alle Entities seit last-ent einsammeln + (setq kf-ss (ssadd)) + (setq ent (if last-ent (entnext last-ent) (entnext))) + (while ent + (ssadd ent kf-ss) + (setq ent (entnext ent))) + ;; Block definieren und einfuegen (OSMODE/ELEVATION/BKS werden dabei sauber + ;; abgeschaltet, siehe ssg-block-wrap-welt) + (ssg-block-wrap-welt kf-bname startpunkt kf-ss) + (setq kf-insert (entlast)) + (ssg-layer-setzen kf-insert (cond ((ssg-json-get variante "layer")) + ((ssg-json-get (kf-daten) "layer")) + ("kettenfoerderer"))) + (ssg-attrib-set-on kf-insert + (list + (cons "Bezeichnung" kf-bname) + ;; FOERDERERTYP = Bauart im Klartext (aus der JSON, mit Sonderzeichen), + ;; WINKEL = Neigungswinkel als nackte Zahl ("26"/"30") + (cons "FOERDERERTYP" (cond ((ssg-json-get (kf-daten) "foerderertyp")) + ("Schraegfoerderer Stahl"))) + (cons "WINKEL" winkel-str) + (cons "H_EINBAUHOEHE_mm" (itoa (fix (+ 0.5 (float hoehe))))) + (cons "L_KF_mm" (itoa (fix (+ 0.5 (float laenge))))) + (cons "FARBE" (cond (farbe) ("RAL 7035"))) + (cons "ANTRIEBSMOTOR" (cond (antriebsmotor) ("Europa 50 Hz"))) + (cons "ANTRIEBSART" (kf-antriebsart (kf-daten))) + (cons "MOVIMOT" (cond (movimot) ("0"))) + (cons "STANDARD_STEUERUNG" (cond (steuerung) ("0"))) + (cons "VERBINDUNGSELEMENTE" (cond (verbindung) ("Nein"))) + (cons "ARTINR" (cond ((ssg-json-get variante "sivas_id")) (""))))) + (ssg-id-generate kf-insert) + kf-insert) + +;; Naechste freie KF-Nummer in der Zeichnung. +(defun kf-naechste-nummer ( / n ss i nm num) + (setq n 0) + (setq ss (ssget "X" '((0 . "INSERT") (2 . "KF_*")))) + (if ss + (progn + (setq i 0) + (while (< i (sslength ss)) + (setq nm (cdr (assoc 2 (entget (ssname ss i))))) + (setq num (atoi (substr nm 4))) + (if (> num n) (setq n num)) + (setq i (1+ i))))) + (1+ n)) + +;; ============================================================ +;; DIALOG +;; ============================================================ +;; Zustand zwischen den Aufrufen (Dialogstand bleibt je Sitzung erhalten). +(if (not (boundp '*kf-dlg-variante*)) (setq *kf-dlg-variante* 0)) +(if (not (boundp '*kf-dlg-variante-gesetzt*)) (setq *kf-dlg-variante-gesetzt* nil)) +(if (not (boundp '*kf-dlg-hoehe*)) (setq *kf-dlg-hoehe* nil)) +(if (not (boundp '*kf-dlg-verbindung*)) (setq *kf-dlg-verbindung* 0)) +(if (not (boundp '*kf-dlg-farbe*)) (setq *kf-dlg-farbe* 0)) +(if (not (boundp '*kf-dlg-antriebsmotor*)) (setq *kf-dlg-antriebsmotor* 0)) +(if (not (boundp '*kf-dlg-movimot*)) (setq *kf-dlg-movimot* 0)) +(if (not (boundp '*kf-dlg-steuerung*)) (setq *kf-dlg-steuerung* 0)) + +;; ------------------------------------------------------------ +;; DCL-VORLAGE: Varianten als Radiobuttons +;; ------------------------------------------------------------ +;; DCL kann Tiles weder zur Laufzeit erzeugen noch radio_button-Labels per +;; set_tile aendern - der Varianten-Block wird darum an der Markerzeile +;; //@VARIANTEN@ eingesetzt (gleiches Vorgehen wie geruest_traeger.dcl). +;; Radiobuttons statt popup_list, weil es nur zwei Werte gibt und beide ohne +;; Aufklappen sichtbar sein sollen. + +(defun kf-radio-key (i) (strcat "v" (itoa i))) + +(defun kf-radio-index (key) + (if (and key (> (strlen key) 1)) (atoi (substr key 2)) 0)) + +;; Beschriftung einer Variante im Dialog: NUR der Winkel ("26 °"), nicht der +;; volle Variantenname - der Gruppenrahmen nennt bereits "Schraegfoerderer +;; Stahl Typ", der Name waere dort doppelt. Die Regelausfuehrung bekommt den +;; Zusatz "(Standard)". +;; +;; Welche Variante das ist, steht als "standard": true in der JSON und NICHT +;; im Code - sonst muesste bei einer Umstellung auf 30 Grad der Code +;; angefasst werden. Das Gradzeichen kommt als (chr 176) ueber einen +;; Platzhalter, damit die Sprachdatei ASCII bleibt (Projektkonvention). +(defun kf-variante-standard-p (variante) + (not (null (ssg-json-get variante "standard")))) + +(defun kf-variante-label (variante / w) + (setq w (kf-zahl (ssg-json-get variante "winkel"))) + (if w + (ssg-textf (if (kf-variante-standard-p variante) + "kf-variante-label-standard" + "kf-variante-label") + (list (itoa (fix (+ 0.5 w))) (chr 176))) + (cond ((ssg-json-get variante "name")) ("")))) + +;; Index der Standard-Variante (fuer die Vorauswahl beim ersten Aufruf); +;; 0, wenn keine als Standard markiert ist. +(defun kf-standard-index (varianten / i treffer) + (setq i 0 treffer nil) + (foreach v varianten + (if (and (null treffer) (kf-variante-standard-p v)) (setq treffer i)) + (setq i (1+ i))) + (cond (treffer) (0))) + +(defun kf-dcl-varianten-zeilen (varianten / i erg) + (setq erg nil i 0) + (foreach v varianten + (setq erg (append erg + (list (strcat " : radio_button { key = \"" (kf-radio-key i) + "\"; label = \"" (kf-variante-label v) "\"; }")))) + (setq i (1+ i))) + erg) + +;; Rueckgabe: Pfad der gefuellten temporaeren DCL-Datei oder nil. +(defun kf-dcl-schreiben (varianten) + (ssg-dcl-vorlage-fuellen (kf-dcl-pfad "kettenfoerderer.dcl") "kettenfoerderer" + (list (cons "VARIANTEN" (kf-dcl-varianten-zeilen varianten))))) + +;; ------------------------------------------------------------ +;; EINBAUHOEHE AUS ZWEI GEPICKTEN PUNKTEN +;; ------------------------------------------------------------ +;; Zweite Eingabeart neben dem Zahlenfeld: statt die Hoehe zu tippen, werden +;; zwei Punkte in der Zeichnung gewaehlt (z.B. Oberkante Zufuehrung und +;; Oberkante Abgabe) und ihr Hoehenunterschied uebernommen. +;; +;; Gemessen wird die Differenz in Z: die Baugruppe steht aufgerichtet im Modell +;; (siehe kf-matrix-aufrecht), ihre Hoehe ist also die Hoehe der Zeichnung. +;; Damit nachvollziehbar bleibt, was gemessen wurde, nennt die Meldung BEIDE +;; Differenzen (dZ und dY): wer in einer DRAUFSICHT pickt, sieht sofort, dass +;; dort dZ = 0 ist, und kann es melden. +;; +;; Reiner Rechenkern (ohne Zeichnung testbar): nimmt zwei Punkte, liefert +;; (dz dy) als Betraege - die HOEHE zuerst. +(defun kf-punkt-differenzen (p1 p2) + (if (and p1 p2 (listp p1) (listp p2)) + (list (abs (- (cond ((caddr p2)) (0.0)) (cond ((caddr p1)) (0.0)))) + (abs (- (cadr p2) (cadr p1)))) + nil)) + +;; Zwei Punkte abfragen und die Hoehe daraus bestimmen. +;; Rueckgabe: Hoehe in mm, oder nil bei Abbruch. +(defun kf-hoehe-aus-punkten ( / p1 p2 d) + (setq p1 (getpoint (ssg-text "kf-prompt-messpunkt-1"))) + (if (null p1) + nil + (progn + (setq p2 (getpoint p1 (ssg-text "kf-prompt-messpunkt-2"))) + (if (null p2) + nil + (progn + (setq d (kf-punkt-differenzen p1 p2)) + (princ (ssg-textf "kf-messung-ergebnis" + (list (rtos (car d) 2 1) (rtos (cadr d) 2 1)))) + (car d)))))) + +;; Wert eines Auswahleintrags ("wert", sonst "name", sonst Vorgabe). +;; Gemeinsam genutzt von Dialog und Konsolen-Rueckfall. +(defun kf-auswahl-wert (liste idx vorgabe / e) + (setq e (nth idx liste)) + (cond ((null e) vorgabe) + ((ssg-json-get e "wert")) + ((ssg-json-get e "name")) + (t vorgabe))) + +;; Dialog zeigen - SCHLEIFE, bis OK oder Abbruch. Rueckgabe: +;; (variante hoehe verbindung farbe antriebsmotor movimot) +;; oder nil bei Abbruch. +;; +;; Die Schleife gibt es wegen der zweiten Hoehen-Eingabeart: der Knopf +;; "Hoehe messen" schliesst den Dialog (done_dialog 2), laesst zwei Punkte in +;; der Zeichnung waehlen und zeigt ihn mit dem gemessenen Wert erneut. Aus +;; einem action_tile heraus laesst sich kein getpoint aufrufen - dasselbe +;; Vorgehen wie beim Speichern/Loeschen im Geruest-Dialog (done_dialog 2/3). +;; +;; Die Laengenvorschau unter dem Hoehenfeld gab es zwischenzeitlich; sie ist +;; auf Nutzerwunsch wieder entfallen. Ein nicht baubarer Wert wird darum erst +;; nach OK gemeldet (alert in c:KETTENFOERDERER). +(defun kf-dialog (daten / varianten verbindungen farben motoren movimots + steuerungen idx ergebnis gemessen) + (setq varianten (ssg-json-get daten "varianten") + verbindungen (ssg-json-get daten "verbindungselemente") + farben (ssg-json-get daten "farbe") + motoren (ssg-json-get daten "antriebsmotor") + movimots (ssg-json-get daten "movimot") + steuerungen (ssg-json-get daten "standard_steuerung")) + (if (null *kf-dlg-hoehe*) + (setq *kf-dlg-hoehe* + (cond ((kf-zahl (ssg-json-get (ssg-json-get daten "einbauhoehe") "vorgabe"))) + (1500.0)))) + ;; Beim ERSTEN Aufruf je Sitzung die Regelausfuehrung vorwaehlen; danach + ;; bleibt die zuletzt getroffene Wahl stehen. + (if (null *kf-dlg-variante-gesetzt*) + (progn + (setq *kf-dlg-variante* (kf-standard-index varianten)) + (setq *kf-dlg-variante-gesetzt* T))) + (setq idx 2) + (while (= idx 2) + (setq idx (kf-dialog-zeigen daten varianten verbindungen farben motoren + movimots steuerungen)) + (if (= idx 2) + (progn + (setq gemessen (kf-hoehe-aus-punkten)) + (if gemessen (setq *kf-dlg-hoehe* gemessen)))) + ) + (if (= idx 1) + (list (nth *kf-dlg-variante* varianten) + *kf-dlg-hoehe* + (kf-auswahl-wert verbindungen *kf-dlg-verbindung* "Nein") + (kf-auswahl-wert farben *kf-dlg-farbe* "RAL 7035") + (kf-auswahl-wert motoren *kf-dlg-antriebsmotor* "Europa 50 Hz") + (kf-auswahl-wert movimots *kf-dlg-movimot* "0") + (kf-auswahl-wert steuerungen *kf-dlg-steuerung* "0")) + nil)) + +;; Den Dialog EINMAL zeigen. Rueckgabe: 1 = OK, 0 = Abbruch, 2 = "Hoehe +;; messen" (der Aufrufer pickt dann zwei Punkte und ruft erneut auf). +(defun kf-dialog-zeigen (daten varianten verbindungen farben motoren movimots + steuerungen / + dcl-pfad dcl-id idx vb-namen fa-namen nz-namen mm-namen + st-namen) + (setq vb-namen (mapcar '(lambda (v) (cond ((ssg-json-get v "name")) (""))) verbindungen)) + (setq fa-namen (mapcar '(lambda (v) (cond ((ssg-json-get v "name")) (""))) farben)) + (setq nz-namen (mapcar '(lambda (v) (cond ((ssg-json-get v "name")) (""))) motoren)) + (setq mm-namen (mapcar '(lambda (v) (cond ((ssg-json-get v "name")) (""))) movimots)) + (setq st-namen (mapcar '(lambda (v) (cond ((ssg-json-get v "name")) (""))) steuerungen)) + ;; Vorlage mit den Varianten-Radiobuttons fuellen (Temp-Datei) + (setq dcl-pfad (kf-dcl-schreiben varianten)) + (if (not dcl-pfad) + (progn (alert (ssg-text "kf-fehler-dcl")) 0) + (progn + (setq dcl-id (load_dialog dcl-pfad)) + (if (not (new_dialog "kettenfoerderer" dcl-id)) + (progn (unload_dialog dcl-id) (vl-file-delete dcl-pfad) + (alert (ssg-text "kf-fehler-dcl")) 0) + (progn + (start_list "verbindung") (mapcar 'add_list vb-namen) (end_list) + (start_list "farbe") (mapcar 'add_list fa-namen) (end_list) + (start_list "antriebsmotor") (mapcar 'add_list nz-namen) (end_list) + (start_list "movimot") (mapcar 'add_list mm-namen) (end_list) + (start_list "standard_steuerung") (mapcar 'add_list st-namen) (end_list) + ;; Radiobuttons: der gewaehlte Button wird einzeln gesetzt, nicht + ;; die Gruppe - set_tile auf den Gruppenschluessel waehlt nicht aus. + (if (>= *kf-dlg-variante* (length varianten)) (setq *kf-dlg-variante* 0)) + (set_tile (kf-radio-key *kf-dlg-variante*) "1") + (set_tile "verbindung" (itoa *kf-dlg-verbindung*)) + (set_tile "farbe" (itoa *kf-dlg-farbe*)) + (set_tile "antriebsmotor" (itoa *kf-dlg-antriebsmotor*)) + (set_tile "movimot" (itoa *kf-dlg-movimot*)) + (set_tile "standard_steuerung" (itoa *kf-dlg-steuerung*)) + (set_tile "hoehe" (rtos *kf-dlg-hoehe* 2 0)) + ;; Merkmal 357: Dropdown mit einer einzigen Option, immer gewaehlt + (start_list "antriebsart") + (mapcar 'add_list + (mapcar '(lambda (v) (cond ((ssg-json-get v "name")) (""))) + (ssg-json-get daten "antriebsart"))) + (end_list) + (set_tile "antriebsart" "0") + ;; Die Radiobutton-Gruppe meldet den KEY des gewaehlten Buttons + ;; ("v0"/"v1"), nicht dessen Index - daher kf-radio-index. + (action_tile "variante" "(setq *kf-dlg-variante* (kf-radio-index $value))") + (action_tile "hoehe" "(setq *kf-dlg-hoehe* (atof $value))") + (action_tile "verbindung" "(setq *kf-dlg-verbindung* (atoi $value))") + (action_tile "farbe" "(setq *kf-dlg-farbe* (atoi $value))") + (action_tile "antriebsmotor" "(setq *kf-dlg-antriebsmotor* (atoi $value))") + (action_tile "movimot" "(setq *kf-dlg-movimot* (atoi $value))") + (action_tile "standard_steuerung" "(setq *kf-dlg-steuerung* (atoi $value))") + ;; Zweite Hoehen-Eingabeart: Dialog schliessen, der Aufrufer laesst + ;; zwei Punkte waehlen und zeigt ihn danach erneut. + (action_tile "hoehe_messen" "(done_dialog 2)") + (action_tile "accept" "(done_dialog 1)") + (action_tile "cancel" "(done_dialog 0)") + (setq idx (start_dialog)) + (unload_dialog dcl-id) + (vl-file-delete dcl-pfad) + idx))))) + +;; DCL-Pfad aufloesen: DXFM_DCL, sonst dcl/ unter der Projektwurzel. +;; Gleiches Vorgehen wie geruest-dcl-pfad (GeruestInsert.lsp) - bewusst eine +;; eigene kurze Funktion statt eines Zugriffs quer ins Geruest-Modul, damit +;; dieses Modul ohne GeruestInsert ladbar bleibt. +(defun kf-dcl-pfad (datei / basis) + (setq basis (cond ((getenv "DXFM_DCL")) + ((strcat (getenv "DXFMAKRO") "/dcl")))) + (if (and basis (/= basis "")) + (findfile (strcat basis "/" datei)) + nil)) + +;; ============================================================ +;; KONSOLEN-RUECKFALL (ohne GUI, fuer automatische Tests) +;; ============================================================ +;; Eine Auswahlliste auf der Konsole abfragen. Rueckgabe: 0-basierter Index +;; (bei ungueltiger Eingabe bleibt die Vorgabe stehen). Alle vier Auswahlen +;; laufen hierueber - vorher stand derselbe Block viermal untereinander. +(defun kf-konsole-wahl (liste header-key prompt-key vorgabe-idx / i v antwort) + (princ (ssg-text header-key)) + (setq i 1) + (foreach v liste + (princ (ssg-textf "kf-konsole-option" (list (itoa i) (ssg-json-get v "name")))) + (setq i (1+ i))) + (setq antwort (ssg-in-int (ssg-text prompt-key) (1+ vorgabe-idx))) + (if (or (null antwort) (< antwort 1) (> antwort (length liste))) + vorgabe-idx + (1- antwort))) + +(defun kf-konsole (daten / varianten verbindungen farben motoren movimots + steuerungen hoehe) + (setq varianten (ssg-json-get daten "varianten") + verbindungen (ssg-json-get daten "verbindungselemente") + farben (ssg-json-get daten "farbe") + motoren (ssg-json-get daten "antriebsmotor") + movimots (ssg-json-get daten "movimot") + steuerungen (ssg-json-get daten "standard_steuerung")) + (setq *kf-dlg-variante* + (kf-konsole-wahl varianten "kf-konsole-variante-header" + "kf-konsole-variante-prompt" *kf-dlg-variante*)) + ;; Hoehe: Zahl tippen ODER 0 eingeben und zwei Punkte messen (zweite + ;; Eingabeart, entspricht dem Knopf im Dialog). + (princ (ssg-text "kf-konsole-hoehe-hinweis")) + (setq hoehe (ssg-in-real (ssg-text "kf-konsole-hoehe-prompt") + (cond (*kf-dlg-hoehe*) (1500.0)))) + (if (and hoehe (<= hoehe 0.0)) + (setq hoehe (kf-hoehe-aus-punkten))) + (if (null hoehe) (setq hoehe (cond (*kf-dlg-hoehe*) (1500.0)))) + (setq *kf-dlg-hoehe* hoehe) + ;; Reihenfolge wie im Dialog und im Sivas-Merkmalstamm: 361 vor 354. + (setq *kf-dlg-steuerung* + (kf-konsole-wahl steuerungen "kf-konsole-steuerung-header" + "kf-konsole-steuerung-prompt" *kf-dlg-steuerung*)) + (setq *kf-dlg-farbe* + (kf-konsole-wahl farben "kf-konsole-farbe-header" + "kf-konsole-farbe-prompt" *kf-dlg-farbe*)) + (setq *kf-dlg-antriebsmotor* + (kf-konsole-wahl motoren "kf-konsole-antriebsmotor-header" + "kf-konsole-antriebsmotor-prompt" *kf-dlg-antriebsmotor*)) + (setq *kf-dlg-movimot* + (kf-konsole-wahl movimots "kf-konsole-movimot-header" + "kf-konsole-movimot-prompt" *kf-dlg-movimot*)) + (setq *kf-dlg-verbindung* + (kf-konsole-wahl verbindungen "kf-konsole-verbindung-header" + "kf-konsole-verbindung-prompt" *kf-dlg-verbindung*)) + (list (nth *kf-dlg-variante* varianten) + hoehe + (kf-auswahl-wert verbindungen *kf-dlg-verbindung* "Nein") + (kf-auswahl-wert farben *kf-dlg-farbe* "RAL 7035") + (kf-auswahl-wert motoren *kf-dlg-antriebsmotor* "Europa 50 Hz") + (kf-auswahl-wert movimots *kf-dlg-movimot* "0") + (kf-auswahl-wert steuerungen *kf-dlg-steuerung* "0"))) + +;; ============================================================ +;; C:KETTENFOERDERER - Hauptbefehl +;; ============================================================ +(defun c:KETTENFOERDERER ( / daten eingabe variante hoehe verbindung + farbe antriebsmotor movimot steuerung + geo erg laenge skala endpunkt + lmin lmax startpunkt drehung last-ent kf-nummer ent + t0) + ;; Laufzeit des Dialogaufbaus protokollieren (Schalter "kf-zeit", siehe + ;; ssg_dbg.lsp). Hintergrund: der Dialog erschien beim ersten Menueklick + ;; spuerbar verzoegert. Statt zu raten, wird die Zeit bis zum Dialog + ;; gemessen - so laesst sich unterscheiden, ob das Lazy-Laden des Moduls, + ;; das Lesen der JSON oder der Dialogaufbau selbst die Zeit kostet. + (setq t0 (getvar "MILLISECS")) + (setq daten (kf-daten)) + (kf-zeit "Daten geladen" t0) + (if (null daten) + (princ (ssg-text "kf-fehler-daten-leer")) + (progn + (setq lmin (kf-zahl (ssg-json-get (ssg-json-get daten "laenge") "min")) + lmax (kf-zahl (ssg-json-get (ssg-json-get daten "laenge") "max"))) + (kf-zeit "vor Dialog" t0) + (setq eingabe (if (ssg-gui-p) (kf-dialog daten) (kf-konsole daten))) + (if (null eingabe) + (princ (ssg-text "kf-abgebrochen")) + (progn + (setq variante (nth 0 eingabe) + hoehe (nth 1 eingabe) + verbindung (nth 2 eingabe) + farbe (nth 3 eingabe) + antriebsmotor (nth 4 eingabe) + movimot (nth 5 eingabe) + steuerung (nth 6 eingabe)) + (setq geo (kf-geometrie-messen variante)) + (setq erg (kf-laenge-fuer-hoehe geo hoehe lmin lmax)) + (if (caddr erg) + (progn + (if (ssg-gui-p) (alert (ssg-text (caddr erg)))) + (princ (ssg-text (caddr erg)))) + (progn + (setq laenge (car erg) skala (cadr erg)) + (setq startpunkt (getpoint (ssg-text "kf-prompt-startpunkt"))) + (if (null startpunkt) + (princ (ssg-text "kf-abgebrochen")) + (progn + (setq drehung (getangle startpunkt (ssg-text "kf-prompt-drehung"))) + (setq drehung (if drehung (/ (* 180.0 drehung) pi) 0.0)) + (ssg-start "Kettenfoerderer" '(("OSMODE") ("ATTREQ") ("ATTDIA"))) + (setvar "OSMODE" 0) + (setvar "ATTREQ" 0) + (setvar "ATTDIA" 0) + (setq last-ent (ssg-lastent-ohne-attribute)) + (setq kf-nummer (kf-naechste-nummer)) + ;; Kette und Draufsicht liegen DECKUNGSGLEICH - beide + ;; beginnen am gewaehlten Punkt. Die Draufsicht gehoert mit + ;; in dieselbe Baugruppe, darum VOR kf-block-erstellen. + (setq endpunkt (kf-kette-bauen variante geo skala startpunkt drehung)) + (kf-ansicht-2d-bauen daten startpunkt endpunkt drehung) + (setq ent (kf-block-erstellen variante geo hoehe laenge verbindung + startpunkt last-ent kf-nummer farbe antriebsmotor movimot + steuerung)) + (ssg-end) + (princ (ssg-textf "kf-fertig" + (list (strcat "KF_" (itoa kf-nummer)) + (rtos (float hoehe) 2 0) + (rtos laenge 2 0)))))))))))) + (princ)) + +;; ============================================================ +;; C:KF_EDIT - Doppelklick auf einen KF_n-Block +;; ============================================================ +;; Zeigt den Dialog mit den Werten des gewaehlten Blocks und baut ihn nach OK +;; an SEINER Stelle neu auf. Vorbild: GEFAELLESTRECKE_EDIT. +;; +;; Einfuegepunkt und Drehung kommen aus dem Block selbst (Gruppencode 10 und +;; 50), nicht aus XDATA: der Kettenfoerderer hat keine Aufbau-Historie, die +;; ueber diese beiden Werte hinausgeht - alles Uebrige steht in den +;; Attributen. Ein von Hand verschobener oder gedrehter Block wird dadurch +;; korrekt an seiner NEUEN Lage neu gebaut. +;; +;; Der alte Block wird erst geloescht, wenn der Benutzer den Dialog mit OK +;; bestaetigt hat - ein Abbruch laesst die Zeichnung unveraendert. + +;; Dialogzustand aus den Attributen eines Blocks vorbelegen. Sucht je +;; Auswahlliste den Index, dessen "wert" zum Attributwert passt; ein +;; unbekannter Wert (von Hand editiert) laesst die Vorgabe stehen, statt den +;; Dialog scheitern zu lassen. +(defun kf-index-fuer-wert (liste wert vorgabe / i treffer) + (setq i 0 treffer nil) + (foreach e liste + (if (and (null treffer) wert + (equal (ssg-json-get e "wert") wert)) + (setq treffer i)) + (setq i (1+ i))) + (cond (treffer) (vorgabe))) + +(defun kf-dlg-aus-attributen (daten attribs / w v) + (setq w (cdr (assoc "WINKEL" attribs))) + (if w + (progn + (setq v (kf-variante-finden (atof w) (ssg-json-get daten "varianten"))) + (if v + (setq *kf-dlg-variante* + (kf-tag-index-liste v (ssg-json-get daten "varianten")))))) + (setq w (cdr (assoc "H_EINBAUHOEHE_mm" attribs))) + (if (and w (/= w "")) (setq *kf-dlg-hoehe* (atof w))) + (setq *kf-dlg-farbe* + (kf-index-fuer-wert (ssg-json-get daten "farbe") + (cdr (assoc "FARBE" attribs)) *kf-dlg-farbe*)) + (setq *kf-dlg-antriebsmotor* + (kf-index-fuer-wert (ssg-json-get daten "antriebsmotor") + (cdr (assoc "ANTRIEBSMOTOR" attribs)) *kf-dlg-antriebsmotor*)) + (setq *kf-dlg-movimot* + (kf-index-fuer-wert (ssg-json-get daten "movimot") + (cdr (assoc "MOVIMOT" attribs)) *kf-dlg-movimot*)) + (setq *kf-dlg-steuerung* + (kf-index-fuer-wert (ssg-json-get daten "standard_steuerung") + (cdr (assoc "STANDARD_STEUERUNG" attribs)) *kf-dlg-steuerung*)) + (setq *kf-dlg-verbindung* + (kf-index-fuer-wert (ssg-json-get daten "verbindungselemente") + (cdr (assoc "VERBINDUNGSELEMENTE" attribs)) *kf-dlg-verbindung*)) + ;; Die Variante wurde eben aus dem Block gesetzt - die Standard-Vorauswahl + ;; darf sie nicht mehr ueberschreiben. + (setq *kf-dlg-variante-gesetzt* T) + (princ)) + +;; Position eines Eintrags in einer Liste (0-basiert), 0 wenn nicht gefunden. +(defun kf-tag-index-liste (eintrag liste / i treffer) + (setq i 0 treffer nil) + (foreach e liste + (if (and (null treffer) (equal e eintrag)) (setq treffer i)) + (setq i (1+ i))) + (cond (treffer) (0))) + +(defun c:KF_EDIT ( / ent ed bname attribs daten eingabe variante hoehe + verbindung farbe antriebsmotor movimot steuerung + geo erg laenge skala lmin lmax startpunkt drehung + last-ent kf-nummer endpunkt) + (setq ent (car (entsel (ssg-text "kf-edit-waehlen")))) + (if (null ent) + (princ (ssg-text "kf-abgebrochen")) + (progn + (setq ed (entget ent)) + (setq bname (cdr (assoc 2 ed))) + (if (or (/= (cdr (assoc 0 ed)) "INSERT") (not (wcmatch bname "KF_*"))) + (alert (ssg-text "kf-edit-kein-kf")) + (progn + (setq daten (kf-daten)) + (if (null daten) + (princ (ssg-text "kf-fehler-daten-leer")) + (progn + (setq attribs (ssg-attrib-read ent)) + (setq startpunkt (cdr (assoc 10 ed))) + (setq drehung (/ (* 180.0 (cond ((cdr (assoc 50 ed))) (0.0))) pi)) + (kf-dlg-aus-attributen daten attribs) + (setq lmin (kf-zahl (ssg-json-get (ssg-json-get daten "laenge") "min")) + lmax (kf-zahl (ssg-json-get (ssg-json-get daten "laenge") "max"))) + (setq eingabe (if (ssg-gui-p) (kf-dialog daten) (kf-konsole daten))) + (if (null eingabe) + (princ (ssg-text "kf-abgebrochen")) + (progn + (setq variante (nth 0 eingabe) + hoehe (nth 1 eingabe) + verbindung (nth 2 eingabe) + farbe (nth 3 eingabe) + antriebsmotor (nth 4 eingabe) + movimot (nth 5 eingabe) + steuerung (nth 6 eingabe)) + (setq geo (kf-geometrie-messen variante)) + (setq erg (kf-laenge-fuer-hoehe geo hoehe lmin lmax)) + (if (caddr erg) + (progn + (if (ssg-gui-p) (alert (ssg-text (caddr erg)))) + (princ (ssg-text (caddr erg)))) + (progn + (setq laenge (car erg) skala (cadr erg)) + (ssg-start "Kettenfoerderer Edit" + '(("OSMODE") ("ATTREQ") ("ATTDIA"))) + (setvar "OSMODE" 0) + (setvar "ATTREQ" 0) + (setvar "ATTDIA" 0) + ;; Erst jetzt loeschen - ein Abbruch oben laesst die + ;; Zeichnung unveraendert. + (entdel ent) + (setq last-ent (ssg-lastent-ohne-attribute)) + (setq kf-nummer (kf-naechste-nummer)) + (setq endpunkt (kf-kette-bauen variante geo skala + startpunkt drehung)) + (kf-ansicht-2d-bauen daten startpunkt endpunkt drehung) + (kf-block-erstellen variante geo hoehe laenge verbindung + startpunkt last-ent kf-nummer farbe antriebsmotor + movimot steuerung) + (ssg-end) + (princ (ssg-textf "kf-edit-fertig" + (list bname (strcat "KF_" (itoa kf-nummer))))))))))))))) + (princ)) + +;; ============================================================ +;; C:KF_KS_PRUEFEN - Diagnose +;; ============================================================ +;; Liest fuer jede Variante die KS-Lage aller drei Bloecke aus und gibt sie +;; aus: Ursprung und Richtung von KS_EIN/KS_AUS, daraus die abgeleiteten +;; Einbauwinkel und Hoehenbeitraege. Erste Anlaufstelle, wenn eine Kette +;; nicht zusammenpasst - zeigt sofort, ob ein Block kein KS_EIN/KS_AUS hat +;; oder ob seine Achsen anders liegen als erwartet. +(defun c:KF_KS_PRUEFEN ( / daten varianten geo b) + (setq daten (kf-daten)) + (if (null daten) + (princ (ssg-text "kf-fehler-daten-leer")) + (progn + (setq varianten (ssg-json-get daten "varianten")) + (princ (ssg-text "kf-pruef-kopf")) + (foreach v varianten + (princ (ssg-textf "kf-pruef-variante" (list (ssg-json-get v "name")))) + (foreach feld '("block_umlenkung" "block_laenge" "block_antrieb") + (setq b (ssg-json-get v feld)) + (kf-pruef-block b)) + (setq geo (kf-geometrie-messen v)) + (if (cdr (assoc "fehler" geo)) + (princ (ssg-textf "kf-pruef-fehler" (list (ssg-text (cdr (assoc "fehler" geo)))))) + (princ (ssg-textf "kf-pruef-geo" + (list (rtos (cdr (assoc "hz-um" geo)) 2 2) + (rtos (cdr (assoc "hz-l" geo)) 2 2) + (rtos (cdr (assoc "hz-an" geo)) 2 2) + (rtos (cdr (assoc "dy-um" geo)) 2 2) + (rtos (cdr (assoc "dy-an" geo)) 2 2) + (rtos (cdr (assoc "dx-l" geo)) 2 2) + (rtos (cdr (assoc "dy-l" geo)) 2 2)))))))) + (princ)) + +;; Einen Block vermessen und protokollieren (Helfer von KF_KS_PRUEFEN). +(defun kf-pruef-block (blockname / ks ein aus) + (if (null (kf-block-laden blockname)) + (princ (ssg-textf "kf-pruef-block-fehlt" (list blockname))) + (progn + (setq ks (ks-block-messen blockname)) + (setq ein (cadr (assoc "KS_EIN" ks)) + aus (cadr (assoc "KS_AUS" ks))) + (if (not (and ein aus)) + (princ (ssg-textf "kf-pruef-ks-fehlt" + (list blockname + (if ein (ssg-text "kf-pruef-ja") (ssg-text "kf-pruef-nein")) + (if aus (ssg-text "kf-pruef-ja") (ssg-text "kf-pruef-nein"))))) + (princ (ssg-textf "kf-pruef-block" + (list blockname + (rtos (car (car ein)) 2 2) (rtos (cadr (car ein)) 2 2) + (cond ((rtos (ks-xy-winkel ein) 2 2)) ("-")) + (rtos (car (car aus)) 2 2) (rtos (cadr (car aus)) 2 2) + (cond ((rtos (ks-xy-winkel aus) 2 2)) ("-"))))))))) + +(setq *ssg-KettenFoerderer-loaded* T) +(princ "\n>>> KettenFoerderer.lsp geladen (KETTENFOERDERER, KF_KS_PRUEFEN)") +(princ) diff --git a/Lisp/OmniModulInsert.lsp b/Lisp/OmniModulInsert.lsp index 8be22f0..19aefc2 100644 --- a/Lisp/OmniModulInsert.lsp +++ b/Lisp/OmniModulInsert.lsp @@ -1538,10 +1538,12 @@ ) ;; --- Omniflo / KettenFoerderer --- -(defun c:OMNI_KettenFoerderer () - (princ "\n[DUMMY] KettenFoerderer") - (princ) -) +;; Der Platzhalter c:OMNI_KettenFoerderer (reiner Dummy, gab nur "[DUMMY] +;; KettenFoerderer" aus und war im Menue ausgegraut) wurde entfernt: der +;; Kettenfoerderer ist seit 2026-10-22 als eigene Baugruppe umgesetzt, der +;; Menueeintrag zeigt jetzt auf (ssg-ensure "KettenFoerderer") KETTENFOERDERER +;; (Lisp/KettenFoerderer.lsp). Damit war der Dummy toter Code - auf ihn verwies +;; nur noch der Menueeintrag selbst. ;; --- Omniflo / Ladeschleifen --- diff --git a/Lisp/README.md b/Lisp/README.md index fd66a9e..af87d76 100644 --- a/Lisp/README.md +++ b/Lisp/README.md @@ -38,14 +38,13 @@ am Label-Anfang, bei Submenue-Schliessern hinter dem `<-` (`[<-~Label]`). | Menuebereich | Eintraege | Definiert in | | --- | --- | --- | -| Omniflo ▸ Anbaugruppen | `OMNI_KettenFoerderer` | `OmniModulInsert.lsp` | | Omniflo ▸ Boegen fuer Weichen | alle `OMNI_APBW_*` (14) | `OmniModulInsert.lsp` | | ILS ▸ Zusatzmodule/Kurven | `ILS_ETF_Etagenfoerderer`, `ILS_K{90,60,30}{L,R}{A,I}` (12), `ILS_Gefaellestrecke` | `SSG_LIB_Commands.lsp` | | Geruest | `GERUEST` (Gattertraeger-Platzhalter, derselbe Befehl wie Saeulen/Rohrabhaenger/Wandbefestigung) | `GeruestInsert.lsp` | `OMNI_TEF_*` (UmlenkR/L, AntriebL/R, TEF Gerade) sind entgegen frueherem Stand -**implementiert** (echtes `omni:insert-dxf`), nur `OMNI_KettenFoerderer` ist -weiterhin Dummy. +**implementiert** (echtes `omni:insert-dxf`). Der Menueeintrag KettenFoerderer ist +ebenfalls aktiv (Modul `KettenFoerderer.lsp`, Beschreibung in `CLAUDE.md`). Aktiv (implementiert) sind u.a.: Kreisel, Eckrad, VarioFoerderer (Standard/Etage/ Linienzug), Gefaellestrecke, Geruest (Saeulen/Rohrabhaenger/Wandbefestigung/ @@ -383,6 +382,10 @@ Gefaellestrecke/`vf_core` geladen sein; Kern-Modul. | `extract-ks-from-block[-raw] name` | KS_EIN/KS_AUS-Punkte eines Blocks ermitteln (Cache `*ks-cache*`) | | `insert-block-by-ks name ks-nr pt` | Block anhand KS-Punkt einfuegen (nur Rz-Rotation) | | `insert-inclined-scaled-block` / `insert-rotated-block-with-ks` | Geneigten/rotierten Block mit KS-Offset einfuegen (Rz*Ry) | +| `ks-xy-winkel ks-eintrag` | Richtung der X-Achse eines KS in der XY-Ebene (Grad); `nil` bei senkrechter Achse | +| `ks-block-messen name` / `ks-versatz ks-data` | KS eines Blocks messen, ohne bleibende Einfuegung / Versatz KS_AUS-KS_EIN | +| `insert-block-ks-skaliert name pt hz skala` | Wie `insert-block-by-ks`, zusaetzlich in X skaliert | +| `insert-block-ks-matrix name pt R skala` | Einfuegen mit **freier 3x3-Rotationsmatrix** (`ks-rot-x/-y/-z`), z.B. fuer aufgerichtete Baugruppen | --- @@ -701,7 +704,7 @@ AutoLISP-Implementierung fuer alle Omniflo-Komponenten: Boegen, Weichen, Verbind | 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 Elemente | `OMNI_TEF_UmlenkR/L`, `OMNI_TEF_AntriebL/R`, `OMNI_TEF_Gerade` | Feste TEF-Bauteile ohne Sivas-Auswahl, direkt per `omni:insert-dxf` an gewaehltem Punkt eingefuegt (Dateien unter `DXFM_OMNIFLO`) | -| Anbaugruppen ⚠️ | `OMNI_KettenFoerderer` | **Dummy** (noch nicht implementiert, im Menue ausgegraut); `OMNI_STOPPER`/`OMNI_SCANNER`/`OMNI_BUERSTENBREMSE` sind implementiert | +| Anbaugruppen | `OMNI_STOPPER`, `OMNI_SCANNER`, `OMNI_BUERSTENBREMSE` | Implementiert | | Verbinder | `OMNI_TV_*` | Verbinder-DWG aus `data/omniflo/Verbinder` (dimensionsunabhaengig, `omni:insert-verbinder` -> `omni:insert-datei`, Punkt + Drehung interaktiv). Zuordnung Befehl -> DWG nach Dateiname, z.B. `OMNI_TV_W_AP60` -> `TV_W-AP60_oben`, `OMNI_TV_ENDPLATTE` -> `VB_ENDP_AP`, `OMNI_TV_W_AP60_KPL` -> `TV_W-AP60_kpl`, `OMNI_TV_ANSCHLAG` -> `VB_ENDP_AP_Spulenzug`, `OMNI_TV_SW_I_AP110` -> `TV_SW_I_SW-AP110`, `OMNI_TV_MUFFE` -> `VB_Muffe`. Alle 18 Eintraege aktiv | | Bogenweichen ⚠️ | `OMNI_APBW_*` | **Dummy** (noch nicht implementiert, im Menue ausgegraut) | | Bearbeiten | `OMNI_EDIT` | Omniflo-Block selektieren und Attribute bearbeiten | diff --git a/Lisp/SSG_LIB_Commands.lsp b/Lisp/SSG_LIB_Commands.lsp index 943e707..05d372f 100644 --- a/Lisp/SSG_LIB_Commands.lsp +++ b/Lisp/SSG_LIB_Commands.lsp @@ -408,6 +408,24 @@ (command "_.EATTEDIT") ) + ;; Kettenfoerderer (KF_n, KettenFoerderer.lsp) -> KF_EDIT (DCL-Dialog mit + ;; Neuaufbau an derselben Stelle). MUSS - aus demselben Grund wie GR_* - + ;; vor dem Omniflo-Zweig stehen: traegt die Baugruppe ein TEILEART- + ;; Attribut, wuerde dort sonst der Omniflo-Elementdialog aufgehen. + ((wcmatch bname "KF_*") + (princ (ssg-textf "cmd-kettenfoerderer-bearbeiten" (list bname))) + (if (and (null *ssg-KettenFoerderer-loaded*) (atoms-family 1 '("SSG-ENSURE"))) + (ssg-ensure "KettenFoerderer") + ) + (if (atoms-family 1 '("C:KF_EDIT")) + (c:KF_EDIT) + (progn + (princ (ssg-textf "tro-edit-modul-fehlt" (list "KettenFoerderer.lsp"))) + (command "_.EATTEDIT") + ) + ) + ) + ;; Omniflo-Bloecke -> OMNI_EDIT ;; Erkennung: TEILEART-Attribut vorhanden, AP110-Blockname oder rein numerisch ((progn diff --git a/Lisp/export.lsp b/Lisp/export.lsp index 7c8dff4..cfb93a7 100644 --- a/Lisp/export.lsp +++ b/Lisp/export.lsp @@ -2397,6 +2397,11 @@ ;; VB_*/TV_* = Omniflo-Verbinder (siehe pattern_verbinder in ;; cfg/export.cfg, OMNI_TV_* in OmniModulInsert.lsp). export_sivas.py ;; fuehrt sie ueber TEILEART/ARTINR im allgemeinen Omniflo-Zweig. +;; KF_* = Kettenfoerderer/Schraegfoerderer Stahl (siehe +;; pattern_kettenfoerderer in cfg/export.cfg, KettenFoerderer.lsp). Wie +;; GR_* eine selbst zusammengebaute Baugruppe mit eigenen Attributen +;; (TEILEART/ARTINR), darum eigenstaendig gesammelt - die drei Bausteine +;; darin werden NICHT einzeln gezaehlt. ;; 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. @@ -2449,6 +2454,7 @@ (wcmatch bname (export:pattern "pattern_ausgang" "Ausgang*")) (wcmatch bname (export:pattern "pattern_geruest" "GR_*")) (wcmatch bname (export:pattern "pattern_verbinder" "VB_*,TV_*")) + (wcmatch bname (export:pattern "pattern_kettenfoerderer" "KF_*")) (wcmatch bname (export:pattern "pattern_unit_marker" "ZZ_UNIT_MARKER*")) ;; Rein numerische Namen = Omniflo SivasNr (Boegen/Weichen) ;; Rein LISP-intern (Python klassifiziert Omniflo stattdessen diff --git a/Lisp/ssg_dbg.lsp b/Lisp/ssg_dbg.lsp index adaf886..178fd1a 100644 --- a/Lisp/ssg_dbg.lsp +++ b/Lisp/ssg_dbg.lsp @@ -86,8 +86,12 @@ ;; Einheiten-Proxies selbst ab; Separator-/Element- ;; Kopien sammeln sich dagegen an - Zeichnung mit ;; eingeschaltetem Schalter nicht speichern. +;; kf-zeit - Zwischenzeiten beim Aufruf von KETTENFOERDERER +;; (Daten lesen / Dialogaufbau), siehe kf-zeit in +;; KettenFoerderer.lsp. Standard 0 - nur einschalten, +;; wenn der Dialog spuerbar verzoegert erscheint. ;; ------------------------------------------------------------ -(foreach nm '(("vf-sepliste" . 1) ("csv_leave_proxies" . 0)) +(foreach nm '(("vf-sepliste" . 1) ("csv_leave_proxies" . 0) ("kf-zeit" . 0)) (if (not (assoc (car nm) *dbg-schalter*)) (setq *dbg-schalter* (cons nm *dbg-schalter*)))) diff --git a/Lisp/ssg_id.lsp b/Lisp/ssg_id.lsp index 794b98b..47b3063 100644 --- a/Lisp/ssg_id.lsp +++ b/Lisp/ssg_id.lsp @@ -47,6 +47,9 @@ ;; das ID-ATTDEF steht seit *geruest-attrib-defs* zur Verfuegung. ;; VB_*/TV_* (Omniflo-Verbinder, siehe pattern_verbinder in ;; cfg/export.cfg) ebenso - fest im Code wie GR_*. +;; KF_* (Kettenfoerderer, siehe pattern_kettenfoerderer in cfg/export.cfg, +;; KettenFoerderer.lsp) ebenso - das ID-ATTDEF kommt aus kf-attrib-defs, das +;; auf dem gemeinsamen Strecken-Schema aufsetzt und darum ID bereits fuehrt. ;; Gefaellebogen_* (siehe pattern_nested_bogen in cfg/export.cfg): einer der ;; Bausteine einer GF-Kette, verpackt in VF_n/GF_n; wird beim Export ebenso ;; als temporaere Kopie ausgepackt wie die uebrigen Vario_*/Staustrecke_*- @@ -84,6 +87,7 @@ (wcmatch bname "Eingang*,Ausgang*") (wcmatch bname "GR_*") (wcmatch bname "VB_*,TV_*") + (wcmatch bname "KF_*") (wcmatch bname "#*") ) (ssadd ename ss-out) diff --git a/Lisp/ssg_ks_insert.lsp b/Lisp/ssg_ks_insert.lsp index 35e21e4..f1c49dd 100644 --- a/Lisp/ssg_ks_insert.lsp +++ b/Lisp/ssg_ks_insert.lsp @@ -72,11 +72,31 @@ ;; Block unter seinem effektiven, dim-suffigierten Namen und GIBT DIESEN ZURUECK, ;; damit Aufrufer ihn zum Einfuegen (vla-InsertBlock/tblsearch) verwenden. ;; Meldung nur bei Fehlschlag. -(defun ensure-block-loaded (blockname / ) - (if (not (ssg-ils-block-laden blockname)) - (princ (ssg-textf "vfc-fehler-block-datei-fehlt" (list blockname))) +;; Sonderfall Bloecke OHNE Dimensionssuffix (data/omniflo//, z.B. die +;; Kettenfoerderer-Bausteine): ssg-ils-blockname wuerde "KF_UM_26" zu +;; "KF_UM_26_2D" machen - das ist die Konvention der FLACHEN Ablage data/ils, +;; die Omniflo-Ordner arbeiten stattdessen mit getrennten Verzeichnissen und +;; unsuffigierten Namen. Liegt die Definition unter dem Rohnamen bereits in +;; der Zeichnung und gibt es KEINE suffigierte Variante, wird der Rohname +;; unveraendert verwendet. Die Bedingung ist bewusst eng gefasst (BEIDE Teile +;; muessen zutreffen), damit die Dim-Aufloesung fuer alle data/ils-Bloecke +;; unberuehrt bleibt: dort existiert die suffigierte Variante, sobald der +;; Block geladen ist, und der erste Zweig greift nie. +;; Realer Fehler, den das behebt: die KS-Messung eines KF-Bausteins lief ins +;; Leere und meldete "KS_EIN/KS_AUS fehlt", obwohl beide KS im Block vorhanden +;; waren - gesucht wurde nur ein Blockname, den es nicht gibt. +(defun ensure-block-loaded (blockname / eff) + (setq eff (ssg-ils-blockname blockname)) + (if (and (not (tblsearch "BLOCK" eff)) + (tblsearch "BLOCK" blockname)) + blockname + (progn + (if (not (ssg-ils-block-laden blockname)) + (princ (ssg-textf "vfc-fehler-block-datei-fehlt" (list blockname))) + ) + eff + ) ) - (ssg-ils-blockname blockname) ) ;; ------------------------------------------------------------ @@ -379,6 +399,265 @@ ) ) +;; ------------------------------------------------------------ +;; KS-ORIENTIERUNG + SKALIERTE KS-EINFUEGUNG +;; ------------------------------------------------------------ +;; Ergaenzt fuer den Kettenfoerderer (KettenFoerderer.lsp): dort ist die +;; Neigung KEIN freier Parameter, sondern steckt fest in der Geometrie der +;; Blockvarianten (KF_UM_26 gegen KF_UM_30). Der Einbauwinkel eines +;; Folgebausteins ergibt sich darum aus der RICHTUNG des KS_AUS seines +;; Vorgaengers. Dafuer reicht der bisher allein ausgewertete KS-URSPRUNG +;; nicht - die X-Achse des KS muss mit gelesen werden. extract-ks-from-block +;; liefert sie als (origin x-end y-end z-end) bereits mit, sie wurde bislang +;; nur von keinem Aufrufer genutzt. + +;; Winkel der X-Achse eines KS-Eintrags in der XY-Ebene, in Grad (-180..180). +;; ks-eintrag = (origin x-end y-end z-end), also genau der cadr-Teil eines +;; extract-ks-from-block-Ergebnisses. Rueckgabe nil, wenn die X-Achse keine +;; XY-Laenge hat (senkrecht stehendes KS) oder der Eintrag unbrauchbar ist. +(defun ks-xy-winkel (ks-eintrag / org xend dx dy) + (if (and ks-eintrag (listp ks-eintrag) (>= (length ks-eintrag) 2) + (listp (car ks-eintrag)) (listp (cadr ks-eintrag))) + (progn + (setq org (car ks-eintrag) xend (cadr ks-eintrag)) + (setq dx (- (car xend) (car org)) + dy (- (cadr xend) (cadr org))) + (if (< (+ (* dx dx) (* dy dy)) 1e-12) + nil + (/ (* 180.0 (atan dy dx)) pi))) + nil)) + +;; Winkel auf -180..180 Grad normalisieren. Reiner Rechenkern. +(defun ks-winkel-normalisieren (w) + (if (null w) + nil + (progn + (setq w (float w)) + (while (> w 180.0) (setq w (- w 360.0))) + (while (<= w -180.0) (setq w (+ w 360.0))) + w))) + +;; Lokalen KS_AUS-KS_EIN-Versatz eines Blocks als (dx dy dz) liefern. +;; ks-data = Ergebnis von extract-ks-from-block/ks-block-messen. +;; Rueckgabe nil, wenn eines der beiden KS fehlt. +(defun ks-versatz (ks-data / ein aus) + (setq ein (cadr (assoc "KS_EIN" ks-data)) + aus (cadr (assoc "KS_AUS" ks-data))) + (if (and ein aus) + (list (- (car (car aus)) (car (car ein))) + (- (cadr (car aus)) (cadr (car ein))) + (- (caddr (car aus)) (caddr (car ein)))) + nil)) + +;; Misst KS_EIN/KS_AUS eines Blocks, ohne eine bleibende Einfuegung zu +;; hinterlassen: der Block wird bei Massstab 1 am Ursprung eingefuegt, +;; vermessen und wieder geloescht. extract-ks-from-block cached das Ergebnis +;; je Blockname, der zweite Aufruf kostet also nichts mehr. +;; Rueckgabe wie extract-ks-from-block (Blockkoordinaten), sonst nil. +(defun ks-block-messen (blockname / eff temp-obj daten) + (setq eff (ensure-block-loaded blockname)) + (if (tblsearch "BLOCK" eff) + (progn + (setq temp-obj (vla-InsertBlock modelspace + (vlax-3D-point '(0 0 0)) eff 1.0 1.0 1.0 0)) + (setq daten (extract-ks-from-block temp-obj)) + (if (not (vlax-erased-p temp-obj)) (vla-Delete temp-obj)) + daten) + nil)) + +;; --- Block per KS_EIN/KS_AUS einfuegen, in X SKALIERT (nur Rz-Rotation) --- +;; Wie insert-block-by-ks, zusaetzlich mit X-Massstab skala - fuer laengen- +;; variable Zwischenstuecke (Kettenfoerderer KF_LAENGE, dieselbe Rolle wie +;; die Staustrecke beim VarioFoerderer). Die lokalen KS-Punkte werden VOR der +;; Rotation in X mitskaliert; nur so liegt KS_EIN exakt auf einfuegepunkt und +;; der zurueckgegebene KS_AUS traegt den skalierten Versatz. +;; Rueckgabe: Weltpunkt von KS_AUS (fuer die Verkettung), sonst einfuegepunkt. +(defun insert-block-ks-skaliert (blockname einfuegepunkt hz skala / + block-obj ks-data ks-ein ks-aus rad-h chv shv + ein-x ein-y ein-z aus-x dx-loc dy-loc dz-loc + offset ausgang) + (if (or (null einfuegepunkt) (not (listp einfuegepunkt))) + (progn + (princ (ssg-textf "vfc-fehler-ungueltiger-einfuegepunkt" (list blockname))) + (exit)) + ) + (if (or (null skala) (<= skala 0.0)) (setq skala 1.0)) + (setq blockname (ensure-block-loaded blockname)) + (if (not (tblsearch "BLOCK" blockname)) + (progn + (princ (ssg-textf "vfc-fehler-block-nicht-in-bibliothek-abgebrochen" (list blockname))) + (exit)) + ) + (princ (ssg-textf "vfc-fuege-block-ein-hz" (list blockname (rtos (float hz) 2 1) (chr 176)))) + (setq rad-h (* (float hz) (/ pi 180.0))) + (setq chv (cos rad-h) shv (sin rad-h)) + ;; KS am UNSKALIERTEN Block messen (der Cache arbeitet je Blockname), die + ;; lokalen Punkte danach selbst in X skalieren. + (setq ks-data (ks-block-messen blockname)) + (setq ks-ein (cadr (assoc "KS_EIN" ks-data))) + (setq ks-aus (cadr (assoc "KS_AUS" ks-data))) + (setq block-obj (vla-InsertBlock modelspace + (vlax-3D-point '(0 0 0)) + blockname skala 1.0 1.0 0)) + (ssg-ils-block-auf-ebene block-obj blockname) + (if (> (abs hz) 0.0001) + (vla-TransformBy block-obj (vlax-tmatrix + (list (list chv (- shv) 0 0) + (list shv chv 0 0) + (list 0 0 1 0) + (list 0 0 0 1)))) + ) + (if (and ks-ein ks-aus) + (progn + (setq ein-x (* skala (car (car ks-ein))) + ein-y (cadr (car ks-ein)) + ein-z (caddr (car ks-ein))) + (setq aus-x (* skala (car (car ks-aus)))) + (setq offset (list + (- (car einfuegepunkt) (- (* chv ein-x) (* shv ein-y))) + (- (cadr einfuegepunkt) (+ (* shv ein-x) (* chv ein-y))) + (- (caddr einfuegepunkt) ein-z))) + (vla-Move block-obj + (vlax-3D-point '(0 0 0)) + (vlax-3D-point offset)) + (setq dx-loc (- aus-x ein-x)) + (setq dy-loc (- (cadr (car ks-aus)) ein-y)) + (setq dz-loc (- (caddr (car ks-aus)) ein-z)) + (setq ausgang (list + (+ (car einfuegepunkt) (- (* chv dx-loc) (* shv dy-loc))) + (+ (cadr einfuegepunkt) (+ (* shv dx-loc) (* chv dy-loc))) + (+ (caddr einfuegepunkt) dz-loc))) + (ssg-sepliste-dbg + (strcat "insert-block-ks-skaliert " blockname + " hz=" (rtos (float hz) 2 2) " skala=" (rtos (float skala) 2 4) + " einfuegepunkt=" (ssg-sepliste-dbg-pt einfuegepunkt) + " offset(=Gruppencode 10)=" (ssg-sepliste-dbg-pt offset) + " ausgang=" (ssg-sepliste-dbg-pt ausgang))) + (princ (ssg-textf "vfc-ks-aus-xyz" + (list (rtos (car ausgang) 2 2) (rtos (cadr ausgang) 2 2) (rtos (caddr ausgang) 2 2)))) + ausgang + ) + (progn + (princ (ssg-text "vfc-warnung-ks-nicht-gefunden")) + einfuegepunkt + ) + ) +) + +;; ------------------------------------------------------------ +;; FREI ROTIERTE KS-EINFUEGUNG (beliebige 3x3-Matrix) +;; ------------------------------------------------------------ +;; Verallgemeinerung von insert-block-by-ks/insert-block-ks-skaliert: statt +;; einer festen Rz- bzw. Rz*Ry-Drehung nimmt sie eine FERTIGE 3x3-Rotations- +;; matrix entgegen. Noetig geworden fuer AUFRECHT eingebaute Baugruppen: der +;; Kettenfoerderer ist in der XY-Ebene gezeichnet (X = Fahrtrichtung, +;; Y = Hoehe), soll aber im Modell stehen - seine Hoehe muss also nach Z. Das +;; ist eine Drehung um X, die weder insert-block-by-ks (nur Rz) noch +;; ssg-rot-matrix-zy (Rz*Ry) erzeugen kann. +;; +;; Bewusst matrixbasiert statt mit einem weiteren Winkelparameter: die +;; Aufrichtung ist nur EIN Anwendungsfall, und jeder weitere haette eine +;; weitere Spezialfunktion gebraucht. Den Aufrufern bleibt freigestellt, +;; welche Drehungen sie kombinieren (beim Kettenfoerderer +;; Rz(basis) * Rx(90) * Rz(eigen), siehe kf-matrix-aufrecht). + +;; Elementare Rotationsmatrizen (3x3, Winkel in GRAD). +(defun ks-rot-x (grad / r c s) + (setq r (* (float grad) (/ pi 180.0)) c (cos r) s (sin r)) + (list (list 1.0 0.0 0.0) + (list 0.0 c (- s)) + (list 0.0 s c ))) + +(defun ks-rot-y (grad / r c s) + (setq r (* (float grad) (/ pi 180.0)) c (cos r) s (sin r)) + (list (list c 0.0 s ) + (list 0.0 1.0 0.0) + (list (- s) 0.0 c ))) + +(defun ks-rot-z (grad / r c s) + (setq r (* (float grad) (/ pi 180.0)) c (cos r) s (sin r)) + (list (list c (- s) 0.0) + (list s c 0.0) + (list 0.0 0.0 1.0))) + +;; 3x3-Rotation in die 4x4-Form bringen, die vla-TransformBy erwartet. +(defun ks-mat3->mat4 (R) + (list (append (car R) '(0.0)) + (append (cadr R) '(0.0)) + (append (caddr R) '(0.0)) + '(0.0 0.0 0.0 1.0))) + +;; --- Block per KS_EIN/KS_AUS einfuegen, frei rotiert und in X skaliert --- +;; blockname Rohname (wird ueber ensure-block-loaded aufgeloest) +;; einfuegepunkt Weltpunkt, auf den KS_EIN zu liegen kommt +;; R 3x3-Rotationsmatrix (Blockkoordinaten -> Welt) +;; skala X-Massstab (1.0 = unskaliert) +;; Rueckgabe: Weltpunkt von KS_AUS (fuer die Verkettung), sonst einfuegepunkt. +;; +;; Reihenfolge wie bei den uebrigen Primitiven: am Ursprung einfuegen, +;; skalieren, drehen, dann so verschieben, dass das GEDREHTE KS_EIN exakt auf +;; einfuegepunkt liegt. Die lokalen KS-Punkte werden dabei zuerst in X +;; mitskaliert - sonst zeigt der zurueckgegebene KS_AUS am skalierten Block +;; vorbei. +(defun insert-block-ks-matrix (blockname einfuegepunkt R skala / + block-obj ks-data ks-ein ks-aus + ein-lok aus-lok ein-welt aus-welt offset ausgang) + (if (or (null einfuegepunkt) (not (listp einfuegepunkt))) + (progn + (princ (ssg-textf "vfc-fehler-ungueltiger-einfuegepunkt" (list blockname))) + (exit)) + ) + (if (or (null skala) (<= skala 0.0)) (setq skala 1.0)) + (setq blockname (ensure-block-loaded blockname)) + (if (not (tblsearch "BLOCK" blockname)) + (progn + (princ (ssg-textf "vfc-fehler-block-nicht-in-bibliothek-abgebrochen" (list blockname))) + (exit)) + ) + (setq ks-data (ks-block-messen blockname)) + (setq ks-ein (cadr (assoc "KS_EIN" ks-data))) + (setq ks-aus (cadr (assoc "KS_AUS" ks-data))) + (setq block-obj (vla-InsertBlock modelspace + (vlax-3D-point '(0 0 0)) + blockname skala 1.0 1.0 0)) + (ssg-ils-block-auf-ebene block-obj blockname) + (vla-TransformBy block-obj (vlax-tmatrix (ks-mat3->mat4 R))) + (if (and ks-ein ks-aus) + (progn + ;; lokale KS-Punkte, in X mitskaliert + (setq ein-lok (list (* skala (car (car ks-ein))) + (cadr (car ks-ein)) + (caddr (car ks-ein)))) + (setq aus-lok (list (* skala (car (car ks-aus))) + (cadr (car ks-aus)) + (caddr (car ks-aus)))) + (setq ein-welt (mat3-mul-vec3 R ein-lok)) + (setq aus-welt (mat3-mul-vec3 R aus-lok)) + (setq offset (list (- (car einfuegepunkt) (car ein-welt)) + (- (cadr einfuegepunkt) (cadr ein-welt)) + (- (caddr einfuegepunkt) (caddr ein-welt)))) + (vla-Move block-obj + (vlax-3D-point '(0 0 0)) + (vlax-3D-point offset)) + (setq ausgang (list (+ (car einfuegepunkt) (- (car aus-welt) (car ein-welt))) + (+ (cadr einfuegepunkt) (- (cadr aus-welt) (cadr ein-welt))) + (+ (caddr einfuegepunkt) (- (caddr aus-welt) (caddr ein-welt))))) + (ssg-sepliste-dbg + (strcat "insert-block-ks-matrix " blockname + " skala=" (rtos (float skala) 2 4) + " einfuegepunkt=" (ssg-sepliste-dbg-pt einfuegepunkt) + " ks_ein_welt=" (ssg-sepliste-dbg-pt ein-welt) + " ks_aus_welt=" (ssg-sepliste-dbg-pt aus-welt) + " ausgang=" (ssg-sepliste-dbg-pt ausgang))) + ausgang + ) + (progn + (princ (ssg-text "vfc-warnung-ks-nicht-gefunden")) + einfuegepunkt + ) + ) +) + ;; ------------------------------------------------------------ ;; SEPLISTE: Separator-/AS-/ES-Reihenfolge als XDATA am fertigen Wrapper ;; ------------------------------------------------------------ diff --git a/cfg/export.cfg b/cfg/export.cfg index 47f186c..22c1efa 100644 --- a/cfg/export.cfg +++ b/cfg/export.cfg @@ -76,6 +76,15 @@ # erkennt sie wie alle Omniflo-Teile ueber TEILEART "Omniflo ..." und schreibt # je Nummer aus ARTINR eine Omniflo-Zeile. # +# pattern_kettenfoerderer: Kettenfoerderer/Schraegfoerderer Stahl +# (KettenFoerderer.lsp, KF_). Eine selbst zusammengebaute Baugruppe wie +# GR_*, mit eigenen Attributen TEILEART ("Omniflo Kettenfoerderer ...") und +# ARTINR - erkannt wird sie darum im allgemeinen Omniflo-Zweig von +# export_sivas.py. Die drei Bausteine darin (Umlenkstation, skaliertes +# Zwischenstueck, Antriebsstation) werden NICHT einzeln gezaehlt. Die im +# Dialog gewaehlten Verbindungselemente stehen als Attribut +# VERBINDUNGSELEMENTE am Block und landen im Merkmale-/details-JSON. +# # pattern_nested_bogen/_streckenelement/_umlenkstation/_motorstation: die # uebrigen Bausteine einer VF_n/GF_n-Kette (Boegen, gerade Foerderer-/ # Gefaellestrecken-Segmente, Umlenk-/Motorstation). Wie pattern_schleuselement @@ -136,6 +145,7 @@ pattern_gerade= AP110*, AP_110*, AP60* # Gerüst pattern_geruest= GR_* pattern_verbinder= VB_*, TV_* +pattern_kettenfoerderer= KF_* [python] # Interpreter fuer die Export-Skripte (lib/export_csv.py, lib/export_sivas.py). diff --git a/cfg/layer.cfg b/cfg/layer.cfg index 3593504..3ec6992 100644 --- a/cfg/layer.cfg +++ b/cfg/layer.cfg @@ -168,6 +168,13 @@ gf_beschriftung = GF_Beschriftung, 7 # Lisp/DrawPlanquadrate.lsp (DRAW_PLANQUADRATE). planquadrate = ZZ_Planquadrate, 8 +# Kettenfoerderer (Schraegfoerderer Stahl), aus Umlenkstation + skaliertem +# Zwischenstueck + Antriebsstation zusammengebauter Block KF_n - siehe +# Lisp/KettenFoerderer.lsp (KETTENFOERDERER). Wie VF_n/GF_n eine +# ZUSAMMENGEBAUTE Baugruppe ohne eigene Blockdatei, der Layer muss darum hier +# stehen. Die drei Bausteine darin behalten ihre eigenen Layer. +kettenfoerderer = Omni_Kettenfoerderer, 4 + # ============================================================ # Farben fuer Layer OHNE Baugruppen-Key diff --git a/cfg/sivas.cfg b/cfg/sivas.cfg index dc1a0c2..c18b63e 100644 --- a/cfg/sivas.cfg +++ b/cfg/sivas.cfg @@ -105,6 +105,41 @@ [geruest] Farbe= FARBE +[kettenfoerderer] +# Kettenfoerderer / Schraegfoerderer Stahl (KF_n, Lisp/KettenFoerderer.lsp). +# Merkmalnummern laut Merkmalstamm des Schraegfoerderers. +# +# 350 kommt aus FOERDERERTYP (Bauart im Klartext). Der Default greift nur +# bei Altbestands-Bloecken ohne das Attribut. +# 359 kommt aus WINKEL, das den Neigungswinkel als nackte Zahl fuehrt +# ("26"/"30"); die Anzeige-Tabelle [kf_typ_anzeige] weiter unten macht +# daraus den Sivas-Optionstext mit Gradzeichen. Grund fuer die +# Zweiteilung: Block-Attribute bleiben laut Projektkonvention ASCII. +# 353 (Anzahl der gleichen KF) steht ABSICHTLICH NICHT hier: der Wert +# entsteht erst aus der Gruppierung baugleicher Bloecke +# (kettenfoerderer_signature in export_sivas.py) und ist damit kein +# flaches Attribut-Mapping - gleiche Bauart wie 3510/3512 beim Kreisel. +# 356 und 361 nutzen den vorhandenen Transform ja_nein, die Attribute +# fuehren "1"/"0". +# 357 "Antriebsart" ist beim Kettenfoerderer FEST (1.1 kW): im Dialog ein +# Dropdown mit genau einer, zwingend gewaehlten Option - das Attribut gibt es trotzdem, damit der Wert am +# Block steht und nicht nur im Export auftaucht. +# 354 "Farbe Foerderer" und 355 "Antriebsmotor Foerderer" sind ZWEI getrennte +# Merkmale (nicht ein kombinierter Text) - im Dialog darum auch zwei +# Felder, Attribute FARBE und ANTRIEBSMOTOR. +# 354/355/391 tragen bereits den vollen Sivas-Optionstext im Attribut (die +# Auswahllisten in data/json/kettenfoerderer.json liefern ihn so) - +# darum kein Transform. +350= FOERDERERTYP : Schrägförderer Stahl +359= WINKEL : 26 | kf_typ_anzeige +361= STANDARD_STEUERUNG : 0 | ja_nein +354= FARBE : RAL 7035 +355= ANTRIEBSMOTOR : Europa 50 Hz +357= ANTRIEBSART : 1.1 kW +356= MOVIMOT : 0 | ja_nein +360= H_EINBAUHOEHE_mm : 0 +391= VERBINDUNGSELEMENTE : Nein + [variofoerderer] # Nur die von der Segmentanzahl UNABHAENGIGEN Top-Level-Felder einer VF_N- # Streckengruppe (siehe build_variofoerderer_details in export_sivas.py). @@ -155,6 +190,17 @@ Farbe= FARBE # zeichen (Attribute selbst bleiben ASCII, siehe CLAUDE.md-Konvention). Schoenenberger Geruest= Schönenberger-Gerüst +[kf_typ_anzeige] +# Attributwert WINKEL (Neigungswinkel als nackte Zahl) -> Optionswert der +# Sivas-Auswahlliste zu Merkmal 359. Gleiche Bauart wie +# [geruest_typ_anzeige]/[as_es_winkel_anzeige]: steht ein Wert hier NICHT +# drin, wird er unveraendert uebernommen. +# +# >>> Lehnt Sivas den Import ab, ist DIESE Tabelle die einzige Stelle zum +# >>> Nachziehen (z.B. "26 °" mit Leerzeichen) - der Code bleibt unberuehrt. +26= Schrägförderer Stahl 26° +30= Schrägförderer Stahl 30° + [weichen] # ILS 2.0 Weiche (6216): eine Summenzeile, Werte aus den Kreisel-Beruehr- # punkten des LISP-Exports (kreisel_weichen.json). Ein Beruehrpunkt ist eine diff --git a/data/json/kettenfoerderer.json b/data/json/kettenfoerderer.json new file mode 100644 index 0000000..07d2122 --- /dev/null +++ b/data/json/kettenfoerderer.json @@ -0,0 +1,67 @@ +{ + "kettenfoerderer": { + "id": "Kettenfoerderer", + "titel": "Kettenfoerderer (Schraegfoerderer Stahl)", + "layer": "kettenfoerderer", + "teileart": "Schraegfoerderer Stahl", + "foerderertyp": "Schrägförderer Stahl", + + "einbauhoehe": { "vorgabe": 1500, "min": 300, "max": 8000 }, + "laenge": { "min": 100, "max": 20000 }, + + "_hinweis_ansicht": "KF_2D liegt DECKUNGSGLEICH mit der Kette (KS_EIN auf KS_EIN der Umlenkung, KS_AUS auf KS_AUS des Antriebs) - die Baugruppe ist EIN Koerper. Den frueheren Versatz (versatz_y) gibt es nicht mehr, seit die Kette aufgerichtet eingebaut wird.", + "ansicht_2d": { "block": "KF_2D" }, + + "_hinweis_antriebsart": "Merkmal 357 ist beim Kettenfoerderer FEST: die Liste hat genau EINEN Eintrag, der im Dialog als Dropdown erscheint und zwingend gewaehlt ist. Weitere Antriebsarten (z.B. 0.55 kW) waeren einfach eine weitere Zeile.", + "antriebsart": [ + { "name": "1.1 kW", "wert": "1.1 kW" } + ], + + "farbe": [ + { "name": "RAL 7035", "wert": "RAL 7035" }, + { "name": "RAL 2000", "wert": "RAL 2000" }, + { "name": "RAL 5018", "wert": "RAL 5018" }, + { "name": "Sonderfarbe", "wert": "Sonderfarbe" } + ], + + "antriebsmotor": [ + { "name": "Europa 50 Hz", "wert": "Europa 50 Hz" } + ], + + "movimot": [ + { "name": "Nein", "wert": "0" }, + { "name": "Ja", "wert": "1" } + ], + + "standard_steuerung": [ + { "name": "Nein", "wert": "0" }, + { "name": "Ja", "wert": "1" } + ], + + "verbindungselemente": [ + { "name": "Verbindungselemente an AP", "wert": "Verbindungselemente an AP" }, + { "name": "Verbindungselemente an ST", "wert": "Verbindungselemente an ST" }, + { "name": "Nein", "wert": "Nein" } + ], + + "varianten": [ + { + "name": "Schraegfoerderer Stahl 26 Grad", + "winkel": 26, + "standard": true, + "block_umlenkung": "KF_UM_26", + "block_laenge": "KF_LAENGE", + "block_antrieb": "KF_AN_26", + "sivas_id": "6120" + }, + { + "name": "Schraegfoerderer Stahl 30 Grad", + "winkel": 30, + "block_umlenkung": "KF_UM_30", + "block_laenge": "KF_LAENGE", + "block_antrieb": "KF_AN_30", + "sivas_id": "6120" + } + ] + } +} diff --git a/data/omniflo/2D/KF_2D.dwg b/data/omniflo/2D/KF_2D.dwg new file mode 100644 index 0000000000000000000000000000000000000000..91ecdddbb8a762d0fa5927ed4bb1f2e872421396 GIT binary patch literal 11200 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 literal 0 HcmV?d00001 diff --git a/data/omniflo/2D/KF_AN_26.dwg b/data/omniflo/2D/KF_AN_26.dwg new file mode 100644 index 0000000000000000000000000000000000000000..882ec1a88542c757318bfe0c3fe92664025d9964 GIT binary patch literal 45086 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�VXCZ{jWSNn!;2z;duL=^&%>g_!&y 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 CoQvlG literal 0 HcmV?d00001 diff --git a/data/omniflo/2D/KF_UM_30.dwg b/data/omniflo/2D/KF_UM_30.dwg new file mode 100644 index 0000000000000000000000000000000000000000..1735c18b396453210276bde65f7bea7c76e38d42 GIT binary patch literal 30113 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

9c} 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 literal 0 HcmV?d00001 diff --git a/dcl/kettenfoerderer.dcl b/dcl/kettenfoerderer.dcl new file mode 100644 index 0000000..f47d542 --- /dev/null +++ b/dcl/kettenfoerderer.dcl @@ -0,0 +1,104 @@ +// ============================================================ +// kettenfoerderer.dcl - Kettenfoerderer (Schraegfoerderer Stahl) +// Verwendet von KettenFoerderer.lsp (kf-dialog, Befehl KETTENFOERDERER). +// +// VORLAGE mit Markerzeilen: nur der Varianten-Block ist datenabhaengig und +// wird zur Laufzeit an //@VARIANTEN@ eingesetzt (kf-dcl-ersetzungen -> +// ssg-dcl-vorlage-fuellen in ssg_core, Temp-Datei). Grund: DCL kann +// radio_button-Tiles weder zur Laufzeit erzeugen noch ihre Labels per +// set_tile aendern - gleiches Vorgehen wie geruest_traeger.dcl. +// +// Varianten bewusst als Radiobuttons statt popup_list: es sind nur zwei +// Werte (26/30 Grad), beide sollen ohne Aufklappen sichtbar sein. +// Die Gruppenbeschriftung steht am boxed_row - ein zusaetzliches Label am +// Tile waere doppelt gemoppelt. +// +// Die Beschriftungen folgen dem Sivas-Merkmalstamm, damit Dialog und +// Stueckliste dieselbe Sprache sprechen (Merkmalnummer in Klammern): +// Schraegfoerderer Stahl Typ (359) H - Einbauhoehe Schraegfoerderer (360) +// Farbe Foerderer (354) Antriebsmotor Foerderer (355) +// Antrieb mit Movimot (356) Verbindungselemente (391) +// Farbe und Antriebsmotor sind dabei ZWEI eigenstaendige Merkmale +// (Attribute FARBE und ANTRIEBSMOTOR), nicht ein kombinierter Text. +// +// Kein Geruest-Bereich: der Kettenfoerderer traegt sich selbst. +// ============================================================ + +kettenfoerderer : dialog { + label = "Kettenfoerderer"; + width = 62; + + : boxed_radio_row { + key = "variante"; + label = "Schraegfoerderer Stahl Typ"; +//@VARIANTEN@ + } + + : boxed_column { + label = "Geometrie"; + + : row { + : edit_box { + key = "hoehe"; + label = "H - Einbauhoehe Schraegfoerderer [ mm ]:"; + edit_width = 12; + } + // Zweite Eingabeart: Hoehe aus zwei gewaehlten Punkten messen. + // Der Knopf schliesst den Dialog (done_dialog 2), der Aufrufer pickt + // die Punkte und zeigt ihn mit dem Ergebnis erneut - aus einem + // action_tile heraus laesst sich kein getpoint aufrufen. + : button { + key = "hoehe_messen"; + label = "aus 2 Punkten messen"; + } + } + } + + : boxed_column { + label = "Ausfuehrung"; + + // Reihenfolge wie im Sivas-Merkmalstamm: 361 vor 354 - Dialog, + // Attributschema und Merkmalstamm bleiben damit in derselben Folge. + : popup_list { + key = "standard_steuerung"; + label = "mit Standard Steuerung:"; + width = 40; + } + + : popup_list { + key = "farbe"; + label = "Farbe Foerderer:"; + width = 40; + } + + : popup_list { + key = "antriebsmotor"; + label = "Antriebsmotor Foerderer:"; + width = 40; + } + + // Antriebsart (Merkmal 357): Dropdown mit genau EINER Option, die immer + // gewaehlt ist - beim Kettenfoerderer ist sie fest 1.1 kW. Als Dropdown + // statt als Textzeile, damit alle Merkmale gleich aussehen; kommt spaeter + // eine zweite Antriebsart dazu, ist es nur eine weitere Zeile in der JSON. + : popup_list { + key = "antriebsart"; + label = "Antriebsart 0,55 / 1,1 kW:"; + width = 40; + } + + : popup_list { + key = "movimot"; + label = "Antrieb mit Movimot:"; + width = 40; + } + + : popup_list { + key = "verbindung"; + label = "Verbindungselemente:"; + width = 40; + } + } + + ok_cancel; +} diff --git a/lang/de_DE.json b/lang/de_DE.json index 2cdeedd..e6b3515 100644 --- a/lang/de_DE.json +++ b/lang/de_DE.json @@ -1148,6 +1148,56 @@ "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." + "exp-kreisel-kollision": "\n[%1] WARNUNG: %2 Kreisel-Kollision(en) - Befehl KREISEL_WEICHEN zeigt sie an.", + "kf-fehler-daten-fehlt": "\nFEHLER: Kettenfoerderer-Daten nicht gefunden: %1", + "kf-fehler-daten-leer": "\nFEHLER: Kettenfoerderer-Daten konnten nicht gelesen werden.", + "kf-fehler-block-datei": "\nFEHLER: Blockdatei fuer %1 nicht gefunden (erwartet in data/omniflo//).", + "kf-fehler-ks-fehlt": "In mindestens einem Baustein fehlt KS_EIN oder KS_AUS - die Kette kann nicht verkettet werden. Befehl KF_KS_PRUEFEN zeigt, welcher Block betroffen ist.", + "kf-fehler-hoehe-zu-klein": "Die Einbauhoehe ist zu klein: Umlenk- und Antriebsstation bringen bereits mehr Hoehe mit, als gefordert ist. Bitte groessere Hoehe waehlen.", + "kf-fehler-laenge-unter-min": "Das Zwischenstueck waere kuerzer als zulaessig. Bitte groessere Einbauhoehe waehlen.", + "kf-fehler-laenge-ueber-max": "Das Zwischenstueck waere laenger als zulaessig. Bitte kleinere Einbauhoehe waehlen.", + "kf-fehler-winkel-null": "Der gemessene Einbauwinkel des Zwischenstuecks ist waagerecht - damit laesst sich keine Hoehe erreichen. Bitte die KS-Lage der Bloecke pruefen (KF_KS_PRUEFEN).", + "kf-fehler-dcl": "Dialogdatei kettenfoerderer.dcl nicht gefunden.", + "kf-prompt-messpunkt-1": "\nErsten Messpunkt waehlen (Hoehenbezug unten): ", + "kf-prompt-messpunkt-2": "\nZweiten Messpunkt waehlen (Hoehenbezug oben): ", + "kf-messung-ergebnis": "\n>>> Gemessen: Hoehendifferenz dZ = %1 mm (zur Kontrolle dY = %2 mm).", + "kf-konsole-hoehe-hinweis": "\n(0 eingeben, um die Hoehe aus zwei Punkten zu messen)", + "kf-2d-gebaut": "\n>>> Draufsicht %1 eingefuegt: Schraeglaenge %2 mm, Neigung %3 Grad.", + "kf-2d-laenge-null": "\nDraufsicht uebersprungen: die Kette hat keine messbare Grundrisslaenge.", + "kf-2d-ks-fehlt": "\nDraufsicht uebersprungen: %1 hat kein messbares KS_EIN/KS_AUS.", + "kf-prompt-startpunkt": "\nEinfuegepunkt waehlen (KS_EIN der Draufsicht; die Seitenansicht entsteht darueber): ", + "kf-prompt-drehung": "\nDrehwinkel der Kette: ", + "kf-abgebrochen": "\nAbgebrochen.", + "kf-fertig": "\n>>> %1 erstellt: Einbauhoehe %2 mm, Zwischenstueck %3 mm.", + "kf-konsole-variante-header": "\n\nSchraegfoerderer Stahl Typ waehlen:", + "kf-konsole-option": "\n %1 - %2", + "kf-konsole-variante-prompt": "\nSchraegfoerderer Stahl Typ", + "kf-konsole-hoehe-prompt": "\nH - Einbauhoehe Schraegfoerderer [mm]", + "kf-konsole-verbindung-header": "\n\nVerbindungselemente waehlen:", + "kf-konsole-verbindung-prompt": "\nVerbindungselemente", + "kf-konsole-farbe-header": "\n\nFarbe Foerderer waehlen:", + "kf-konsole-farbe-prompt": "\nFarbe", + "kf-konsole-antriebsmotor-header": "\n\nAntriebsmotor Foerderer waehlen:", + "kf-konsole-antriebsmotor-prompt": "\nAntriebsmotor Foerderer", + "kf-variante-label": "%1 %2", + "kf-variante-label-standard": "%1 %2 (Standard)", + "kf-konsole-movimot-header": "\n\nAntrieb mit Movimot?", + "kf-konsole-movimot-prompt": "\nAntrieb mit Movimot", + "kf-konsole-steuerung-header": "\n\nMit Standard Steuerung?", + "kf-konsole-steuerung-prompt": "\nMit Standard Steuerung", + "kf-pruef-kopf": "\n\n=== KF_KS_PRUEFEN: KS-Lage der Kettenfoerderer-Bloecke ===", + "kf-pruef-variante": "\n\n--- Variante: %1 ---", + "kf-pruef-block": "\n %1: KS_EIN (%2, %3) Richtung %4 | KS_AUS (%5, %6) Richtung %7", + "kf-pruef-block-fehlt": "\n %1: Blockdatei nicht gefunden.", + "kf-pruef-ks-fehlt": "\n %1: KS unvollstaendig - KS_EIN: %2, KS_AUS: %3", + "kf-pruef-ks-fehlt-kurz": "\n[KF] KS_EIN/KS_AUS nicht lesbar am Baustein: %1 (Befehl KF_KS_PRUEFEN zeigt Einzelheiten)", + "kf-pruef-fehler": "\n Geometrie nicht ermittelbar: %1", + "kf-pruef-geo": "\n Einbauwinkel (Grad): Umlenkung %1, Zwischenstueck %2, Antrieb %3\n Hoehenbeitrag (mm): Umlenkung %4, Antrieb %5\n Zwischenstueck nativ (mm): dx %6, dy %7", + "kf-pruef-ja": "vorhanden", + "kf-pruef-nein": "FEHLT", + "kf-edit-waehlen": "\nKettenfoerderer (KF_n) zum Bearbeiten waehlen: ", + "kf-edit-kein-kf": "Der gewaehlte Block ist kein Kettenfoerderer (KF_n).", + "kf-edit-fertig": "\n>>> %1 neu aufgebaut als %2.", + "cmd-kettenfoerderer-bearbeiten": "\n>>> Kettenfoerderer %1 wird bearbeitet ..." } } diff --git a/lang/en_GB.json b/lang/en_GB.json index 7a115c0..76d365f 100644 --- a/lang/en_GB.json +++ b/lang/en_GB.json @@ -1148,6 +1148,56 @@ "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." + "exp-kreisel-kollision": "\n[%1] WARNING: %2 Kreisel collision(s) - command KREISEL_WEICHEN shows them.", + "kf-fehler-daten-fehlt": "\nERROR: chain conveyor data not found: %1", + "kf-fehler-daten-leer": "\nERROR: chain conveyor data could not be read.", + "kf-fehler-block-datei": "\nERROR: block file for %1 not found (expected in data/omniflo//).", + "kf-fehler-ks-fehlt": "At least one component is missing KS_EIN or KS_AUS - the chain cannot be linked. Command KF_KS_PRUEFEN shows which block is affected.", + "kf-fehler-hoehe-zu-klein": "The installation height is too small: the deflection and drive stations already contribute more height than requested. Please choose a greater height.", + "kf-fehler-laenge-unter-min": "The intermediate section would be shorter than permitted. Please choose a greater installation height.", + "kf-fehler-laenge-ueber-max": "The intermediate section would be longer than permitted. Please choose a smaller installation height.", + "kf-fehler-winkel-null": "The measured installation angle of the intermediate section is horizontal - no height can be gained. Please check the KS positions of the blocks (KF_KS_PRUEFEN).", + "kf-fehler-dcl": "Dialog file kettenfoerderer.dcl not found.", + "kf-prompt-messpunkt-1": "\nSelect first measuring point (lower reference): ", + "kf-prompt-messpunkt-2": "\nSelect second measuring point (upper reference): ", + "kf-messung-ergebnis": "\n>>> Measured: height difference dZ = %1 mm (for reference dY = %2 mm).", + "kf-konsole-hoehe-hinweis": "\n(enter 0 to measure the height from two points)", + "kf-2d-gebaut": "\n>>> Plan view %1 inserted: slope length %2 mm, inclination %3 deg.", + "kf-2d-laenge-null": "\nPlan view skipped: the chain has no measurable plan length.", + "kf-2d-ks-fehlt": "\nPlan view skipped: %1 has no measurable KS_EIN/KS_AUS.", + "kf-prompt-startpunkt": "\nSelect insertion point (KS_EIN of the plan view; the side view is built above it): ", + "kf-prompt-drehung": "\nRotation angle of the chain: ", + "kf-abgebrochen": "\nCancelled.", + "kf-fertig": "\n>>> %1 created: installation height %2 mm, intermediate section %3 mm.", + "kf-konsole-variante-header": "\n\nSelect inclined steel conveyor type:", + "kf-konsole-option": "\n %1 - %2", + "kf-konsole-variante-prompt": "\nInclined steel conveyor type", + "kf-konsole-hoehe-prompt": "\nH - installation height of inclined conveyor [mm]", + "kf-konsole-verbindung-header": "\n\nSelect connecting elements:", + "kf-konsole-verbindung-prompt": "\nConnecting elements", + "kf-konsole-farbe-header": "\n\nSelect conveyor colour:", + "kf-konsole-farbe-prompt": "\nColour", + "kf-konsole-antriebsmotor-header": "\n\nSelect conveyor drive motor:", + "kf-konsole-antriebsmotor-prompt": "\nConveyor drive motor", + "kf-variante-label": "%1 %2", + "kf-variante-label-standard": "%1 %2 (default)", + "kf-konsole-movimot-header": "\n\nDrive with Movimot?", + "kf-konsole-movimot-prompt": "\nDrive with Movimot", + "kf-konsole-steuerung-header": "\n\nWith standard control system?", + "kf-konsole-steuerung-prompt": "\nWith standard control system", + "kf-pruef-kopf": "\n\n=== KF_KS_PRUEFEN: KS positions of the chain conveyor blocks ===", + "kf-pruef-variante": "\n\n--- Variant: %1 ---", + "kf-pruef-block": "\n %1: KS_EIN (%2, %3) direction %4 | KS_AUS (%5, %6) direction %7", + "kf-pruef-block-fehlt": "\n %1: block file not found.", + "kf-pruef-ks-fehlt": "\n %1: KS incomplete - KS_EIN: %2, KS_AUS: %3", + "kf-pruef-ks-fehlt-kurz": "\n[KF] KS_EIN/KS_AUS not readable on component: %1 (command KF_KS_PRUEFEN shows details)", + "kf-pruef-fehler": "\n Geometry could not be determined: %1", + "kf-pruef-geo": "\n Installation angles (deg): deflection %1, intermediate %2, drive %3\n Height contribution (mm): deflection %4, drive %5\n Intermediate section native (mm): dx %6, dy %7", + "kf-pruef-ja": "present", + "kf-pruef-nein": "MISSING", + "kf-edit-waehlen": "\nSelect chain conveyor (KF_n) to edit: ", + "kf-edit-kein-kf": "The selected block is not a chain conveyor (KF_n).", + "kf-edit-fertig": "\n>>> %1 rebuilt as %2.", + "cmd-kettenfoerderer-bearbeiten": "\n>>> Editing chain conveyor %1 ..." } } diff --git a/lib/export_sivas.py b/lib/export_sivas.py index a0d9fcc..dc7892e 100644 --- a/lib/export_sivas.py +++ b/lib/export_sivas.py @@ -101,6 +101,18 @@ def get_id(block): # Sprachdatei, da rein Sivas-seitig. GERUEST_TYP_ANZEIGE = sivas_mapping.load_flat_mapping("geruest_typ_anzeige") +# Kettenfoerderer: Attribut WINKEL fuehrt den Neigungswinkel als nackte Zahl +# ("26"/"30"), Sivas-Merkmal 359 erwartet den Optionstext mit Gradzeichen. +# Gleiche Bauart und derselbe Grund wie GERUEST_TYP_ANZEIGE. +KF_TYP_ANZEIGE = sivas_mapping.load_flat_mapping("kf_typ_anzeige") + +# TeileArt-Spalte der Kettenfoerderer-Zeilen. Fester Wert statt Block-Attribut: +# er ist fuer jeden Kettenfoerderer gleich, stuende also an jedem Block nur +# redundant herum. Die Erkennung laeuft ueber den Blocknamen +# (pattern_kettenfoerderer), nicht ueber TEILEART. Ein Block, der das Attribut +# doch mitbringt (Altbestand), hat weiterhin Vorrang. +KF_TEILEART = "Schraegfoerderer Stahl" + SEPARATOR_BEARBEITUNG_ANZEIGE = "ohne HARTCOAT HRC 50 ± 5" # Sivas-Merkmale "Streckengruppe - Variofoerderer 001..005 - Hoehe in Meter". @@ -113,6 +125,12 @@ def geruest_typ_anzeige(wert): return GERUEST_TYP_ANZEIGE.get(wert, wert) +def kf_typ_anzeige(wert): + """Wandelt den Kettenfoerderer-Winkel ("26"/"30") in die Sivas-Anzeige + zu Merkmal 359 um ("Schrägförderer Stahl 26°").""" + return KF_TYP_ANZEIGE.get(str(wert).strip(), wert) + + def ja_nein(flag_1_0): """Wandelt ein "0"/"1"-Attribut in die Sivas-Anzeige "Ja"/"Nein" um.""" return "Ja" if flag_1_0 == "1" else "Nein" @@ -191,6 +209,7 @@ def es_seite(wert, attribs): TRANSFORMS = { "ja_nein": ja_nein, "geruest_typ_anzeige": geruest_typ_anzeige, + "kf_typ_anzeige": kf_typ_anzeige, "mm_zu_m": mm_zu_m, "as_es_vorhanden": as_es_vorhanden, "as_es_winkel": as_es_winkel, @@ -275,6 +294,42 @@ def eckrad_signature(block): ) +def kettenfoerderer_signature(block): + """ + Schluessel zur Gruppierung gleichartiger Kettenfoerderer (KF_n): baugleiche + Bloecke landen in EINER Sivas-Zeile, deren Anzahl das Merkmal 353 ("Anzahl + der gleichen Schraegfoerderer") traegt. + + In den Schluessel gehoert JEDES Attribut, aus dem ein Merkmal der Zeile + entsteht - sonst verschmelzen zwei unterschiedlich ausgefuehrte Foerderer + zu einer Zeile, und die Merkmale des ERSTEN Blocks gelten fuer beide + (gleiche Ueberlegung wie bei gefaellestrecke_signature/as_es_signatur). + Die Kommentare nennen darum je Attribut die Merkmalnummer; einzige + Ausnahme ist 353, das erst aus der Gruppengroesse entsteht. + + Das ist zugleich die Voraussetzung dafuer, dass items_zusammenfassen (fuer + die _zusammengefasst.csv) zwei KF-Zeilen NIE verschmelzen kann: gleiche + Merkmale wuerden schon hier dieselbe Gruppe ergeben. Sonst wuerde dort + "anzahl" addiert, waehrend 353 unveraendert stehen bliebe. + + Die Laenge des Zwischenstuecks (L_KF_mm) ist eine FOLGE der Einbauhoehe und + steht darum nicht eigens im Schluessel. + """ + attribs = block.get("attribs", {}) + return ( + attribs.get("WINKEL", "26"), # 359 + attribs.get("STANDARD_STEUERUNG", "0"), # 361 + attribs.get("FOERDERERTYP", ""), # 350 + attribs.get("H_EINBAUHOEHE_mm", "0"), # 360 + attribs.get("FARBE", ""), # 354 + attribs.get("ANTRIEBSMOTOR", ""), # 355 + attribs.get("ANTRIEBSART", ""), # 357 + attribs.get("MOVIMOT", "0"), # 356 + attribs.get("VERBINDUNGSELEMENTE", "Nein"), # 391 + attribs.get("ARTINR", ""), + ) + + def gefaellestrecke_signature(block): """ Schluessel zur Gruppierung gleichartiger Gefaellestrecken: Bloecke mit @@ -404,6 +459,7 @@ 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") +SIVAS_MAPPING_KETTENFOERDERER = sivas_mapping.load_section_mapping("kettenfoerderer") def build_kreisel_details(block): @@ -431,6 +487,21 @@ def build_eckrad_details(block): return sivas_mapping.anwenden(SIVAS_MAPPING_ECKRAD, attribs, TRANSFORMS) +def build_kettenfoerderer_details(block, anzahl=1): + """Merkmale-Dict fuer eine Gruppe baugleicher Kettenfoerderer (KF_n, + Lisp/KettenFoerderer.lsp). + + 350/359/354/356/360/391 kommen flach aus cfg/sivas.cfg [kettenfoerderer]. + 353 ("Anzahl der gleichen Schraegfoerderer") entsteht erst hier aus der + Gruppengroesse und kann darum nicht im Mapping stehen - gleiche Bauart wie + 3510/3512 beim Kreisel. + """ + attribs = block.get("attribs", {}) + details = sivas_mapping.anwenden(SIVAS_MAPPING_KETTENFOERDERER, attribs, TRANSFORMS) + details["353"] = str(anzahl) + return details + + # def build_geruest_details(block): #"""Merkmale-Dict fuer eine GR_-Geruest-Baugruppe (siehe pattern_geruest in #cfg/export.cfg, GeruestInsert.lsp *geruest-attrib-defs*). ARTINR/ @@ -549,6 +620,8 @@ def process_blocks(blocks, anzahl_weichen_ils=0): btmt_beladung_ids = [] # IDs aller BTMT-Beladung*-Bloecke (eine gruppierte Zeile) btmt_entladung_ids = [] # IDs aller SC_Entladung*-Bloecke (eine gruppierte Zeile) geruest_list = [] # Liste von GR_-Geruest-Baugruppenbloecken, je einer Zeile + # Kettenfoerderer: kettenfoerderer_signature(block) -> {"block", "ids", "anzahl"} + kettenfoerderer_groups = {} counters = { "anzahl_kreisel": 0, @@ -571,6 +644,7 @@ def process_blocks(blocks, anzahl_weichen_ils=0): "anzahl_btbeladung": 0, "anzahl_abwurfstationen": 0, "anzahl_geruest": 0, + "anzahl_kettenfoerderer": 0, } for block in blocks: @@ -620,6 +694,22 @@ def process_blocks(blocks, anzahl_weichen_ils=0): counters["anzahl_geruest"] += 1 continue + # --- Kettenfoerderer / Schraegfoerderer Stahl (KF_n) --- + # MUSS vor dem allgemeinen Omniflo-Zweig stehen: die KF-Baugruppe + # traegt TEILEART "Omniflo Kettenfoerderer ..." und wuerde dort sonst + # ueber ARTINR aufgeloest - ihre eigenen Merkmale (350/353/354/356/ + # 359/360/391) gingen dabei verloren. Anders als das Geruest werden + # BAUGLEICHE Foerderer zu einer Zeile zusammengefasst, weil Merkmal + # 353 genau diese Stueckzahl verlangt. + if matches_any(bname, BLOCKPATTERNS.get("pattern_kettenfoerderer", [])): + sig = kettenfoerderer_signature(block) + if sig not in kettenfoerderer_groups: + kettenfoerderer_groups[sig] = {"block": block, "ids": [], "anzahl": 0} + kettenfoerderer_groups[sig]["anzahl"] += 1 + kettenfoerderer_groups[sig]["ids"].append(get_id(block)) + counters["anzahl_kettenfoerderer"] += 1 + continue + # --- Omniflo Gerade (AP110) --- # VOR dem allgemeinen Omniflo-Zweig: Geraden tragen ggf. ebenfalls # TEILEART "Omniflo ...", brauchen aber die Laengensumme. @@ -886,6 +976,31 @@ def process_blocks(blocks, anzahl_weichen_ils=0): }) nr += 1 + # Kettenfoerderer / Schraegfoerderer Stahl (KF_n) - eine Zeile je Gruppe + # BAUGLEICHER Foerderer, deren Groesse als Merkmal 353 mitgefuehrt wird. + # Die Sivasnr kommt - wie beim Geruest - aus dem ARTINR-Attribut (gepflegt + # als "sivas_id" je Variante in data/json/kettenfoerderer.json); ist dort + # nichts hinterlegt, bleibt die Spalte leer und die Zeile traegt allein + # ihre Merkmale. Die drei Bausteine des Foerderers (Umlenkstation, + # skaliertes Zwischenstueck, Antriebsstation) bekommen KEINE eigenen + # Zeilen - der Schraegfoerderer ist in Sivas EIN Artikel. + for gruppe in kettenfoerderer_groups.values(): + block = gruppe["block"] + attribs = block.get("attribs", {}) + items.append({ + "nr": nr, + "teileart": attribs.get("TEILEART") or attribs.get("FOERDERERTYP") or KF_TEILEART, + "sivasnr": attribs.get("ARTINR", ""), + "sivasnr_quoted": True, + "bezeichnung": attribs.get("Bezeichnung", ""), + "dispgruppe": 20, + "anzahl": gruppe["anzahl"], + "laenge_mm": attribs.get("L_KF_mm", ""), + "ids": gruppe["ids"], + "details": build_kettenfoerderer_details(block, gruppe["anzahl"]), + }) + nr += 1 + # VarioFoerderer-Compound-Bloecke (VF_N) - je Block eine eigene Zeile mit # vollstaendigen Gefaelle-/Angetrieben-Teilgruppen (immer TYP "Streckengruppe"). for block in vario_list: diff --git a/menu/SSG_LIB.cui b/menu/SSG_LIB.cui index e104881..f7710f7 100644 --- a/menu/SSG_LIB.cui +++ b/menu/SSG_LIB.cui @@ -401,7 +401,7 @@ KettenFoerderer - ^C^C(ssg-ensure "OmniModulInsert") OMNI_KettenFoerderer + ^C^C(ssg-ensure "KettenFoerderer") KETTENFOERDERER @@ -1150,7 +1150,7 @@ - ~KettenFoerderer + KettenFoerderer diff --git a/menu/SSG_LIB.mnl b/menu/SSG_LIB.mnl index 79a6fd1..408f553 100644 --- a/menu/SSG_LIB.mnl +++ b/menu/SSG_LIB.mnl @@ -158,6 +158,11 @@ (ssg-ensure "export") (ssg-ensure "external_call") (ssg-ensure "OmniModulInsert") + ;; Kettenfoerderer vorladen statt lazy: der erste Menueklick musste sonst + ;; das Laden des Moduls mitbezahlen, der Dialog erschien dadurch spuerbar + ;; verzoegert. Das Modul ist klein, der Startaufschlag faellt nicht ins + ;; Gewicht. (Zum Nachmessen: Schalter "kf-zeit", siehe KettenFoerderer.lsp) + (ssg-ensure "KettenFoerderer") ;; Tro_Edit wird nicht vorgeladen - der Dispatcher SSG_BLOCKEDIT und die ;; Menuemakros holen es per (ssg-ensure "Tro_Edit") bei Bedarf nach. (ssg-ensure "SSG_LIB_Commands") diff --git a/menu/SSG_LIB.mnu b/menu/SSG_LIB.mnu index ce4d883..dd5ba6d 100644 --- a/menu/SSG_LIB.mnu +++ b/menu/SSG_LIB.mnu @@ -92,7 +92,7 @@ [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 +[KettenFoerderer]^C^C(ssg-ensure "KettenFoerderer") KETTENFOERDERER [Ladeschleifen]^C^C(ssg-ensure "OmniModulInsert") OMNI_LADESCHLEIFE_AP110 [->Verbinder] [TV W-AP110 kpl]^C^C(ssg-ensure "OmniModulInsert") OMNI_TV_W_AP110 diff --git a/tests/test_unit.lsp b/tests/test_unit.lsp index 2fd0e54..83283bf 100644 --- a/tests/test_unit.lsp +++ b/tests/test_unit.lsp @@ -2464,7 +2464,7 @@ "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") + "tu-tests-werkzeuge" "tu-tests-kf") (tu-run-block fn)) (princ "\n\n----------------------------------------------------------------") @@ -2487,3 +2487,255 @@ (princ "\ntest_unit.lsp geladen. Befehl: TEST_UNIT") (princ) +;; ------------------------------------------------------------ +;; TESTS: KettenFoerderer.lsp + ssg_ks_insert.lsp (KS-Orientierung) +;; ------------------------------------------------------------ +;; Reine Rechenkerne des Kettenfoerderers: Hoehenbilanz, Massstab, +;; Grenzpruefung, KS-Winkel. Alles ohne Zeichnung und ohne Blockdateien - +;; die zeichnungsabhaengigen Teile (kf-geometrie-messen, kf-kette-bauen) +;; brauchen BricsCAD und bleiben dem Befehl KF_KS_PRUEFEN vorbehalten. +(defun tu-tests-kf ( / _l _s _defs) + (princ "\n[KettenFoerderer]") + (if (not (car (atoms-family 1 '("KF-LAENGE-BERECHNEN")))) + (tu-record nil "KettenFoerderer-Modul geladen" + "KettenFoerderer.lsp nicht geladen - siehe tests/test_unit.scr") + (progn + ;; --- ks-winkel-normalisieren: auf -180..180 falten --- + (tu-eqf "ks-winkel-norm/190" -170.0 (ks-winkel-normalisieren 190.0)) + (tu-eqf "ks-winkel-norm/-190" 170.0 (ks-winkel-normalisieren -190.0)) + (tu-eqf "ks-winkel-norm/26" 26.0 (ks-winkel-normalisieren 26.0)) + (tu-eqf "ks-winkel-norm/360" 0.0 (ks-winkel-normalisieren 360.0)) + (tu-nil "ks-winkel-norm/nil" (ks-winkel-normalisieren nil)) + + ;; --- ks-xy-winkel: X-Achse eines KS-Eintrags (origin x-end y-end z-end) + (tu-eqf "ks-xy-winkel/entlang-X" 0.0 + (ks-xy-winkel '((0.0 0.0 0.0) (1.0 0.0 0.0) (0.0 2.0 0.0) (0.0 0.0 3.0)))) + (tu-eqf "ks-xy-winkel/entlang-Y" 90.0 + (ks-xy-winkel '((0.0 0.0 0.0) (0.0 1.0 0.0) (-2.0 0.0 0.0) (0.0 0.0 3.0)))) + ;; versetzter Ursprung: nur die RICHTUNG zaehlt, nicht die Lage + (tu-eqf "ks-xy-winkel/versetzt-45" 45.0 + (ks-xy-winkel (list '(100.0 50.0 0.0) '(101.0 51.0 0.0) '(99.0 51.0 0.0) '(100.0 50.0 1.0)))) + ;; senkrechte X-Achse (keine XY-Laenge) -> nil statt Division durch 0 + (tu-nil "ks-xy-winkel/senkrecht" + (ks-xy-winkel '((0.0 0.0 0.0) (0.0 0.0 1.0) (0.0 2.0 0.0) (0.0 0.0 3.0)))) + + ;; --- kf-hz-folge: Folgebaustein an KS_AUS des Vorgaengers ausrichten --- + ;; Vorgaenger zeigt 26 Grad, Folgebaustein ist waagerecht gezeichnet (0) + (tu-eqf "kf-hz-folge/26-auf-0" 26.0 (kf-hz-folge 26.0 0.0)) + ;; Folgebaustein ist selbst schon um 26 Grad gezeichnet -> keine Drehung + (tu-eqf "kf-hz-folge/26-auf-26" 0.0 (kf-hz-folge 26.0 26.0)) + ;; fehlende Eigenrichtung -> Baustein uebernimmt die Vorgaengerrichtung + (tu-eqf "kf-hz-folge/ohne-beta" 26.0 (kf-hz-folge 26.0 nil)) + (tu-nil "kf-hz-folge/ohne-phi" (kf-hz-folge nil 0.0)) + + ;; --- kf-dy-eingebaut: Hoehenbeitrag nach Rz(hz) und X-Massstab --- + ;; waagerechter Baustein, waagerecht eingebaut -> kein Hoehengewinn + (tu-eqf "kf-dy/0-grad" 0.0 (kf-dy-eingebaut 1000.0 0.0 0.0 1.0)) + ;; 1000 mm unter 30 Grad -> 500 mm (sin 30 = 0.5) + (tu-eqf "kf-dy/30-grad" 500.0 (kf-dy-eingebaut 1000.0 0.0 30.0 1.0)) + ;; derselbe Baustein doppelt skaliert -> doppelter Hoehengewinn + (tu-eqf "kf-dy/30-grad-skaliert-2" 1000.0 (kf-dy-eingebaut 1000.0 0.0 30.0 2.0)) + ;; senkrecht eingebaut -> die volle Laenge als Hoehe + (tu-eqf "kf-dy/90-grad" 1000.0 (kf-dy-eingebaut 1000.0 0.0 90.0 1.0)) + + ;; --- kf-laenge-berechnen: Hoehenbilanz nach der Laenge aufloesen --- + ;; H=1000, Stationen tragen nichts bei, 30 Grad -> L = 1000/sin30 = 2000 + (tu-eqf "kf-laenge/30-grad-glatt" 2000.0 + (kf-laenge-berechnen 1000.0 0.0 0.0 0.0 30.0)) + ;; Stationen tragen 100+150 bei -> nur 750 mm bleiben fuer das Stueck + (tu-eqf "kf-laenge/mit-stationen" 1500.0 + (kf-laenge-berechnen 1000.0 100.0 150.0 0.0 30.0)) + ;; Gegenprobe: die errechnete Laenge ergibt wieder genau H + (setq _l (kf-laenge-berechnen 1000.0 100.0 150.0 0.0 30.0)) + (tu-eqf "kf-laenge/rundlauf-ergibt-H" 1000.0 + (+ 100.0 150.0 (kf-dy-eingebaut _l 0.0 30.0 1.0))) + ;; bereits geneigt gezeichnetes Zwischenstueck: dy-nativ wird abgezogen + ;; (dy-nativ skaliert NICHT mit, nur X wird skaliert) + (tu-eqf "kf-laenge/mit-dy-nativ" 1800.0 + (kf-laenge-berechnen 1000.0 0.0 0.0 (/ 100.0 (cos (/ pi 6.0))) 30.0)) + ;; waagerecht -> keine Hoehe erreichbar + (tu-nil "kf-laenge/0-grad-unloesbar" + (kf-laenge-berechnen 1000.0 0.0 0.0 0.0 0.0)) + + ;; --- kf-skala-berechnen --- + (tu-eqf "kf-skala/2000-von-1000" 2.0 (kf-skala-berechnen 2000.0 1000.0)) + (tu-nil "kf-skala/ohne-nativlaenge" (kf-skala-berechnen 2000.0 0.0)) + (tu-nil "kf-skala/ohne-laenge" (kf-skala-berechnen nil 1000.0)) + + ;; --- kf-laenge-pruefen: Grenzen aus der JSON --- + (tu-nil "kf-pruefen/in-ordnung" (kf-laenge-pruefen 2000.0 100.0 20000.0)) + (tu-eq "kf-pruefen/zu-kurz" "kf-fehler-laenge-unter-min" + (kf-laenge-pruefen 50.0 100.0 20000.0)) + (tu-eq "kf-pruefen/zu-lang" "kf-fehler-laenge-ueber-max" + (kf-laenge-pruefen 99999.0 100.0 20000.0)) + ;; negative Laenge = Stationen bringen mehr Hoehe mit als gefordert + (tu-eq "kf-pruefen/negativ" "kf-fehler-hoehe-zu-klein" + (kf-laenge-pruefen -10.0 100.0 20000.0)) + (tu-eq "kf-pruefen/nil" "kf-fehler-winkel-null" + (kf-laenge-pruefen nil 100.0 20000.0)) + + ;; --- kf-zahl: JSON-Wert als Zahl (Parser liefert Zahl ODER String) --- + (tu-eqf "kf-zahl/int" 26.0 (kf-zahl 26)) + (tu-eqf "kf-zahl/real" 26.0 (kf-zahl 26.0)) + (tu-eqf "kf-zahl/string" 26.0 (kf-zahl "26")) + (tu-nil "kf-zahl/nil" (kf-zahl nil)) + + ;; --- kf-attrib-defs: VF-Schema + KF-Zusatz, KEIN Tag doppelt --- + (setq _defs (kf-attrib-defs)) + (tu-true "kf-attrib-defs/hat-ID" (assoc "ID" _defs)) + (tu-true "kf-attrib-defs/hat-FOERDERERTYP" (assoc "FOERDERERTYP" _defs)) + (tu-true "kf-attrib-defs/hat-WINKEL" (assoc "WINKEL" _defs)) + (tu-true "kf-attrib-defs/hat-STANDARD_STEUERUNG" + (assoc "STANDARD_STEUERUNG" _defs)) + ;; Reihenfolge wie im Sivas-Merkmalstamm: 361 folgt direkt auf den Typ, + ;; STANDARD_STEUERUNG steht also unmittelbar hinter WINKEL + (tu-eq "kf-attrib-defs/361-folgt-auf-winkel" "STANDARD_STEUERUNG" + (car (nth (1+ (kf-tag-index "WINKEL" _defs)) _defs))) + ;; FOERDERERTYP ist die BAUART im Klartext, der Winkel steht in WINKEL + (tu-eq "kf-attrib-defs/FOERDERERTYP-ist-bauart" "Schraegfoerderer Stahl" + (cadr (assoc "FOERDERERTYP" _defs))) + (tu-true "kf-attrib-defs/hat-MOVIMOT" (assoc "MOVIMOT" _defs)) + ;; Farbe und Antriebsmotor sind ZWEI Eigenschaften (Sivas 354 bzw. 355), nicht + ;; ein kombiniertes AUSFUEHRUNG-Attribut + (tu-true "kf-attrib-defs/hat-FARBE" (assoc "FARBE" _defs)) + (tu-true "kf-attrib-defs/hat-ANTRIEBSMOTOR" (assoc "ANTRIEBSMOTOR" _defs)) + (tu-nil "kf-attrib-defs/kein-AUSFUEHRUNG" (assoc "AUSFUEHRUNG" _defs)) + ;; Der Kettenfoerderer traegt sich selbst - die Geruest-Tags aus dem + ;; VF-Schema werden bewusst NICHT mitgefuehrt + (tu-nil "kf-attrib-defs/kein-GERUEST_EINZELMODUL" + (assoc "GERUEST_EINZELMODUL" _defs)) + (tu-nil "kf-attrib-defs/kein-GERUEST_TYP" (assoc "GERUEST_TYP" _defs)) + ;; TYP/ARTINR stehen in BEIDEN Listen - der Zusatz muss gewinnen, und + ;; der Tag darf nur EINMAL vorkommen (zwei ATTDEFs gleichen Tags waeren + ;; im Block ungueltig, ssg-attrib-set-on traefe nur den ersten). + (tu-eq "kf-attrib-defs/TYP-ueberschrieben" "Kettenfoerderer" + (cadr (assoc "TYP" _defs))) + (setq _s 0) + (foreach _d _defs (if (= (car _d) "TYP") (setq _s (1+ _s)))) + (tu-eq "kf-attrib-defs/TYP-nur-einmal" 1 _s) + (setq _s 0) + (foreach _d _defs (if (= (car _d) "ARTINR") (setq _s (1+ _s)))) + (tu-eq "kf-attrib-defs/ARTINR-nur-einmal" 1 _s) + ;; Der Winkel steht ALLEIN in FOERDERERTYP - kein zweites Winkel-Tag + (tu-nil "kf-attrib-defs/kein-KF_WINKEL" (assoc "KF_WINKEL" _defs)) + (tu-nil "kf-attrib-defs/kein-KF_TYP" (assoc "KF_TYP" _defs)) + ;; Das Schema ist EIGENSTAENDIG, nicht vom VF-Streckenschema geerbt - + ;; keines der VF-Tags darf durchschlagen + (tu-nil "kf-attrib-defs/kein-SEITE_AS" (assoc "SEITE_AS" _defs)) + (tu-nil "kf-attrib-defs/kein-ANZAHL_VF" (assoc "ANZAHL_VF" _defs)) + (tu-nil "kf-attrib-defs/kein-TEILEART" (assoc "TEILEART" _defs)) + (tu-eq "kf-attrib-defs/LAYER-ist-F-3" "F-3" (cadr (assoc "LAYER" _defs))) + + ;; --- kf-variante-finden: ueber den Winkel, auch als String --- + (tu-eq "kf-variante-finden/26" "A" + (cdr (assoc "name" + (kf-variante-finden 26 + '((("winkel" . 26) ("name" . "A")) + (("winkel" . 30) ("name" . "B"))))))) + (tu-eq "kf-variante-finden/30-als-string" "B" + (cdr (assoc "name" + (kf-variante-finden 30 + '((("winkel" . "26") ("name" . "A")) + (("winkel" . "30") ("name" . "B"))))))) + (tu-nil "kf-variante-finden/unbekannt" + (kf-variante-finden 45 '((("winkel" . 26) ("name" . "A"))))) + + ;; --- kf-antriebsart (Merkmal 357): Dropdown mit EINER Option --- + ;; Der Wert kommt aus dem ERSTEN Listeneintrag - nicht aus dem Code. + (tu-eq "kf-antriebsart/aus-daten" "1.1 kW" + (kf-antriebsart '(("antriebsart" (("name" . "1.1 kW") ("wert" . "1.1 kW")))))) + ;; eine zweite Option aendert die Vorgabe NICHT (Index 0 bleibt massgebend) + (tu-eq "kf-antriebsart/erster-eintrag-gewinnt" "1.1 kW" + (kf-antriebsart '(("antriebsart" (("name" . "1.1 kW") ("wert" . "1.1 kW")) + (("name" . "0.55 kW") ("wert" . "0.55 kW")))))) + ;; Liste fehlt -> Rueckfall, aber als STRING (nicht T/nil) + (tu-eq "kf-antriebsart/ohne-eintrag" "1.1 kW" (kf-antriebsart '(("titel" . "x")))) + (tu-true "kf-antriebsart/ist-string" + (= (type (kf-antriebsart '(("titel" . "x")))) 'STR)) + ;; Dezimalpunkt, kein Komma (Nutzervorgabe) + (tu-nil "kf-antriebsart/kein-komma" + (vl-string-search "," (kf-antriebsart '(("titel" . "x"))))) + + ;; --- kf-neigung-aus-punkten: Kern der 3D-Aufrichtung --- + ;; Liefert (Schraeglaenge Neigungswinkel) aus Ketten-Start und -Ende. + ;; Die Kette wird AUFGERICHTET eingebaut, die Hoehe laeuft also in Z. + ;; 3-4-5-Dreieck: 3000 waagerecht, 4000 hoch -> 5000 schraeg + (tu-eqf "kf-neigung/3-4-5-laenge" 5000.0 + (car (kf-neigung-aus-punkten '(0.0 0.0 0.0) '(3000.0 0.0 4000.0) 0.0))) + ;; 45 Grad: gleiche Betraege waagerecht und hoch + (tu-eqf "kf-neigung/45-grad" 45.0 + (cadr (kf-neigung-aus-punkten '(0.0 0.0 0.0) '(1000.0 0.0 1000.0) 0.0))) + ;; waagerecht -> Winkel 0, Laenge = Horizontalabstand + (tu-eqf "kf-neigung/waagerecht" '(2000.0 0.0) + (kf-neigung-aus-punkten '(0.0 0.0 0.0) '(2000.0 0.0 0.0) 0.0)) + ;; Der Horizontalanteil wird auf die KETTENRICHTUNG projiziert, nicht + ;; auf die Welt-X-Achse: bei 90 Grad Basisdrehung laeuft die Kette in Y. + ;; Ohne Projektion kaeme hier 0 heraus und der Winkel waere 90 statt 45. + (tu-eqf "kf-neigung/gedreht-90" '(1414.213562 45.0) + (kf-neigung-aus-punkten '(0.0 0.0 0.0) '(0.0 1000.0 1000.0) 90.0)) + ;; fallende Kette -> negativer Winkel + (tu-eqf "kf-neigung/fallend" -45.0 + (cadr (kf-neigung-aus-punkten '(0.0 0.0 1000.0) '(1000.0 0.0 0.0) 0.0))) + ;; Start = Ende -> nil statt Division durch 0 + (tu-nil "kf-neigung/nulllaenge" + (kf-neigung-aus-punkten '(5.0 5.0 5.0) '(5.0 5.0 5.0) 0.0)) + + ;; --- kf-matrix-aufrecht: Y_lokal wird zu Z_welt --- + ;; Das ist die Kernaussage der Aufrichtung. Geprueft wird sie an den + ;; Bildern der Einheitsvektoren, nicht an den Matrixeintraegen - so + ;; bleibt der Test lesbar und unabhaengig von der Schreibweise. + (tu-eqf "kf-matrix/Y-lokal-wird-Z-welt" '(0.0 0.0 1.0) + (mat3-mul-vec3 (kf-matrix-aufrecht 0.0 0.0) '(0.0 1.0 0.0))) + ;; Die Fahrtrichtung (X) bleibt bei Basisdrehung 0 die Welt-X-Achse + (tu-eqf "kf-matrix/X-bleibt-X" '(1.0 0.0 0.0) + (mat3-mul-vec3 (kf-matrix-aufrecht 0.0 0.0) '(1.0 0.0 0.0))) + ;; Basisdrehung 90 Grad dreht die Fahrtrichtung in die Welt-Y-Achse, + ;; die Hoehe bleibt dabei in Z + (tu-eqf "kf-matrix/basis-90-dreht-X-nach-Y" '(0.0 1.0 0.0) + (mat3-mul-vec3 (kf-matrix-aufrecht 90.0 0.0) '(1.0 0.0 0.0))) + (tu-eqf "kf-matrix/basis-90-haelt-Z" '(0.0 0.0 1.0) + (mat3-mul-vec3 (kf-matrix-aufrecht 90.0 0.0) '(0.0 1.0 0.0))) + + ;; --- Standard-Variante: steht in den DATEN, nicht im Code --- + (tu-true "kf-variante-standard-p/markiert" + (kf-variante-standard-p '(("winkel" . 26) ("standard" . T)))) + (tu-nil "kf-variante-standard-p/ohne-feld" + (kf-variante-standard-p '(("winkel" . 30)))) + ;; Index fuer die Vorauswahl - auch wenn der Standard NICHT der erste ist + (tu-eq "kf-standard-index/zweiter" 1 + (kf-standard-index '((("winkel" . 26)) + (("winkel" . 30) ("standard" . T))))) + (tu-eq "kf-standard-index/erster" 0 + (kf-standard-index '((("winkel" . 26) ("standard" . T)) + (("winkel" . 30))))) + ;; keine Variante markiert -> erste, statt nil (der Dialog braucht einen Index) + (tu-eq "kf-standard-index/keiner" 0 + (kf-standard-index '((("winkel" . 26)) (("winkel" . 30))))) + + ;; --- kf-punkt-differenzen: zweite Hoehen-Eingabeart (2 Punkte) --- + ;; Gemessen wird die Differenz in Z - die Baugruppe steht aufgerichtet im + ;; Modell. Zurueckgegeben wird (dz dy): die HOEHE zuerst, dY nur zur + ;; Kontrolle (wer in einer Draufsicht pickt, sieht dz = 0). + (tu-eqf "kf-punkt-diff/dz" '(1500.0 0.0) + (kf-punkt-differenzen '(0.0 0.0 0.0) '(2000.0 0.0 1500.0))) + ;; Vorzeichen egal - die Reihenfolge der Punkte darf keine Rolle spielen + (tu-eqf "kf-punkt-diff/umgekehrt" '(1500.0 0.0) + (kf-punkt-differenzen '(2000.0 0.0 1500.0) '(0.0 0.0 0.0))) + ;; in einer Draufsicht gepickt: dz = 0, dY gross + (tu-eqf "kf-punkt-diff/draufsicht" '(0.0 1200.0) + (kf-punkt-differenzen '(0.0 0.0 0.0) '(3000.0 1200.0 0.0))) + ;; Punkte ohne Z (2D-Pick) duerfen nicht abstuerzen: dz ist dann 0 + (tu-eqf "kf-punkt-diff/ohne-z" '(0.0 800.0) + (kf-punkt-differenzen '(0.0 0.0) '(100.0 800.0))) + (tu-nil "kf-punkt-diff/nil" (kf-punkt-differenzen nil '(1.0 2.0 3.0))) + + ;; --- kf-auswahl-wert: "wert" vor "name", Vorgabe bei leerem Index --- + (tu-eq "kf-auswahl-wert/wert" "Nein" + (kf-auswahl-wert '((("name" . "Nein") ("wert" . "Nein"))) 0 "X")) + (tu-eq "kf-auswahl-wert/ausserhalb" "X" + (kf-auswahl-wert '((("name" . "Nein") ("wert" . "Nein"))) 5 "X")) + ) + ) +) + + diff --git a/tests/test_unit.scr b/tests/test_unit.scr index 8660b4d..871cc6d 100644 --- a/tests/test_unit.scr +++ b/tests/test_unit.scr @@ -10,6 +10,7 @@ ; Modul, gehoert dessen load-Zeile HIER dazu (nicht in test_unit.lsp). (load (strcat (getenv "DXFM_LISP") "/GeruestInsert.lsp") "GeruestInsert.lsp nicht gefunden") (load (strcat (getenv "DXFM_LISP") "/count_sep_scan.lsp") "count_sep_scan.lsp nicht gefunden") +(load (strcat (getenv "DXFM_LISP") "/KettenFoerderer.lsp") "KettenFoerderer.lsp nicht gefunden") (load (strcat (getenv "DXFMAKRO") "/tests/test_unit.lsp")) (c:TEST_UNIT) (command "_.QUIT")