From 503b02a005dcc6cc37545231974765e3fb5b53c5 Mon Sep 17 00:00:00 2001 From: Yuelin Wang Date: Fri, 30 Oct 2026 15:42:58 +0100 Subject: [PATCH] refactor: Kettenfoerderer als Seitenansicht mit Beschriftung und Hoehenmass, Draufsicht als eigener Hilfsblock KF2D_n - Hauptblock KF_n: flache, ungedrehte Seitenansicht mit Beschriftung und Hoehenmass; nur er wird exportiert - Draufsicht KF_2D als getrennter Hilfsblock KF2D_n (projizierte Laenge, frei verschiebbar, per KF_EDIT regenerierbar), Verknuepfung ueber XDATA-Schluessel - L_KF_mm = waagerechte Laenge, Sivas-Zeile ohne Laenge, Export nur KF_ - ensure-block-loaded: Block ohne ILS-Datei (KF_2D) nicht mehr als fehlend melden - Layer kettenfoerderer_beschriftung, Dispatcher-Zweig KF2D_*, Sprachdateien und Unit-Tests angepasst Co-Authored-By: Claude Sonnet 5.5 --- CLAUDE.md | 2 +- Lisp/KettenFoerderer.lsp | 615 +++++++++++++++++++-------------- Lisp/SSG_LIB_Commands.lsp | 6 + Lisp/ssg_ks_insert.lsp | 20 +- cfg/layer.cfg | 2 + data/json/kettenfoerderer.json | 4 +- lang/de_DE.json | 8 +- lang/en_GB.json | 8 +- lib/export_sivas.py | 16 +- tests/test_unit.lsp | 125 +++---- 10 files changed, 469 insertions(+), 337 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 3318d89..eed0c22 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -121,7 +121,7 @@ Detaillierte Dokumentation aller Funktionen und Befehle: siehe `Lisp/README.md`. | `Jam_Edit.lsp` | JAM_EDIT | Staubereich `JAM_AREA` aus sps_skel (`tro_annotate.py --anlage`, Name `Jam_`) bearbeiten: Foerderer und laufende Nummer per Dialog `jam_edit.dcl`, Knopf zurueck auf den abgeleiteten Wert (`AUTO`), Warnung bei doppeltem Namen. `TRO`/`ENTRY` sind stabiler Schluessel und werden nicht geaendert. SSG_BLOCKEDIT leitet `JAM_*` hierher; in die SCL kommt die Aenderung ueber `tro_annotate --read-jams` | | `TEFInsert.lsp` | TEF_FOERDERER_LINIE | Linie zwischen zwei Punkten zeichnen und mit 1000-mm-TEF-Geraden (`0_B10030`) belegen; Rest ueber ein zurueckversetztes Element, damit der Foerderer exakt am Linienende endet; geneigte Linien per Rz*Ry wie `ssg_ks_insert.lsp` | | `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` (Radiobuttons, siehe oben), **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, der die vorgewaehlte Auswahl nutzt und nur echte `KF_` zulaesst - die Bausteine `KF_UM_26`, `KF_2D` usw. tragen dasselbe Praefix). **Die Blocknummer wird ueber die Blocktabelle vergeben** (`kf-naechste-nummer` ueberspringt jede Nummer mit vorhandener Block-DEFINITION, wie bei `GF_n`/`VF_n`): nur ueber die Referenzen im Modell zu zaehlen war ein realer Fehler - `KF_EDIT` loescht den alten Block, die Definition lebt aber weiter, `_.-BLOCK` traf auf den vorhandenen Namen, die "Neu definieren?"-Abfrage verschluckte die Folge-Eingaben, und die Einzelteile blieben lose liegen ("Block aufgeloest"). **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` | +| `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**: SEITENANSICHT in der XY-Ebene (Hoehe = Y), ungedreht und nicht aufgerichtet, Einfuegepunkt = KS_EIN der Umlenkung; jeder Baustein bekommt nur den aus den KS gemessenen Verkettungswinkel (`insert-block-by-ks`/`insert-block-ks-skaliert`). 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`). **Hauptblock + Hilfsblock**: `KF_` traegt Beschriftung (`KF_1 26 Grad H=..mm L_horizontal=..mm`, Layer `kettenfoerderer_beschriftung`) und ein Hoehenmass (ActiveX `AddDimRotated`, scheitert es, wird ohne Mass gebaut) und ist der EINZIGE exportierte Block. Die Draufsicht `KF_2D` ist ein getrennter Hilfsblock `KF2D_` (kein Objekt, nur zum Anschliessen von Schienen): flach, auf die PROJIZIERTE Laenge skaliert (`kf-waagerecht-laenge`), beim ersten Einfuegen `ansicht_2d.versatz_y` (-1000) mm unter dem Hauptblock, danach frei verschieb- und loeschbar, gleiche Beschriftung. Beide Bloecke tragen denselben Schluessel als XDATA `SSG_KF_EDIT` (`kf-xdata-key`); KF_EDIT findet damit den - auch verschobenen - Hilfsblock (`kf-hilfsblock-finden`), behaelt dessen Lage und erzeugt ihn bei jedem Edit neu (auch wenn er geloescht war). Der Hilfsblock passt nicht aufs Export-Muster `KF_*`; `export_sivas.py` nimmt ausserdem nur `KF_`. Eine von Hand gesetzte Drehung des Hauptblocks (DXF 50) wird beim Edit wieder aufgesetzt. **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). `L_KF_mm` ist die WAAGERECHTE Laenge des ganzen Foerderers (der Draufsicht `KF_2D`, `kf-waagerecht-laenge`), nicht die Laenge des schraegen Mittelstuecks; sie geht NICHT in die Sivas-Zeile (Spalte Laenge bleibt leer) - Sivas berechnet ZL, EL und Kettenlaenge selbst, uebernommen wird nur die Hoehe (360). 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, der die vorgewaehlte Auswahl nutzt und nur echte `KF_` zulaesst - die Bausteine `KF_UM_26`, `KF_2D` usw. tragen dasselbe Praefix). **Die Blocknummer wird ueber die Blocktabelle vergeben** (`kf-naechste-nummer` ueberspringt jede Nummer mit vorhandener Block-DEFINITION, wie bei `GF_n`/`VF_n`): nur ueber die Referenzen im Modell zu zaehlen war ein realer Fehler - `KF_EDIT` loescht den alten Block, die Definition lebt aber weiter, `_.-BLOCK` traf auf den vorhandenen Namen, die "Neu definieren?"-Abfrage verschluckte die Folge-Eingaben, und die Einzelteile blieben lose liegen ("Block aufgeloest"). **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) | | `LagerInsert.lsp` | LG_GESCHOSS, LG_GESCHOSS_AKTIV, LG_GESCHOSS_KOPIEREN, LG_BEREICH, LG_REIHE, LG_GANG, LG_EDIT, LG_REGALE, LAGER_EXPORT, LAGER_HAND_YAML | Lagerplanung GOH-Buehnenlager -> RFQ-YAML (Phase 0+1: Mengen, 2D; Konzept/Schema/YAML-Zuordnung in `doc/plan/lager_rfq.md`). Bloecke `LG_GESCHOSS` (Kopfsymbol, XDATA `SSG_LG` `kontur` -> Belagskontur), `LG_BEREICH` (Kontur + Parameter, fuellt sich selbst), `LG_REIHE__`/`LG_GANG___` (Definition je Geometrie wiederverwendet, Werte in den Attributen der Referenz; Kinder tragen XDATA `bereich`; der Belag steht im Gang-Blocknamen, weil die Definition seine Schraffur enthaelt - Muster aus `lager.json belag_schraffur`, per `vla-AddHatch` auf Layer 0/VONBLOCK, `hallenboden` ohne; BricsCAD-`iso.pat` kennt kein `NET`, Gitterrost = `GRID`). Alle tragen `ID`, `GESCHOSS`, `LAYER`. Blockdefinitionen per `entmake` (BLOCK...ENDBLK), Einfuegen per `vla-InsertBlock` in WCS - kein `_.-BLOCK`/`_.INSERT`. **Attribut-Tags haben gemischte Schreibweise** (`HOEHE_BELAG_mm`) - beim Setzen ueber `vla-get-TagString` daher case-insensitiv zuordnen (`lg-wert-fuer-tag`), sonst bleiben diese Attribute still leer (realer Fehler beim Bau). Layer je Geschoss = Basis aus `cfg/layer.cfg [lager]` + `_G`; `LG_GESCHOSS_AKTIV` friert die uebrigen (2D-Grundrisse liegen uebereinander), aktives Geschoss als `vlax-ldata` (Dictionary `SSG_LAGER`). Geschosszuordnung `lg-geschoss-fuer-punkt-kern`: eine Kontur -> eindeutig, mehrere nur ueber Z unterscheidbar, wenn die Konturen auf verschiedenen Hoehen liegen (3D), sonst nil. Jede Kontur gehoert genau einem Block (vergebene Kontur wird kopiert). Auto-Fuellung `lg-fuellung-berechnen` (reiner Kern, Muster Reihe|Gang|Reihe Ruecken an Ruecken bzw. Reihe|Gang, nur volle Module, Streifen an Unter-/Mittel-/Oberkante gegen die Kontur geschnitten, Quergaenge ausgerichtet und nicht am Reihenende); Bereichs-Edit = Neuaufbau aller Kinder. **Randgaenge** `RANDGANG_N/O/S/W_mm` (Nord = +Y Welt) laufen entlang der echten Konturkanten (`lg-randgang-streifen`, Himmelsrichtung der Aussennormale ueber `lg-kompass`, auch L-Form; Ecken: N/S besitzen sie - konvex O/W kuerzen, einspringend N/S verlaengern) und werden wie manuelle Gaenge (`LG_GANG`, `QUELLE=manuell`, Kern `lg-gang-einfuegen`; per Linie oder Option `Rechteck` = Flaeche mit dem echten BricsCAD-Befehl RECHTECK gezeichnet, inkl. aller seiner Optionen (`lg-rechteck-zeichnen`: `_.RECTANG` + `(command pause)`-Schleife bei CMDECHO 1, Polylinie danach ueber `lg-rechteck-ecken-aus-poly` gelesen und geloescht; nur 4 Ecken ohne Boegen, sonst Meldung), Breite/Lage aus dem Rechteck ueber `lg-gang-aus-ecken`, Schleife `lg-linien-schleife` mit `rechteck-fn`) als **Sperrflaechen** aus Reihen/Haupt-/Quergaengen ausgeschnitten (`ssg-poly-band-x`). **Von Hand Gesetztes loest KEINE Neuberechnung aus**: `LG_REIHE`/`LG_GANG` (`lg-manuell-einfuegen`) und das Bearbeiten einer Reihe/eines Gangs (`lg-linie-neu-aufbauen-sperre`) schneiden nur die eigene Flaeche aus den rangniedrigeren Objekten heraus (`lg-sperre-anwenden`, reiner Kern `lg-linie-zuschnitt`; Rang `lg-quelle-rang`: auto < auto_geaendert < manuell) - frueher fuellte ein manueller Gang den ganzen Bereich neu, dabei kamen geloeschte Objekte zurueck und geaenderte wurden ersetzt. Die ausdrueckliche Neuberechnung (`lg-bereich-fuellen`, Bereich bearbeiten) entfernt nur `QUELLE=auto`; `manuell`/`auto_geaendert`-Kinder und unverknuepfte manuelle Objekte in der Kontur (`lg-manuelle-in-kontur`) bleiben und sperren ihre Flaeche. `lg-blockdef` ergaenzt bei Altbestand fehlende ATTDEFs (Neudefinition + `_.ATTSYNC`). `LAGER_EXPORT` schreibt `_lager_raw.json` (eigener JSON-Schreiber `lg-json-*`), zaehlt `goh_rack` ueber die eigene Erkennung `lg-racks-sammeln`/`lg-racks-finden` mit (parametrischer BricsCAD-Block: Name ueber `getpropertyvalue "EffectiveName"`, Parameter `Laenge`/`Ebenen` als MCAD-Eigenschaft `~MCAD` vor dynamischer Eigenschaft vor Attribut vor ATTDEF-Vorgabe, Einheit -> mm per `lg-zahl-einheit`; auch in assoziativen Arrays, MINSERT-Rastern und verschachtelten Bloecken, Transformationen `lg-insert-tf`/`lg-tf-verketten`; Ergebnis je Rack `(name laenge ebenen mitte top-ent)`, Zugriff `lg-rk-*`; Befehl `LG_REGALE` (Menue Lager -> Regale zaehlen) zeigt Anzahl, Laenge und Laenge x Ebenen je Geschoss in der Befehlszeile, reiner Kern `lg-racks-auswerten`) und ruft `lib/lager_yaml_export.py` ueber `export:run-python`. Vorgaben aus `data/json/lager.json`. Dialoge `dcl/lager.dcl` ueber den generischen Treiber `lg-dialog` (Feldlisten `*lg-felder-*`, Konsolen-Rueckfall `lg-konsole`, ohne GUI Vorgaben). **Geschoss kopieren** (`LG_GESCHOSS_KOPIEREN`, Dialog `lg_geschoss_kopie`, Kern `lg-geschosse-kopieren`): Quelle (Vorgabe oberstes Geschoss, Erweitern G3 -> G4), Anzahl, Geschosshoehe je Kopie, Belag, Versatz X/Y je Kopie (0 = deckungsgleich; 3D-Zeichnung zusaetzlich +Z). Kein BricsCAD-KOPIEREN (XDATA-Verweise zeigten aufs Original): jedes Objekt entsteht ueber die normalen Einfuegefunktionen neu (`lg-geschoss-anlegen`, `lg-bereich-block-anlegen` = Bereich ohne Fuellung, `lg-linie-einfuegen`), Kinder 1:1 inkl. `auto_geaendert`/manuell, kein Neufuellen; Belaege gleich dem Quell-Geschossbelag folgen dem neuen (`lg-kopie-belag`). goh_rack per `vla-Copy` auf `lg-layer "goh_rack" `; Originale ohne `_G`-Layer werden auf den Regal-Layer der Quelle gelegt. Export ordnet goh_racks zuerst ueber den Layer `LG_*_G` des obersten INSERTs zu (`lg-rack-geschoss`, `lg-rk-top`), erst dann ueber die Kontur - damit eindeutig auch bei 2D-Geschossen uebereinander. Tests: `tu-tests-lager` (test_unit) und `tests/test_lager.lsp` | | `LagerPruefung.lsp` | LG_REGELN | Regelprofile der Lagerplanung: `cfg/regelwerk/.json` laden, `erbt_von` aufloesen (`lg-profil-mischen`), je Regel den STRENGSTEN Wert aller zutreffenden Quellen (`lg-regel-aufloesen`: `art` min/max/wert, `bedingung` gegen einen Kontext - fehlt der Schluessel, konservativ einbezogen -, `tabelle` + `tabellen_schluessel`) mit Protokoll. Jede Pruefregel der Lagerplanung wird genau hier implementiert (Python rendert nur). Alle Startwerte `verifiziert: false` | diff --git a/Lisp/KettenFoerderer.lsp b/Lisp/KettenFoerderer.lsp index 2fd9614..df2407d 100644 --- a/Lisp/KettenFoerderer.lsp +++ b/Lisp/KettenFoerderer.lsp @@ -26,6 +26,14 @@ ;; insert-block-by-ks/insert-block-ks-skaliert, die Rz*Ry-Primitiven des ;; VarioFoerderers werden hier nicht gebraucht. ;; +;; HAUPT- UND HILFSBLOCK: KF_ ist die Seitenansicht (ungedreht, Einfuege- +;; punkt = KS_EIN der Umlenkung) mit Beschriftung und Hoehenmass; nur dieser +;; Block wird exportiert. Die Draufsicht KF_2D ist ein getrennter Hilfsblock +;; KF2D_ (zum Anschliessen von Schienen), der aus der projizierten Laenge +;; entsteht, beim ersten Mal 1000 mm unter dem Hauptblock liegt, frei +;; verschiebbar und loeschbar ist und per Doppelklick auf KF_ neu erzeugt +;; wird. Beide tragen denselben Schluessel in der XDATA (SSG_KF_EDIT). +;; ;; 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. @@ -223,7 +231,8 @@ ;; 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 +;; L_KF_mm - WAAGERECHTE Laenge des ganzen Foerderers (der Draufsicht +;; KF_2D), CSV-Spalte "Laenge" ;; FARBE - Merkmal 354 ;; ANTRIEBSMOTOR - Merkmal 355 (eigenes Merkmal, nicht Teil von 354) ;; ANTRIEBSART - Merkmal 357, FEST "1.1 kW" (Wert aus der JSON, @@ -327,27 +336,6 @@ (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 ;; ============================================================ @@ -433,156 +421,261 @@ (list laenge skala nil))))))) ;; ============================================================ -;; KETTE BAUEN (aufgerichtet, 3D) +;; KETTE BAUEN (Seitenansicht) ;; ============================================================ -;; 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. +;; Fuegt die drei Bausteine ab startpunkt verkettet ein. Die Kette ist eine +;; SEITENANSICHT: sie liegt flach in der XY-Ebene (X = Fahrtrichtung, +;; Y = Hoehe), so wie die Bausteine gezeichnet sind. Es wird nichts +;; aufgerichtet und nichts gedreht; jeder Baustein bekommt nur den aus den KS +;; gemessenen Verkettungswinkel (kf-hz-folge). +;; (Zwischenzeitlich wurde die Kette aufgerichtet in Z eingebaut - nach +;; Abstimmung wieder zurueckgenommen; die Draufsicht ist jetzt ein EIGENER +;; Hilfsblock, siehe kf-hilfsblock-bauen.) ;; Rueckgabe: Weltpunkt des KS_AUS der Antriebsstation (Kettenende). -(defun kf-kette-bauen (variante geo skala startpunkt basis-hz / pt) +(defun kf-kette-bauen (variante geo skala startpunkt / 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)) + (setq pt (insert-block-by-ks + (ssg-json-get variante "block_umlenkung") pt + (cdr (assoc "hz-um" geo)))) + (setq pt (insert-block-ks-skaliert + (ssg-json-get variante "block_laenge") pt + (cdr (assoc "hz-l" geo)) skala)) + (setq pt (insert-block-by-ks + (ssg-json-get variante "block_antrieb") pt + (cdr (assoc "hz-an" geo)))) pt) ;; ------------------------------------------------------------ -;; DRAUFSICHT (KF_2D) - deckungsgleich mit der Kette +;; MASSE AUS START- UND ENDPUNKT (reine Rechenkerne) ;; ------------------------------------------------------------ -;; 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) +;; Horizontalanteil von p1 nach p2, auf die Kettenrichtung (basis-hz, Grad) +;; projiziert und nicht die blosse X-Differenz - sonst stimmt es bei einem von +;; Hand gedrehten Block nicht. Die Hoehe (Y) geht NICHT ein. +(defun kf-horiz-projektion (p1 p2 basis-hz / rad dx dy) (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)))) + (setq dx (- (car p2) (car p1)) + dy (- (cadr p2) (cadr p1))) + (+ (* dx (cos rad)) (* dy (sin rad)))) -;; 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) +;; WAAGERECHTE (projizierte) Laenge der Kette = Laenge der Draufsicht KF_2D. +;; Das ist der Wert im Attribut L_KF_mm. Immer positiv. +(defun kf-waagerecht-laenge (p1 p2 basis-hz) + (abs (kf-horiz-projektion p1 p2 basis-hz))) + +;; Neigungswinkel der Variante als nackte Ganzzahl ("26"/"30"), bewusst ueber +;; itoa/fix statt rtos: rtos haengt an DIMZIN/LUPREC/LUNITS der Zeichnung. Der +;; Wert steht im Attribut WINKEL und ist Schluessel der Tabelle +;; [kf_typ_anzeige] im Sivas-Export - er muss profilunabhaengig sein. +(defun kf-winkel-text (variante) + (itoa (fix (+ 0.5 (cond ((kf-zahl (ssg-json-get variante "winkel"))) (0.0)))))) + +;; Punkt um versatz-y in Y verschoben (Z bleibt, fehlendes Z = 0). +(defun kf-versatzpunkt (p versatz-y) + (list (car p) (+ (cadr p) (float versatz-y)) (cond ((caddr p)) (0.0)))) + +;; Versatz des Hilfsblocks (Draufsicht) gegen den Hauptblock beim ERSTEN +;; Einfuegen, in mm (Y). Steht in der JSON, nicht im Code; Vorgabe -1000. +;; (Pruefung ueber if, nicht ueber and/or: and liefert T statt der Zahl.) +(defun kf-hilfsblock-versatz (daten / cfg wert) (setq cfg (ssg-json-get daten "ansicht_2d")) - (setq bname (if cfg (ssg-json-get cfg "block"))) - (if (or (null bname) (= bname "")) - nil + (setq wert (if cfg (kf-zahl (ssg-json-get cfg "versatz_y")))) + (cond (wert) (-1000.0))) + +;; ------------------------------------------------------------ +;; BESCHRIFTUNG UND HOEHENMASS +;; ------------------------------------------------------------ +;; Beschriftung wie bei VF_/GF_ (gf-make-label): ein TEXT, Hoehe 100, auf +;; einem eigenen Layer aus cfg/layer.cfg. Haupt- und Hilfsblock tragen +;; dieselbe Zeile: "KF_1 26 Grad-Zeichen H=1500mm L_horizontal=4951mm". +(defun kf-label-text (nummer winkel-str hoehe waagerecht) + (strcat "KF_" (itoa nummer) " " winkel-str (chr 176) + " H=" (itoa (fix (+ 0.5 (float hoehe)))) "mm" + " L_horizontal=" (itoa (fix (+ 0.5 (float waagerecht)))) "mm")) + +;; Layer der Beschriftung (Baugruppe "kettenfoerderer_beschriftung" in +;; cfg/layer.cfg), sonst Hartwert - wie bei GF_/VF_. +(defun kf-beschriftungs-layer ( / lay) + (setq lay (ssg-layer-anlegen "kettenfoerderer_beschriftung" nil)) + (if (null lay) (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))))))))) + (setq lay "KF_Beschriftung") + (ssg-make-layer lay "7" nil))) + lay) + +(defun kf-label-zeichnen (txt pt lay) + (entmake + (list '(0 . "TEXT") + (cons 10 pt) + (cons 11 pt) + '(40 . 100.0) + (cons 1 txt) + '(7 . "Standard") + (cons 8 lay) + '(72 . 1) + '(73 . 2))) + (princ)) + +;; Senkrechtes Hoehenmass zwischen p-unten und p-oben, rechts neben dem +;; Kettenende. Echtes DIMENSION-Objekt per ActiveX (AddDimRotated, 90 Grad) - +;; kein Befehlsaufruf, der bei einem Fehler im Befehl haengen bleiben koennte. +;; Jeder Schritt laeuft unter vl-catch-all-apply: scheitert das Mass, geht der +;; Bau OHNE Mass weiter und meldet es (Hinweis, kein Abbruch). Textgroesse und +;; Pfeile werden gesetzt, damit das Mass neben der 100-mm-Beschriftung lesbar +;; ist; fehlt eine Eigenschaft, bleibt der Wert der Bemassungsvariablen. +(defun kf-hoehenmass-zeichnen (p-unten p-oben lay / obj) + (setq obj (vl-catch-all-apply 'vla-AddDimRotated + (list modelspace + (vlax-3d-point p-unten) + (vlax-3d-point p-oben) + (vlax-3d-point (list (+ (car p-oben) 400.0) + (/ (+ (cadr p-unten) (cadr p-oben)) 2.0) + (caddr p-oben))) + (/ pi 2.0)))) + (if (vl-catch-all-error-p obj) + (progn (princ (ssg-text "kf-bemassung-fehlt")) nil) + (progn + (foreach prop (list (list 'vla-put-TextHeight 100.0) + (list 'vla-put-ArrowheadSize 50.0) + (list 'vla-put-PrimaryUnitsPrecision 0) + (list 'vla-put-Layer lay)) + (vl-catch-all-apply (car prop) (cons obj (cdr prop)))) + T))) + +;; Alles seit last-ent Entstandene als Auswahlsatz (fuer _.-BLOCK). +(defun kf-sammeln (last-ent / ss ent) + (setq ss (ssadd)) + (setq ent (if last-ent (entnext last-ent) (entnext))) + (while ent + (ssadd ent ss) + (setq ent (entnext ent))) + ss) + +;; Referenz um rot-rad (Bogenmass) um ihren Einfuegepunkt drehen. Der Block +;; wird immer ungedreht gebaut (ssg-block-wrap-welt fuegt mit Drehung 0 ein); +;; eine von Hand vorgenommene Drehung wird so beim Neuaufbau wieder aufgesetzt. +(defun kf-rotation-setzen (ent rot-rad / ed) + (if (and ent rot-rad (/= rot-rad 0.0)) + (progn + (setq ed (entget ent)) + (if (assoc 50 ed) + (entmod (subst (cons 50 rot-rad) (assoc 50 ed) ed))))) + ent) ;; ============================================================ -;; DREHWINKEL DES BLOCKS (XDATA) +;; SCHLUESSEL ZWISCHEN HAUPT- UND HILFSBLOCK (XDATA) ;; ============================================================ -;; Der Drehwinkel der Baugruppe wird beim Bau in die GEOMETRIE eingerechnet -;; (kf-matrix-aufrecht), nicht ueber die Drehung der Blockreferenz: -;; ssg-block-wrap-welt fuegt den fertigen Block immer mit Drehung 0 ein. Die -;; Referenz traegt damit nirgends mehr, wie die Kette gedreht wurde - KF_EDIT -;; wuerde sie beim Neuaufbau ungedreht neu bauen. Darum steht der Winkel als -;; XDATA am Block (Projektregel: Editierdaten gehoeren in XDATA, nicht in -;; .dbg). Eine spaetere Drehung der Referenz von Hand kommt ueber -;; Gruppencode 50 hinzu und wird addiert. +;; Haupt- (KF_) und Hilfsblock (KF2D_) tragen denselben Schluessel als +;; XDATA. Der Name taugt dafuer nicht: die Nummer wechselt bei JEDEM Neuaufbau +;; (KF_EDIT vergibt eine neue). Mit dem Schluessel findet KF_EDIT den +;; Hilfsblock auch dann, wenn der Benutzer ihn verschoben hat, und erzeugt ihn +;; an seiner jetzigen Stelle neu (Projektregel: Editierdaten gehoeren in +;; XDATA, nicht in .dbg). (setq *kf-xdata-app* "SSG_KF_EDIT") -;; Gesamtdrehung = gebauter Winkel (XDATA, Grad) + Drehung der Referenz -;; (Gruppencode 50, Bogenmass), auf -180..180 Grad normalisiert. Reiner -;; Rechenkern; fehlende Werte zaehlen als 0 (Altbestand ohne XDATA). -(defun kf-drehung-gesamt (xdata-grad dxf50-rad / g r) - (setq g (cond (xdata-grad (float xdata-grad)) (0.0))) - (setq r (cond (dxf50-rad (float dxf50-rad)) (0.0))) - (ks-winkel-normalisieren (+ g (/ (* 180.0 r) pi)))) +(defun kf-neuer-key () (ssg-timestamp)) -(defun kf-xdata-schreiben (ent drehung-grad / eintrag) +(defun kf-xdata-schreiben (ent key / eintrag) (regapp *kf-xdata-app*) (setq eintrag (list *kf-xdata-app* (cons 1000 "kf-edit") - (cons 1040 (float drehung-grad)))) + (cons 1000 key))) (entmod (append (entget ent) (list (list -3 eintrag)))) ent) -;; Gebauter Drehwinkel (Grad) aus der XDATA, nil wenn keine vorhanden ist. -(defun kf-xdata-lesen (ent / xd daten paar) - (setq xd (entget ent (list *kf-xdata-app*))) - (setq daten (cdr (assoc -3 xd))) +;; Schluessel aus einer entget-Liste mit XDATA (reiner Kern, testbar): das +;; ZWEITE 1000-Feld; nil bei fehlender oder Altbestands-XDATA (nur Marker). +(defun kf-key-aus-xdata (xd / daten teile) + (setq daten (cdr (assoc -3 xd)) teile '()) (if daten + (foreach p (cdr (car daten)) + (if (= (car p) 1000) (setq teile (append teile (list (cdr p))))))) + (if (>= (length teile) 2) (cadr teile) nil)) + +(defun kf-xdata-key (ent) + (kf-key-aus-xdata (entget ent (list *kf-xdata-app*)))) + +;; Hilfsblock (KF2D_*) mit diesem Schluessel suchen; nil, wenn es keinen gibt +;; (nie gebaut oder vom Benutzer geloescht). +(defun kf-hilfsblock-finden (key / ss i e treffer) + (setq treffer nil) + (if key (progn - (setq paar (assoc 1040 (cdr (car daten)))) - (if paar (cdr paar) nil)) - nil)) + (regapp *kf-xdata-app*) + (setq ss (ssget "X" (list '(0 . "INSERT") '(2 . "KF2D_*") + (list -3 (list *kf-xdata-app*))))) + (if ss + (progn + (setq i 0) + (while (and (null treffer) (< i (sslength ss))) + (setq e (ssname ss i)) + (if (equal (kf-xdata-key e) key) (setq treffer e)) + (setq i (1+ i))))))) + treffer) + +;; ============================================================ +;; HILFSBLOCK KF2D_ (Draufsicht) +;; ============================================================ +;; Die Draufsicht KF_2D ist KEIN Teil des Foerderers, sondern ein +;; Hilfsblock, an dem der Benutzer Schienen anschliesst. Darum: +;; - eigener Block KF2D_ (nicht KF_*): die Export-Muster erfassen ihn nicht, +;; exportiert wird nur der Hauptblock KF_ +;; - flach in der XY-Ebene, auf die PROJIZIERTE Laenge des Foerderers skaliert +;; - unabhaengig verschiebbar; loescht der Benutzer ihn, bleibt der Hauptblock +;; unberuehrt, und KF_EDIT erzeugt ihn bei Bedarf neu +;; - traegt dieselbe Beschriftung wie der Hauptblock +;; Rueckgabe: die Referenz, sonst nil. +(defun kf-hilfsblock-bauen (daten nummer key winkel-str hoehe waagerecht + einfuegepunkt rot-rad / + cfg bname ks ein-dx skala last-ent lay hname ss ins) + (setq cfg (ssg-json-get daten "ansicht_2d")) + (setq bname (if cfg (ssg-json-get cfg "block"))) + (cond + ((or (null bname) (= bname "")) nil) + ((null (kf-block-laden bname)) + (princ (ssg-textf "kf-fehler-block-datei" (list bname))) + nil) + (t + (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 waagerecht ein-dx)) + (if (null skala) + (progn (princ (ssg-textf "kf-2d-ks-fehlt" (list bname))) nil) + (progn + (setq hname (strcat "KF2D_" (itoa nummer))) + (setq last-ent (ssg-lastent-ohne-attribute)) + (insert-block-ks-skaliert bname einfuegepunkt 0.0 skala) + (setq lay (kf-beschriftungs-layer)) + (kf-label-zeichnen + (kf-label-text nummer winkel-str hoehe waagerecht) + (list (+ (car einfuegepunkt) (/ waagerecht 2.0)) + (+ (cadr einfuegepunkt) 250.0) + (cond ((caddr einfuegepunkt)) (0.0))) + lay) + (setq ss (kf-sammeln last-ent)) + (ssg-block-wrap-welt hname einfuegepunkt ss) + (setq ins (entlast)) + (ssg-layer-setzen ins (cond ((ssg-json-get daten "layer")) ("kettenfoerderer"))) + (kf-rotation-setzen ins rot-rad) + (kf-xdata-schreiben ins key) + (princ (ssg-textf "kf-2d-gebaut" (list hname (rtos waagerecht 2 0)))) + ins))))) ;; ============================================================ ;; 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 drehung / - kf-bname kf-ss ent kf-insert winkel-str) +;; Beschriftung und Hoehenmass zeichnen, alle seit last-ent erzeugten Objekte +;; sammeln, ATTDEFs anlegen und alles zu KF_ zusammenfassen. Vorbild: +;; gf-block-erstellen (Gefaellestrecke.lsp). +;; waagerecht = projizierte Laenge (Attribut L_KF_mm), key = Schluessel zum +;; Hilfsblock, rot-rad = von Hand gesetzte Drehung der Referenz (0 beim ersten +;; Einfuegen). +(defun kf-block-erstellen (variante geo hoehe waagerecht verbindung + startpunkt endpunkt last-ent kf-nummer + farbe antriebsmotor movimot steuerung key rot-rad / + kf-bname kf-ss kf-insert winkel-str lay z) (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")))))) + (setq winkel-str (kf-winkel-text variante)) + (setq z (cond ((caddr startpunkt)) (0.0))) ;; ATTDEFs (unsichtbar, am Startpunkt) nach dem KF-Schema (foreach def (kf-attrib-defs) (entmake @@ -596,18 +689,20 @@ '(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))) + ;; Beschriftung unter der Kette, Hoehenmass rechts neben dem Kettenende + (setq lay (kf-beschriftungs-layer)) + (kf-label-zeichnen + (kf-label-text kf-nummer winkel-str hoehe waagerecht) + (list (/ (+ (car startpunkt) (car endpunkt)) 2.0) (- (cadr startpunkt) 300.0) z) + lay) + (kf-hoehenmass-zeichnen (list (car endpunkt) (cadr startpunkt) z) + (list (car endpunkt) (cadr endpunkt) z) + lay) + (setq kf-ss (kf-sammeln last-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)) - ;; Gebauten Drehwinkel festhalten, damit KF_EDIT ihn wiederfindet - (kf-xdata-schreiben kf-insert drehung) (ssg-layer-setzen kf-insert (cond ((ssg-json-get variante "layer")) ((ssg-json-get (kf-daten) "layer")) ("kettenfoerderer"))) @@ -620,7 +715,9 @@ ("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))))) + ;; WAAGERECHTE (projizierte) Laenge des ganzen Foerderers, nicht die + ;; Laenge des schraegen Mittelstuecks + (cons "L_KF_mm" (itoa (fix (+ 0.5 (float waagerecht))))) (cons "FARBE" (cond (farbe) ("RAL 7035"))) (cons "ANTRIEBSMOTOR" (cond (antriebsmotor) ("Europa 50 Hz"))) (cons "ANTRIEBSART" (kf-antriebsart (kf-daten))) @@ -629,6 +726,9 @@ (cons "VERBINDUNGSELEMENTE" (cond (verbindung) ("Nein"))) (cons "ARTINR" (cond ((ssg-json-get variante "sivas_id")) (""))))) (ssg-id-generate kf-insert) + ;; Reihenfolge: erst Drehung (entmod der Referenz), ZULETZT die XDATA + (kf-rotation-setzen kf-insert rot-rad) + (kf-xdata-schreiben kf-insert key) kf-insert) ;; Naechste freie KF-Nummer in der Zeichnung. @@ -655,9 +755,10 @@ (while (apply belegt-p (list n)) (setq n (1+ n))) n) -;; Ist die Block-DEFINITION KF_ in der Zeichnung vorhanden? +;; Ist die Block-DEFINITION KF_ ODER KF2D_ in der Zeichnung vorhanden? (defun kf-nummer-belegt-p (n) - (not (null (tblsearch "BLOCK" (strcat "KF_" (itoa n)))))) + (not (null (or (tblsearch "BLOCK" (strcat "KF_" (itoa n))) + (tblsearch "BLOCK" (strcat "KF2D_" (itoa n))))))) ;; Naechste freie KF-Nummer. Bewusst NICHT nur ueber die Referenzen im Modell: ;; loescht man einen Foerderer (oder baut KF_EDIT ihn neu auf), lebt seine @@ -756,8 +857,8 @@ ;; 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. +;; Gemessen wird die Differenz in Z (die Hoehe der Punkte in der Zeichnung); die +;; Differenz in Y wird nur mit ausgegeben. ;; 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. @@ -981,72 +1082,91 @@ (kf-auswahl-wert movimots *kf-dlg-movimot* "0") (kf-auswahl-wert steuerungen *kf-dlg-steuerung* "0"))) +;; ============================================================ +;; KF_BAUEN - gemeinsamer Bau fuer Befehl und Edit +;; ============================================================ +;; Baut Hauptblock KF_ (Seitenansicht mit Beschriftung + Hoehenmass) und +;; danach den Hilfsblock KF2D_ (Draufsicht). Beide Bloecke werden ungedreht +;; gebaut; eine Drehung (Edit eines von Hand gedrehten Blocks) setzt +;; kf-rotation-setzen danach wieder auf. +;; eingabe = Ergebnis von kf-dialog/kf-konsole (Variante, Hoehe, ...) +;; startpunkt = KS_EIN der Umlenkstation (Einfuegepunkt Hauptblock) +;; rot-rad = Drehung des Hauptblocks, hilfs-pt/hilfs-rot = Lage des Hilfsblocks +;; key = Schluessel Haupt <-> Hilfsblock (nil -> neuer) +;; Rueckgabe: Liste (Blockname Hoehe Laenge) bei Erfolg, sonst nil (die +;; Fehlermeldung ist dann schon ausgegeben, die Zeichnung unveraendert). +(defun kf-bauen (daten eingabe startpunkt rot-rad hilfs-pt hilfs-rot key + loeschen / + variante hoehe verbindung farbe antriebsmotor movimot steuerung + geo erg laenge skala lmin lmax endpunkt waagerecht + last-ent kf-nummer winkel-str) + (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 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 geo (kf-geometrie-messen variante)) + (setq erg (kf-laenge-fuer-hoehe geo hoehe lmin lmax)) + (cond + ((caddr erg) + (if (ssg-gui-p) (alert (ssg-text (caddr erg)))) + (princ (ssg-text (caddr erg))) + nil) + (t + (setq laenge (car erg) skala (cadr erg)) + (ssg-start "Kettenfoerderer" '(("OSMODE") ("ATTREQ") ("ATTDIA"))) + (setvar "OSMODE" 0) + (setvar "ATTREQ" 0) + (setvar "ATTDIA" 0) + ;; Edit: alte Bloecke (Liste von Enames) erst jetzt loeschen + (foreach e loeschen (if e (entdel e))) + (if (null key) (setq key (kf-neuer-key))) + (setq last-ent (ssg-lastent-ohne-attribute)) + (setq kf-nummer (kf-naechste-nummer)) + (setq winkel-str (kf-winkel-text variante)) + (setq endpunkt (kf-kette-bauen variante geo skala startpunkt)) + (setq waagerecht (kf-waagerecht-laenge startpunkt endpunkt 0.0)) + (kf-block-erstellen variante geo hoehe waagerecht verbindung + startpunkt endpunkt last-ent kf-nummer + farbe antriebsmotor movimot steuerung key rot-rad) + (kf-hilfsblock-bauen daten kf-nummer key winkel-str hoehe waagerecht + hilfs-pt hilfs-rot) + (ssg-end) + (list (strcat "KF_" (itoa kf-nummer)) hoehe laenge)))) + ;; ============================================================ ;; 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) +(defun c:KETTENFOERDERER ( / daten eingabe startpunkt erg 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. + ;; ssg_dbg.lsp): unterscheidet Lazy-Laden, JSON-Lesen und Dialogaufbau. (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) + (setq startpunkt (getpoint (ssg-text "kf-prompt-startpunkt"))) + (if (null startpunkt) + (princ (ssg-text "kf-abgebrochen")) (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 drehung)) - (ssg-end) - (princ (ssg-textf "kf-fertig" - (list (strcat "KF_" (itoa kf-nummer)) - (rtos (float hoehe) 2 0) - (rtos laenge 2 0)))))))))))) + ;; Hilfsblock beim ersten Mal 1000 mm unter dem Hauptblock + (setq erg (kf-bauen daten eingabe startpunkt 0.0 + (kf-versatzpunkt startpunkt (kf-hilfsblock-versatz daten)) + 0.0 nil nil)) + (if erg + (princ (ssg-textf "kf-fertig" + (list (car erg) (rtos (float (cadr erg)) 2 0) + (rtos (caddr erg) 2 0))))))))))) (princ)) ;; ============================================================ @@ -1056,10 +1176,11 @@ ;; 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. +;; 50), alles Uebrige steht in den Attributen. Ein von Hand verschobener oder +;; gedrehter Block wird dadurch an seiner NEUEN Lage neu gebaut. Die XDATA +;; traegt nur den Schluessel zum Hilfsblock KF2D_: er wird mit dem Hauptblock +;; neu erzeugt (auch wenn der Benutzer ihn geloescht hatte) und behaelt die +;; Lage, an die der Benutzer ihn zuletzt gelegt hat. ;; ;; Der alte Block wird erst geloescht, wenn der Benutzer den Dialog mit OK ;; bestaetigt hat - ein Abbruch laesst die Zeichnung unveraendert. @@ -1115,13 +1236,10 @@ (setq i (1+ i))) (cond (treffer) (0))) -(defun c:KF_EDIT ( / ss 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) +(defun c:KF_EDIT ( / ss ent ed bname attribs daten eingabe startpunkt + rot-rad key hilfs hilfs-pt hilfs-rot erg) ;; Erst die VORGEWAEHLTE Auswahl (Doppelklick setzt sie), nur ohne sie per - ;; Klick nachfragen - wie GEFAELLESTRECKE_EDIT/KreiselEdit. Vorher fragte - ;; KF_EDIT auch nach einem Doppelklick noch einmal nach einer Auswahl. + ;; Klick nachfragen - wie GEFAELLESTRECKE_EDIT/KreiselEdit. (setq ss (ssget "I")) (setq ent (if (and ss (= (sslength ss) 1)) (ssname ss 0) @@ -1132,8 +1250,7 @@ (setq ed (entget ent)) (setq bname (cdr (assoc 2 ed))) ;; Nur echte Foerderer "KF_": die Bausteine (KF_UM_26, KF_2D, ...) - ;; tragen ebenfalls das Praefix KF_, haben aber keine Attribute - ein - ;; Neuaufbau aus ihnen waere sinnlos und wuerde sie loeschen. + ;; tragen ebenfalls das Praefix KF_, haben aber keine Attribute. (if (or (/= (cdr (assoc 0 ed)) "INSERT") (null (kf-nummer-aus-name bname))) (alert (ssg-text "kf-edit-kein-kf")) (progn @@ -1141,53 +1258,35 @@ (if (null daten) (princ (ssg-text "kf-fehler-daten-leer")) (progn - (setq attribs (ssg-attrib-read ent)) + (setq attribs (ssg-attrib-read ent)) (setq startpunkt (cdr (assoc 10 ed))) - ;; Der gebaute Winkel steckt in der XDATA, eine nachtraegliche - ;; Drehung der Referenz in Gruppencode 50 - beides zaehlt. - (setq drehung (kf-drehung-gesamt (kf-xdata-lesen ent) - (cdr (assoc 50 ed)))) + (setq rot-rad (cond ((cdr (assoc 50 ed))) (0.0))) + ;; Schluessel zum Hilfsblock; Altbestand ohne Schluessel -> nil, + ;; dann gibt es keinen auffindbaren Hilfsblock und ein neuer + ;; entsteht an der Standardposition. + (setq key (kf-xdata-key ent)) + (setq hilfs (kf-hilfsblock-finden key)) + (if hilfs + (setq hilfs-pt (cdr (assoc 10 (entget hilfs))) + hilfs-rot (cond ((cdr (assoc 50 (entget hilfs)))) (0.0))) + (setq hilfs-pt (kf-versatzpunkt startpunkt + (kf-hilfsblock-versatz daten)) + hilfs-rot 0.0)) (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 drehung) - (ssg-end) - (princ (ssg-textf "kf-edit-fertig" - (list bname (strcat "KF_" (itoa kf-nummer))))))))))))))) + ;; Der Hilfsblock wird bei JEDEM Edit neu erzeugt, damit er + ;; zur neuen Hoehe/Laenge passt - an der Stelle, an der der + ;; Benutzer ihn zuletzt hingelegt hat. + (setq erg + (kf-bauen daten eingabe startpunkt rot-rad hilfs-pt hilfs-rot + (cond (key) (kf-neuer-key)) + (list ent hilfs))) + (if erg + (princ (ssg-textf "kf-edit-fertig" + (list bname (car erg))))))))))))) (princ)) ;; ============================================================ diff --git a/Lisp/SSG_LIB_Commands.lsp b/Lisp/SSG_LIB_Commands.lsp index 483f7f5..dbf3c04 100644 --- a/Lisp/SSG_LIB_Commands.lsp +++ b/Lisp/SSG_LIB_Commands.lsp @@ -462,6 +462,12 @@ ) ) + ;; Hilfsblock (Draufsicht) des Kettenfoerderers: kein eigener Edit - er wird + ;; mit dem Hauptblock KF_ neu erzeugt (Doppelklick dort). + ((wcmatch bname "KF2D_*") + (princ (ssg-text "cmd-kf2d-hilfsblock")) + ) + ;; Omniflo-Bloecke -> OMNI_EDIT ;; Erkennung: TEILEART-Attribut vorhanden, AP110-Blockname oder rein numerisch ((progn diff --git a/Lisp/ssg_ks_insert.lsp b/Lisp/ssg_ks_insert.lsp index f1c49dd..887d3e9 100644 --- a/Lisp/ssg_ks_insert.lsp +++ b/Lisp/ssg_ks_insert.lsp @@ -85,10 +85,21 @@ ;; 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. +;; +;; Zweiter Sonderfall - Namen, die SELBST auf _2D/_3D enden (z.B. die +;; Kettenfoerderer-Draufsicht "KF_2D"): ssg-ils-blockname laesst solche Namen +;; unveraendert (eff = blockname), die Bedingung "eff existiert nicht" kann +;; dann nie zutreffen, und der Block ging in den ILS-Ladepfad. Der suchte +;; data/ils/KF_2D.dwg und meldete "Block-Datei nicht gefunden", obwohl der +;; Block in der Zeichnung vorhanden war. Massgeblich ist darum, ob es fuer den +;; Namen ueberhaupt eine ILS-Datei gibt: ohne Datei ist es kein ILS-Block und +;; die vorhandene Definition wird genommen. Echte ILS-Bloecke haben eine Datei +;; und laufen unveraendert durch den Ladepfad (Aktualisierung aus der Datei). (defun ensure-block-loaded (blockname / eff) (setq eff (ssg-ils-blockname blockname)) - (if (and (not (tblsearch "BLOCK" eff)) - (tblsearch "BLOCK" blockname)) + (if (and (tblsearch "BLOCK" blockname) + (or (= eff blockname) (not (tblsearch "BLOCK" eff))) + (null (ssg-ils-block-datei blockname))) blockname (progn (if (not (ssg-ils-block-laden blockname)) @@ -558,8 +569,9 @@ ;; 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). +;; welche Drehungen sie kombinieren. (Der Kettenfoerderer hat sie kurz fuer +;; Rz(basis) * Rx(90) * Rz(eigen) genutzt, ist inzwischen aber wieder eine flache +;; Seitenansicht - aktuell ohne Aufrufer.) ;; Elementare Rotationsmatrizen (3x3, Winkel in GRAD). (defun ks-rot-x (grad / r c s) diff --git a/cfg/layer.cfg b/cfg/layer.cfg index a90399b..05c8dac 100644 --- a/cfg/layer.cfg +++ b/cfg/layer.cfg @@ -174,6 +174,8 @@ planquadrate = ZZ_Planquadrate, 8 # ZUSAMMENGEBAUTE Baugruppe ohne eigene Blockdatei, der Layer muss darum hier # stehen. Die drei Bausteine darin behalten ihre eigenen Layer. kettenfoerderer = Omni_Kettenfoerderer, 4 +; Beschriftung (KF_n ... H=, L projiziert=) und Hoehenmass, auf Haupt- und Hilfsblock +kettenfoerderer_beschriftung = KF_Beschriftung, 7 # ============================================================ diff --git a/data/json/kettenfoerderer.json b/data/json/kettenfoerderer.json index 07d2122..7aa02eb 100644 --- a/data/json/kettenfoerderer.json +++ b/data/json/kettenfoerderer.json @@ -9,8 +9,8 @@ "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_ansicht": "KF_2D ist ein eigener Hilfsblock KF2D_ (Draufsicht zum Anschliessen von Schienen), kein Teil des Foerderers. Er wird auf die projizierte Laenge skaliert und beim ERSTEN Einfuegen versatz_y mm (Y) gegen den Hauptblock versetzt; danach liegt er, wo der Benutzer ihn hinlegt.", + "ansicht_2d": { "block": "KF_2D", "versatz_y": -1000 }, "_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": [ diff --git a/lang/de_DE.json b/lang/de_DE.json index e56e22b..9523d1d 100644 --- a/lang/de_DE.json +++ b/lang/de_DE.json @@ -1300,11 +1300,11 @@ "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-gebaut": "\n>>> Hilfsblock %1 (Draufsicht) eingefuegt: KF waagerechte Laenge %2 mm.", + "kf-bemassung-fehlt": "\nHinweis: Das Hoehenmass konnte nicht gezeichnet werden - der Foerderer wurde trotzdem gebaut.", + "cmd-kf2d-hilfsblock": "\nHilfsblock (Draufsicht): kein eigener Dialog - Doppelklick auf den Hauptblock KF_ erzeugt ihn neu.", "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-prompt-startpunkt": "\nEinfuegepunkt waehlen (KS_EIN der Umlenkstation): ", "kf-abgebrochen": "\nAbgebrochen.", "kf-fertig": "\n>>> %1 erstellt: Einbauhoehe %2 mm, Zwischenstueck %3 mm.", "kf-konsole-variante-header": "\n\nSchraegfoerderer Stahl Typ waehlen:", diff --git a/lang/en_GB.json b/lang/en_GB.json index 9497ff7..24bb07f 100644 --- a/lang/en_GB.json +++ b/lang/en_GB.json @@ -1300,11 +1300,11 @@ "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-gebaut": "\n>>> Helper block %1 (plan view) inserted: KF horizontal length %2 mm.", + "kf-bemassung-fehlt": "\nNote: the height dimension could not be drawn - the conveyor was built anyway.", + "cmd-kf2d-hilfsblock": "\nHelper block (plan view): no dialog of its own - double-click the main block KF_ to regenerate it.", "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-prompt-startpunkt": "\nSelect insertion point (KS_EIN of the return station): ", "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:", diff --git a/lib/export_sivas.py b/lib/export_sivas.py index 62480bc..1c402bf 100644 --- a/lib/export_sivas.py +++ b/lib/export_sivas.py @@ -313,8 +313,8 @@ def kettenfoerderer_signature(block): 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. + Die waagerechte Laenge (L_KF_mm) ist eine FOLGE der Einbauhoehe (und des + Winkels) und steht darum nicht eigens im Schluessel. """ attribs = block.get("attribs", {}) return ( @@ -702,7 +702,11 @@ def process_blocks(blocks, anzahl_weichen_ils=0): # 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", [])): + # Nur echte Foerderer "KF_": das Muster KF_* erfasst sonst auch + # lose Bausteine (KF_UM_26, KF_2D). Der Hilfsblock KF2D_ passt nicht + # aufs Muster und wird nie exportiert. + if (matches_any(bname, BLOCKPATTERNS.get("pattern_kettenfoerderer", [])) + and re.fullmatch(r"KF_\d+", bname, re.IGNORECASE)): sig = kettenfoerderer_signature(block) if sig not in kettenfoerderer_groups: kettenfoerderer_groups[sig] = {"block": block, "ids": [], "anzahl": 0} @@ -996,7 +1000,11 @@ def process_blocks(blocks, anzahl_weichen_ils=0): "bezeichnung": attribs.get("Bezeichnung", ""), "dispgruppe": 20, "anzahl": gruppe["anzahl"], - "laenge_mm": attribs.get("L_KF_mm", ""), + # Bewusst LEER: Sivas berechnet ZL, EL und die Kettenlaenge selbst, aus + # dem Kettenfoerderer wird nur die Hoehe (360) uebernommen. L_KF_mm + # (waagerechte Laenge des Foerderers) steht als Attribut am Block, + # geht aber nicht in die Sivas-Zeile. + "laenge_mm": "", "ids": gruppe["ids"], "details": build_kettenfoerderer_details(block, gruppe["anzahl"]), }) diff --git a/tests/test_unit.lsp b/tests/test_unit.lsp index c948006..49e679c 100644 --- a/tests/test_unit.lsp +++ b/tests/test_unit.lsp @@ -2892,66 +2892,71 @@ (tu-eq "kf-frei/Start-mitten" 5 (kf-freie-nummer 3 '(lambda (n) (member n '(3 4))))) - ;; --- kf-drehung-gesamt: Drehwinkel fuer KF_EDIT --- - ;; Regression: der Drehwinkel wird beim Bau in die Geometrie eingerechnet, - ;; die Blockreferenz selbst hat IMMER Drehung 0 (ssg-block-wrap-welt). - ;; KF_EDIT las nur Gruppencode 50 und baute eine mit 90 Grad gebaute - ;; Kette ungedreht neu. Der gebaute Winkel steht jetzt als XDATA. - (tu-eqf "kf-drehung/nur-xdata" 90.0 (kf-drehung-gesamt 90.0 0.0)) - ;; Referenz nachtraeglich von Hand um 90 Grad gedreht (Gruppencode 50 in - ;; Bogenmass) -> beides addiert sich - (tu-eqf "kf-drehung/xdata-plus-referenz" 180.0 - (kf-drehung-gesamt 90.0 (/ pi 2.0))) - ;; Altbestand ohne XDATA: nur die Referenzdrehung (bisheriges Verhalten) - (tu-eqf "kf-drehung/ohne-xdata" 45.0 (kf-drehung-gesamt nil (/ pi 4.0))) - ;; nichts bekannt -> 0, kein Absturz und kein nil - (tu-eqf "kf-drehung/nichts" 0.0 (kf-drehung-gesamt nil nil)) - (tu-true "kf-drehung/ist-zahl" (numberp (kf-drehung-gesamt nil nil))) - ;; Summe ueber 180 Grad wird auf -180..180 gefaltet (270 = -90) - (tu-eqf "kf-drehung/normalisiert" -90.0 - (kf-drehung-gesamt 180.0 (/ pi 2.0))) - ;; ganze Zahl aus der XDATA (nicht real) darf nicht abstuerzen - (tu-eqf "kf-drehung/ganzzahl" 30.0 (kf-drehung-gesamt 30 0.0)) + ;; --- kf-label-text: Beschriftung von Haupt- UND Hilfsblock --- + ;; Eine Zeile wie bei VF_/GF_: Nummer, Winkel mit Gradzeichen, Hoehe, + ;; projizierte Laenge (ganze mm) + (tu-eq "kf-label/normal" + (strcat "KF_1 26" (chr 176) " H=1500mm L_horizontal=4951mm") + (kf-label-text 1 "26" 1500.0 4951.4)) + ;; rundet kaufmaennisch auf ganze mm, nimmt auch Ganzzahlen + (tu-eq "kf-label/rundet-auf" + (strcat "KF_12 30" (chr 176) " H=2000mm L_horizontal=3001mm") + (kf-label-text 12 "30" 2000 3000.5)) - ;; --- 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-winkel-text: Neigung als nackte Ganzzahl (Attribut WINKEL) --- + (tu-eq "kf-winkel-text/real" "26" (kf-winkel-text '(("winkel" . 26.0)))) + (tu-eq "kf-winkel-text/string" "30" (kf-winkel-text '(("winkel" . "30")))) + (tu-eq "kf-winkel-text/fehlt" "0" (kf-winkel-text '(("name" . "x")))) - ;; --- 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))) + ;; --- kf-versatzpunkt: Hilfsblock beim ersten Einfuegen unter dem Hauptblock --- + (tu-eqf "kf-versatzpunkt/minus-1000" '(100.0 -980.0 5.0) + (kf-versatzpunkt '(100.0 20.0 5.0) -1000)) + ;; 2D-Punkt ohne Z -> Z = 0, kein Absturz + (tu-eqf "kf-versatzpunkt/ohne-z" '(0.0 -1000.0 0.0) + (kf-versatzpunkt '(0.0 0.0) -1000.0)) + + ;; --- kf-hilfsblock-versatz: Wert steht in der JSON, Vorgabe -1000 --- + (tu-eqf "kf-versatz/aus-daten" -500.0 + (kf-hilfsblock-versatz '(("ansicht_2d" ("block" . "KF_2D") ("versatz_y" . -500))))) + (tu-eqf "kf-versatz/ohne-feld" -1000.0 + (kf-hilfsblock-versatz '(("ansicht_2d" ("block" . "KF_2D"))))) + (tu-eqf "kf-versatz/ohne-ansicht" -1000.0 + (kf-hilfsblock-versatz '(("id" . "x")))) + ;; Zahl, nicht T (and/or-Typfalle) + (tu-true "kf-versatz/ist-zahl" + (numberp (kf-hilfsblock-versatz '(("id" . "x"))))) + + ;; --- kf-key-aus-xdata: Schluessel Haupt- <-> Hilfsblock --- + (tu-eq "kf-key/aus-xdata" "20261030120000" + (kf-key-aus-xdata + '((-3 ("SSG_KF_EDIT" (1000 . "kf-edit") (1000 . "20261030120000")))))) + ;; Altbestand: nur der Marker, kein Schluessel -> nil (-> neuer Schluessel) + (tu-nil "kf-key/nur-marker" + (kf-key-aus-xdata '((-3 ("SSG_KF_EDIT" (1000 . "kf-edit")))))) + (tu-nil "kf-key/keine-xdata" (kf-key-aus-xdata '((0 . "INSERT")))) + (tu-nil "kf-key/nil" (kf-key-aus-xdata nil)) + + ;; --- kf-waagerecht-laenge: Wert des Attributs L_KF_mm --- + ;; Waagerechte Laenge des GANZEN Foerderers (der Draufsicht KF_2D), also + ;; ohne die Steigung - nicht die Laenge des schraegen Mittelstuecks. + ;; 3-4-5: 3000 waagerecht, 4000 hoch -> waagerecht bleibt 3000 (nicht 5000) + (tu-eqf "kf-waagerecht/ohne-steigung" 3000.0 + (kf-waagerecht-laenge '(0.0 0.0 0.0) '(3000.0 0.0 4000.0) 0.0)) + ;; nur Hoehe, keine waagerechte Strecke -> 0 + (tu-eqf "kf-waagerecht/nur-hoehe" 0.0 + (kf-waagerecht-laenge '(0.0 0.0 0.0) '(0.0 0.0 1500.0) 0.0)) + ;; auf die KETTENRICHTUNG projiziert: bei 90 Grad Basisdrehung laeuft die + ;; Kette in Y, die Welt-X-Differenz waere 0 + (tu-eqf "kf-waagerecht/gedreht-90" 1000.0 + (kf-waagerecht-laenge '(0.0 0.0 0.0) '(0.0 1000.0 1000.0) 90.0)) + (tu-eqf "kf-waagerecht/gedreht-45" 1000.0 + (kf-waagerecht-laenge '(0.0 0.0 0.0) '(707.10678 707.10678 500.0) 45.0)) + ;; fallende Kette / Ende "hinter" dem Start: Betrag, nie negativ + (tu-eqf "kf-waagerecht/immer-positiv" 2000.0 + (kf-waagerecht-laenge '(2000.0 0.0 0.0) '(0.0 0.0 800.0) 0.0)) + ;; muss eine Zahl sein (Typfalle: nie T/nil) + (tu-true "kf-waagerecht/ist-zahl" + (numberp (kf-waagerecht-laenge '(0.0 0.0 0.0) '(10.0 0.0 0.0) 0.0))) ;; --- Standard-Variante: steht in den DATEN, nicht im Code --- (tu-true "kf-variante-standard-p/markiert" @@ -2970,8 +2975,8 @@ (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 + ;; Gemessen wird die Differenz in Z (Hoehe der gepickten Punkte). + ;; 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)))