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 0000000..91ecddd Binary files /dev/null and b/data/omniflo/2D/KF_2D.dwg differ diff --git a/data/omniflo/2D/KF_AN_26.dwg b/data/omniflo/2D/KF_AN_26.dwg new file mode 100644 index 0000000..882ec1a Binary files /dev/null and b/data/omniflo/2D/KF_AN_26.dwg differ diff --git a/data/omniflo/2D/KF_AN_30.dwg b/data/omniflo/2D/KF_AN_30.dwg new file mode 100644 index 0000000..50beae3 Binary files /dev/null and b/data/omniflo/2D/KF_AN_30.dwg differ diff --git a/data/omniflo/2D/KF_LAENGE.dwg b/data/omniflo/2D/KF_LAENGE.dwg new file mode 100644 index 0000000..d82ce37 Binary files /dev/null and b/data/omniflo/2D/KF_LAENGE.dwg differ diff --git a/data/omniflo/2D/KF_UM_26.dwg b/data/omniflo/2D/KF_UM_26.dwg new file mode 100644 index 0000000..69ff450 Binary files /dev/null and b/data/omniflo/2D/KF_UM_26.dwg differ diff --git a/data/omniflo/2D/KF_UM_30.dwg b/data/omniflo/2D/KF_UM_30.dwg new file mode 100644 index 0000000..1735c18 Binary files /dev/null and b/data/omniflo/2D/KF_UM_30.dwg differ 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")