diff --git a/CLAUDE.md b/CLAUDE.md index cf52b28..3318d89 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -23,8 +23,9 @@ AutoLISP-Makros (SSG_LIB) fuer BricsCAD Pro zur automatisierten Erstellung von F | `cfg/` | Konfigurationsdateien | | `data/` | JSON-Daten, DXF-Quellen, Block-Libraries | | `data/ils/` | DWG-Bloecke fuer ILS-Kreisel/Eckrad (AN8, SP8, AP-Typen) | -| `data/json/` | Komponentendaten (Masse, Sivas-IDs); Geruest: `geruest.json` (GERUEST-Dialog) und `geruest_traeger.json` (Traeger mit Linien-Logik); `omniflo_fahrstrecke.json` (Omniflo-Fahrstrecke, gleiche Linien-Logik) | +| `data/json/` | Komponentendaten (Masse, Sivas-IDs); Geruest: `geruest.json` (GERUEST-Dialog) und `geruest_traeger.json` (Traeger mit Linien-Logik); `omniflo_fahrstrecke.json` (Omniflo-Fahrstrecke, gleiche Linien-Logik); `lager.json` (Vorgaben der Lagerplanung) | | `data/block_libraries/` | DXF Block-Libraries (ils_library.dxf) | +| `cfg/regelwerk/` | Regelprofile der Lagerplanung je Land (`DE.json`, `SK.json`/`PL.json` erben DE), gelesen von `LagerPruefung.lsp` | | `cfg/rahmen/` | DIN-Rahmen + Schriftkopf-DWGs (A0-A4, Kopf11 DE/EN/FR/IT), kopiert aus `\\infor-ln\SSG_Tools\ACADSST\KOEPFE`, fuer `DIN_FORMATE` (DinFormate.lsp) | | `dcl/` | DCL-Dialogdefinitionen | | `doc/` | Dokumentation, Entwicklungsplan | @@ -80,10 +81,11 @@ Die `start_briscad.bat` ruft `setenv.bat` auf und startet BricsCAD. | 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 | +| Lager | `LagerInsert` + `LagerPruefung` | LG_GESCHOSS, LG_GESCHOSS_AKTIV, LG_GESCHOSS_KOPIEREN, LG_BEREICH, LG_REIHE, LG_GANG, LG_EDIT, LG_REGALE, LAGER_HAND_YAML, LAGER_EXPORT, LG_REGELN | | 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, KF_*->KF_EDIT, CONNECTION_*->CONNECTION_EDIT, JAM_*->JAM_EDIT, TRO_*->TRO_EDIT, GR_*->EATTEDIT) | +| Doppelklick/Kontextmenue | `SSG_LIB_Commands` | SSG_BLOCKEDIT (dispatcht Kreisel->KreiselEdit, Eckrad->EckradEdit, Omniflo->OMNI_EDIT, GF_*->GEFAELLESTRECKE_EDIT, VF_*->VARIOFOERDERER_EDIT, KF_*->KF_EDIT, CONNECTION_*->CONNECTION_EDIT, JAM_*->JAM_EDIT, TRO_*->TRO_EDIT, GR_*->EATTEDIT, LG_*->lg-edit-ent) | ### Entfernte Legacy-Dateien @@ -97,7 +99,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_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). **Polygon-Helfer** (2D, rein, `tu-tests-poly`): `ssg-poly-flaeche`/`-umfang`/`-kanten`/`-bbox`/`-drehen`, `ssg-punkt-in-poly-p`, `ssg-punkt-drehen`, `ssg-poly-schnitt-y` (innere Abschnitte einer Waagrechten, halboffene Kantenregel), `ssg-poly-flaeche-signiert` (Umlaufsinn), `ssg-poly-clip-y`/`ssg-poly-band-x` (x-Ausdehnung im Band, Kantenberuehrung zaehlt nicht), `ssg-intervalle-schnitt`/`-minus`; dazu `ssg-lwpoly-punkte` (WCS, Erhebung) und `ssg-lwpoly-boegen-p`. **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 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) | @@ -121,6 +123,8 @@ Detaillierte Dokumentation aller Funktionen und Befehle: siehe `Lisp/README.md`. | `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` | | `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` | | `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). | @@ -146,6 +150,7 @@ Detaillierte Dokumentation aller Funktionen und Befehle: siehe `Lisp/README.md`. | `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) | +| `lager.dcl` | LG_GESCHOSS/LG_GESCHOSS_KOPIEREN/LG_BEREICH/LG_REIHE/LG_GANG + Edit (LagerInsert.lsp, generischer Treiber `lg-dialog`); Dialoge `lg_geschoss`, `lg_geschoss_kopie`, `lg_bereich` (inkl. Randgaenge N/O/S/W), `lg_reihe`, `lg_gang`, Tile-Keys = Feldlisten `*lg-felder-*` | | `vfl_edit.dcl` | Linienzug-Editieren/Fortsetzen (vf_linienzug.lsp, `vfl-dlg-position`, aufgerufen aus `vfl-edit-ent`) | | `vfl_edit_modus.dcl` | Linienzug-Edit: Auswahl des Edit-Modus (vf_linienzug.lsp) | | `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` | @@ -214,6 +219,7 @@ Abhaengigkeiten: `ezdxf`, Standard-Library. Benoetigen Umgebungsvariablen (`DXFM | `dxf_vf_abbild.py` | Abbild der `VF_`/`GF_`-Wrapperbloecke einer Testzeichnung: Attribute UND Bausteinzusammensetzung je Kette, Ketten ueber den Einfuegepunkt der Spec zugeordnet (der Blockname taugt nicht - die VF-Nummer beginnt in jeder Zeichnung neu bei 1). Regressionsnetz fuer Umbauten am Bau-Ablauf: `--referenz` vergleicht gegen `tests/reference/hundm05_attribute.json` (Exit 1 bei Abweichung), `--schreibe-referenz` frischt sie nach einem GEPRUEFTEN Lauf auf. Faengt genau das, was der Ergebnis-Record nicht sieht - eine vertauschte `vfl-acc-*`-Reihenfolge (Komma-Listen `L_VF_m`/`L_GF_m`/`GF_WINKEL`, in der Zeichnung unsichtbar, im Sivas-Export falsch) und eine falsche Blockvariante. Liest ASCII-DXF streamend; binaere DXF kann `dxf_scan_components.py`. Genutzt von `tests/test_vf_geometrie.py` | | `vf_spec_export.py` | Uebersetzt ein Eingabe-Journal in eine **Spec** und zurueck (`spec_aus_journal` / `journal_aus_spec`) - Gegenstueck zu `vsp-journal` in `Lisp/vf_spec.lsp`, beide muessen dieselbe Tokenfolge liefern. Der Rundlauf `journal_aus_spec(spec_aus_journal(tok)) == tok` laeuft in `tests/test_vf_spec.py` gegen die 5 echten HundM-Ketten und beweist, dass die Spec das Journal verlustfrei abbildet. Schreibt das flache Spec-JSON (vom LISP-Parser lesbar). Objekte der Ziel-Datei, die keine Kette beschreiben (von Hand ergaenzte Anlagenteile mit `function`, z.B. Kreisel), werden beim Neuschreiben unveraendert ans Dateiende uebernommen - sonst waeren sie beim naechsten Lauf weg. Aufruf: `python vf_spec_export.py ` — erzeugt `tests/testdata/hundm05.json` (Spec, Eingabe fuer `TEST_HUNDM05`/`VF_SPEC_BAU`) aus `tests/testdata/hm_recformat.json` (Aufzeichnung) | | `dxf_anlage_export.py` | Gegenstueck zum Scanner: liest eine mit unseren Makros GEBAUTE (und von Hand nachbearbeitete) Anlage aus der Zeichnung zurueck in Nachbau-Testdaten - je Wrapper genau die Parameter, die auch dessen Edit-Befehl liest (KREISEL_n: Einfuegepunkt, `abstand` exakt aus der Blockdefinition, `drehrichtung` aus DREHRICHTUNG; GF_n: SSG_GF_EDIT + SEITE_AS/ES, endet die GF auf einer Kreisel-Kette, wird dessen Hoehe `hoehe_ende_mm`; VF_n `standard`/`etage`: mubea-Schema wie `vfs-edit-basis-lesen`; VF_n `linienzug`: Journal UNVERAENDERT als `"typ": "linienzug_journal"` - bewusst keine Spec, weil der Bau-Dialog neuer ist als die Spec-Grammatik; uebrige Bloecke als Einzel-Insert ohne `_2D`/`_3D`-Suffix). Reihenfolge Kreisel, VF, GF, Einzelbloecke (VF vor GF wie mubea). Aufruf: `python dxf_anlage_export.py [--projekt NAME]` — erzeugt `tests/testdata/dematic_arvato.json` aus `Dematic_TST.dxf`. Test `tests/test_dxf_anlage_export.py` | +| `lager_yaml_export.py` | Lagerplanung: `_lager_raw.json` (LAGER_EXPORT) -> `_drawing.yaml` (Overlay im Schema der RFQ-Hand-YAML, nur Zeichnungsfelder, plus `_provenance`/`_conflicts`/`_warnings`) und optional `--nachweis` HTML. `--hand ` gleicht ab (Hand gewinnt; Konflikte bei > 1 % bzw. 0,005). Aggregiert nur, Regeln leben in LISP. Braucht `pyyaml`. Test `tests/test_lager_yaml.py`. Aufruf: `python lager_yaml_export.py [--hand ...] [--nachweis ...]` | | `dxf_scan_components.py` | Erkennt ILS-/Omniflo-Komponenten in einer **fremden** Projektzeichnung (ohne unsere Wrapper-Bloecke) und schreibt sie im JSON-Schema aus `doc/json-input-lisp.md` (Nachfolger des geloeschten `tests/testdata/object_data.md`) — Grundlage fuer Nachbau-Testfaelle wie `tests/testdata/mubea.json`. Liest binaere DXF im GB-Bereich streamend per mmap (ezdxf-DOM waere zu gross), loest INSERTs inkl. OCS/Extrusionsrichtung rekursiv in Weltkoordinaten auf, paart AN8/SP8 zu Kreiseln und Antrieb/Umlenkung zu Foerderern. Gefaellestrecken, die als 3D-LINE auf einem Staustrecken-Layer gezeichnet sind (`GF_LINIE_LAYER`, z.B. `6-SP`), werden in einem zweiten Lesedurchgang zu Modus-1-Eintraegen: Start/Ende werden auf die Ketten der anschliessenden Kreisel verlaengert (gezeichnet ist nur die Staustrecke; `deltaL` reicht von AS- bis ES-Ursprung), AS-Seite aus dem Ausschleuselement am oberen Ende; dieses und Separatoren auf der Achse entfallen als Einzel-Insert (die GF baut beide selbst). Sitzt der AS am UNTEREN Ende, ist es ein Steigfoerderer und wird nur gemeldet (`gf_eintraege_aus_linien`, Test `tests/test_dxf_scan_gf.py`). `--ohne-hoehen` setzt alle Z auf 0, `--kreisel-hoehe X Y Z` gibt einem Kreisel seine Hoehe - die daran haengenden GF bekommen daraus `start_mm.z`/`hoehe_ende_mm`, den Winkel rechnet der Treiber (`dematic_arvato:gf-geometrie`: atan ueber die HORIZONTALE Laenge, ES-Laenge UND -Abfall bei der Einlaufneigung gemessen und iteriert, Staustrecke daraus - die flache ES-Laenge liess steile GF ~144 mm vor der Kette enden - NICHT `gf-winkel-berechnen`, dessen asin bei steilen GF wie im Showroom ~50 statt ~37 Grad liefert; `dematic_arvato:gf-endhoehe-pruefen` meldet `warnung` bei > 5 mm Abweichung von HOEHE_BIS). Flache 2D-Plaene: `--flach` (waagrechte Staustrecke mit AS an genau einem Ende = GF, zerstueckelte Linien werden vereint, ES am unteren Ende wird verbraucht, AS/ES-Seite ohne Namensangabe aus der Lage zur Fahrtrichtung), `--kreisel-achse X1 Y1 X2 Y2` (Kreisel ohne AN8/SP8), Einzelteile auf einer Kreisel-Kette erben dessen Anwender-Hoehe (`sensor_hoehen_von_kreiseln`). Kreisel-Paarung mit Tausch-Nachbesserung auf minimale Abstandssumme (zwei Kreisel auf einer Achse). Genutzt fuer `tests/testdata/showroom_ils.json` (`TEST_SHOWROOM_ILS`). Aufruf: `python dxf_scan_components.py [--fenster X0 Y0 X1 Y1]` — ohne `--fenster` liefert eine Anlagenzeichnung leicht ueber 1300 Eintraege, zuviel fuer einen Testlauf | ### Entwickler-Tools (`lib/`) - kein ezdxf, reine Text-/AutoLISP-Verarbeitung diff --git a/Lisp/LagerInsert.lsp b/Lisp/LagerInsert.lsp new file mode 100644 index 0000000..14cc7cd --- /dev/null +++ b/Lisp/LagerInsert.lsp @@ -0,0 +1,2598 @@ +;; ============================================================ +;; LagerInsert.lsp - Lagerplanung GOH-Buehnenlager (Phase 1: Mengen, 2D) +;; +;; Erzeugt parametrische Attributbloecke fuer ein mehrgeschossiges +;; Haengewarenlager und sammelt daraus die Mengen fuer die RFQ-YAML +;; (Overlay, siehe doc/plan/lager_rfq.md): +;; +;; LG_GESCHOSS Geschoss-Kopfsymbol, verknuepft mit seiner Belagskontur +;; LG_BEREICH Lagerbereich (Kontur + Parameter), fuellt sich selbst +;; mit Regalreihen und Gaengen (Auto-Fuellung) +;; LG_REIHE__ Regalreihe (Rechteck L x T + Schienenlinie) +;; LG_GANG___ Gang/Quergang/Randgang (Rechteck L x B, Schraffur +;; je Belag aus lager.json belag_schraffur) +;; goh_rack parametrischer BricsCAD-Block (Bestand), wird als +;; Regalreihe mitgezaehlt (lg-racks-sammeln) +;; +;; Befehle: LG_GESCHOSS, LG_GESCHOSS_AKTIV, LG_GESCHOSS_KOPIEREN, LG_BEREICH, +;; LG_REIHE, LG_GANG, LG_EDIT, LG_REGALE, LAGER_EXPORT, LAGER_HAND_YAML +;; +;; Grundsaetze (CLAUDE.md): +;; - Alle Werte selbstbeschreibend in mm/Grad, keine Index-Codes. +;; - Verknuepfungen (Kontur <-> Block, Kind -> Bereich) als XDATA-Handles +;; (App SSG_LG), nie in .dbg. +;; - Jeder Block traegt ID, GESCHOSS, LAYER. +;; - Layer je Geschoss: Basis aus cfg/layer.cfg [lager] + "_G", damit +;; LG_GESCHOSS_AKTIV die uebrigen Geschosse frieren kann. +;; - Blockdefinitionen entstehen per entmake (BLOCK...ENDBLK), Einfuegen per +;; vla-InsertBlock in Weltkoordinaten - kein _.-BLOCK/_.INSERT, damit weder +;; BKS noch ATTREQ/ATTDIA dazwischenfunken. +;; ============================================================ + +(vl-load-com) + +;; Regelprofile aus LagerPruefung. Guarded Nachlade-Load fuer das isolierte +;; Laden dieses Moduls; im Normalfall hat die MNL es schon geladen. +(if (and (not (car (atoms-family 1 '("LG-REGEL-AUFLOESEN")))) + (ssg-lisp-datei-pfad "LagerPruefung.lsp")) + (load (ssg-lisp-datei-pfad "LagerPruefung.lsp") nil)) + +(setq *lg-app* "SSG_LG") +(if (not (boundp '*lg-daten*)) (setq *lg-daten* nil)) + +;; ============================================================ +;; DATEN (data/json/lager.json) +;; ============================================================ + +(defun lg-daten-pfad ( / basis) + (setq basis (cond ((getenv "DXFM_DATA") (vl-string-translate "\\" "/" (getenv "DXFM_DATA"))) + ((getenv "DXFMAKRO") + (strcat (vl-string-translate "\\" "/" (getenv "DXFMAKRO")) "/data")))) + (if basis (strcat (vl-string-right-trim "/" basis) "/json/lager.json")) +) + +(defun lg-daten ( / pfad) + (if (null *lg-daten*) + (progn + (setq pfad (lg-daten-pfad)) + (if (and pfad (findfile pfad)) + (setq *lg-daten* (ssg-json-laden pfad)) + (princ (ssg-textf "lg-daten-fehlt" (list (if pfad pfad "lager.json")))))) + ) + *lg-daten* +) + +;; Wert unter einem Schluesselpfad, z.B. (lg-vorgabe '("bereich" "gangbreite_mm") 1000) +(defun lg-vorgabe (pfad vorgabe / obj) + (setq obj (lg-daten)) + (foreach k pfad (setq obj (if (listp obj) (ssg-json-get obj k)))) + (if (null obj) vorgabe obj) +) + +(defun lg-land ( / l) + (setq l (lg-vorgabe '("land") "DE")) + (if (= (type l) 'STR) l "DE") +) + +;; ============================================================ +;; ZAHL-/TEXT-HELFER (rein, getestet in tests/test_unit.lsp) +;; ============================================================ + +;; Zahl aus String/Zahl, Komma wie Punkt. nil bei leer/ungueltig. +(defun lg-zahl (v / s) + (cond + ((numberp v) (float v)) + ((= (type v) 'STR) + (setq s (vl-string-translate "," "." (vl-string-trim " \t" v))) + (if (/= s "") (distof s 2))) + (T nil) + ) +) + +;; mm-Wert als Attributtext: ganze Zahl ohne Nachkommastelle, sonst eine +;; Stelle (unabhaengig von DIMZIN). +(defun lg-mm-str (w / r s) + (if (null w) + "" + (progn + (setq w (float w) + r (if (< w 0) (fix (- w 0.5)) (fix (+ w 0.5)))) + (if (equal w r 1e-6) + (itoa r) + (progn + (setq s (rtos w 2 1)) + (if (not (vl-string-search "." s)) (setq s (strcat s ".0"))) + s))) + ) +) + +;; Auf ganze mm runden (Blocknamen und Geometrie muessen uebereinstimmen). +(defun lg-mm-runden (w) + (float (if (< w 0) (fix (- w 0.5)) (fix (+ w 0.5)))) +) + +;; Haengeebenen "2300/1200;1100/1100" -> ((2300.0 1200.0) (1100.0 1100.0)). +;; Ungueltige Teile fallen weg; fehlende Warenlaenge = 0. +(defun lg-haengeebenen-parse (s / erg teil ab h w) + (setq erg nil) + (if (and s (= (type s) 'STR)) + (foreach teil (ssg-sepliste-split-feld s ";") + (setq ab (ssg-sepliste-split-feld teil "/") + h (lg-zahl (car ab)) + w (lg-zahl (cadr ab))) + (if h (setq erg (cons (list h (if w w 0.0)) erg))) + ) + ) + (reverse erg) +) + +(defun lg-haengeebenen-string (ebenen) + (ssg-join (mapcar '(lambda (e) (strcat (lg-mm-str (car e)) "/" (lg-mm-str (cadr e)))) + ebenen) + ";") +) + +;; Haengeebenen auf n Ebenen begrenzen (goh_rack kennt nur die Anzahl): +;; die obersten n der Vorgabe. Fehlen Ebenen in der Vorgabe, bleibt es bei +;; dem, was da ist. +(defun lg-haengeebenen-n (s n / ebenen erg i) + (setq ebenen (lg-haengeebenen-parse s) erg nil i 0) + (while (and ebenen (< i n)) + (setq erg (cons (car ebenen) erg) ebenen (cdr ebenen) i (1+ i))) + (lg-haengeebenen-string (reverse erg)) +) + +;; Feldwert pruefen/normalisieren. art: "mm" (Zahl >= 0), "mm_leer" (wie mm, +;; leer erlaubt), "int" (ganze Zahl >= 1), "int0" (>= 0), "grad" (beliebige +;; Zahl), "mm_vz" (mm mit Vorzeichen, z.B. Versatz), "text", "ebenen" +;; (Haengeebenen-Liste, mind. eine Ebene). +;; Rueckgabe: normalisierter String oder nil (ungueltig). +(defun lg-feld-pruefen (art s / w) + (if (null s) (setq s "")) + (setq s (vl-string-trim " \t" s)) + (cond + ((= art "text") s) + ((and (= art "mm_leer") (= s "")) "") + ((member art '("mm" "mm_leer")) + (if (and (setq w (lg-zahl s)) (>= w 0.0)) (lg-mm-str w))) + ((member art '("grad" "mm_vz")) + (if (setq w (lg-zahl s)) (lg-mm-str w))) + ((member art '("int" "int0")) + (if (and (setq w (lg-zahl s)) (equal w (fix w) 1e-9) + (>= w (if (= art "int") 1 0))) + (itoa (fix w)))) + ((= art "ebenen") + (if (lg-haengeebenen-parse s) (lg-haengeebenen-string (lg-haengeebenen-parse s)))) + (T s) + ) +) + +;; ============================================================ +;; LAYER JE GESCHOSS +;; ============================================================ + +;; Basis-Layername eines Lager-Schluessels aus cfg/layer.cfg [lager]. +;; Rueckgabe: (name . farbe), Rueckfall "LG_" / Farbe 7. +(defun lg-layer-basis (schluessel / e) + (setq e (ssg-layer-cfg-eintrag (ssg-layer-cfg-load) "lager" schluessel)) + (if e e (cons (strcat "LG_" schluessel) "7")) +) + +;; Reiner Kern: Layername fuer Geschoss nr (nil/"" -> ohne Suffix). +(defun lg-layer-name (basis nr) + (if (and nr (/= (vl-princ-to-string nr) "")) + (strcat basis "_G" (vl-princ-to-string nr)) + basis) +) + +;; Layer anlegen (Farbe nur bei Neuanlage) und Namen liefern. +(defun lg-layer (schluessel nr / b name) + (setq b (lg-layer-basis schluessel) + name (lg-layer-name (car b) nr)) + (ssg-make-layer name (cdr b) nil) + name +) + +;; ============================================================ +;; ZEICHNUNGSWEITE EINSTELLUNGEN (Dictionary per vlax-ldata) +;; ============================================================ + +(defun lg-ldata-lesen (key / r) + (setq r (vl-catch-all-apply 'vlax-ldata-get (list "SSG_LAGER" key))) + (if (vl-catch-all-error-p r) nil r) +) + +(defun lg-ldata-setzen (key wert) + (vl-catch-all-apply 'vlax-ldata-put (list "SSG_LAGER" key wert)) + wert +) + +;; ============================================================ +;; XDATA (App SSG_LG): Folge von (1000 . schluessel) + Wertgruppe +;; (1005 = Handle, 1000 = Text). Gelesen als Alist (schluessel . wert). +;; ============================================================ + +(defun lg-regapp () + (if (not (tblsearch "APPID" *lg-app*)) (regapp *lg-app*)) +) + +;; paare = (("kontur" 1005 . "2A") ("rolle" 1000 . "reihe") ...) +(defun lg-xdata-setzen (ent paare / ed gruppen) + (lg-regapp) + (setq gruppen nil) + (foreach p paare + (setq gruppen (append gruppen (list (cons 1000 (car p)) (cons (cadr p) (cddr p)))))) + (setq ed (vl-remove-if '(lambda (g) (= (car g) -3)) (entget ent))) + (entmod (append ed (list (list -3 (cons *lg-app* gruppen))))) + (entupd ent) +) + +(defun lg-xdata-lesen (ent / xd gruppen erg) + (setq xd (assoc -3 (entget ent (list *lg-app*)))) + (setq gruppen (if xd (cdr (assoc *lg-app* (cdr xd))))) + (setq erg nil) + (while (cadr gruppen) + (if (= (car (car gruppen)) 1000) + (setq erg (cons (cons (cdr (car gruppen)) (cdr (cadr gruppen))) erg))) + (setq gruppen (cddr gruppen)) + ) + (reverse erg) +) + +(defun lg-xdata-wert (ent key) (cdr (assoc key (lg-xdata-lesen ent)))) + +(defun lg-handle (ent) (cdr (assoc 5 (entget ent)))) + +(defun lg-ent-aus-handle (h / e) + (if (and h (setq e (handent h)) (entget e)) e) +) + +;; ============================================================ +;; BLOCKBAU +;; ============================================================ + +(defun lg-geom-linie (p1 p2) + (list '(0 . "LINE") '(8 . "0") (cons 10 p1) (cons 11 p2)) +) + +(defun lg-geom-rechteck (x0 y0 x1 y1) + (list '(0 . "LWPOLYLINE") '(100 . "AcDbEntity") '(8 . "0") + '(100 . "AcDbPolyline") '(90 . 4) '(70 . 1) + (list 10 x0 y0) (list 10 x1 y0) (list 10 x1 y1) (list 10 x0 y1)) +) + +(defun lg-geom-kreis (pt r) + (list '(0 . "CIRCLE") '(8 . "0") (cons 10 pt) (cons 40 r)) +) + +;; ATTDEF fuer eine Blockdefinition. sichtbar = nil -> unsichtbar. +(defun lg-geom-attdef (tag wert pt hoehe sichtbar) + (list '(0 . "ATTDEF") '(8 . "0") (cons 10 pt) (cons 40 hoehe) + (cons 1 wert) (cons 3 tag) (cons 2 tag) + (cons 70 (if sichtbar 0 1))) +) + +;; Unsichtbare ATTDEFs fuer alle Tags eines Schemas ((TAG . vorgabe) ...). +(defun lg-attdefs-unsichtbar (schema / y erg) + (setq y 0.0 erg nil) + (foreach p schema + (setq erg (cons (lg-geom-attdef (car p) "" (list 0.0 y 0.0) 50.0 nil) erg) + y (- y 100.0))) + (reverse erg) +) + +(defun lg-blockdef-anlegen (bname ents) + (entmake (list '(0 . "BLOCK") (cons 2 bname) '(70 . 2) '(10 0.0 0.0 0.0))) + (foreach e ents (entmake e)) + (entmake '((0 . "ENDBLK"))) +) + +;; Objekte einer vorhandenen Blockdefinition (entget-Listen). +(defun lg-blockdef-objekte (bname / e ed erg) + (setq e (cdr (assoc -2 (tblsearch "BLOCK" bname))) erg nil) + (while (and e (setq ed (entget e)) (/= (cdr (assoc 0 ed)) "ENDBLK")) + (setq erg (cons ed erg) e (entnext e))) + (reverse erg) +) + +;; Blockdefinition anlegen, falls sie fehlt. ents = entmake-Listen. +;; Altbestand: fehlen einer vorhandenen Definition ATTDEFs aus ents (Schema +;; wurde erweitert), wird sie neu definiert und per ATTSYNC auf die +;; Referenzen uebertragen - sonst verschluckt lg-attribute-setzen die neuen +;; Tags still. +(defun lg-blockdef (bname ents / vorhanden fehlt) + (if (not (tblsearch "BLOCK" bname)) + (lg-blockdef-anlegen bname ents) + (progn + (setq vorhanden (mapcar '(lambda (ed) (strcase (cdr (assoc 2 ed)))) + (vl-remove-if-not '(lambda (ed) (= (cdr (assoc 0 ed)) "ATTDEF")) + (lg-blockdef-objekte bname)))) + (setq fehlt (vl-remove-if '(lambda (tg) (member tg vorhanden)) + (mapcar '(lambda (e) (strcase (cdr (assoc 2 e)))) + (vl-remove-if-not '(lambda (e) (= (cdr (assoc 0 e)) "ATTDEF")) ents)))) + (if fehlt + (progn + (lg-blockdef-anlegen bname ents) + (vl-catch-all-apply '(lambda () (command "_.ATTSYNC" "_N" bname))) + (princ (ssg-textf "lg-blockdef-ergaenzt" (list bname (ssg-join fehlt ", ")))))) + ) + ) + bname +) + +(defun lg-modelspace () + (vla-get-ModelSpace (vla-get-ActiveDocument (vlax-get-acad-object))) +) + +;; Wert zu einem Attribut-Tag aus werte (("TAG" . "wert") ...), Gross-/ +;; Kleinschreibung egal: die Tags tragen gemischte Schreibweise +;; (HOEHE_BELAG_mm), Attribut-Tags gelten aber case-insensitiv. +(defun lg-wert-fuer-tag (tag werte / g) + (setq g (strcase tag)) + (cdr (vl-some '(lambda (p) (if (= (strcase (car p)) g) p)) werte)) +) + +;; Block bei pt (WCS) mit Drehung rot (Bogenmass) einfuegen, Layer setzen, +;; Attribute aus werte (("TAG" . "wert") ...) eintragen. +;; Rueckgabe: ename oder nil. +(defun lg-block-einfuegen (bname pt rot layer werte / obj res w) + (setq res + (vl-catch-all-apply + '(lambda () + (setq obj (vla-InsertBlock (lg-modelspace) (vlax-3d-point pt) bname 1.0 1.0 1.0 rot)) + (if layer (vla-put-Layer obj layer)) + (foreach a (vlax-invoke obj 'GetAttributes) + (if (setq w (lg-wert-fuer-tag (vla-get-TagString a) werte)) + (vla-put-TextString a w))) + (vlax-vla-object->ename obj)))) + (if (vl-catch-all-error-p res) + (progn (princ (ssg-textf "lg-einfuegen-fehler" (list bname (vl-catch-all-error-message res)))) nil) + res) +) + +;; Attribute eines vorhandenen Blocks setzen (nur bestehende Tags). +(defun lg-attribute-setzen (ent werte / obj w) + (setq obj (vlax-ename->vla-object ent)) + (foreach a (vlax-invoke obj 'GetAttributes) + (if (setq w (lg-wert-fuer-tag (vla-get-TagString a) werte)) + (vla-put-TextString a w))) + ent +) + +;; Effektiver Blockname (unsere Bloecke sind nie dynamisch, aber +;; Benutzerkopien koennten es sein). +(defun lg-blockname (ent / r) + (setq r (vl-catch-all-apply 'vla-get-EffectiveName (list (vlax-ename->vla-object ent)))) + (if (vl-catch-all-error-p r) (cdr (assoc 2 (entget ent))) r) +) + +(defun lg-alle-bloecke (muster / ss i erg) + (setq erg nil) + (if (setq ss (ssget "_X" (list '(0 . "INSERT") (cons 2 muster)))) + (progn + (setq i 0) + (repeat (sslength ss) + (setq erg (cons (ssname ss i) erg) i (1+ i))))) + (reverse erg) +) + +;; ============================================================ +;; ATTRIBUT-SCHEMATA (Tag . Vorgabe) +;; ============================================================ + +(setq *lg-schema-geschoss* + '(("ID" . "") ("GESCHOSS" . "") ("LAYER" . "") ("NR" . "") ("BEZEICHNUNG" . "") + ("HOEHE_BELAG_mm" . "0") ("KONSTR_HOEHE_mm" . "") ("LICHTE_HOEHE_mm" . "") + ("BELAG_ART" . "") ("PERSONEN" . "") ("BRANDGEFAEHRDUNG" . ""))) + +(setq *lg-schema-bereich* + '(("ID" . "") ("GESCHOSS" . "") ("LAYER" . "") ("NR" . "") + ("GANGBREITE_mm" . "") ("REGALTIEFE_mm" . "") ("REIHEN_JE_GANG" . "") + ("SCHIENEN_JE_GANG" . "") ("QUERGANG_ABSTAND_mm" . "") ("QUERGANG_BREITE_mm" . "") + ("RICHTUNG" . "") ("HAENGEEBENEN" . "") ("BELAG" . "") + ("RANDGANG_N_mm" . "0") ("RANDGANG_O_mm" . "0") ("RANDGANG_S_mm" . "0") ("RANDGANG_W_mm" . "0") + ("RESTBREITE_mm" . ""))) + +(setq *lg-schema-reihe* + '(("ID" . "") ("GESCHOSS" . "") ("LAYER" . "") ("LAENGE" . "") ("TIEFE_mm" . "") + ("HAENGEEBENEN" . "") ("SCHIENEN" . "") ("BEREICH" . "") ("QUELLE" . ""))) + +(setq *lg-schema-gang* + '(("ID" . "") ("GESCHOSS" . "") ("LAYER" . "") ("LAENGE" . "") ("BREITE_mm" . "") + ("ART" . "") ("BELAG" . "") ("BEREICH" . "") ("QUELLE" . ""))) + +;; ============================================================ +;; GESCHOSSE +;; ============================================================ + +(defun lg-geschoss-blockdef ( / ) + (lg-blockdef "LG_GESCHOSS" + (append + (list (lg-geom-kreis '(0.0 0.0 0.0) 300.0) + (lg-geom-linie '(-300.0 0.0 0.0) '(300.0 0.0 0.0))) + ;; sichtbar: Nummer, Bezeichnung, Belagshoehe + (list (lg-geom-attdef "NR" "" '(-120.0 40.0 0.0) 180.0 T) + (lg-geom-attdef "BEZEICHNUNG" "" '(400.0 60.0 0.0) 150.0 T) + (lg-geom-attdef "HOEHE_BELAG_mm" "" '(400.0 -200.0 0.0) 150.0 T)) + (lg-attdefs-unsichtbar + (vl-remove-if '(lambda (p) (member (car p) '("NR" "BEZEICHNUNG" "HOEHE_BELAG_mm"))) + *lg-schema-geschoss*)))) +) + +;; Record eines Geschoss-Blocks: (ent nr attribs kontur-punkte) +(defun lg-geschoss-record (ent / attribs k) + (setq attribs (ssg-attrib-read ent) + k (lg-ent-aus-handle (lg-xdata-wert ent "kontur"))) + (list ent (atoi (cond ((cdr (assoc "NR" attribs))) ("0"))) attribs + (if k (ssg-lwpoly-punkte k))) +) + +(defun lg-geschosse ( / ) + (vl-sort (mapcar 'lg-geschoss-record (lg-alle-bloecke "LG_GESCHOSS")) + '(lambda (a b) (< (cadr a) (cadr b)))) +) + +(defun lg-geschoss-finden (nr) + (vl-some '(lambda (g) (if (= (cadr g) nr) g)) (lg-geschosse)) +) + +(defun lg-geschoss-hoehe (rec) + (cond ((lg-zahl (cdr (assoc "HOEHE_BELAG_mm" (caddr rec))))) (0.0)) +) + +;; Z einer Kontur (Erhebung des ersten Punkts) eines Kandidaten. +(defun lg-kandidat-z (k) + (cond ((caddr (car (caddr k)))) (0.0)) +) + +;; Reiner Kern der Geschosszuordnung. +;; kandidaten = ((nr hoehe-mm kontur-punkte) ...), pt = (x y z). +;; Liegt pt in genau einer Kontur -> deren nr. Liegt er in mehreren, hilft +;; nur Z: liegen die Konturen auf verschiedenen Hoehen (3D), gewinnt die +;; hoechste Kontur bis z + 50 mm. Liegen sie alle auf derselben Hoehe +;; (2D-Grundrisse uebereinander), ist die Zuordnung nicht eindeutig -> nil. +(defun lg-geschoss-fuer-punkt-kern (pt kandidaten / innen z z0 passend) + (setq innen (vl-remove-if-not '(lambda (k) (and (caddr k) (ssg-punkt-in-poly-p pt (caddr k)))) + kandidaten)) + (cond + ((null innen) nil) + ((null (cdr innen)) (car (car innen))) + (T + (setq z (if (caddr pt) (caddr pt) 0.0) + z0 (lg-kandidat-z (car innen))) + (if (vl-every '(lambda (k) (equal (lg-kandidat-z k) z0 1.0)) innen) + nil + (progn + (setq passend (vl-remove-if-not '(lambda (k) (<= (lg-kandidat-z k) (+ z 50.0))) innen)) + (if passend + (car (car (vl-sort passend '(lambda (a b) (> (lg-kandidat-z a) (lg-kandidat-z b)))))))))) + ) +) + +(defun lg-geschoss-kandidaten ( / ) + (mapcar '(lambda (g) (list (cadr g) (lg-geschoss-hoehe g) (cadddr g))) (lg-geschosse)) +) + +;; Aktives Geschoss (Zahl) oder nil. +(defun lg-geschoss-aktiv ( / nr) + (setq nr (lg-ldata-lesen "GESCHOSS_AKTIV")) + (if (and (numberp nr) (> nr 0) (lg-geschoss-finden nr)) nr) +) + +;; Geschoss fuer ein neues Objekt: aktives Geschoss, sonst Kontur-Zuordnung, +;; sonst Rueckfrage. nil = kein Geschoss vorhanden. +(defun lg-geschoss-ermitteln (pt / nr gs) + (setq gs (lg-geschosse)) + (cond + ((null gs) (princ (ssg-text "lg-kein-geschoss")) nil) + ((setq nr (lg-geschoss-aktiv)) nr) + ((setq nr (lg-geschoss-fuer-punkt-kern pt (lg-geschoss-kandidaten))) nr) + ((null (cdr gs)) (cadr (car gs))) + (T + (setq nr (ssg-in-int (ssg-text "lg-prompt-geschoss-nr") (cadr (car gs)))) + (if (lg-geschoss-finden nr) nr + (progn (princ (ssg-textf "lg-geschoss-unbekannt" (list (itoa nr)))) nil))) + ) +) + +(defun lg-geschoss-naechste-nr ( / gs) + (setq gs (lg-geschosse)) + (if gs (1+ (apply 'max (mapcar 'cadr gs))) 1) +) + +;; ------------------------------------------------------------ +;; Konturen +;; ------------------------------------------------------------ + +(defun lg-kontur-p (ent / ed) + (setq ed (entget ent)) + (and (= (cdr (assoc 0 ed)) "LWPOLYLINE") + (>= (length (ssg-lwpoly-punkte ent)) 3)) +) + +;; Kontur geschlossen setzen (Flag 1), Duplikat-Schlusspunkt stoert nicht. +(defun lg-kontur-schliessen (ent / ed f) + (setq ed (entget ent) f (cdr (assoc 70 ed))) + (if (= 0 (logand f 1)) + (progn (entmod (subst (cons 70 (logior f 1)) (assoc 70 ed) ed)) (entupd ent))) + ent +) + +;; Kopie einer Polylinie ohne Handle/Besitzer/XDATA (fuer eine Kontur, die +;; schon einem anderen Geschoss/Bereich gehoert - die 2D-Grundrisse liegen +;; uebereinander, jede Kontur gehoert genau einem Block). +(defun lg-kontur-kopieren (ent) + (entmakex (vl-remove-if '(lambda (g) (member (car g) '(-1 5 330 102 360 -3))) + (entget ent))) +) + +;; Kontur fuer einen neuen Block vorbereiten: kopieren, falls schon +;; vergeben, schliessen, auf layer legen. Rueckgabe: ename der Kontur. +(defun lg-kontur-uebernehmen (ent layer / ed) + (if (lg-xdata-wert ent "besitzer") (setq ent (lg-kontur-kopieren ent))) + (lg-kontur-schliessen ent) + (setq ed (entget ent)) + (entmod (subst (cons 8 layer) (assoc 8 ed) ed)) + (entupd ent) + ent +) + +;; Interaktiv: geschlossene Polylinie waehlen, Enter = neu zeichnen. +(defun lg-kontur-waehlen (prompt / sel ent fertig) + (while (not fertig) + (setq sel (entsel prompt)) + (cond + ((null sel) + (if (= (getvar "ERRNO") 52) + (progn + ;; Enter: neue Kontur zeichnen + (princ (ssg-text "lg-kontur-zeichnen")) + (setq ent (entlast)) + (command "_.PLINE") + (while (> (getvar "CMDACTIVE") 0) (command pause)) + (if (and (entlast) (not (eq ent (entlast))) (lg-kontur-p (entlast))) + (setq ent (entlast)) + (setq ent nil)) + (setq fertig T)) + (setq fertig T ent nil))) + ((lg-kontur-p (car sel)) (setq ent (car sel) fertig T)) + (T (princ (ssg-text "lg-keine-kontur"))) + ) + ) + ent +) + +;; ------------------------------------------------------------ +;; Geschoss anlegen / aendern +;; ------------------------------------------------------------ + +(defun lg-geschoss-vorgaben (nr / ) + (list (cons "NR" (itoa nr)) + (cons "GESCHOSS" (itoa nr)) + (cons "BEZEICHNUNG" (if (= nr 0) "ground floor" (strcat "Level " (itoa nr)))) + (cons "HOEHE_BELAG_mm" "0") + (cons "KONSTR_HOEHE_mm" (lg-mm-str (lg-zahl (lg-vorgabe '("geschoss" "konstr_hoehe_mm") 250)))) + (cons "LICHTE_HOEHE_mm" "") + (cons "BELAG_ART" (lg-vorgabe '("geschoss" "belag_art") "spanplatte")) + (cons "PERSONEN" (itoa (fix (lg-zahl (lg-vorgabe '("geschoss" "personen") 20))))) + (cons "BRANDGEFAEHRDUNG" (lg-vorgabe '("geschoss" "brandgefaehrdung") "erhoeht_ohne_loeschanlage"))) +) + +;; Geschoss-Block zur Kontur anlegen (nicht interaktiv - auch fuer Tests). +;; werte = vollstaendige Attributwerte (lg-geschoss-vorgaben + Dialog). +;; Rueckgabe: ename des Blocks oder nil. +(defun lg-geschoss-anlegen (kontur werte / nr lay pts bb pt blk) + (setq nr (atoi (cdr (assoc "NR" werte)))) + (if (lg-geschoss-finden nr) + (progn (princ (ssg-textf "lg-geschoss-existiert" (list (itoa nr)))) nil) + (progn + (setq lay (lg-layer "geschoss" nr)) + (setq kontur (lg-kontur-uebernehmen kontur lay)) + (setq pts (ssg-lwpoly-punkte kontur) + bb (ssg-poly-bbox pts)) + ;; Kopfsymbol unten links an der Kontur, auf deren Z + (setq pt (list (car (car bb)) (cadr (car bb)) (if (caddr (car pts)) (caddr (car pts)) 0.0))) + (lg-geschoss-blockdef) + (setq werte (append (list (cons "GESCHOSS" (itoa nr)) (cons "LAYER" lay) + (cons "ID" (strcat "G" (itoa nr)))) + werte)) + (if (setq blk (lg-block-einfuegen "LG_GESCHOSS" pt 0.0 lay werte)) + (progn + (lg-xdata-setzen blk (list (cons "kontur" (cons 1005 (lg-handle kontur))))) + (lg-xdata-setzen kontur (list (cons "besitzer" (cons 1005 (lg-handle blk))) + (cons "rolle" (cons 1000 "geschoss")))) + blk)) + ) + ) +) + +;; ============================================================ +;; DIALOGE (generisch: Feldliste -> DCL bzw. Konsole) +;; ============================================================ +;; feld = (tile-key attribut-tag art optionen) +;; art siehe lg-feld-pruefen, zusaetzlich "wahl" (popup_list aus optionen) +;; Konsolen-Prompt: ssg-text "lg-feld-". + +(setq *lg-felder-geschoss* + '(("nr" "NR" "int0" nil) + ("bezeichnung" "BEZEICHNUNG" "text" nil) + ("hoehe" "HOEHE_BELAG_mm" "mm" nil) + ("konstr" "KONSTR_HOEHE_mm" "mm" nil) + ("lichte" "LICHTE_HOEHE_mm" "mm_leer" nil) + ("belag" "BELAG_ART" "wahl" belag) + ("personen" "PERSONEN" "int0" nil) + ("brand" "BRANDGEFAEHRDUNG" "wahl" brand))) + +(setq *lg-felder-bereich* + '(("gang" "GANGBREITE_mm" "mm" nil) + ("tiefe" "REGALTIEFE_mm" "mm" nil) + ("reihen" "REIHEN_JE_GANG" "wahl" reihen) + ("schienen" "SCHIENEN_JE_GANG" "int0" nil) + ("q_abstand" "QUERGANG_ABSTAND_mm" "mm" nil) + ("q_breite" "QUERGANG_BREITE_mm" "mm" nil) + ("richtung" "RICHTUNG" "grad" nil) + ("ebenen" "HAENGEEBENEN" "ebenen" nil) + ("belag" "BELAG" "wahl" belag) + ("rand_n" "RANDGANG_N_mm" "mm" nil) + ("rand_o" "RANDGANG_O_mm" "mm" nil) + ("rand_s" "RANDGANG_S_mm" "mm" nil) + ("rand_w" "RANDGANG_W_mm" "mm" nil))) + +(setq *lg-felder-gang* + '(("laenge" "LAENGE" "mm" nil) + ("breite" "BREITE_mm" "mm" nil) + ("art" "ART" "wahl" gangart) + ("belag" "BELAG" "wahl" belag))) + +(setq *lg-felder-reihe* + '(("laenge" "LAENGE" "mm" nil) + ("tiefe" "TIEFE_mm" "mm" nil) + ("ebenen" "HAENGEEBENEN" "ebenen" nil) + ("schienen" "SCHIENEN" "int0" nil))) + +;; LG_GESCHOSS_KOPIEREN - die Tags sind reine Dialogwerte, kein Block-Schema. +(setq *lg-felder-kopie* + '(("quelle" "QUELLE" "wahl" geschosse) + ("anzahl" "ANZAHL" "int" nil) + ("abstand" "ABSTAND_mm" "mm" nil) + ("belag" "BELAG_ART" "wahl" belag) + ("versatz_x" "VERSATZ_X_mm" "mm_vz" nil) + ("versatz_y" "VERSATZ_Y_mm" "mm_vz" nil))) + +(defun lg-optionen (sym) + (cond + ((eq sym 'belag) (lg-vorgabe '("geschoss" "belag_arten") '("spanplatte" "gitterrost" "hallenboden"))) + ((eq sym 'brand) (lg-vorgabe '("brandgefaehrdungen") '("normal" "erhoeht_mit_loeschanlage" "erhoeht_ohne_loeschanlage"))) + ((eq sym 'reihen) '("1" "2")) + ((eq sym 'gangart) '("gang" "randgang" "quergang")) + ((eq sym 'geschosse) (mapcar '(lambda (g) (itoa (cadr g))) (lg-geschosse))) + ) +) + +(defun lg-dcl-pfad ( / d) + (setq d (getenv "DXFM_DCL")) + (if d (strcat (vl-string-right-trim "/\\" (vl-string-translate "\\" "/" d)) "/lager.dcl")) +) + +;; Dialog zeigen. gesperrt = Liste von tile-keys, die nicht editierbar sind. +;; Rueckgabe: aktualisierte werte oder nil (Abbruch). Faellt bei fehlender +;; GUI/DCL auf die Konsole zurueck. +(defun lg-dialog (dlg-name felder werte gesperrt / pfad dat erg) + (setq pfad (lg-dcl-pfad)) + (cond + ((not (ssg-gui-p)) werte) ; Tests/Batch: Vorgaben uebernehmen + ((or (null pfad) (not (findfile pfad)) (< (setq dat (load_dialog pfad)) 0)) + (princ (ssg-textf "lg-dialog-fehlt" (list (if pfad pfad "lager.dcl")))) + (lg-konsole felder werte gesperrt)) + ((not (new_dialog dlg-name dat)) + (unload_dialog dat) + (lg-konsole felder werte gesperrt)) + (T + (setq *lg-dlg-werte* werte *lg-dlg-felder* felder *lg-dlg-ergebnis* nil) + (foreach f felder + (if (= (caddr f) "wahl") + (progn + (start_list (car f)) + (mapcar 'add_list (lg-optionen (cadddr f))) + (end_list) + (set_tile (car f) + (itoa (cond ((vl-position (cdr (assoc (cadr f) werte)) (lg-optionen (cadddr f)))) + (0))))) + (set_tile (car f) (cond ((cdr (assoc (cadr f) werte))) ("")))) + (if (member (car f) gesperrt) (mode_tile (car f) 1)) + ) + (action_tile "accept" "(lg-dialog-ok)") + (action_tile "cancel" "(done_dialog 0)") + (setq erg (start_dialog)) + (unload_dialog dat) + (if (= erg 1) *lg-dlg-ergebnis*) + ) + ) +) + +;; OK-Knopf: alle Felder pruefen; bei Fehler Dialog offen lassen. +(defun lg-dialog-ok ( / werte fehler f s n) + (setq werte *lg-dlg-werte* fehler nil) + (foreach f *lg-dlg-felder* + (setq s (get_tile (car f))) + (if (= (caddr f) "wahl") + (setq n (nth (atoi s) (lg-optionen (cadddr f)))) + (setq n (lg-feld-pruefen (caddr f) s))) + (if (null n) + (if (null fehler) (setq fehler (car f))) + (setq werte (lg-wert-setzen werte (cadr f) n))) + ) + (if fehler + (progn (set_tile "error" (ssg-textf "lg-feld-ungueltig" (list (ssg-text (strcat "lg-feld-" fehler))))) + (mode_tile fehler 2)) + (progn (setq *lg-dlg-ergebnis* werte) (done_dialog 1))) +) + +(defun lg-wert-setzen (werte tag wert) + (if (assoc tag werte) + (subst (cons tag wert) (assoc tag werte) werte) + (append werte (list (cons tag wert)))) +) + +;; Konsolen-Rueckfall: jedes Feld mit Vorgabe abfragen (Enter = Vorgabe). +(defun lg-konsole (felder werte gesperrt / f alt neu opts) + (foreach f felder + (if (not (member (car f) gesperrt)) + (progn + (setq alt (cond ((cdr (assoc (cadr f) werte))) (""))) + (setq neu nil) + (while (null neu) + (if (= (caddr f) "wahl") + (progn + (setq opts (lg-optionen (cadddr f))) + (princ (strcat "\n " (ssg-join opts " | "))) + (setq neu (ssg-in-string (ssg-text (strcat "lg-feld-" (car f))) alt)) + (if (not (member neu opts)) (progn (princ (ssg-text "lg-wahl-ungueltig")) (setq neu nil)))) + (progn + (setq neu (lg-feld-pruefen (caddr f) + (ssg-in-string (ssg-text (strcat "lg-feld-" (car f))) alt))) + (if (null neu) (princ (ssg-text "lg-wert-ungueltig"))))) + ) + (setq werte (lg-wert-setzen werte (cadr f) neu)) + ) + ) + ) + werte +) + +;; ============================================================ +;; REGALREIHEN UND GAENGE +;; ============================================================ + +(defun lg-reihe-blockname (laenge tiefe) + (strcat "LG_REIHE_" (lg-mm-str laenge) "_" (lg-mm-str tiefe)) +) + +;; Der Belag steht im Namen, weil die Definition seine Schraffur enthaelt +;; und je Geometrie wiederverwendet wird. +(defun lg-gang-blockname (laenge breite belag) + (strcat "LG_GANG_" (lg-mm-str laenge) "_" (lg-mm-str breite) + (if (and belag (= (type belag) 'STR) (/= belag "")) + (strcat "_" (strcase (vl-string-translate " " "_" belag))) + "")) +) + +;; Reihe: Rechteck L x T um die Schienenlinie (0,0)-(L,0). +(defun lg-reihe-blockdef (laenge tiefe / h) + (setq h (/ tiefe 2.0)) + (lg-blockdef (lg-reihe-blockname laenge tiefe) + (append (list (lg-geom-rechteck 0.0 (- h) laenge h) + (lg-geom-linie '(0.0 0.0 0.0) (list laenge 0.0 0.0))) + (lg-attdefs-unsichtbar *lg-schema-reihe*))) +) + +;; Schraffur-Vorgabe eines Belags aus der Tabelle belag_schraffur +;; (lager.json). Rueckgabe: (muster massstab winkel-grad) oder nil (keine +;; Schraffur: Belag null, unbekannt oder ohne Muster). Rein, getestet. +(defun lg-schraffur-vorgabe (belag tabelle / e m) + (if (and belag (= (type belag) 'STR)) + (progn + (setq e (ssg-json-get tabelle belag)) + (if (and e (listp e)) + (progn + (setq m (ssg-json-get e "muster")) + (if (and m (= (type m) 'STR) (/= m "")) + (list m + (float (cond ((ssg-json-get e "massstab")) (1.0))) + (float (cond ((ssg-json-get e "winkel")) (0.0))))))))) +) + +(if (not (boundp '*lg-schraffur-gemeldet*)) (setq *lg-schraffur-gemeldet* nil)) + +;; Schraffur in eine vorhandene Blockdefinition einsetzen: umrandet die +;; erste Polylinie der Definition, Layer 0 + Farbe VONBLOCK (erbt den +;; Geschoss-Layer der Referenz). vorgabe = (muster massstab winkel-grad). +;; Rueckgabe: T oder nil (Muster unbekannt o.ae., Meldung). +(defun lg-schraffur-einsetzen (bname vorgabe / blk pl h res) + (setq res + (vl-catch-all-apply + '(lambda () + (setq blk (vla-Item (vla-get-Blocks (vla-get-ActiveDocument (vlax-get-acad-object))) bname)) + (vlax-for o blk + (if (and (null pl) (= (vla-get-ObjectName o) "AcDbPolyline")) (setq pl o))) + (setq h (vla-AddHatch blk 1 (car vorgabe) :vlax-false)) ; 1 = vordefiniert + (vla-put-Layer h "0") + (vla-put-Color h 0) ; 0 = VONBLOCK + (vla-put-PatternScale h (cadr vorgabe)) + (vla-put-PatternAngle h (* (/ (caddr vorgabe) 180.0) pi)) + (vlax-invoke h 'AppendOuterLoop (list pl)) + (vla-Evaluate h) + T))) + (if (vl-catch-all-error-p res) + (progn + (if h (vl-catch-all-apply 'vla-Delete (list h))) + (princ (ssg-textf "lg-schraffur-fehler" (list (car vorgabe) bname (vl-catch-all-error-message res)))) + nil) + res) +) + +(defun lg-gang-blockdef (laenge breite belag / h bname tab vorgabe) + (setq h (/ breite 2.0) + bname (lg-gang-blockname laenge breite belag) + tab (lg-vorgabe '("belag_schraffur") nil) + vorgabe (lg-schraffur-vorgabe belag tab)) + (if (and belag (= (type belag) 'STR) (/= belag "") + (not (assoc belag tab)) (not (member belag *lg-schraffur-gemeldet*))) + (progn + (setq *lg-schraffur-gemeldet* (cons belag *lg-schraffur-gemeldet*)) + (princ (ssg-textf "lg-schraffur-unbekannt" (list belag))))) + (lg-blockdef bname + (append (list (lg-geom-rechteck 0.0 (- h) laenge h)) + (lg-attdefs-unsichtbar *lg-schema-gang*))) + ;; Schraffur nur einmal je Definition (auch nach einer Neudefinition) + (if (and vorgabe + (not (vl-some '(lambda (ed) (= (cdr (assoc 0 ed)) "HATCH")) (lg-blockdef-objekte bname)))) + (lg-schraffur-einsetzen bname vorgabe)) + bname +) + +;; Linienobjekt (Reihe/Gang) von p1 nach p2 (WCS) einfuegen. +;; art = "reihe"|"gang", werte = Attribute ohne LAENGE/LAYER/ID. +;; Rueckgabe: ename oder nil (zu kurz). +(defun lg-linie-einfuegen (art p1 p2 werte bereich-ent / laenge quer gs lay bname ent) + (setq laenge (lg-mm-runden (distance (list (car p1) (cadr p1)) (list (car p2) (cadr p2))))) + (if (> laenge 0.0) + (progn + (setq gs (cdr (assoc "GESCHOSS" werte))) + (if (= art "reihe") + (setq quer (lg-zahl (cdr (assoc "TIEFE_mm" werte))) + lay (lg-layer "reihe" gs) + bname (progn (lg-reihe-blockdef laenge quer) (lg-reihe-blockname laenge quer))) + (setq quer (lg-zahl (cdr (assoc "BREITE_mm" werte))) + lay (lg-layer "gang" gs) + bname (lg-gang-blockdef laenge quer (cdr (assoc "BELAG" werte))))) + (setq werte (append (list (cons "LAENGE" (lg-mm-str laenge)) (cons "LAYER" lay)) werte)) + (if (setq ent (lg-block-einfuegen bname p1 (angle p1 p2) lay werte)) + (progn + (lg-attribute-setzen ent (list (cons "ID" (lg-handle ent)))) + (if bereich-ent + (lg-xdata-setzen ent (list (cons "bereich" (cons 1005 (lg-handle bereich-ent)))))) + ent)) + ) + ) +) + +;; Wie lg-linie-neu-aufbauen, danach schneidet das neue Objekt seine +;; Flaeche aus den rangniedrigeren Reihen/Gaengen heraus (lg-sperre- +;; anwenden) - fuer das Bearbeiten von Hand. Rueckgabe: neuer ename. +(defun lg-linie-neu-aufbauen-sperre (ent werte / neu) + (if (setq neu (lg-linie-neu-aufbauen ent werte)) + (lg-sperre-anwenden neu)) + neu +) + +;; Reihe/Gang mit neuen Werten (inkl. LAENGE) an derselben Stelle neu +;; aufbauen. Rueckgabe: neuer ename. +(defun lg-linie-neu-aufbauen (ent werte / ed p1 rot art laenge p2 bereich neu) + (setq ed (entget ent) + p1 (cdr (assoc 10 ed)) + rot (cond ((cdr (assoc 50 ed))) (0.0)) + art (if (wcmatch (strcase (lg-blockname ent)) "LG_REIHE_*") "reihe" "gang") + laenge (lg-zahl (cdr (assoc "LAENGE" werte))) + bereich (lg-ent-aus-handle (lg-xdata-wert ent "bereich"))) + (setq p2 (polar p1 rot laenge)) + (setq werte (vl-remove-if '(lambda (p) (member (car p) '("LAENGE" "LAYER" "ID"))) werte)) + (if (setq neu (lg-linie-einfuegen art p1 (list (car p2) (cadr p2) (caddr p1)) werte bereich)) + (entdel ent)) + neu +) + +;; ============================================================ +;; AUTO-FUELLUNG (reiner Kern, getestet) +;; ============================================================ +;; pts = Bereichskontur in LOKALEN Koordinaten: Reihen laufen entlang +X. +;; params = Alist ("gang" "tiefe" "reihen" "q_abstand" "q_breite" +;; "min_reihe" "min_gang"), Zahlen in mm. +;; Muster quer zu den Reihen (in +Y ab Unterkante der Kontur), je Modul: +;; 2 Reihen je Gang: Reihe | Gang | Reihe (benachbarte Module stossen +;; Ruecken an Ruecken) +;; 1 Reihe je Gang: Reihe | Gang +;; Es werden nur volle Module gelegt; der Rest quer zu den Reihen wird als +;; restbreite zurueckgegeben (Protokoll, Attribut RESTBREITE_mm). +;; Quergaenge (q_abstand > 0): Luecken der Breite q_breite nach jeweils +;; q_abstand Reihenlaenge, ueber alle Reihen ausgerichtet (gemessen ab +;; linker Konturkante). Hauptgaenge laufen durch die Quergaenge durch. +;; Ein Streifen gilt nur dort als belegbar, wo Unterkante, Mitte und +;; Oberkante innerhalb der Kontur liegen. +;; Optionale params: +;; "rand" = (("N" . mm) ("O" . mm) ("S" . mm) ("W" . mm)) Randgaenge +;; nach Himmelsrichtung (Nord = +Y Welt), entlang der echten +;; Konturkanten (auch L-Form), siehe lg-randgang-streifen +;; "richtung" = Drehung lokal -> Welt (Bogenmass), fuer die Himmelsrichtung +;; "sperr" = Sperrpolygone (lokal, z.B. manuelle Gaenge) +;; Randgaenge und Sperrpolygone werden aus Reihen, Haupt- und Quergaengen +;; ausgeschnitten (exakt ueber das Band jedes Streifens, ssg-poly-band-x). +;; Randgaenge an der Unter-/Oberkante verschieben den Modulstapel, an der +;; linken/rechten Kante den Bezug der Quergaenge. +;; Rueckgabe: (records restbreite module) mit +;; record = (typ p1 p2 breite), typ = "reihe" | "gang" | "quergang" | +;; "randgang", p1/p2 = Anfang/Ende der Mittellinie (lokal, 2D). +;; Randgaenge stehen am Ende der Liste. + +;; Himmelsrichtung einer Aussennormalen (Weltwinkel, Bogenmass), Nord = +Y. +;; Genau diagonale Kanten (45 Grad) zaehlen zu N/S. +(defun lg-kompass (winkel / g) + (setq g (* (/ winkel pi) 180.0)) + (while (< g 0.0) (setq g (+ g 360.0))) + (while (>= g 360.0) (setq g (- g 360.0))) + (cond + ((and (>= g (- 45.0 1e-6)) (<= g (+ 135.0 1e-6))) "N") + ((and (>= g (- 225.0 1e-6)) (<= g (+ 315.0 1e-6))) "S") + ((and (> g 135.0) (< g 225.0)) "W") + (T "O") + ) +) + +;; Laengenkorrektur eines Randgangs an einer Ecke (+ verlaengern, - kuerzen). +;; N/S-Randgaenge besitzen die Ecken: an einer konvexen Ecke wird der O/W- +;; Randgang um die Breite des N/S-Nachbarn gekuerzt (keine Doppelflaeche), +;; an einer einspringenden Ecke der N/S-Randgang um die Breite des O/W- +;; Nachbarn verlaengert (sonst bliebe dort ein freies Quadrat, in das eine +;; Reihe passt, und der Gang waere unterbrochen). +(defun lg-randgang-ecke (eigen nachbar nb konvex / ns-eigen ns-nachbar) + (setq ns-eigen (if (member eigen '("N" "S")) T nil) + ns-nachbar (if (member nachbar '("N" "S")) T nil)) + (cond + ((or (<= nb 0.0) (= eigen nachbar)) 0.0) + ((and konvex (not ns-eigen) ns-nachbar) (- nb)) + ((and (not konvex) ns-eigen (not ns-nachbar)) nb) + (T 0.0) + ) +) + +;; Kreuzprodukt der Kanten (a b) und (b c) - Vorzeichen = Knickrichtung. +(defun lg-knick (k1 k2 / a b c) + (setq a (car k1) b (cadr k1) c (cadr k2)) + (- (* (- (car b) (car a)) (- (cadr c) (cadr b))) + (* (- (cadr b) (cadr a)) (- (car c) (car b)))) +) + +;; Randgang-Streifen entlang der Konturkanten (lokale Koordinaten). +;; w = Drehung lokal -> Welt (Bogenmass), rand = Breiten je Himmelsrichtung. +;; Jede Kante bekommt den Randgang ihrer Himmelsrichtung (Aussennormale), +;; als Rechteck von der Kante nach innen; Ecken nach lg-randgang-ecke. +;; Rueckgabe: ((kompass p1 p2 breite polygon kante) ...), p1/p2 = Mittellinie. +(defun lg-randgang-streifen (pts w rand min-g / s kanten n kompasse breiten erg i k b + p q a vor nach t0 t1 nin p1 p2) + (setq pts (mapcar '(lambda (p) (list (car p) (cadr p))) pts) + s (ssg-poly-flaeche-signiert pts) + kanten (ssg-poly-kanten pts) + n (length kanten) + erg nil) + (if (and rand (> n 2) (not (equal s 0.0 1e-9))) + (progn + (setq kompasse + (mapcar '(lambda (k / a) + (setq a (angle (car k) (cadr k))) + (lg-kompass (+ w (if (> s 0.0) (- a (/ pi 2.0)) (+ a (/ pi 2.0)))))) + kanten)) + (setq breiten (mapcar '(lambda (r) (cond ((lg-zahl (cdr (assoc r rand)))) (0.0))) kompasse)) + (setq i 0) + (foreach k kanten + (setq b (nth i breiten)) + (if (> b 0.0) + (progn + (setq vor (rem (+ i n -1) n) + nach (rem (1+ i) n) + p (car k) q (cadr k) a (angle p q) + nin (if (> s 0.0) (+ a (/ pi 2.0)) (- a (/ pi 2.0)))) + (setq t0 (lg-randgang-ecke (nth i kompasse) (nth vor kompasse) (nth vor breiten) + (> (* s (lg-knick (nth vor kanten) k)) 0.0)) + t1 (lg-randgang-ecke (nth i kompasse) (nth nach kompasse) (nth nach breiten) + (> (* s (lg-knick k (nth nach kanten))) 0.0))) + (setq p1 (polar p a (- t0)) + p2 (polar q a t1)) + (if (>= (+ (distance p q) t0 t1) min-g) + (setq erg (cons (list (nth i kompasse) + (polar p1 nin (/ b 2.0)) (polar p2 nin (/ b 2.0)) b + (list p1 p2 (polar p2 nin b) (polar p1 nin b)) + k) + erg)))) + ) + (setq i (1+ i)) + ) + ) + ) + (reverse erg) +) + +;; Gesperrte x-Intervalle eines Bandes y0..y1 (je Sperrpolygon eins). +(defun lg-sperr-intervalle (sperr y0 y1 / erg iv) + (setq erg nil) + (foreach s sperr + (if (setq iv (ssg-poly-band-x s y0 y1)) (setq erg (cons iv erg)))) + erg +) + +(defun lg-streifen-intervalle (pts y0 y1 / e) + (setq e (min 0.01 (/ (- y1 y0) 4.0))) + (ssg-intervalle-schnitt + (ssg-intervalle-schnitt (ssg-poly-schnitt-y pts (+ y0 e)) + (ssg-poly-schnitt-y pts (/ (+ y0 y1) 2.0))) + (ssg-poly-schnitt-y pts (- y1 e))) +) + +;; Quergang-Luecken entlang X. Eine Luecke entsteht nur, wenn dahinter noch +;; ein Reihenstueck von mindestens min-rest Platz hat - ein Quergang am +;; Reihenende waere sinnlos und kostete nur Regalflaeche. +(defun lg-quergang-luecken (xmin xmax q-abstand q-breite min-rest / erg x) + (setq erg nil) + (if (and q-abstand (> q-abstand 0.0) q-breite (> q-breite 0.0)) + (progn + (setq x (+ xmin q-abstand)) + (while (<= (+ x q-breite min-rest) xmax) + (setq erg (cons (list x (+ x q-breite)) erg) + x (+ x q-breite q-abstand))))) + (reverse erg) +) + +;; Parameter mit Vorgabe (nil/fehlend -> vorgabe). +(defun lg-param (params k vorgabe) + (cond ((cdr (assoc k params))) (vorgabe)) +) + +(defun lg-fuellung-berechnen (pts params / bb xmin xmax ymin ymax g tf r muster pitch + y module recs luecken gang-y y-start iv yc b typ + pts-t xc m i l min-r min-g rand-str sperr sperr-t + unten oben links rechts k gesperrt) + (setq g (float (lg-param params "gang" 1000.0)) + tf (float (lg-param params "tiefe" 700.0)) + r (lg-param params "reihen" 2) + min-r (lg-param params "min_reihe" 500.0) + min-g (lg-param params "min_gang" 100.0)) + (setq muster (if (= r 1) + (list (list "reihe" tf) (list "gang" g)) + (list (list "reihe" tf) (list "gang" g) (list "reihe" tf)))) + (setq pitch (apply '+ (mapcar 'cadr muster))) + (setq bb (ssg-poly-bbox pts) + xmin (car (car bb)) ymin (cadr (car bb)) + xmax (car (cadr bb)) ymax (cadr (cadr bb))) + ;; Randgaenge + Sperrpolygone + (setq rand-str (lg-randgang-streifen pts (lg-param params "richtung" 0.0) + (lg-param params "rand" nil) min-g)) + (setq sperr (append (lg-param params "sperr" nil) (mapcar '(lambda (s) (nth 4 s)) rand-str))) + ;; Randgaenge an den Aussenkanten der Huelle verschieben Stapel und Quergang-Bezug + (setq unten 0.0 oben 0.0 links 0.0 rechts 0.0) + (foreach s rand-str + (setq k (nth 5 s)) + (cond + ((equal (cadr (car k)) (cadr (cadr k)) 1e-6) ; waagrechte Kante + (cond ((equal (cadr (car k)) ymin 1e-3) (setq unten (max unten (nth 3 s)))) + ((equal (cadr (car k)) ymax 1e-3) (setq oben (max oben (nth 3 s)))))) + ((equal (car (car k)) (car (cadr k)) 1e-6) ; senkrechte Kante + (cond ((equal (car (car k)) xmin 1e-3) (setq links (max links (nth 3 s)))) + ((equal (car (car k)) xmax 1e-3) (setq rechts (max rechts (nth 3 s)))))) + ) + ) + (setq luecken (lg-quergang-luecken (+ xmin links) (- xmax rechts) (lg-param params "q_abstand" 0.0) + (lg-param params "q_breite" 0.0) min-r)) + (setq y (+ ymin unten) y-start y module 0 recs nil gang-y nil) + (while (<= (+ y pitch) (+ (- ymax oben) 1e-6)) + (foreach m muster + (setq typ (car m) b (cadr m) yc (+ y (/ b 2.0))) + (setq iv (lg-streifen-intervalle pts y (+ y b)) + gesperrt (lg-sperr-intervalle sperr y (+ y b))) + (if (= typ "reihe") + (progn + (setq iv (ssg-intervalle-minus (ssg-intervalle-minus iv luecken) gesperrt)) + (foreach i iv + (if (>= (- (cadr i) (car i)) min-r) + (setq recs (cons (list "reihe" (list (car i) yc) (list (cadr i) yc) b) recs))))) + (progn + (setq gang-y (cons (list y (+ y b)) gang-y)) + (foreach i (ssg-intervalle-minus iv gesperrt) + (if (>= (- (cadr i) (car i)) min-g) + (setq recs (cons (list "gang" (list (car i) yc) (list (cadr i) yc) b) recs)))))) + (setq y (+ y b)) + ) + (setq module (1+ module)) + ) + ;; Quergaenge: senkrechte Streifen in den Luecken, nur im belegten + ;; Modulbereich und ohne die Hauptgaenge (die zaehlen schon als Gang). + (if (and luecken (> module 0)) + (progn + (setq pts-t (mapcar '(lambda (q) (list (cadr q) (car q))) pts) + sperr-t (mapcar '(lambda (s) (mapcar '(lambda (q) (list (cadr q) (car q))) s)) sperr) + gang-y (reverse gang-y)) + (foreach l luecken + (setq xc (/ (+ (car l) (cadr l)) 2.0)) + (setq iv (ssg-intervalle-schnitt (lg-streifen-intervalle pts-t (car l) (cadr l)) + (list (list y-start y)))) + (setq iv (ssg-intervalle-minus (ssg-intervalle-minus iv gang-y) + (lg-sperr-intervalle sperr-t (car l) (cadr l)))) + (foreach i iv + (if (>= (- (cadr i) (car i)) min-g) + (setq recs (cons (list "quergang" (list xc (car i)) (list xc (cadr i)) + (- (cadr l) (car l))) + recs)))) + ) + ) + ) + (foreach s rand-str + (setq recs (cons (list "randgang" (cadr s) (caddr s) (cadddr s)) recs))) + (list (reverse recs) (- (- ymax oben) y) module) +) + +;; Richtung der Reihen als Vorgabe: Winkel der laengsten Konturkante (Grad, +;; auf [0,180) normiert). +(defun lg-kontur-richtung (pts / best w l) + (setq best nil l 0.0) + (foreach k (ssg-poly-kanten pts) + (if (> (distance (list (car (car k)) (cadr (car k))) (list (car (cadr k)) (cadr (cadr k)))) l) + (setq l (distance (list (car (car k)) (cadr (car k))) (list (car (cadr k)) (cadr (cadr k)))) + best k))) + (if best + (progn + (setq w (* (/ (angle (car best) (cadr best)) pi) 180.0)) + (while (>= w 180.0) (setq w (- w 180.0))) + (if (equal w 180.0 1e-6) 0.0 w)) + 0.0) +) + +;; ============================================================ +;; LAGERBEREICHE +;; ============================================================ + +(defun lg-bereich-blockdef ( / ) + (lg-blockdef "LG_BEREICH" + (append + (list (lg-geom-kreis '(0.0 0.0 0.0) 250.0) + ;; Pfeil zeigt die Reihenrichtung (Einfuegedrehung = RICHTUNG) + (lg-geom-linie '(250.0 0.0 0.0) '(800.0 0.0 0.0)) + (lg-geom-linie '(800.0 0.0 0.0) '(650.0 80.0 0.0)) + (lg-geom-linie '(800.0 0.0 0.0) '(650.0 -80.0 0.0)) + (lg-geom-attdef "NR" "" '(-100.0 -80.0 0.0) 160.0 T)) + (lg-attdefs-unsichtbar (vl-remove-if '(lambda (p) (= (car p) "NR")) *lg-schema-bereich*)))) +) + +(defun lg-bereich-naechste-nr ( / nrs) + (setq nrs (mapcar '(lambda (e) (atoi (cond ((cdr (assoc "NR" (ssg-attrib-read e)))) ("0")))) + (lg-alle-bloecke "LG_BEREICH"))) + (if nrs (1+ (apply 'max nrs)) 1) +) + +(defun lg-bereich-vorgaben (pts / ) + (list (cons "GANGBREITE_mm" (lg-mm-str (lg-zahl (lg-vorgabe '("bereich" "gangbreite_mm") 1000)))) + (cons "REGALTIEFE_mm" (lg-mm-str (lg-zahl (lg-vorgabe '("bereich" "regaltiefe_mm") 700)))) + (cons "REIHEN_JE_GANG" (itoa (fix (lg-zahl (lg-vorgabe '("bereich" "reihen_je_gang") 2))))) + (cons "SCHIENEN_JE_GANG" (itoa (fix (lg-zahl (lg-vorgabe '("bereich" "schienen_je_gang") 1))))) + (cons "QUERGANG_ABSTAND_mm" (lg-mm-str (lg-zahl (lg-vorgabe '("bereich" "quergang_abstand_mm") 0)))) + (cons "QUERGANG_BREITE_mm" (lg-mm-str (lg-zahl (lg-vorgabe '("bereich" "quergang_breite_mm") 1200)))) + (cons "RICHTUNG" (lg-mm-str (if pts (lg-kontur-richtung pts) 0.0))) + (cons "HAENGEEBENEN" (lg-vorgabe '("reihe" "haengeebenen") "2300/1200;1100/1100")) + (cons "BELAG" (lg-vorgabe '("bereich" "belag") "spanplatte")) + (cons "RANDGANG_N_mm" (lg-mm-str (lg-zahl (lg-vorgabe '("bereich" "randgang_mm" "n") 0)))) + (cons "RANDGANG_O_mm" (lg-mm-str (lg-zahl (lg-vorgabe '("bereich" "randgang_mm" "o") 0)))) + (cons "RANDGANG_S_mm" (lg-mm-str (lg-zahl (lg-vorgabe '("bereich" "randgang_mm" "s") 0)))) + (cons "RANDGANG_W_mm" (lg-mm-str (lg-zahl (lg-vorgabe '("bereich" "randgang_mm" "w") 0))))) +) + +;; Randgang-Breiten eines Bereichs (Attribute) fuer lg-fuellung-berechnen. +;; Fehlt ein Attribut (Altbestand), gilt 0. +(defun lg-bereich-rand (a) + (mapcar '(lambda (r) (cons r (cond ((lg-zahl (cdr (assoc (strcat "RANDGANG_" r "_mm") a)))) (0.0)))) + '("N" "O" "S" "W")) +) + +;; Lagerbereich, in dessen Kontur pt liegt (gleiches Geschoss), oder nil. +(defun lg-bereich-fuer-punkt (pt gs / ) + (vl-some '(lambda (b / k) + (if (and (= (cdr (assoc "GESCHOSS" (ssg-attrib-read b))) (vl-princ-to-string gs)) + (setq k (lg-ent-aus-handle (lg-xdata-wert b "kontur"))) + (ssg-punkt-in-poly-p pt (ssg-lwpoly-punkte k))) + b)) + (lg-alle-bloecke "LG_BEREICH")) +) + +;; Grundriss einer Reihe/eines Gangs als Rechteck (WCS, 2D) aus +;; Einfuegepunkt, Drehung, LAENGE und Breite (Gang BREITE_mm, Reihe +;; TIEFE_mm) - beide liegen mittig auf ihrer Linie. +(defun lg-linie-polygon (ent / ed a p1 p2 rot l b n) + (setq ed (entget ent) a (ssg-attrib-read ent) + p1 (cdr (assoc 10 ed)) p1 (list (car p1) (cadr p1)) + rot (cond ((cdr (assoc 50 ed))) (0.0)) + l (cond ((lg-zahl (cdr (assoc "LAENGE" a)))) (0.0)) + b (cond ((lg-zahl (cdr (assoc "BREITE_mm" a)))) ((lg-zahl (cdr (assoc "TIEFE_mm" a)))) (0.0)) + p2 (polar p1 rot l) + n (+ rot (/ pi 2.0))) + (list (polar p1 n (/ b -2.0)) (polar p2 n (/ b -2.0)) (polar p2 n (/ b 2.0)) (polar p1 n (/ b 2.0))) +) + +(defun lg-quelle (ent) + (cdr (assoc "QUELLE" (ssg-attrib-read ent))) +) + +(defun lg-manuell-p (ent) + (= (lg-quelle ent) "manuell") +) + +;; Von Hand gesetzt oder geaendert: bleibt bei jeder Neuberechnung stehen. +(defun lg-von-hand-p (ent) + (if (member (lg-quelle ent) '("manuell" "auto_geaendert")) T) +) + +;; Rang einer QUELLE fuer das Ausschneiden: ein Objekt schneidet nur aus +;; rangniedrigeren heraus (auto < auto_geaendert < manuell). Rein, getestet. +(defun lg-quelle-rang (q) + (cond ((= q "manuell") 2) ((= q "auto_geaendert") 1) (T 0)) +) + +;; Reiner Kern: was von einer Reihe/einem Gang uebrig bleibt, wenn die +;; Sperrpolygone herausgeschnitten werden. Lokal: Linie von 0 bis laenge +;; entlang +X, Breite b mittig darauf; sperr = Polygone im selben System. +;; Stuecke unter min-l fallen weg. Rueckgabe ((x0 x1) ...) - leer = ganz +;; ueberdeckt - oder 'unveraendert, wenn kein Polygon die Flaeche schneidet. +(defun lg-linie-zuschnitt (laenge b sperr min-l / ganz rest) + (setq ganz (list (list 0.0 (float laenge))) + rest (ssg-intervalle-minus ganz (lg-sperr-intervalle sperr (/ b -2.0) (/ b 2.0)))) + (if (equal rest ganz 1e-6) + 'unveraendert + (vl-remove-if '(lambda (i) (< (- (cadr i) (car i)) min-l)) rest)) +) + +;; Sperrflaeche eines von Hand gesetzten oder geaenderten Objekts (Reihe/ +;; Gang) auf die rangniedrigeren Reihen und Gaenge desselben Geschosses +;; anwenden (lg-quelle-rang: ein manuelles Objekt schneidet aus auto und +;; auto_geaendert, ein geaendertes nur aus auto): ueberdeckte Stuecke werden +;; herausgeschnitten, die Reste behalten ihre Attribute (QUELLE, BEREICH ...). +;; Bewusst KEINE Neuberechnung des Bereichs - sonst kaemen von Hand +;; geloeschte Objekte zurueck und von Hand geaenderte wuerden ersetzt. +;; Rueckgabe: Anzahl zugeschnittener Objekte. +(defun lg-sperre-anwenden (sperr-ent / gs rang poly n a ed p1 rot art l b min-l lok stuecke werte bereich) + (setq a (ssg-attrib-read sperr-ent) + gs (cdr (assoc "GESCHOSS" a)) + rang (lg-quelle-rang (cdr (assoc "QUELLE" a))) + poly (lg-linie-polygon sperr-ent) + n 0) + (foreach k (append (lg-alle-bloecke "LG_REIHE_*") (lg-alle-bloecke "LG_GANG_*")) + (setq a (ssg-attrib-read k)) + (if (and (not (equal k sperr-ent)) + (= (cdr (assoc "GESCHOSS" a)) gs) + (< (lg-quelle-rang (cdr (assoc "QUELLE" a))) rang)) + (progn + (setq ed (entget k) + p1 (cdr (assoc 10 ed)) + rot (cond ((cdr (assoc 50 ed))) (0.0)) + art (if (wcmatch (strcase (lg-blockname k)) "LG_REIHE_*") "reihe" "gang") + l (cond ((lg-zahl (cdr (assoc "LAENGE" a)))) (0.0)) + b (cond ((lg-zahl (cdr (assoc (if (= art "reihe") "TIEFE_mm" "BREITE_mm") a)))) (0.0)) + min-l (lg-zahl (if (= art "reihe") + (lg-vorgabe '("bereich" "min_reihenlaenge_mm") 500) + (lg-vorgabe '("bereich" "min_ganglaenge_mm") 100))) + lok (ssg-poly-drehen (mapcar '(lambda (q) (list (- (car q) (car p1)) (- (cadr q) (cadr p1)))) poly) + (- rot) '(0.0 0.0)) + stuecke (lg-linie-zuschnitt l b (list lok) min-l)) + (if (not (eq stuecke 'unveraendert)) + (progn + (setq werte (vl-remove-if '(lambda (p) (member (car p) '("LAENGE" "LAYER" "ID"))) a) + bereich (lg-ent-aus-handle (lg-xdata-wert k "bereich"))) + (foreach s stuecke + (lg-linie-einfuegen art (polar p1 rot (car s)) (polar p1 rot (cadr s)) werte bereich)) + (entdel k) + (setq n (1+ n))))))) + n +) + +;; Alle Kinder (Reihen/Gaenge) eines Bereichs. +(defun lg-bereich-kinder (bereich-ent / h) + (setq h (lg-handle bereich-ent)) + (vl-remove-if-not '(lambda (e) (equal (lg-xdata-wert e "bereich") h)) + (append (lg-alle-bloecke "LG_REIHE_*") (lg-alle-bloecke "LG_GANG_*"))) +) + +;; Von Hand gesetzte Reihen/Gaenge (QUELLE manuell) desselben Geschosses, +;; die nicht mit einem Bereich verknuepft sind, aber mit ihrer Mitte in der +;; Kontur pts liegen (z.B. Altbestand von LG_REIHE vor der Verknuepfung). +(defun lg-manuelle-in-kontur (gs pts / a p q) + (vl-remove-if-not + '(lambda (e) + (setq a (ssg-attrib-read e)) + (if (and (= (cdr (assoc "GESCHOSS" a)) gs) + (= (cdr (assoc "QUELLE" a)) "manuell") + (null (lg-xdata-wert e "bereich"))) + (progn + (setq p (lg-linie-polygon e) + q (list (/ (+ (car (car p)) (car (caddr p))) 2.0) (/ (+ (cadr (car p)) (cadr (caddr p))) 2.0))) + (ssg-punkt-in-poly-p q pts)))) + (append (lg-alle-bloecke "LG_REIHE_*") (lg-alle-bloecke "LG_GANG_*"))) +) + +;; Reihen/Gaenge eines Bereichs aus seinen Attributen (neu) erzeugen. +;; Die automatisch erzeugten Kinder (QUELLE auto) werden vorher entfernt +;; (Sektions-Neuaufbau statt Feld-Splice). Von Hand gesetzte (manuell) und +;; von Hand geaenderte (auto_geaendert) Reihen und Gaenge bleiben stehen und +;; sparen ihre Flaeche aus - ebenso unverknuepfte manuelle Objekte in der +;; Kontur. Rueckgabe: (reihen gaenge restbreite). +(defun lg-bereich-fuellen (bereich-ent / a kontur pts w pts-l erg z gs + n-reihen n-gaenge werte-reihe werte-gang p1 p2 behalten) + (setq a (ssg-attrib-read bereich-ent) + kontur (lg-ent-aus-handle (lg-xdata-wert bereich-ent "kontur"))) + (if (null kontur) + (progn (princ (ssg-text "lg-bereich-ohne-kontur")) nil) + (progn + (setq behalten nil) + (foreach k (lg-bereich-kinder bereich-ent) + (if (lg-von-hand-p k) + (setq behalten (cons k behalten)) + (entdel k))) + (setq pts (ssg-lwpoly-punkte kontur) + behalten (append behalten (lg-manuelle-in-kontur (cdr (assoc "GESCHOSS" a)) pts))) + (if behalten + (princ (ssg-textf "lg-bereich-beibehalten" (list (itoa (length behalten)) (cdr (assoc "NR" a)))))) + (setq z (if (caddr (car pts)) (caddr (car pts)) 0.0) + w (* (/ (cond ((lg-zahl (cdr (assoc "RICHTUNG" a)))) (0.0)) 180.0) pi) + pts-l (ssg-poly-drehen pts (- w) '(0.0 0.0))) + (setq erg (lg-fuellung-berechnen pts-l + (list (cons "gang" (lg-zahl (cdr (assoc "GANGBREITE_mm" a)))) + (cons "tiefe" (lg-zahl (cdr (assoc "REGALTIEFE_mm" a)))) + (cons "reihen" (atoi (cdr (assoc "REIHEN_JE_GANG" a)))) + (cons "q_abstand" (lg-zahl (cdr (assoc "QUERGANG_ABSTAND_mm" a)))) + (cons "q_breite" (lg-zahl (cdr (assoc "QUERGANG_BREITE_mm" a)))) + (cons "min_reihe" (lg-zahl (lg-vorgabe '("bereich" "min_reihenlaenge_mm") 500))) + (cons "min_gang" (lg-zahl (lg-vorgabe '("bereich" "min_ganglaenge_mm") 100))) + (cons "rand" (lg-bereich-rand a)) + (cons "richtung" w) + (cons "sperr" (mapcar '(lambda (g) (ssg-poly-drehen (lg-linie-polygon g) (- w) '(0.0 0.0))) + behalten))))) + (setq gs (cdr (assoc "GESCHOSS" a)) n-reihen 0 n-gaenge 0) + (setq werte-reihe + (list (cons "GESCHOSS" gs) (cons "TIEFE_mm" (cdr (assoc "REGALTIEFE_mm" a))) + (cons "HAENGEEBENEN" (cdr (assoc "HAENGEEBENEN" a))) + (cons "SCHIENEN" (cdr (assoc "SCHIENEN_JE_GANG" a))) + (cons "BEREICH" (cdr (assoc "NR" a))) (cons "QUELLE" "auto"))) + (foreach r (car erg) + (setq p1 (ssg-punkt-drehen (cadr r) w '(0.0 0.0)) + p2 (ssg-punkt-drehen (caddr r) w '(0.0 0.0)) + p1 (list (car p1) (cadr p1) z) + p2 (list (car p2) (cadr p2) z)) + (if (= (car r) "reihe") + (if (lg-linie-einfuegen "reihe" p1 p2 werte-reihe bereich-ent) + (setq n-reihen (1+ n-reihen))) + (progn + (setq werte-gang + (list (cons "GESCHOSS" gs) (cons "BREITE_mm" (lg-mm-str (cadddr r))) + (cons "ART" (car r)) (cons "BELAG" (cdr (assoc "BELAG" a))) + (cons "BEREICH" (cdr (assoc "NR" a))) (cons "QUELLE" "auto"))) + (if (lg-linie-einfuegen "gang" p1 p2 werte-gang bereich-ent) + (setq n-gaenge (1+ n-gaenge))))) + ) + (lg-attribute-setzen bereich-ent (list (cons "RESTBREITE_mm" (lg-mm-str (cadr erg))))) + (princ (ssg-textf "lg-bereich-gefuellt" + (list (cdr (assoc "NR" a)) (itoa n-reihen) (itoa n-gaenge) (lg-mm-str (cadr erg))))) + (list n-reihen n-gaenge (cadr erg)) + ) + ) +) + +;; Bereich zur Kontur anlegen und fuellen (nicht interaktiv). +;; werte = Bereichsparameter (lg-bereich-vorgaben + Dialog); GESCHOSS muss +;; gesetzt sein. Rueckgabe: ename des Bereich-Blocks oder nil. +(defun lg-bereich-anlegen (kontur werte / blk) + (if (setq blk (lg-bereich-block-anlegen kontur werte)) + (progn (lg-bereich-fuellen blk) blk)) +) + +;; Nur den Bereich-Block zur Kontur anlegen und verknuepfen, ohne Fuellung +;; (Geschoss kopieren uebernimmt die Kinder 1:1). NR, LAYER und ID werden +;; neu vergeben. Rueckgabe: ename oder nil. +(defun lg-bereich-block-anlegen (kontur werte / gs nr lay pts bb pt blk) + (setq gs (cdr (assoc "GESCHOSS" werte)) + nr (lg-bereich-naechste-nr) + lay (lg-layer "bereich" gs)) + (setq kontur (lg-kontur-uebernehmen kontur lay)) + (setq pts (ssg-lwpoly-punkte kontur) + bb (ssg-poly-bbox pts)) + (setq pt (list (/ (+ (car (car bb)) (car (cadr bb))) 2.0) + (/ (+ (cadr (car bb)) (cadr (cadr bb))) 2.0) + (if (caddr (car pts)) (caddr (car pts)) 0.0))) + (if (not (ssg-punkt-in-poly-p pt pts)) (setq pt (car pts))) + (lg-bereich-blockdef) + ;; vorne angefuegt: assoc findet die neuen Werte vor evtl. alten (Kopie) + (setq werte (append (list (cons "NR" (itoa nr)) (cons "LAYER" lay) + (cons "ID" (strcat "B" (itoa nr)))) + werte)) + (if (setq blk (lg-block-einfuegen "LG_BEREICH" pt + (* (/ (cond ((lg-zahl (cdr (assoc "RICHTUNG" werte)))) (0.0)) 180.0) pi) + lay werte)) + (progn + (lg-xdata-setzen blk (list (cons "kontur" (cons 1005 (lg-handle kontur))))) + (lg-xdata-setzen kontur (list (cons "besitzer" (cons 1005 (lg-handle blk))) + (cons "rolle" (cons 1000 "bereich")))) + blk)) +) + +;; ============================================================ +;; GOH_RACK-ERKENNUNG (lg-racks-sammeln, Befehl LG_REGALE) +;; ============================================================ +;; goh_rack ist ein parametrischer BricsCAD-Block (MCAD-Parametrik), KEIN +;; AutoCAD-Dynamikblock: seine Referenzen tragen einen anonymen Namen +;; ("*U29"), IsDynamicBlock ist FALSE, und der echte Name sowie die +;; Parameter stehen nur in den Objekteigenschaften: +;; (getpropertyvalue ent "EffectiveName") -> "goh_rack" +;; (getpropertyvalue ent "Laenge~MCAD") -> "7187.52 mm" +;; Gefunden werden Racks direkt im Modellbereich, in assoziativen Arrays +;; (anonymer Block mit Referenzen), in MINSERT-Rastern und in beliebig +;; verschachtelten Bloecken (bis *lg-rack-max-tiefe*). Blockname und +;; Parameternamen aus lager.json "goh_rack". +;; Ergebnis je Rack: (name laenge-mm ebenen mitte top-ent), Zugriff ueber +;; lg-rk-*. top-ent = oberstes INSERT im Modellbereich, in dem das Rack +;; steckt (bei einem direkt eingefuegten Rack es selbst) - ueber dessen +;; Layer _G ordnet lg-rack-geschoss das Geschoss zu. + +(setq *lg-rack-max-tiefe* 8) + +(defun lg-rk-name (r) (nth 0 r)) +(defun lg-rk-laenge (r) (nth 1 r)) +(defun lg-rk-ebenen (r) (nth 2 r)) +(defun lg-rk-mitte (r) (nth 3 r)) +(defun lg-rk-top (r) (nth 4 r)) + +;; Reiner Kern: Zahl aus einem Parameterwert. MCAD-Parameter kommen als +;; Text mit Einheit ("700 mm", "71.8752 cm", "12.9 m") - auf mm umgerechnet; +;; ohne Einheit unveraendert. Unbekannte Einheit/kein Zahlanfang -> nil. +(defun lg-zahl-einheit (v / s i c ziffern zahl rest faktor) + (cond + ((numberp v) (float v)) + ((= (type v) 'STR) + (setq s (vl-string-translate "," "." (vl-string-trim " \t\r\n" v)) + i 1 ziffern "") + (while (and (<= i (strlen s)) + (setq c (substr s i 1)) + (or (wcmatch c "#") (member c '("." "-" "+")))) + (setq ziffern (strcat ziffern c) i (1+ i))) + (setq zahl (if (/= ziffern "") (distof ziffern 2)) + rest (strcase (vl-string-trim " \t" (substr s i))) + faktor (cdr (assoc rest '(("" . 1.0) ("MM" . 1.0) ("CM" . 10.0) ("DM" . 100.0) ("M" . 1000.0))))) + (if (and zahl faktor) (* zahl faktor))) + (T nil)) +) + +;; Reiner Kern: ein echter Blockname (nicht leer, nicht anonym "*U.."). +(defun lg-name-brauchbar-p (s) + (if (and (= (type s) 'STR) (/= s "") (/= (substr s 1 1) "*")) T) +) + +;; Objekteigenschaft lesen, nil wenn es sie nicht gibt. +(defun lg-prop (ent name / r) + (setq r (vl-catch-all-apply 'getpropertyvalue (list ent name))) + (if (vl-catch-all-error-p r) nil r) +) + +;; Name, unter dem eine Referenz gefuehrt wird: Objekteigenschaft +;; EffectiveName (auch fuer MCAD-Bloecke), sonst ActiveX-EffectiveName, +;; sonst DXF-Blockname. +(defun lg-rack-name (ent / n) + (cond + ((lg-name-brauchbar-p (setq n (lg-prop ent "EffectiveName"))) n) + (T (lg-blockname ent))) +) + +(defun lg-rack-p (ent / n) + (setq n (lg-rack-name ent)) + (and n (= (strcase n) (strcase *lg-rack-suchname*))) +) + +;; MCAD-Parameter (Kennung "~MCAD" in listallproperties, Gross-/ +;; Kleinschreibung egal). +(defun lg-mcad-param (ent name / props id soll) + (setq props (vl-catch-all-apply 'listallproperties (list ent)) + soll (strcase (strcat name "~MCAD"))) + (if (and (not (vl-catch-all-error-p props)) (listp props) + (setq id (vl-some '(lambda (p / s) + (setq s (vl-princ-to-string p)) + (if (= (strcase s) soll) s)) + props))) + (lg-prop ent id)) +) + +;; Dynamische Blockeigenschaft (AutoCAD-Dynamikbloecke). +(defun lg-dyn-param (ent name / r) + (setq r (vl-catch-all-apply + '(lambda ( / obj) + (setq obj (vlax-ename->vla-object ent)) + (if (and (vlax-property-available-p obj 'IsDynamicBlock) + (= :vlax-true (vla-get-IsDynamicBlock obj))) + (vl-some '(lambda (p) + (if (= (strcase (vla-get-PropertyName p)) (strcase name)) + (vlax-variant-value (vla-get-Value p)))) + (vlax-invoke obj 'GetDynamicBlockProperties)))))) + (if (vl-catch-all-error-p r) nil r) +) + +;; Parameterwert einer Referenz: MCAD-Parameter vor dynamischer +;; Eigenschaft vor Attribut vor konstanter ATTDEF-Vorgabe. +(defun lg-rack-param (ent name / w) + (cond + ((setq w (lg-mcad-param ent name)) w) + ((setq w (lg-dyn-param ent name)) w) + ((setq w (lg-wert-fuer-tag name (ssg-attrib-read ent))) w) + ((lg-wert-fuer-tag name + (mapcar '(lambda (ed) (cons (cdr (assoc 2 ed)) (cdr (assoc 1 ed)))) + (vl-remove-if-not '(lambda (ed) (= (cdr (assoc 0 ed)) "ATTDEF")) + (lg-blockdef-objekte (cdr (assoc 2 (entget ent)))))))) + ) +) + +;; --- Transformationen: tf = (ursprung x-achse y-achse z-achse), Achsen +;; mit Skalierung; p' = o + px*ax + py*ay + pz*az (rein, getestet) --- +(defun lg-v3 (p) (list (float (car p)) (float (cadr p)) (if (caddr p) (float (caddr p)) 0.0))) +(defun lg-v+ (a b) (mapcar '+ a b)) +(defun lg-v* (v k) (mapcar '(lambda (x) (* x k)) v)) +(defun lg-tf-id () '((0.0 0.0 0.0) (1.0 0.0 0.0) (0.0 1.0 0.0) (0.0 0.0 1.0))) +(defun lg-tf-vek (tf v / v3) + (setq v3 (lg-v3 v)) + (lg-v+ (lg-v* (nth 1 tf) (car v3)) (lg-v+ (lg-v* (nth 2 tf) (cadr v3)) (lg-v* (nth 3 tf) (caddr v3)))) +) +(defun lg-tf-pkt (tf p) (lg-v+ (car tf) (lg-tf-vek tf p))) +;; innen ist im System von aussen beschrieben -> Gesamttransformation +(defun lg-tf-verketten (aussen innen) + (list (lg-tf-pkt aussen (car innen)) (lg-tf-vek aussen (nth 1 innen)) + (lg-tf-vek aussen (nth 2 innen)) (lg-tf-vek aussen (nth 3 innen))) +) + +(defun lg-skal (s) (if (and s (not (equal s 0.0 1e-12))) s 1.0)) + +;; Transformation einer INSERT (entget-Liste) im System ihres Containers. +;; sp/zl = MINSERT-Spalte/-Zeile (0/0 = Basis). Beruecksichtigt Extrusion +;; (210), Drehung, Skalierung, Rasterabstaende (44/45, entlang der +;; gedrehten Achsen) und den Basispunkt der Blockdefinition. +(defun lg-insert-tf (ed sp zl / nor rot ax ay az xr yr o bp) + (setq nor (lg-v3 (cond ((cdr (assoc 210 ed))) ('(0.0 0.0 1.0)))) + rot (cond ((cdr (assoc 50 ed))) (0.0))) + (if (equal nor '(0.0 0.0 1.0) 1e-12) + (setq ax '(1.0 0.0 0.0) ay '(0.0 1.0 0.0) az '(0.0 0.0 1.0)) + (setq ax (lg-v3 (trans '(1.0 0.0 0.0) nor 0 T)) + ay (lg-v3 (trans '(0.0 1.0 0.0) nor 0 T)) + az (lg-v3 (trans '(0.0 0.0 1.0) nor 0 T)))) + (setq xr (lg-v+ (lg-v* ax (cos rot)) (lg-v* ay (sin rot))) + yr (lg-v+ (lg-v* ax (- (sin rot))) (lg-v* ay (cos rot))) + o (lg-tf-pkt (list '(0.0 0.0 0.0) ax ay az) (cdr (assoc 10 ed))) + o (lg-v+ o (lg-v+ (lg-v* xr (* sp (cond ((cdr (assoc 44 ed))) (0.0)))) + (lg-v* yr (* zl (cond ((cdr (assoc 45 ed))) (0.0))))))) + (setq xr (lg-v* xr (lg-skal (cdr (assoc 41 ed)))) + yr (lg-v* yr (lg-skal (cdr (assoc 42 ed)))) + az (lg-v* az (lg-skal (cdr (assoc 43 ed)))) + bp (lg-v3 (cond ((cdr (assoc 10 (tblsearch "BLOCK" (cdr (assoc 2 ed)))))) ('(0.0 0.0 0.0))))) + ;; der Basispunkt der Definition liegt auf dem Einfuegepunkt + (list (lg-v+ o (lg-v* (lg-tf-vek (list nil xr yr az) bp) -1.0)) xr yr az) +) + +;; Bounding-Box (min max) eines Objekts in den Koordinaten seines +;; Containers, nil wenn nicht ermittelbar. +(defun lg-bbox (ent / r) + (setq r (vl-catch-all-apply + '(lambda ( / ll ur) + (vla-GetBoundingBox (vlax-ename->vla-object ent) 'll 'ur) + (list (lg-v3 (vlax-safearray->list ll)) (lg-v3 (vlax-safearray->list ur)))))) + (if (vl-catch-all-error-p r) nil r) +) + +;; Enthaelt die Blockdefinition (rekursiv) ein Rack? Gemerkt in +;; *lg-rack-cache*, Zirkelschutz ueber *lg-rack-pfad*, XRefs ausgenommen. +(defun lg-rack-enthaelt-p (bname tiefe / k vorh def res) + (setq k (strcase bname)) + (cond + ((setq vorh (assoc k *lg-rack-cache*)) (cdr vorh)) + ((or (>= tiefe *lg-rack-max-tiefe*) (member k *lg-rack-pfad*)) nil) + (T + (setq def (tblsearch "BLOCK" bname) res nil) + (if (and def (= 0 (logand (cond ((cdr (assoc 70 def))) (0)) 4))) + (progn + (setq *lg-rack-pfad* (cons k *lg-rack-pfad*)) + (setq res (vl-some '(lambda (ed) + (if (= (cdr (assoc 0 ed)) "INSERT") + (or (lg-rack-p (cdr (assoc -1 ed))) + (lg-rack-enthaelt-p (cdr (assoc 2 ed)) (1+ tiefe))))) + (lg-blockdef-objekte bname))) + (setq *lg-rack-pfad* (cdr *lg-rack-pfad*)))) + (setq *lg-rack-cache* (cons (cons k res) *lg-rack-cache*)) + res)) +) + +;; Eine INSERT pruefen (inkl. MINSERT-Raster) bzw. in ihre Definition +;; absteigen. ptf = Transformation des Containers nach Welt. +(defun lg-rack-insert (ent ptf tiefe top / ed spalten zeilen raster sp zl ctf bb name laenge ebenen) + (setq ed (entget ent) + spalten (max 1 (cond ((cdr (assoc 70 ed))) (1))) + zeilen (max 1 (cond ((cdr (assoc 71 ed))) (1))) + raster (> (* spalten zeilen) 1)) + (cond + ((lg-rack-p ent) + (setq name (lg-rack-name ent) + laenge (lg-zahl-einheit (lg-rack-param ent *lg-rack-param-laenge*)) + ebenen (lg-zahl-einheit (lg-rack-param ent *lg-rack-param-ebenen*)) + bb (if (not raster) (lg-bbox ent)) + sp 0) + (repeat spalten + (setq zl 0) + (repeat zeilen + (setq *lg-racks* + (cons (list name laenge ebenen + (if bb + (lg-tf-pkt ptf (mapcar '(lambda (a b) (/ (+ a b) 2.0)) (car bb) (cadr bb))) + (car (lg-tf-verketten ptf (lg-insert-tf ed sp zl)))) + top) + *lg-racks*) + zl (1+ zl))) + (setq sp (1+ sp)))) + ((and (< tiefe *lg-rack-max-tiefe*) (lg-rack-enthaelt-p (cdr (assoc 2 ed)) 0)) + (setq sp 0) + (repeat spalten + (setq zl 0) + (repeat zeilen + (setq ctf (lg-tf-verketten ptf (lg-insert-tf ed sp zl))) + (foreach sub (lg-blockdef-objekte (cdr (assoc 2 ed))) + (if (= (cdr (assoc 0 sub)) "INSERT") + (lg-rack-insert (cdr (assoc -1 sub)) ctf (1+ tiefe) top))) + (setq zl (1+ zl))) + (setq sp (1+ sp)))) + ) +) + +;; Alle goh_racks im Modellbereich (Blockname/Parameter aus lager.json). +;; Rueckgabe: Liste (name laenge-mm ebenen mitte top-ent), Fundreihenfolge. +(defun lg-racks-sammeln ( / ) + (lg-racks-finden (lg-vorgabe '("goh_rack" "blockname") "goh_rack") + (lg-vorgabe '("goh_rack" "param_laenge") "Laenge") + (lg-vorgabe '("goh_rack" "param_ebenen") "Ebenen")) +) + +;; Wie lg-racks-sammeln, Blockname und Parameternamen als Argument. +(defun lg-racks-finden (name p-laenge p-ebenen / ss i) + (setq *lg-rack-suchname* name + *lg-rack-param-laenge* p-laenge + *lg-rack-param-ebenen* p-ebenen + *lg-racks* nil *lg-rack-cache* nil *lg-rack-pfad* nil) + (if (setq ss (ssget "_X" '((0 . "INSERT") (410 . "Model")))) + (progn + (setq i 0) + (repeat (sslength ss) + (lg-rack-insert (ssname ss i) (lg-tf-id) 0 (ssname ss i)) + (setq i (1+ i))))) + (reverse *lg-racks*) +) + +;; Reiner Kern: Racks je Geschoss zusammenfassen. eintraege = ((geschoss +;; laenge-mm ebenen) ...), geschoss nil = nicht zuordenbar, fehlende Laenge/ +;; Ebenen zaehlen 0. Rueckgabe ((geschoss anzahl summe-laenge +;; summe-laenge-x-ebenen) ...), nach Geschoss sortiert, nil zuletzt. +(defun lg-racks-auswerten (eintraege / erg vorh l k) + (setq erg nil) + (foreach x eintraege + (setq l (cond ((cadr x)) (0.0)) + k (* l (cond ((caddr x)) (0.0))) + vorh (assoc (car x) erg)) + (setq erg (if vorh + (subst (list (car x) (1+ (cadr vorh)) (+ (caddr vorh) l) (+ (cadddr vorh) k)) vorh erg) + (cons (list (car x) 1 l k) erg)))) + (vl-sort erg '(lambda (a b) (cond ((null (car a)) nil) ((null (car b)) T) (T (< (car a) (car b)))))) +) + +;; Befehl LG_REGALE: goh_racks zaehlen, Anzahl, Laenge und Laenge x Ebenen +;; (Haengekapazitaet) je Geschoss in der Befehlszeile. +(defun c:LG_REGALE ( / kand racks zeilen ohne-laenge) + (setq kand (lg-geschoss-kandidaten) + racks (lg-racks-sammeln)) + (if (null racks) + (princ (ssg-textf "lg-regale-keine" (list (lg-vorgabe '("goh_rack" "blockname") "goh_rack")))) + (progn + (setq zeilen (lg-racks-auswerten + (mapcar '(lambda (r) (list (lg-rack-geschoss (lg-ent-layer (lg-rk-top r)) (lg-rk-mitte r) kand) + (lg-rk-laenge r) (lg-rk-ebenen r))) + racks)) + ohne-laenge (length (vl-remove-if 'lg-rk-laenge racks))) + (princ (ssg-text "lg-regale-kopf")) + (foreach z zeilen + (princ (ssg-textf "lg-regale-zeile" + (list (if (car z) (ssg-textf "lg-regale-geschoss" (list (itoa (car z)))) + (ssg-text "lg-regale-ohne-geschoss")) + (itoa (cadr z)) (rtos (/ (caddr z) 1000.0) 2 2) (rtos (/ (cadddr z) 1000.0) 2 2))))) + (princ (ssg-textf "lg-regale-zeile" + (list (ssg-text "lg-regale-summe") (itoa (length racks)) + (rtos (/ (apply '+ (mapcar 'caddr zeilen)) 1000.0) 2 2) + (rtos (/ (apply '+ (mapcar 'cadddr zeilen)) 1000.0) 2 2)))) + (if (> ohne-laenge 0) (princ (ssg-textf "lg-regale-ohne-laenge" (list (itoa ohne-laenge))))))) + (princ) +) + +;; ============================================================ +;; GESCHOSS KOPIEREN (LG_GESCHOSS_KOPIEREN) +;; ============================================================ +;; Obergeschosse haben meist dieselben Regale und Flaechen wie das Geschoss +;; darunter. Ein BricsCAD-KOPIEREN taugt dafuer nicht: die XDATA-Verweise +;; (kontur/besitzer/bereich) zeigten weiter auf die Originale, GESCHOSS, +;; Layer _G und IDs blieben die alten. Darum wird jedes Objekt ueber die +;; normalen Einfuegefunktionen NEU erzeugt (Layer, ID, Verknuepfung, +;; Blockname/Schraffur, ATTSYNC kommen von dort) - die Kinder 1:1 aus der +;; Quelle, ohne Neufuellen, damit Handaenderungen (auto_geaendert, manuelle +;; Reihen/Gaenge) erhalten bleiben. goh_rack-Bloecke (parametrisch) werden +;; per vla-Copy kopiert und auf den Regal-Layer des Zielgeschosses gelegt. +;; Die Kopie liegt deckungsgleich (2D uebereinander), optional je Kopie um +;; (dx dy) versetzt; in einer 3D-Zeichnung (Konturen auf Belagshoehe) +;; zusaetzlich in Z um den Hoehenabstand. + +;; Layer eines Objekts oder nil. +(defun lg-ent-layer (ent / ed) + (if (and ent (setq ed (entget ent))) (cdr (assoc 8 ed))) +) + +;; Reiner Kern: Geschossnummer aus einem Lager-Layer "LG__G" +;; (Muster wie lg-geschoss-aktivieren), sonst nil. +(defun lg-geschoss-aus-layer (name / s p rest) + (if (and (= (type name) 'STR) (wcmatch (setq s (strcase name)) "LG_*_G#*")) + (progn + (setq p (vl-string-position (ascii "_") s nil T) + rest (substr s (+ p 2))) + (if (and (> (strlen rest) 1) (= (substr rest 1 1) "G") + (vl-every '(lambda (c) (<= 48 c 57)) (vl-string->list (substr rest 2)))) + (atoi (substr rest 2))))) +) + +;; Reiner Kern: Geschoss eines goh_rack. Ein Lager-Layer _G eines +;; vorhandenen Geschosses entscheidet (eindeutig auch bei 2D-Geschossen +;; uebereinander), sonst die Kontur (lg-geschoss-fuer-punkt-kern). +;; kandidaten = ((nr hoehe-mm kontur-punkte) ...) +(defun lg-rack-geschoss (layer pt kandidaten / n) + (setq n (lg-geschoss-aus-layer layer)) + (if (and n (assoc n kandidaten)) + n + (lg-geschoss-fuer-punkt-kern pt kandidaten)) +) + +;; Reiner Kern: Belag eines kopierten Bereichs/Gangs. Entsprach er dem +;; Geschossbelag der Quelle, folgt er dem neuen Geschossbelag; bewusst +;; abweichende (z.B. Gitterrost-Gang auf Spanplatte-Buehne) bleiben. +(defun lg-kopie-belag (belag alt neu) + (if (and belag alt neu (= (strcase belag) (strcase alt))) neu belag) +) + +;; Reiner Kern: Attributwerte einer kopierten Reihe/eines Gangs fuer +;; lg-linie-einfuegen. LAENGE/LAYER/ID entstehen dort neu; GESCHOSS und +;; BEREICH (nil = ohne Bereich -> "") werden ersetzt, BELAG folgt +;; lg-kopie-belag, alles uebrige (QUELLE!) bleibt. +(defun lg-kopie-werte (attribs gs-neu bereich-nr belag-alt belag-neu / w) + (setq w (vl-remove-if '(lambda (p) (member (strcase (car p)) '("LAENGE" "LAYER" "ID" "GESCHOSS" "BEREICH"))) + attribs)) + (if (assoc "BELAG" w) + (setq w (lg-wert-setzen w "BELAG" (lg-kopie-belag (cdr (assoc "BELAG" w)) belag-alt belag-neu)))) + (append (list (cons "GESCHOSS" (vl-princ-to-string gs-neu)) + (cons "BEREICH" (if bereich-nr (vl-princ-to-string bereich-nr) ""))) + w) +) + +;; Reiner Kern: Kopierplan ((i hoehe (vx vy vz)) ...) fuer n Kopien. +;; Kopie i liegt auf h-src + i*abstand und um i*(dx dy) versetzt; in Z nur +;; bei einer 3D-Zeichnung (z3d). +(defun lg-kopie-plan (h-src abstand n dx dy z3d / i erg) + (setq i 1 erg nil) + (while (<= i n) + (setq erg (cons (list i (+ h-src (* i abstand)) + (list (* i dx) (* i dy) (if z3d (* i abstand) 0.0))) + erg) + i (1+ i))) + (reverse erg) +) + +;; Reiner Kern: Vorgabe Hoehenabstand. Abstand zum Geschoss darunter, sonst +;; lichte Hoehe + Konstruktionshoehe der Quelle, sonst nil (Eingabe noetig). +(defun lg-kopie-abstand-vorgabe (h-src h-unter lichte konstr) + (cond + ((and h-unter (> (- h-src h-unter) 1.0)) (- h-src h-unter)) + ((and lichte (> lichte 0.0)) (+ lichte (if konstr konstr 0.0))) + (T nil)) +) + +;; Reiner Kern: liegt die Zeichnung in 3D (mindestens ein Geschoss mit +;; Hoehe != 0, dessen Kontur auf seiner Belagshoehe liegt)? +(defun lg-kopie-3d-p (kandidaten) + (if (vl-some '(lambda (k) (and (caddr k) (> (abs (cadr k)) 1.0) + (equal (lg-kandidat-z k) (cadr k) 1.0))) + kandidaten) + T) +) + +;; Dialog-Vorgaben: oberstes Geschoss als Quelle (Erweitern G3 -> G4). +(defun lg-kopie-vorgaben ( / gs top h-top unter a belag) + (setq gs (vl-sort (lg-geschosse) '(lambda (x y) (< (lg-geschoss-hoehe x) (lg-geschoss-hoehe y)))) + top (last gs) + h-top (lg-geschoss-hoehe top) + unter (vl-remove-if-not '(lambda (g) (< (lg-geschoss-hoehe g) (- h-top 1.0))) gs) + a (caddr top) + belag (cdr (assoc "BELAG_ART" a))) + (if (or (null belag) (= belag "hallenboden")) + (setq belag (lg-vorgabe '("geschoss" "belag_art") "spanplatte"))) + (list (cons "QUELLE" (itoa (cadr top))) + (cons "ANZAHL" "1") + (cons "ABSTAND_mm" (lg-mm-str (lg-kopie-abstand-vorgabe + h-top (if unter (lg-geschoss-hoehe (last unter))) + (lg-zahl (cdr (assoc "LICHTE_HOEHE_mm" a))) + (lg-zahl (cdr (assoc "KONSTR_HOEHE_mm" a)))))) + (cons "BELAG_ART" belag) + (cons "VERSATZ_X_mm" "0") + (cons "VERSATZ_Y_mm" "0")) +) + +(defun lg-ent-verschieben (ent v) + (vla-Move (vlax-ename->vla-object ent) (vlax-3d-point '(0.0 0.0 0.0)) (vlax-3d-point v)) + ent +) + +;; Kontur kopieren (ohne Handle/XDATA) und um v verschieben. +(defun lg-kontur-kopie (ent v / neu) + (if (setq neu (lg-kontur-kopieren ent)) (lg-ent-verschieben neu v)) +) + +;; goh_rack-Bloecke des Geschosses src als oberste INSERTs im Modellbereich +;; (ein Array mit Racks zaehlt als ein Objekt). Vor der ersten Kopie +;; aufrufen: danach waere die Kontur-Zuordnung deckungsgleicher Geschosse +;; mehrdeutig. Rueckgabe: (racks anzahl-mehrdeutig) +(defun lg-racks-der-quelle (src kandidaten / src-pts racks mehrdeutig top gs) + (setq racks nil mehrdeutig nil + src-pts (caddr (assoc src kandidaten))) + (foreach r (lg-racks-sammeln) + (setq top (lg-rk-top r) + gs (lg-rack-geschoss (lg-ent-layer top) (lg-rk-mitte r) kandidaten)) + (cond + ((and gs (= gs src)) + (if (not (member top racks)) (setq racks (cons top racks)))) + ((and (null gs) src-pts (ssg-punkt-in-poly-p (lg-rk-mitte r) src-pts)) + (if (not (member top mehrdeutig)) (setq mehrdeutig (cons top mehrdeutig)))))) + (list (reverse racks) + (length (vl-remove-if '(lambda (m) (member m racks)) mehrdeutig))) +) + +;; Racks der Quelle ohne Lager-Layer auf den Regal-Layer der Quelle legen - +;; nach der Kopie liegen sie deckungsgleich mit den Kopien, nur der Layer +;; trennt sie dann (Export, LG_GESCHOSS_AKTIV). Rueckgabe: Anzahl. +(defun lg-racks-umhaengen (racks src / lay n) + (setq n 0) + (foreach r racks + (if (null (lg-geschoss-aus-layer (lg-ent-layer r))) + (progn + (if (null lay) (setq lay (lg-layer "goh_rack" src))) + (vla-put-Layer (vlax-ename->vla-object r) lay) + (setq n (1+ n))))) + n +) + +;; Racks per vla-Copy (MCAD-Parameter bleiben) um v versetzt ins Geschoss +;; nr kopieren. Rueckgabe: Anzahl. +(defun lg-racks-kopieren (racks nr v / lay n res) + (setq n 0 lay (if racks (lg-layer "goh_rack" nr))) + (foreach r racks + (setq res (vl-catch-all-apply + '(lambda ( / obj) + (setq obj (vla-Copy (vlax-ename->vla-object r))) + (vla-Move obj (vlax-3d-point '(0.0 0.0 0.0)) (vlax-3d-point v)) + (vla-put-Layer obj lay) + T))) + (if (vl-catch-all-error-p res) + (princ (ssg-textf "lg-einfuegen-fehler" (list (lg-blockname r) (vl-catch-all-error-message res)))) + (setq n (1+ n)))) + n +) + +;; Eine Kopie des Geschosses src-nr als Geschoss nr auf hoehe, um v versetzt. +;; Rueckgabe: (geschoss-ent reihen gaenge racks) oder nil. +(defun lg-geschoss-kopieren (src-nr nr hoehe belag-neu v racks + / src a belag-alt k werte blk zuordnung kk nb ab ed p1 p2 rot + laenge nb-h art n-reihen n-gaenge s-gs) + (setq src (lg-geschoss-finden src-nr) + a (caddr src) + belag-alt (cdr (assoc "BELAG_ART" a)) + s-gs (itoa src-nr) + k (if src (lg-ent-aus-handle (lg-xdata-wert (car src) "kontur")))) + (cond + ((null src) (princ (ssg-textf "lg-geschoss-unbekannt" (list s-gs))) nil) + ((null k) (princ (ssg-textf "lg-exp-geschoss-ohne-kontur" (list s-gs))) nil) + (T + ;; 1. Geschoss: Attribute der Quelle, neu sind Nummer, Bezeichnung, + ;; Belagshoehe und Belag (GESCHOSS/LAYER/ID setzt lg-geschoss-anlegen) + (setq werte (append + (list (cons "NR" (itoa nr)) + (assoc "BEZEICHNUNG" (lg-geschoss-vorgaben nr)) + (cons "HOEHE_BELAG_mm" (lg-mm-str hoehe)) + (cons "BELAG_ART" belag-neu)) + (vl-remove-if '(lambda (p) (member (strcase (car p)) + '("NR" "BEZEICHNUNG" "HOEHE_BELAG_MM" "BELAG_ART" + "GESCHOSS" "LAYER" "ID"))) + a))) + (setq kk (lg-kontur-kopie k v)) + (if (null (setq blk (lg-geschoss-anlegen kk werte))) + (progn (if kk (entdel kk)) nil) + (progn + ;; 2. Bereiche (ohne Fuellung), Zuordnung alter -> neuer Handle + (setq zuordnung nil) + (foreach b (lg-alle-bloecke "LG_BEREICH") + (setq ab (ssg-attrib-read b)) + (if (and (= (cdr (assoc "GESCHOSS" ab)) s-gs) + (setq k (lg-ent-aus-handle (lg-xdata-wert b "kontur"))) + (setq kk (lg-kontur-kopie k v))) + (progn + (setq werte (vl-remove-if '(lambda (p) (member (strcase (car p)) '("NR" "LAYER" "ID" "GESCHOSS"))) + ab)) + (if (assoc "BELAG" werte) + (setq werte (lg-wert-setzen werte "BELAG" + (lg-kopie-belag (cdr (assoc "BELAG" werte)) belag-alt belag-neu)))) + (if (setq nb (lg-bereich-block-anlegen kk (cons (cons "GESCHOSS" (itoa nr)) werte))) + (setq zuordnung (cons (cons (lg-handle b) nb) zuordnung)) + (entdel kk))))) + ;; 3. Reihen und Gaenge 1:1 (auch ohne Bereich), kein Neufuellen + (setq n-reihen 0 n-gaenge 0) + (foreach e (append (lg-alle-bloecke "LG_REIHE_*") (lg-alle-bloecke "LG_GANG_*")) + (setq ab (ssg-attrib-read e)) + (if (= (cdr (assoc "GESCHOSS" ab)) s-gs) + (progn + (setq ed (entget e) + p1 (mapcar '+ (cdr (assoc 10 ed)) v) + rot (cond ((cdr (assoc 50 ed))) (0.0)) + laenge (cond ((lg-zahl (cdr (assoc "LAENGE" ab)))) (0.0)) + p2 (polar p1 rot laenge) + p2 (list (car p2) (cadr p2) (caddr p1)) + nb (cdr (assoc (lg-xdata-wert e "bereich") zuordnung)) + art (if (wcmatch (strcase (lg-blockname e)) "LG_REIHE_*") "reihe" "gang")) + (if (lg-linie-einfuegen art p1 p2 + (lg-kopie-werte ab nr (if nb (cdr (assoc "NR" (ssg-attrib-read nb)))) + belag-alt belag-neu) + nb) + (if (= art "reihe") (setq n-reihen (1+ n-reihen)) (setq n-gaenge (1+ n-gaenge))))))) + ;; 4. goh_rack + (list blk n-reihen n-gaenge (lg-racks-kopieren racks nr v)))) + ) + ) +) + +;; Geschoss kopieren nach Dialogwerten (QUELLE ANZAHL ABSTAND_mm BELAG_ART +;; VERSATZ_X_mm VERSATZ_Y_mm), nicht interaktiv - auch fuer Tests. +;; Rueckgabe: Liste der neuen Geschossnummern oder nil. +(defun lg-geschosse-kopieren (werte / src-nr src n abstand belag dx dy kandidaten + rq racks nr erg neu) + (setq src-nr (atoi (cond ((cdr (assoc "QUELLE" werte))) ("0"))) + src (lg-geschoss-finden src-nr) + n (atoi (cond ((cdr (assoc "ANZAHL" werte))) ("1"))) + abstand (lg-zahl (cdr (assoc "ABSTAND_mm" werte))) + belag (cdr (assoc "BELAG_ART" werte)) + dx (cond ((lg-zahl (cdr (assoc "VERSATZ_X_mm" werte)))) (0.0)) + dy (cond ((lg-zahl (cdr (assoc "VERSATZ_Y_mm" werte)))) (0.0))) + (cond + ((null src) (princ (ssg-textf "lg-geschoss-unbekannt" (list (itoa src-nr)))) nil) + ((or (null abstand) (<= abstand 0.0)) (princ (ssg-text "lg-kopie-abstand-ungueltig")) nil) + ((< n 1) nil) + (T + (setq kandidaten (lg-geschoss-kandidaten)) + ;; Racks VOR der ersten Kopie bestimmen (danach waere die Kontur mehrdeutig) + (setq rq (lg-racks-der-quelle src-nr kandidaten) + racks (car rq)) + (if (> (cadr rq) 0) (princ (ssg-textf "lg-kopie-racks-mehrdeutig" (list (itoa (cadr rq)))))) + (if (> (setq neu (lg-racks-umhaengen racks src-nr)) 0) + (princ (ssg-textf "lg-kopie-racks-umgehaengt" (list (itoa neu) (itoa src-nr))))) + (setq erg nil) + (foreach p (lg-kopie-plan (lg-geschoss-hoehe src) abstand n dx dy (lg-kopie-3d-p kandidaten)) + (if (vl-some '(lambda (k) (equal (cadr k) (cadr p) 1.0)) (lg-geschoss-kandidaten)) + (princ (ssg-textf "lg-kopie-hoehe-belegt" (list (lg-mm-str (cadr p)))))) + (setq nr (lg-geschoss-naechste-nr)) + (if (setq neu (lg-geschoss-kopieren src-nr nr (cadr p) belag (caddr p) racks)) + (progn + (setq erg (cons nr erg)) + (princ (ssg-textf "lg-kopie-fertig" + (list (itoa src-nr) (itoa nr) (lg-mm-str (cadr p)) + (itoa (cadr neu)) (itoa (caddr neu)) (itoa (cadddr neu)))))))) + (if erg + (progn + (lg-ldata-setzen "GESCHOSS_AKTIV" (car erg)) + (princ (ssg-text "lg-kopie-hinweis-aktiv")))) + (reverse erg)) + ) +) + +;; ============================================================ +;; BEFEHLE +;; ============================================================ + +(defun c:LG_GESCHOSS ( / kontur nr werte blk) + (ssg-start (ssg-text "lg-titel-geschoss") nil) + (setq kontur (lg-kontur-waehlen (ssg-text "lg-prompt-kontur-geschoss"))) + (if kontur + (progn + (setq nr (lg-geschoss-naechste-nr)) + (setq werte (lg-dialog "lg_geschoss" *lg-felder-geschoss* (lg-geschoss-vorgaben nr) nil)) + (if (and werte (setq blk (lg-geschoss-anlegen kontur werte))) + (progn + (lg-ldata-setzen "GESCHOSS_AKTIV" (atoi (cdr (assoc "NR" werte)))) + (princ (ssg-textf "lg-geschoss-angelegt" (list (cdr (assoc "NR" werte)) + (cdr (assoc "HOEHE_BELAG_mm" werte))))))))) + (ssg-end) + (princ) +) + +;; Geschoss aktivieren: Layer der uebrigen Geschosse frieren (0 = alle zeigen). +(defun lg-geschoss-aktivieren (nr / tab name suffix) + (if (and (> nr 0) (null (lg-geschoss-finden nr))) + (progn (princ (ssg-textf "lg-geschoss-unbekannt" (list (itoa nr)))) nil) + (progn + (lg-ldata-setzen "GESCHOSS_AKTIV" nr) + ;; aktuellen Layer weg von evtl. zu frierenden Lager-Layern + (if (and (> nr 0) (wcmatch (strcase (getvar "CLAYER")) "LG_*_G#*") + (not (wcmatch (strcase (getvar "CLAYER")) (strcat "LG_*_G" (itoa nr))))) + (setvar "CLAYER" "0")) + (setq suffix (strcat "_G" (itoa nr))) + (setq tab (tblnext "LAYER" T)) + (while tab + (setq name (cdr (assoc 2 tab))) + (if (wcmatch (strcase name) "LG_*_G#*") + (lg-layer-frieren name (and (> nr 0) (not (wcmatch (strcase name) (strcat "LG_*" suffix)))))) + (setq tab (tblnext "LAYER"))) + (princ (if (> nr 0) (ssg-textf "lg-geschoss-aktiv" (list (itoa nr))) (ssg-text "lg-geschoss-alle"))) + nr) + ) +) + +(defun lg-layer-frieren (name frieren / e ed f) + (if (and (setq e (tblobjname "LAYER" name)) + (not (and frieren (= (strcase name) (strcase (getvar "CLAYER")))))) + (progn + (setq ed (entget e) f (cdr (assoc 70 ed))) + (setq f (if frieren (logior f 1) (logand f (~ 1)))) + (entmod (subst (cons 70 f) (assoc 70 ed) ed)))) +) + +;; Geschoss mit Belag, Bereichen, Reihen, Gaengen und goh_racks in ein oder +;; mehrere neue Geschosse kopieren (siehe lg-geschosse-kopieren). +(defun c:LG_GESCHOSS_KOPIEREN ( / werte) + (ssg-start (ssg-text "lg-titel-kopie") nil) + (if (null (lg-geschosse)) + (princ (ssg-text "lg-kein-geschoss")) + (if (setq werte (lg-dialog "lg_geschoss_kopie" *lg-felder-kopie* (lg-kopie-vorgaben) nil)) + (lg-geschosse-kopieren werte))) + (ssg-end) + (princ) +) + +(defun c:LG_GESCHOSS_AKTIV ( / gs nr) + (setq gs (lg-geschosse)) + (if (null gs) + (princ (ssg-text "lg-kein-geschoss")) + (progn + (princ (strcat "\n" (ssg-join (mapcar '(lambda (g) (strcat (itoa (cadr g)) " = " + (cond ((cdr (assoc "BEZEICHNUNG" (caddr g)))) ("")))) gs) + ", "))) + (setq nr (ssg-in-int (ssg-text "lg-prompt-geschoss-aktiv") + (cond ((lg-geschoss-aktiv)) (0)))) + (if nr (lg-geschoss-aktivieren nr)) + (command "_.REGEN"))) + (princ) +) + +(defun c:LG_BEREICH ( / kontur pts gs werte blk) + (ssg-start (ssg-text "lg-titel-bereich") nil) + (setq kontur (lg-kontur-waehlen (ssg-text "lg-prompt-kontur-bereich"))) + (if kontur + (progn + (setq pts (ssg-lwpoly-punkte kontur)) + (if (ssg-lwpoly-boegen-p kontur) (princ (ssg-text "lg-kontur-boegen"))) + (setq gs (lg-geschoss-ermitteln (car pts))) + (if gs + (progn + (setq werte (lg-dialog "lg_bereich" *lg-felder-bereich* (lg-bereich-vorgaben pts) nil)) + (if werte + (setq blk (lg-bereich-anlegen kontur (cons (cons "GESCHOSS" (itoa gs)) werte)))))))) + (ssg-end) + (princ) +) + +;; Reiner Kern: Gang aus einem Rechteck. ecken = 4 Eckpunkte im Umlauf +;; (WCS), die erste = die zuerst gepickte Ecke. Die laengere Seite ist die +;; Gangrichtung, die kurze Seite die Breite (auf ganze mm). Die Rundung +;; verschiebt NUR die Laengsseite gegenueber der ersten Ecke: die Seite, an +;; der der Anwender angesetzt hat (z.B. buendig an einer Regalreihe), bleibt +;; exakt liegen - sonst wuerde der Gang die Reihe um Bruchteile eines mm +;; ueberdecken und sie beim Zuschnitt entfernen. Ebenso beginnt die Linie an +;; der Querseite der ersten Ecke (die Laengenrundung wirkt am anderen Ende). +;; Quadrat: Richtung der ersten Kante. Z = Z der ersten Ecke. +;; Rueckgabe: (p1 p2 breite) oder nil (entartet). +(defun lg-gang-aus-ecken (ecken / a b c d z l1 l2 w ende quer u) + (setq a (nth 0 ecken) b (nth 1 ecken) c (nth 2 ecken) d (nth 3 ecken) + z (cond ((caddr a)) (0.0)) + l1 (distance (list (car a) (cadr a)) (list (car b) (cadr b))) + l2 (distance (list (car b) (cadr b)) (list (car c) (cadr c)))) + (if (and (>= l1 1.0) (>= l2 1.0)) + (progn + ;; ende = Ecke am Ende der Laengsseite durch a, quer = Nachbar quer dazu + (if (>= l1 l2) + (setq ende b quer d w (lg-mm-runden l2)) + (setq ende d quer b w (lg-mm-runden l1))) + ;; halbe gerundete Breite von der Laengsseite durch a nach innen + (setq u (/ (/ w 2.0) (distance (list (car a) (cadr a)) (list (car quer) (cadr quer)))) + u (list (* u (- (car quer) (car a))) (* u (- (cadr quer) (cadr a))))) + (list (list (+ (car a) (car u)) (+ (cadr a) (cadr u)) z) + (list (+ (car ende) (car u)) (+ (cadr ende) (cadr u)) z) + w))) +) + +;; Rechteck mit dem BricsCAD-Befehl RECHTECK zeichnen lassen - mit allen +;; seinen Optionen (Abmessungen, Flaeche, Drehung, Objektfang ...). Die +;; entstandene Polylinie wird als 4 Eckpunkte (WCS) gelesen und wieder +;; geloescht. Rueckgabe: Eckpunkte, nil (nichts gezeichnet = Ende) oder +;; 'kein-rechteck (z.B. mit Fase/Rundung gezeichnet). +(defun lg-rechteck-zeichnen ( / marke echo e pts) + (setq marke (entlast) echo (getvar "CMDECHO")) + ;; ohne CMDECHO zeigt RECHTECK seine Fragen und Optionen nicht an + (setvar "CMDECHO" 1) + (command "_.RECTANG") + (while (> (getvar "CMDACTIVE") 0) (command pause)) + (setvar "CMDECHO" echo) + (setq e (entlast)) + (if (and e (not (equal e marke))) + (progn + (setq pts (lg-rechteck-ecken-aus-poly e)) + (entdel e) + pts)) +) + +;; Eckpunkte (WCS) einer mit RECHTECK gezeichneten Polylinie oder +;; 'kein-rechteck (anderes Objekt, Fase/Rundung, mehr als 4 Ecken). +(defun lg-rechteck-ecken-aus-poly (e / pts) + (if (and (= (cdr (assoc 0 (entget e))) "LWPOLYLINE") + (= (length (setq pts (ssg-lwpoly-punkte e))) 4) + (not (ssg-lwpoly-boegen-p e))) + pts + 'kein-rechteck) +) + +;; Punkteingabe fuer Linienobjekte (Reihe/Gang): Startpunkt, dann Endpunkt +;; ODER getippte Laenge (+ Richtung), bis ESC/Enter (Muster Geruest-Traeger). +;; einfuegen-fn bekommt je Punktpaar (p1 p2) in WCS und liefert nil bei +;; Misserfolg. rechteck-fn (nil = ohne): schaltet am Startpunkt das +;; Schluesselwort "Rechteck" frei (prompt-start nennt es ueber %1); danach +;; wird jede Flaeche mit dem BricsCAD-Befehl RECHTECK gezeichnet +;; (lg-rechteck-zeichnen) und rechteck-fn bekommt die 4 Eckpunkte (WCS). +;; Enter ohne Rechteck beendet. Rueckgabe: Anzahl erfolgreicher Aufrufe. +(defun lg-linien-schleife (prompt-start einfuegen-fn rechteck-fn / weiter modus k-rechteck + ecken p1 eingabe p2 laenge winkel n) + (setq weiter T n 0 modus "linie" + k-rechteck (ssg-text "lg-key-rechteck")) + (while weiter + (if (= modus "rechteck") + (progn + (princ (ssg-text "lg-rechteck-hinweis")) + (setq ecken (lg-rechteck-zeichnen)) + (cond + ((null ecken) (setq weiter nil)) + ((eq ecken 'kein-rechteck) (princ (ssg-text "lg-rechteck-kein-rechteck"))) + ((apply rechteck-fn (list ecken)) (setq n (1+ n))))) + (progn + (if rechteck-fn (initget k-rechteck)) + (setq p1 (getpoint (if rechteck-fn (vl-string-subst k-rechteck "%1" prompt-start) prompt-start))) + (cond + ((null p1) (setq weiter nil)) + ((= (type p1) 'STR) (setq modus "rechteck")) + (T + (initget 128) + (setq eingabe (getpoint p1 (ssg-text "lg-prompt-reihe-ende"))) + (setq p2 nil) + (cond + ((null eingabe) (setq weiter nil)) + ((listp eingabe) (setq p2 eingabe)) + ((= eingabe "") (setq weiter nil)) + ((setq laenge (lg-zahl eingabe)) + (setq winkel (getangle p1 (ssg-text "lg-prompt-reihe-richtung"))) + (setq p2 (polar p1 (if winkel winkel 0.0) laenge))) + (T (princ (ssg-text "lg-wert-ungueltig")))) + (if p2 + (progn + ;; Objekt liegt auf Z des Startpunkts; erst p2, dann p1 + ;; nach WCS (p2 braucht das UCS-Z von p1) + (setq p2 (trans (list (car p2) (cadr p2) (caddr p1)) 1 0) + p1 (trans p1 1 0)) + (if (apply einfuegen-fn (list p1 p2)) + (setq n (1+ n))))))))) + ) + n +) + +;; Einzelreihen per Linie. +(defun c:LG_REIHE ( / gs werte n) + (ssg-start (ssg-text "lg-titel-reihe") nil) + (setq gs (lg-geschoss-ermitteln '(0.0 0.0 0.0))) + (if gs + (progn + (setq werte (lg-dialog "lg_reihe" *lg-felder-reihe* + (list (cons "LAENGE" "0") + (cons "TIEFE_mm" (lg-mm-str (lg-zahl (lg-vorgabe '("bereich" "regaltiefe_mm") 700)))) + (cons "HAENGEEBENEN" (lg-vorgabe '("reihe" "haengeebenen") "2300/1200;1100/1100")) + (cons "SCHIENEN" (itoa (fix (lg-zahl (lg-vorgabe '("reihe" "schienen") 1)))))) + '("laenge"))) + (if werte + (progn + (setq werte (append (list (cons "GESCHOSS" (itoa gs)) (cons "BEREICH" "") (cons "QUELLE" "manuell")) + (vl-remove-if '(lambda (p) (= (car p) "LAENGE")) werte))) + (setq n (lg-linien-schleife (ssg-text "lg-prompt-reihe-start") + '(lambda (p1 p2) (lg-manuell-einfuegen "reihe" p1 p2 werte)) + nil)) + (princ (ssg-textf "lg-reihen-eingefuegt" (list (itoa n)))))))) + (ssg-end) + (princ) +) + +(defun lg-gang-vorgaben ( / ) + (list (cons "LAENGE" "0") + (cons "BREITE_mm" (lg-mm-str (lg-zahl (lg-vorgabe '("bereich" "gangbreite_mm") 1000)))) + (cons "ART" "gang") + (cons "BELAG" (lg-vorgabe '("bereich" "belag") "spanplatte"))) +) + +;; Reihe/Gang von Hand von p1 nach p2 (WCS, mittig auf der Linie) einfuegen +;; (nicht interaktiv - auch fuer Tests). art = "reihe"|"gang"; werte wie +;; bei lg-linie-einfuegen mit GESCHOSS. Liegt die Mitte in einem +;; Lagerbereich, wird das Objekt mit ihm verknuepft. Danach schneidet es +;; seine Flaeche aus den automatisch erzeugten Reihen/Gaengen heraus +;; (lg-sperre-anwenden) - der Bereich wird NICHT neu berechnet, und bei +;; jeder spaeteren Neuberechnung bleibt das Objekt stehen. +;; Rueckgabe: ename oder nil. +(defun lg-manuell-einfuegen (art p1 p2 werte / mitte bereich ent) + (setq mitte (list (/ (+ (car p1) (car p2)) 2.0) (/ (+ (cadr p1) (cadr p2)) 2.0) + (cond ((caddr p1)) (0.0)))) + (setq bereich (lg-bereich-fuer-punkt mitte (cdr (assoc "GESCHOSS" werte)))) + (setq werte (lg-wert-setzen werte "QUELLE" "manuell")) + (setq werte (lg-wert-setzen werte "BEREICH" + (if bereich (cdr (assoc "NR" (ssg-attrib-read bereich))) ""))) + (if (setq ent (lg-linie-einfuegen art p1 p2 werte bereich)) + (progn + (lg-sperre-anwenden ent) + ent)) +) + +;; Manuellen Gang einfuegen; werte braucht GESCHOSS, BREITE_mm, ART, BELAG. +(defun lg-gang-einfuegen (p1 p2 werte) + (lg-manuell-einfuegen "gang" p1 p2 werte) +) + +;; Manuellen Gang aus einem Rechteck (4 Eckpunkte WCS) einfuegen - Breite +;; und Lage kommen aus der Flaeche, BREITE_mm aus werte gilt dann nicht. +;; Rueckgabe: ename oder nil. +(defun lg-gang-rechteck-einfuegen (ecken werte / g) + (if (setq g (lg-gang-aus-ecken ecken)) + (lg-gang-einfuegen (car g) (cadr g) (lg-wert-setzen werte "BREITE_mm" (lg-mm-str (caddr g)))) + (progn (princ (ssg-text "lg-rechteck-zu-klein")) nil)) +) + +;; Einzelne Gaenge per Linie oder Rechteck (Randgang, Fluchtweg, Durchgang ...). +(defun c:LG_GANG ( / gs werte n) + (ssg-start (ssg-text "lg-titel-gang") nil) + (setq gs (lg-geschoss-ermitteln '(0.0 0.0 0.0))) + (if gs + (progn + (setq werte (lg-dialog "lg_gang" *lg-felder-gang* (lg-gang-vorgaben) '("laenge"))) + (if werte + (progn + (setq werte (cons (cons "GESCHOSS" (itoa gs)) + (vl-remove-if '(lambda (p) (= (car p) "LAENGE")) werte))) + (setq n (lg-linien-schleife (ssg-text "lg-prompt-gang-start") + '(lambda (p1 p2) (lg-gang-einfuegen p1 p2 werte)) + '(lambda (ecken) (lg-gang-rechteck-einfuegen ecken werte)))) + (princ (ssg-textf "lg-gaenge-eingefuegt" (list (itoa n)))))))) + (ssg-end) + (princ) +) + +;; Doppelklick-Ziel (SSG_BLOCKEDIT, Zweig LG_*) und Befehl LG_EDIT. +(defun lg-edit-ent (ent / name a neu) + (setq name (strcase (lg-blockname ent)) + a (ssg-attrib-read ent)) + (cond + ((= name "LG_GESCHOSS") + ;; Nummer bleibt fest - an ihr haengen Layer und GESCHOSS der Kinder + (if (setq neu (lg-dialog "lg_geschoss" *lg-felder-geschoss* a '("nr"))) + (progn (lg-attribute-setzen ent neu) (princ (ssg-text "lg-geaendert"))))) + ((= name "LG_BEREICH") + (if (setq neu (lg-dialog "lg_bereich" *lg-felder-bereich* a nil)) + (progn + (lg-attribute-setzen ent neu) + ;; Pfeil des Symbols folgt der Reihenrichtung + (vla-put-Rotation (vlax-ename->vla-object ent) + (* (/ (cond ((lg-zahl (cdr (assoc "RICHTUNG" neu)))) (0.0)) 180.0) pi)) + (lg-bereich-fuellen ent)))) + ((wcmatch name "LG_REIHE_*") + (if (setq neu (lg-dialog "lg_reihe" *lg-felder-reihe* a nil)) + (progn + (if (= (cdr (assoc "QUELLE" neu)) "auto") + (setq neu (lg-wert-setzen neu "QUELLE" "auto_geaendert"))) + (lg-linie-neu-aufbauen-sperre ent neu)))) + ((wcmatch name "LG_GANG_*") + (if (setq neu (lg-dialog "lg_gang" *lg-felder-gang* a nil)) + (progn + (if (/= (cdr (assoc "QUELLE" neu)) "manuell") + (setq neu (lg-wert-setzen neu "QUELLE" "auto_geaendert"))) + (lg-linie-neu-aufbauen-sperre ent neu)))) + (T (command "_.EATTEDIT" ent)) + ) + (princ) +) + +(defun c:LG_EDIT ( / ss) + (setq ss (ssget "_I")) + (if (or (null ss) (/= (sslength ss) 1)) + (setq ss (ssget ":S" '((0 . "INSERT") (2 . "LG_*"))))) + (if ss + (progn + (ssg-start (ssg-text "lg-titel-edit") nil) + (lg-edit-ent (ssname ss 0)) + (ssg-end))) + (princ) +) + +;; ============================================================ +;; EXPORT (lager_raw.json) +;; ============================================================ + +;; --- JSON-Schreiber (rein, getestet) --- +;; Werte: nil -> null, T -> true, String, Ganz-/Fliesszahl, (ROH "json"). +(defun lg-json-zahl (w / s) + (setq s (rtos w 2 3)) + (if (vl-string-search "." s) + (progn + (setq s (vl-string-right-trim "0" s)) + (if (= (substr s (strlen s)) ".") (setq s (substr s 1 (1- (strlen s))))))) + (if (= s "-0") "0" s) +) + +(defun lg-json-wert (v) + (cond + ((null v) "null") + ((eq v T) "true") + ((= (type v) 'STR) (strcat "\"" (ssg-json-escape v) "\"")) + ((= (type v) 'INT) (itoa v)) + ((= (type v) 'REAL) (lg-json-zahl v)) + ((and (listp v) (eq (car v) 'ROH)) (cadr v)) + (T (strcat "\"" (ssg-json-escape (vl-princ-to-string v)) "\"")) + ) +) + +(defun lg-json-obj (paare) + (strcat "{" + (ssg-join (mapcar '(lambda (p) (strcat "\"" (car p) "\": " (lg-json-wert (cdr p)))) paare) + ", ") + "}") +) + +;; Liste bereits gerenderter JSON-Texte als Array (ein Element je Zeile). +(defun lg-json-arr (texte einrueckung) + (if texte + (strcat "[\n" einrueckung (ssg-join texte (strcat ",\n" einrueckung)) "\n" (substr einrueckung 3) "]") + "[]") +) + +(defun lg-json-zahlen (zahlen) + (list 'ROH (strcat "[" (ssg-join (mapcar 'lg-json-wert zahlen) ", ") "]")) +) + +(defun lg-json-ebenen (s) + (list 'ROH + (strcat "[" + (ssg-join (mapcar '(lambda (e) (lg-json-obj (list (cons "schienenhoehe_mm" (car e)) + (cons "warenlaenge_mm" (cadr e))))) + (lg-haengeebenen-parse s)) + ", ") + "]")) +) + +;; Attributwert als Zahl bzw. Ganzzahl (nil, wenn leer). +(defun lg-a-zahl (a tag) (lg-zahl (cdr (assoc tag a)))) +(defun lg-a-int (a tag / w) (if (setq w (lg-zahl (cdr (assoc tag a)))) (fix w))) + +(defun lg-export-geschoss (g / a k pts bb) + (setq a (caddr g) pts (cadddr g) bb (ssg-poly-bbox pts) + k (lg-xdata-wert (car g) "kontur")) + (lg-json-obj + (list (cons "nr" (cadr g)) + (cons "bezeichnung" (cdr (assoc "BEZEICHNUNG" a))) + (cons "hoehe_belag_mm" (lg-a-zahl a "HOEHE_BELAG_mm")) + (cons "konstr_hoehe_mm" (lg-a-zahl a "KONSTR_HOEHE_mm")) + (cons "lichte_hoehe_mm" (lg-a-zahl a "LICHTE_HOEHE_mm")) + (cons "belag_art" (cdr (assoc "BELAG_ART" a))) + (cons "personen" (lg-a-int a "PERSONEN")) + (cons "brandgefaehrdung" (cdr (assoc "BRANDGEFAEHRDUNG" a))) + (cons "kontur_flaeche_mm2" (if pts (ssg-poly-flaeche pts))) + (cons "kontur_umfang_mm" (if pts (ssg-poly-umfang pts))) + (cons "kontur_bbox" (if bb (lg-json-zahlen (append (car bb) (cadr bb))))) + (cons "handle" (lg-handle (car g))) + (cons "kontur_handle" k))) +) + +(defun lg-export-bereich (e / a k pts bb) + (setq a (ssg-attrib-read e) + k (lg-ent-aus-handle (lg-xdata-wert e "kontur")) + pts (if k (ssg-lwpoly-punkte k)) + bb (ssg-poly-bbox pts)) + (lg-json-obj + (list (cons "nr" (lg-a-int a "NR")) + (cons "geschoss" (lg-a-int a "GESCHOSS")) + (cons "gangbreite_mm" (lg-a-zahl a "GANGBREITE_mm")) + (cons "regaltiefe_mm" (lg-a-zahl a "REGALTIEFE_mm")) + (cons "reihen_je_gang" (lg-a-int a "REIHEN_JE_GANG")) + (cons "schienen_je_gang" (lg-a-int a "SCHIENEN_JE_GANG")) + (cons "quergang_abstand_mm" (lg-a-zahl a "QUERGANG_ABSTAND_mm")) + (cons "quergang_breite_mm" (lg-a-zahl a "QUERGANG_BREITE_mm")) + (cons "richtung_grad" (lg-a-zahl a "RICHTUNG")) + (cons "haengeebenen" (lg-json-ebenen (cdr (assoc "HAENGEEBENEN" a)))) + (cons "belag" (cdr (assoc "BELAG" a))) + (cons "randgang_mm" (list 'ROH (lg-json-obj + (mapcar '(lambda (r) (cons (strcase (car r) T) (cdr r))) + (lg-bereich-rand a))))) + (cons "restbreite_mm" (lg-a-zahl a "RESTBREITE_mm")) + (cons "kontur_flaeche_mm2" (if pts (ssg-poly-flaeche pts))) + (cons "kontur_bbox" (if bb (lg-json-zahlen (append (car bb) (cadr bb))))) + (cons "handle" (lg-handle e)))) +) + +(defun lg-export-linie (e art / a ed) + (setq a (ssg-attrib-read e) ed (entget e)) + (lg-json-obj + (append + (list (cons "geschoss" (lg-a-int a "GESCHOSS")) + (cons "laenge_mm" (lg-a-zahl a "LAENGE"))) + (if (= art "reihe") + (list (cons "tiefe_mm" (lg-a-zahl a "TIEFE_mm")) + (cons "haengeebenen" (lg-json-ebenen (cdr (assoc "HAENGEEBENEN" a)))) + (cons "schienen" (lg-a-int a "SCHIENEN")) + (cons "quelle" (cdr (assoc "QUELLE" a)))) + (list (cons "breite_mm" (lg-a-zahl a "BREITE_mm")) + (cons "art" (cdr (assoc "ART" a))) + (cons "belag" (cdr (assoc "BELAG" a))) + (cons "quelle" (if (member (cdr (assoc "QUELLE" a)) '(nil "")) "auto" + (cdr (assoc "QUELLE" a)))))) + (list (cons "bereich" (lg-a-int a "BEREICH")) + (cons "start" (lg-json-zahlen (cdr (assoc 10 ed)))) + (cons "winkel_grad" (* (/ (cond ((cdr (assoc 50 ed))) (0.0)) pi) 180.0)) + (cons "handle" (lg-handle e))))) +) + +;; goh_rack-Bloecke sammeln (lg-racks-sammeln). Geschoss ueber den Layer +;; des obersten INSERTs (_G), sonst ueber die Kontur (lg-rack-geschoss). +;; Rueckgabe: (json-texte warnungen) +(defun lg-export-goh-racks (kandidaten / texte warn gs ebenen vorgabe) + (setq texte nil warn nil + vorgabe (lg-vorgabe '("reihe" "haengeebenen") "2300/1200;1100/1100")) + (foreach r (lg-racks-sammeln) + (setq gs (lg-rack-geschoss (lg-ent-layer (lg-rk-top r)) (lg-rk-mitte r) kandidaten)) + (if (and (null gs) kandidaten) + (setq warn (cons (ssg-textf "lg-exp-rack-ohne-geschoss" + (list (rtos (car (lg-rk-mitte r)) 2 0) (rtos (cadr (lg-rk-mitte r)) 2 0))) + warn))) + (setq ebenen (if (lg-rk-ebenen r) (fix (lg-rk-ebenen r)))) + (if (null (lg-rk-laenge r)) + (setq warn (cons (ssg-textf "lg-exp-rack-ohne-laenge" (list (lg-rk-name r))) warn))) + (setq texte (cons + (lg-json-obj + (list (cons "geschoss" gs) + (cons "name" (lg-rk-name r)) + (cons "laenge_mm" (lg-rk-laenge r)) + (cons "ebenen" ebenen) + (cons "tiefe_mm" (lg-zahl (lg-vorgabe '("goh_rack" "tiefe_mm") 700))) + (cons "haengeebenen" (lg-json-ebenen (if ebenen (lg-haengeebenen-n vorgabe ebenen) vorgabe))) + (cons "haengeebenen_quelle" "vorgabe") + (cons "position" (lg-json-zahlen (lg-rk-mitte r))))) + texte))) + (list (reverse texte) (reverse warn)) +) + +;; Alle Daten als JSON-Text. +(defun lg-export-json ( / gs kandidaten racks warn gs-ohne) + (setq gs (lg-geschosse) + kandidaten (lg-geschoss-kandidaten) + racks (lg-export-goh-racks kandidaten) + warn (cadr racks)) + (if (null gs) (setq warn (cons (ssg-text "lg-exp-keine-geschosse") warn))) + (foreach g gs + (if (null (cadddr g)) + (setq warn (cons (ssg-textf "lg-exp-geschoss-ohne-kontur" (list (itoa (cadr g)))) warn)))) + (strcat + "{\n" + " \"version\": 1,\n" + " \"zeichnung\": " (lg-json-wert (strcat (getvar "DWGPREFIX") (getvar "DWGNAME"))) ",\n" + " \"erzeugt\": " (lg-json-wert (ssg-timestamp)) ",\n" + " \"einheit\": \"mm\",\n" + " \"land\": " (lg-json-wert (lg-land)) ",\n" + " \"belag_yaml\": " (lg-json-obj (vl-remove-if '(lambda (p) (or (/= (type (cdr p)) 'STR) + (= (car p) "beschreibung"))) + (lg-vorgabe '("belag_yaml") nil))) ",\n" + " \"reihen_als_gitterrost\": " (lg-json-wert (if (lg-vorgabe '("flaechen" "reihen_als_gitterrost") nil) T nil)) ",\n" + " \"geschosse\": " (lg-json-arr (mapcar 'lg-export-geschoss gs) " ") ",\n" + " \"bereiche\": " (lg-json-arr (mapcar 'lg-export-bereich (lg-alle-bloecke "LG_BEREICH")) " ") ",\n" + " \"reihen\": " (lg-json-arr (mapcar '(lambda (e) (lg-export-linie e "reihe")) (lg-alle-bloecke "LG_REIHE_*")) " ") ",\n" + " \"gaenge\": " (lg-json-arr (mapcar '(lambda (e) (lg-export-linie e "gang")) (lg-alle-bloecke "LG_GANG_*")) " ") ",\n" + " \"goh_racks\": " (lg-json-arr (car racks) " ") ",\n" + " \"warnungen\": " (lg-json-arr (mapcar 'lg-json-wert warn) " ") "\n" + "}\n") +) + +(defun lg-ausgabe-verzeichnis ( / d) + (setq d (cond ((getenv "DXFM_RESULTS")) ((getvar "DWGPREFIX")))) + (setq d (vl-string-right-trim "/\\" (vl-string-translate "\\" "/" d))) + (if (not (vl-file-directory-p d)) (vl-mkdir d)) + d +) + +(defun lg-dwg-basis ( / ) + (if (= (getvar "DWGTITLED") 1) (vl-filename-base (getvar "DWGNAME")) "lager") +) + +;; JSON schreiben (pfad) -> pfad oder nil. +(defun lg-export-schreiben (pfad / fh) + (if (setq fh (open pfad "w")) + (progn (princ (lg-export-json) fh) (close fh) pfad) + (progn (princ (ssg-textf "lg-exp-datei-fehler" (list pfad))) nil)) +) + +;; Python-Skript lager_yaml_export.py aufrufen (nutzt die Interpreter- +;; Ermittlung aus export.lsp). Rueckgabe: Exitcode oder nil. +(defun lg-python-aufrufen (raw yaml html / skript hand log args rc) + (cond + ((not (car (atoms-family 1 '("EXPORT:RUN-PYTHON")))) + (princ (ssg-text "lg-exp-ohne-python")) nil) + ((null (getenv "DXFM_LIB")) (princ (ssg-text "lg-exp-ohne-python")) nil) + (T + (setq skript (strcat (vl-string-translate "\\" "/" (getenv "DXFM_LIB")) "/lager_yaml_export.py") + hand (lg-ldata-lesen "HAND_YAML") + log (strcat (lg-ausgabe-verzeichnis) "/lager_export_python.log")) + (setq args (list raw yaml "--nachweis" html)) + (if (and hand (= (type hand) 'STR) (findfile hand)) + (setq args (append args (list "--hand" hand)))) + (setq rc (export:run-python (export:python-exe) skript args log)) + (if (and rc (/= rc 0)) + (princ (ssg-textf "lg-exp-python-fehler" (list (vl-princ-to-string rc) log)))) + rc) + ) +) + +(defun c:LAGER_EXPORT ( / dir basis raw yaml html rc) + (setq dir (lg-ausgabe-verzeichnis) basis (lg-dwg-basis)) + (setq raw (strcat dir "/" basis "_lager_raw.json") + yaml (strcat dir "/" basis "_drawing.yaml") + html (strcat dir "/" basis "_nachweis.html")) + (if (lg-export-schreiben raw) + (progn + (princ (ssg-textf "lg-exp-raw" (list raw))) + (setq rc (lg-python-aufrufen raw yaml html)) + (if (and rc (= rc 0)) + (princ (ssg-textf "lg-exp-yaml" (list yaml html)))))) + (princ) +) + +;; Hand-YAML fuer den Abgleich (Konfliktliste) festlegen; leer = keiner. +(defun c:LAGER_HAND_YAML ( / alt neu) + (setq alt (lg-ldata-lesen "HAND_YAML")) + (setq neu (if (ssg-gui-p) + (getfiled (ssg-text "lg-titel-hand-yaml") (if (= (type alt) 'STR) alt "") "yaml" 0) + (ssg-in-string (ssg-text "lg-titel-hand-yaml") (if (= (type alt) 'STR) alt "")))) + (lg-ldata-setzen "HAND_YAML" (if neu neu "")) + (princ (ssg-textf "lg-hand-yaml" (list (if (and neu (/= neu "")) neu "-")))) + (princ) +) + +(princ) diff --git a/Lisp/LagerPruefung.lsp b/Lisp/LagerPruefung.lsp new file mode 100644 index 0000000..63b1ebf --- /dev/null +++ b/Lisp/LagerPruefung.lsp @@ -0,0 +1,234 @@ +;; ============================================================ +;; LagerPruefung.lsp - Regelprofile der Lagerplanung (Phase 0) +;; +;; Laedt das Laenderprofil cfg/regelwerk/.json, loest die Vererbung +;; (erbt_von) auf und liefert je Regel den STRENGSTEN Wert aller Quellen, +;; deren Bedingung zutrifft - mit Protokoll, welche Quelle gewonnen hat. +;; Jede Pruefregel der Lagerplanung wird genau einmal hier implementiert +;; (Python rendert nur, siehe doc/plan/lager_rfq.md). +;; +;; Spaetere Phasen (Treppen-/Leiterpruefung, Fluchtweg, Verstoss-Marker, +;; LAGER_PRUEFEN) kommen in dieses Modul. +;; +;; Profil-Format (Auszug): +;; {"land": "DE", "erbt_von": null, "gepflegt": true, +;; "regeln": [{"id": "treppe.steigung_max", "art": "max", "einheit": "mm", +;; "werte": [{"wert": 190, "quelle": "ASR A1.8", "abschnitt": "4.5", +;; "fassung": "...", "bedingung": "...", "verifiziert": false}]}]} +;; art = "min" -> Mindestwert, strengster = groesster +;; "max" -> Hoechstwert, strengster = kleinster +;; "wert" -> fester Wert, der erste zutreffende gilt +;; Ein Eintrag mit "tabelle" [[bis, wert] ...] + "tabellen_schluessel" liefert +;; den Wert der ersten Zeile, deren "bis" den Kontextwert abdeckt. +;; "bedingung": " ", op = = <= >= < >. Fehlt der +;; Schluessel im Kontext, wird der Eintrag KONSERVATIV mit einbezogen +;; (Protokoll: "Bedingung nicht auswertbar"). +;; ============================================================ + +(if (not (boundp '*lg-profil-cache*)) (setq *lg-profil-cache* nil)) + +;; ------------------------------------------------------------ +;; Profil laden +;; ------------------------------------------------------------ + +(defun lg-regelwerk-pfad (land / cfg) + (setq cfg (getenv "DXFM_CFG")) + (if cfg + (strcat (vl-string-right-trim "/\\" (vl-string-translate "\\" "/" cfg)) + "/regelwerk/" (strcase land) ".json")) +) + +;; Zwei Profile mischen: Regeln von kind ersetzen gleichnamige (id) von +;; basis vollstaendig, neue kommen hinzu. Kopf-Felder (land, gepflegt ...) +;; kommen vom Kind. Reine Funktion. +(defun lg-profil-mischen (basis kind / regeln r id alt) + (setq regeln (ssg-json-get basis "regeln")) + (foreach r (ssg-json-get kind "regeln") + (setq id (ssg-json-get r "id")) + (if (setq alt (vl-some '(lambda (x) (if (= (ssg-json-get x "id") id) x)) regeln)) + (setq regeln (subst r alt regeln)) + (setq regeln (append regeln (list r)))) + ) + (cons (cons "regeln" regeln) + (vl-remove-if '(lambda (p) (= (car p) "regeln")) kind)) +) + +;; Profil inkl. Vererbung laden (rekursiv, Zirkelschutz ueber besucht). +;; Rueckgabe: gemischtes Profil (Alist) oder nil. +(defun lg-profil-laden-roh (land besucht / pfad daten basis-land basis) + (setq pfad (lg-regelwerk-pfad land)) + (if (and pfad (findfile pfad) + (not (member (strcase land) besucht))) + (progn + (setq daten (ssg-json-laden pfad)) + (setq basis-land (ssg-json-get daten "erbt_von")) + (if (and basis-land (= (type basis-land) 'STR) (/= basis-land "")) + (progn + (setq basis (lg-profil-laden-roh basis-land (cons (strcase land) besucht))) + (if basis (lg-profil-mischen basis daten) daten)) + daten) + ) + ) +) + +;; Gecachtes Profil des Landes. Meldet ein nicht gepflegtes Profil einmalig. +(defun lg-profil (land / eintrag profil) + (if (null land) (setq land "DE")) + (setq eintrag (assoc (strcase land) *lg-profil-cache*)) + (if eintrag + (cdr eintrag) + (progn + (setq profil (lg-profil-laden-roh land nil)) + (if (null profil) + (princ (ssg-textf "lg-regel-profil-fehlt" (list land (lg-regelwerk-pfad land)))) + (if (not (ssg-json-get profil "gepflegt")) + (princ (ssg-textf "lg-regel-profil-ungepflegt" (list land))))) + (setq *lg-profil-cache* (cons (cons (strcase land) profil) *lg-profil-cache*)) + profil + ) + ) +) + +(defun lg-profil-reset ( / ) (setq *lg-profil-cache* nil) (princ)) + +(defun lg-regel-finden (profil id) + (vl-some '(lambda (r) (if (= (ssg-json-get r "id") id) r)) + (ssg-json-get profil "regeln")) +) + +;; ------------------------------------------------------------ +;; Aufloesung (reine Funktionen) +;; ------------------------------------------------------------ + +;; Kontextwert als Zahl, falls moeglich (Strings wie "3100" werden gewandelt). +(defun lg-kontext-zahl (v) + (cond + ((numberp v) (float v)) + ;; NICHT ((and (= (type v) 'STR) (distof v 2))) - and liefert T statt der Zahl + ((= (type v) 'STR) (distof v 2)) + (T nil) + ) +) + +;; Bedingung pruefen. Rueckgabe: 'ja, 'nein oder 'unbekannt (Schluessel fehlt +;; im Kontext oder Bedingung nicht lesbar). +(defun lg-bedingung-pruefen (bedingung kontext / ops op pos key wert ist a b) + (if (or (null bedingung) (= bedingung "")) + 'ja + (progn + ;; laengere Operatoren zuerst, sonst wird "<=" als "<" gelesen + (setq ops '("<=" ">=" "=" "<" ">") op nil) + (foreach o ops + (if (and (null op) (setq pos (vl-string-search o bedingung))) + (setq op o + key (vl-string-trim " " (substr bedingung 1 pos)) + wert (vl-string-trim " " (substr bedingung (+ pos 1 (strlen o))))))) + (cond + ((null op) 'unbekannt) + ((null (setq ist (cdr (assoc key kontext)))) 'unbekannt) + ((= op "=") + (if (and (lg-kontext-zahl ist) (lg-kontext-zahl wert)) + (if (equal (lg-kontext-zahl ist) (lg-kontext-zahl wert) 1e-9) 'ja 'nein) + (if (= (strcase (vl-princ-to-string ist)) (strcase wert)) 'ja 'nein))) + ((and (setq a (lg-kontext-zahl ist)) (setq b (lg-kontext-zahl wert))) + (if (cond ((= op "<=") (<= a b)) ((= op ">=") (>= a b)) + ((= op "<") (< a b)) ((= op ">") (> a b))) + 'ja 'nein)) + (T 'unbekannt) + ) + ) + ) +) + +;; Wert eines Eintrags (fester Wert oder Tabellenzeile). nil, wenn der +;; Tabellenschluessel im Kontext fehlt oder ueber der letzten Zeile liegt +;; (dann die letzte Zeile = groesster Tabellenwert, konservativ). +(defun lg-eintrag-wert (eintrag kontext / tab k n erg) + (setq tab (ssg-json-get eintrag "tabelle")) + (if (null tab) + (ssg-json-get eintrag "wert") + (progn + (setq k (ssg-json-get eintrag "tabellen_schluessel") + n (lg-kontext-zahl (cdr (assoc k kontext)))) + (if (null n) + ;; Schluessel fehlt -> konservativ der groesste Tabellenwert + (apply 'max (mapcar 'cadr tab)) + (progn + (setq erg (vl-some '(lambda (z) (if (<= n (car z)) (cadr z))) tab)) + (if erg erg (cadr (last tab)))))) + ) +) + +;; Text "Quelle Abschnitt (Fassung)" eines Eintrags fuer das Protokoll. +(defun lg-eintrag-quelle (eintrag / q a f) + (setq q (cond ((ssg-json-get eintrag "quelle")) ("?")) + a (ssg-json-get eintrag "abschnitt") + f (ssg-json-get eintrag "fassung")) + (strcat q (if a (strcat " " a) "") (if f (strcat " (" f ")") "")) +) + +;; Regel aufloesen. kontext = Alist (("brandgefaehrdung" . "normal") ...). +;; Rueckgabe: (wert gewinner-eintrag protokoll) oder nil, wenn kein Eintrag +;; zutrifft. protokoll = Liste von Strings (je Kandidat eine Zeile + Ergebnis). +(defun lg-regel-aufloesen (regel kontext / art kandidaten prot status w best) + (setq art (cond ((ssg-json-get regel "art")) ("wert")) + kandidaten nil prot nil) + (foreach e (ssg-json-get regel "werte") + (setq status (lg-bedingung-pruefen (ssg-json-get e "bedingung") kontext) + w (lg-eintrag-wert e kontext)) + (cond + ((eq status 'nein) + (setq prot (cons (strcat "nicht anwendbar: " (lg-eintrag-quelle e) + " [" (ssg-json-get e "bedingung") "]") prot))) + ((null w) nil) + (T + (setq kandidaten (append kandidaten (list (cons w e)))) + (setq prot (cons (strcat (vl-princ-to-string w) " " (lg-eintrag-quelle e) + (if (eq status 'unbekannt) + " [Bedingung nicht auswertbar - konservativ einbezogen]" + "")) + prot))) + ) + ) + (if kandidaten + (progn + (setq best (car kandidaten)) + (foreach k (cdr kandidaten) + (cond + ((and (= art "min") (> (car k) (car best))) (setq best k)) + ((and (= art "max") (< (car k) (car best))) (setq best k)) + ) + ) + (setq prot (cons (strcat "-> " (vl-princ-to-string (car best)) + " (" art ", strengster: " (lg-eintrag-quelle (cdr best)) ")") + prot)) + (list (car best) (cdr best) (reverse prot)) + ) + ) +) + +;; Bequemer Zugriff: Wert der Regel id im Profil des Landes (nil = unbekannt). +(defun lg-regel-wert (id kontext land / regel erg) + (setq regel (lg-regel-finden (lg-profil land) id)) + (if regel + (car (lg-regel-aufloesen regel kontext)) + (progn (princ (ssg-textf "lg-regel-unbekannt" (list id))) nil)) +) + +;; Diagnose: alle Regeln des Landes mit Ergebnis und Protokoll in die Konsole. +(defun c:LG_REGELN ( / land profil kontext erg) + (setq land (if (car (atoms-family 1 '("LG-LAND"))) (lg-land) "DE")) + (setq profil (lg-profil land)) + (setq kontext (list (cons "brandgefaehrdung" "erhoeht_ohne_loeschanlage"))) + (princ (ssg-textf "lg-regel-liste-kopf" (list land (itoa (length (ssg-json-get profil "regeln")))))) + (foreach r (ssg-json-get profil "regeln") + (setq erg (lg-regel-aufloesen r kontext)) + (princ (strcat "\n " (ssg-json-get r "id") " = " + (if erg (vl-princ-to-string (car erg)) "-") + " " (cond ((ssg-json-get r "einheit")) ("")))) + (foreach z (caddr erg) (princ (strcat "\n " z))) + ) + (princ) +) + +(princ) diff --git a/Lisp/README.md b/Lisp/README.md index a693db8..2438660 100644 --- a/Lisp/README.md +++ b/Lisp/README.md @@ -867,6 +867,51 @@ Das fertige Raster (Linien + Zaehl-Texte) wird zu einem einzigen Block --- +### `LagerInsert.lsp` / `LagerPruefung.lsp` – Lagerplanung (GOH-Buehnenlager -> RFQ-YAML) + +Parametrische Attributbloecke fuer ein mehrgeschossiges Haengewarenlager; die +Mengen gehen ueber `LAGER_EXPORT` als Overlay-YAML an das RFQ-Programm +(`lib/lager_yaml_export.py`). Konzept, Datenmodell und YAML-Zuordnung: +[`doc/plan/lager_rfq.md`](../doc/plan/lager_rfq.md). Stand: Phase 0 + 1 +(Mengen, 2D). + +| Befehl | Beschreibung | +| --- | --- | +| `c:LG_GESCHOSS` | Belagskontur waehlen/zeichnen, Dialog `lg_geschoss` (`dcl/lager.dcl`), Block `LG_GESCHOSS` + XDATA-Verknuepfung zur Kontur | +| `c:LG_GESCHOSS_AKTIV` | Aktives Geschoss setzen, Layer `LG_*_G` der anderen Geschosse frieren (0 = alle zeigen) | +| `c:LG_GESCHOSS_KOPIEREN` | Geschoss mit Belagskontur, Bereichen, Reihen, Gaengen und goh_racks 1:1 in ein oder mehrere neue Geschosse kopieren (Dialog `lg_geschoss_kopie`: Quelle - Vorgabe oberstes Geschoss -, Anzahl, Geschosshoehe je Kopie, Belag, Versatz X/Y je Kopie, 0 = deckungsgleich). Belaege gleich dem Quell-Geschossbelag folgen dem neuen Belag; Verknuepfungen, Layer `_G` und IDs sind neu | +| `c:LG_BEREICH` | Lagerbereich-Kontur + Dialog `lg_bereich`; Auto-Fuellung mit `LG_REIHE__` / `LG_GANG___`, optional Randgaenge Nord/Ost/Sued/West entlang der Konturkanten (auch L-Form) | +| `c:LG_REIHE` | Einzelne Regalreihen per Start/Ende oder getippter Laenge; wie bei `LG_GANG` mit dem Bereich verknuepft und ausgeschnitten statt neu berechnet (`lg-manuell-einfuegen`) | +| `c:LG_GANG` | Einzelne Gaenge (Dialog `lg_gang`: Breite, Art gang/randgang/quergang, Belag), mittig auf der Linie - oder ueber die Option `Rechteck` als Flaeche, gezeichnet mit dem BricsCAD-Befehl RECHTECK samt aller seiner Optionen (Abmessungen, Flaeche, Drehung ...; `lg-rechteck-zeichnen`, Auswertung `lg-rechteck-ecken-aus-poly`, Fase/Rundung wird abgelehnt) (Lage und Breite aus dem Rechteck, laengere Seite = Gangrichtung; Kern `lg-gang-aus-ecken`, `lg-gang-rechteck-einfuegen`, `Linie` schaltet zurueck); liegt die Mitte in einem Bereich, wird der Gang mit ihm verknuepft. Der Gang schneidet seine Flaeche aus den automatisch erzeugten Reihen/Gaengen heraus (`lg-sperre-anwenden`), OHNE den Bereich neu zu berechnen - von Hand geloeschte oder geaenderte Objekte bleiben, wie sie sind; bei jeder Neuberechnung bleibt er stehen (Sperrflaeche) | +| `c:LG_EDIT` | Bearbeiten (auch per Doppelklick ueber `SSG_BLOCKEDIT`, Zweig `LG_*`): Bereich -> Neuberechnung (von Hand eingefuegte und geaenderte Reihen/Gaenge bleiben stehen und werden ausgespart), Reihe/Gang -> Neuaufbau, danach Zuschnitt der rangniedrigeren Objekte (`auto` < `auto_geaendert` < `manuell`, `lg-quelle-rang`) statt Neuberechnung des Bereichs | +| `c:LG_REGALE` | goh_racks zaehlen (`lg-racks-sammeln`): Anzahl, Laenge und Laenge x Ebenen je Geschoss (Zuordnung wie beim Export) plus Gesamtsumme in der Befehlszeile | +| `c:LAGER_EXPORT` | `_lager_raw.json` + Python -> `_drawing.yaml` und `_nachweis.html` in `DXFM_RESULTS` | +| `c:LAGER_HAND_YAML` | Hand-YAML fuer den Abgleich (Konfliktliste) festlegen | +| `c:LG_REGELN` | Regelprofil (`cfg/regelwerk/.json`) mit aufgeloesten Werten und Quellen anzeigen | + +Gangflaechen sind je Belag schraffiert (Schraffur in der Blockdefinition, Muster aus +`data/json/lager.json` `belag_schraffur`: Spanplatte ANSI31, Gitterrost GRID, +Hallenboden keine). + +Reine Kerne (`lg-fuellung-berechnen`, `lg-randgang-streifen`, `lg-kompass`, +`lg-schraffur-vorgabe`, `lg-geschoss-fuer-punkt-kern`, `lg-geschoss-aus-layer`, +`lg-rack-geschoss`, `lg-kopie-*`, `lg-zahl-einheit`, `lg-insert-tf`/`lg-tf-verketten`, +`lg-regel-aufloesen`, `lg-profil-mischen`, JSON-Schreiber, Feldpruefung) in +`tests/test_unit.lsp` (`tu-tests-lager`); Zeichnungsablauf in +`tests/test_lager.lsp` (`TEST_LAGER`, inkl. Rack-Erkennung mit Verschachtelung und +MINSERT sowie goh_rack-Regression gegen `tests/testdata/goh_storage_test.dwg`). + +`goh_rack`-Bloecke (parametrischer BricsCAD-Block) erkennt und zaehlt `LagerInsert.lsp` +selbst (`lg-racks-sammeln`): Name ueber die Objekteigenschaft `EffectiveName`, +Parameter `Laenge`/`Ebenen` (`lager.json` `goh_rack`) als MCAD-Eigenschaft, dynamische +Eigenschaft, Attribut oder ATTDEF-Vorgabe, Einheiten auf mm umgerechnet; gefunden auch +in assoziativen Arrays, MINSERT-Rastern und verschachtelten Bloecken. `LAGER_EXPORT` +ordnet jedes goh_rack zuerst ueber den Layer `LG_*_G` seines obersten INSERTs +(`lg-rk-top`), erst dann ueber die Geschosskontur zu. `LG_REGALE` zeigt die Zaehlung +je Geschoss in der Befehlszeile. + +--- + ### `Jam_Edit.lsp` – Staubereiche (SPS) Staubereiche zeichnet sps_skel (`tro_annotate.py --anlage`) als Block `JAM_AREA` diff --git a/Lisp/SSG_LIB_Commands.lsp b/Lisp/SSG_LIB_Commands.lsp index 720d115..483f7f5 100644 --- a/Lisp/SSG_LIB_Commands.lsp +++ b/Lisp/SSG_LIB_Commands.lsp @@ -434,6 +434,16 @@ (command "_.EATTEDIT") ) + ;; Lager-Bloecke (LG_GESCHOSS, LG_BEREICH, LG_REIHE_*, LG_GANG_*) -> + ;; eigener Editor in LagerInsert.lsp. Ebenfalls VOR dem Omniflo-Zweig, + ;; aus demselben Grund wie GR_* (Attribut-Bloecke). + ((wcmatch (strcase bname) "LG_*") + (ssg-ensure "LagerInsert") + (ssg-start nil nil) + (lg-edit-ent ent) + (ssg-end) + ) + ;; 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- diff --git a/Lisp/ssg_core.lsp b/Lisp/ssg_core.lsp index 53af850..eda7b2b 100644 --- a/Lisp/ssg_core.lsp +++ b/Lisp/ssg_core.lsp @@ -1629,6 +1629,210 @@ ) +;; ------------------------------------------------------------ +;; POLYGON-HELFER (2D, reine Berechnung - ohne Zeichnung testbar) +;; ------------------------------------------------------------ +;; Ein Polygon ist eine Liste von Punkten ((x y [z]) ...), implizit +;; geschlossen (der letzte Punkt wird mit dem ersten verbunden; ein +;; doppelter Schlusspunkt stoert nicht). Z wird ignoriert. +;; Genutzt von LagerInsert.lsp (Geschoss-/Bereichskonturen, Auto-Fuellung), +;; bewusst hier, weil nichts davon lagerspezifisch ist. + +;; Vorzeichenbehaftete Flaeche (Gauss'sche Trapezformel): > 0 = Umlauf +;; gegen den Uhrzeigersinn, < 0 = im Uhrzeigersinn. +(defun ssg-poly-flaeche-signiert (pts / s p q rest) + (setq s 0.0 rest pts) + (while rest + (setq p (car rest) + q (if (cdr rest) (cadr rest) (car pts))) + (setq s (+ s (- (* (car p) (cadr q)) (* (car q) (cadr p))))) + (setq rest (cdr rest)) + ) + (/ s 2.0) +) + +;; Flaeche, immer >= 0. +(defun ssg-poly-flaeche (pts) + (abs (ssg-poly-flaeche-signiert pts)) +) + +;; Kanten als Liste ((p1 p2) ...), inklusive Schlusskante. Kanten der +;; Laenge 0 (doppelter Schlusspunkt) fallen weg. +(defun ssg-poly-kanten (pts / erg rest p q) + (setq erg nil rest pts) + (while rest + (setq p (car rest) + q (if (cdr rest) (cadr rest) (car pts))) + (if (> (distance (list (car p) (cadr p)) (list (car q) (cadr q))) 1e-9) + (setq erg (cons (list p q) erg))) + (setq rest (cdr rest)) + ) + (reverse erg) +) + +;; Umfang (Summe aller Kantenlaengen in XY). +(defun ssg-poly-umfang (pts / s) + (setq s 0.0) + (foreach k (ssg-poly-kanten pts) + (setq s (+ s (distance (list (car (car k)) (cadr (car k))) + (list (car (cadr k)) (cadr (cadr k))))))) + s +) + +;; Achsparallele Huelle -> ((xmin ymin) (xmax ymax)), nil fuer leere Liste. +(defun ssg-poly-bbox (pts) + (if pts + (list (list (apply 'min (mapcar 'car pts)) (apply 'min (mapcar 'cadr pts))) + (list (apply 'max (mapcar 'car pts)) (apply 'max (mapcar 'cadr pts)))) + ) +) + +;; Punkt im Polygon? (Strahlverfahren, Punkte AUF dem Rand sind nicht +;; eindeutig zugeordnet - fuer Zuordnungen mit Toleranz unkritisch.) +(defun ssg-punkt-in-poly-p (pt pts / innen p q x y) + (setq innen nil x (car pt) y (cadr pt)) + (foreach k (ssg-poly-kanten pts) + (setq p (car k) q (cadr k)) + (if (and (not (eq (> (cadr p) y) (> (cadr q) y))) + (< x (+ (car p) (/ (* (- (car q) (car p)) (- y (cadr p))) + (- (cadr q) (cadr p)))))) + (setq innen (not innen))) + ) + innen +) + +;; Punkt um zentrum (2D) um winkel (Bogenmass, gegen Uhrzeigersinn) drehen. +;; Ein Z-Wert bleibt erhalten. +(defun ssg-punkt-drehen (pt winkel zentrum / dx dy c s erg) + (setq dx (- (car pt) (car zentrum)) + dy (- (cadr pt) (cadr zentrum)) + c (cos winkel) + s (sin winkel)) + (setq erg (list (+ (car zentrum) (- (* dx c) (* dy s))) + (+ (cadr zentrum) (+ (* dx s) (* dy c))))) + (if (caddr pt) (append erg (list (caddr pt))) erg) +) + +(defun ssg-poly-drehen (pts winkel zentrum) + (mapcar '(lambda (p) (ssg-punkt-drehen p winkel zentrum)) pts) +) + +;; Innere Abschnitte der waagrechten Geraden y im Polygon. +;; Rueckgabe: aufsteigend sortierte Intervalle ((x1 x2) ...). +;; Halboffene Kantenregel (ymin <= y < ymax): eine Gerade genau durch eine +;; Ecke zaehlt die Kante nur einmal; waagrechte Kanten zaehlen nie. +(defun ssg-poly-schnitt-y (pts y / xs p q erg) + (setq xs nil) + (foreach k (ssg-poly-kanten pts) + (setq p (car k) q (cadr k)) + (if (or (and (<= (cadr p) y) (< y (cadr q))) + (and (<= (cadr q) y) (< y (cadr p)))) + (setq xs (cons (+ (car p) (/ (* (- (car q) (car p)) (- y (cadr p))) + (- (cadr q) (cadr p)))) + xs))) + ) + (setq xs (vl-sort xs '<) erg nil) + (while (cadr xs) + (if (> (- (cadr xs) (car xs)) 1e-9) + (setq erg (cons (list (car xs) (cadr xs)) erg))) + (setq xs (cddr xs)) + ) + (reverse erg) +) + +;; Polygon an der Waagrechten y = grenze abschneiden (Sutherland-Hodgman, +;; eine Kante). oben = T behaelt den Teil mit y >= grenze, sonst y <= grenze. +;; Rueckgabe: Punktliste (2D), leer = nichts bleibt. +(defun ssg-poly-clip-y (pts grenze oben / erg rest p q pin qin) + (setq erg nil rest pts) + (while rest + (setq p (car rest) + q (if (cdr rest) (cadr rest) (car pts))) + (setq pin (if oben (>= (cadr p) grenze) (<= (cadr p) grenze)) + qin (if oben (>= (cadr q) grenze) (<= (cadr q) grenze))) + (if pin (setq erg (cons (list (car p) (cadr p)) erg))) + (if (not (eq pin qin)) + (setq erg (cons (list (+ (car p) (/ (* (- (car q) (car p)) (- grenze (cadr p))) + (- (cadr q) (cadr p)))) + grenze) + erg))) + (setq rest (cdr rest)) + ) + (reverse erg) +) + +;; x-Ausdehnung des Polygonteils im Band y0 <= y <= y1: (xmin xmax) oder nil. +;; Ein Polygon, das das Band nur an einer Kante beruehrt (Teil ohne Hoehe), +;; zaehlt nicht - ein Gang direkt neben einer Reihe sperrt sie nicht. +;; Exakt fuer konvexe Polygone (Gang-Rechtecke); bei konkaven die Huelle. +(defun ssg-poly-band-x (pts y0 y1 / c ys xs) + (setq c (ssg-poly-clip-y (ssg-poly-clip-y pts y0 T) y1 nil)) + (if c + (progn + (setq ys (mapcar 'cadr c) xs (mapcar 'car c)) + (if (and (> (- (apply 'max ys) (apply 'min ys)) 1e-6) + (> (- (apply 'max xs) (apply 'min xs)) 1e-6)) + (list (apply 'min xs) (apply 'max xs))))) +) + +;; Schnittmenge zweier sortierter Intervall-Listen ((a1 a2) ...). +(defun ssg-intervalle-schnitt (a b / erg lo hi) + (setq erg nil) + (while (and a b) + (setq lo (max (car (car a)) (car (car b))) + hi (min (cadr (car a)) (cadr (car b)))) + (if (> hi lo) (setq erg (cons (list lo hi) erg))) + (if (< (cadr (car a)) (cadr (car b))) + (setq a (cdr a)) + (setq b (cdr b))) + ) + (reverse erg) +) + +;; Intervall-Liste a ohne die Intervalle aus b (beide sortiert). +(defun ssg-intervalle-minus (a b / erg iv rest x1 x2 bb) + (setq erg nil) + (foreach iv a + (setq rest (list iv)) + (foreach bb b + (setq rest + (apply 'append + (mapcar + '(lambda (r / x1 x2) + (setq x1 (car r) x2 (cadr r)) + (cond + ((or (<= (cadr bb) x1) (>= (car bb) x2)) (list r)) + (T (append + (if (> (car bb) x1) (list (list x1 (car bb)))) + (if (< (cadr bb) x2) (list (list (cadr bb) x2))))))) + rest))) + ) + (setq erg (append erg rest)) + ) + erg +) + +;; Eckpunkte einer LWPOLYLINE in Weltkoordinaten (Z aus der Erhebung, Code +;; 38). Bogensegmente (Bulge /= 0) werden wie gerade Kanten behandelt; ob +;; welche vorkommen, meldet ssg-lwpoly-boegen-p. +(defun ssg-lwpoly-punkte (ent / ed z nrm erg) + (setq ed (entget ent) + z (cond ((cdr (assoc 38 ed))) (0.0)) + nrm (cond ((cdr (assoc 210 ed))) ('(0.0 0.0 1.0)))) + (setq erg nil) + (foreach g ed + (if (= (car g) 10) + (setq erg (cons (trans (list (cadr g) (caddr g) z) nrm 0) erg))) + ) + (reverse erg) +) + +(defun ssg-lwpoly-boegen-p (ent) + (vl-some '(lambda (g) (and (= (car g) 42) (not (equal (cdr g) 0.0 1e-9)))) + (entget ent)) +) + + ;; ------------------------------------------------------------ ;; DCL ZUR LAUFZEIT GENERIEREN ;; ------------------------------------------------------------ diff --git a/Lisp/ssg_load.lsp b/Lisp/ssg_load.lsp index c282152..de14c49 100644 --- a/Lisp/ssg_load.lsp +++ b/Lisp/ssg_load.lsp @@ -45,7 +45,10 @@ (load (strcat base-path "ssg_blocktools.lsp") "ssg_blocktools.lsp nicht gefunden") (load (strcat base-path "ssg_texttools.lsp") "ssg_texttools.lsp nicht gefunden") (load (strcat base-path "ssg_stueckliste.lsp") "ssg_stueckliste.lsp nicht gefunden") - (prompt "\nSSG-Bibliotheken geladen: ssg_core, ssg_lang, ssg_dbg, ssg_dialog, ssg_layer, ssg_input, KreiselInsert, VarioFoerderer, Gefaellestrecke, export, OmniModulInsert, SSG_LIB_Commands, ssg_blocktools, ssg_texttools, ssg_stueckliste") + ;; Lagerplanung: Regelprofile VOR LagerInsert, das sie nutzt + (load (strcat base-path "LagerPruefung.lsp") "LagerPruefung.lsp nicht gefunden") + (load (strcat base-path "LagerInsert.lsp") "LagerInsert.lsp nicht gefunden") + (prompt "\nSSG-Bibliotheken geladen: ssg_core, ssg_lang, ssg_dbg, ssg_dialog, ssg_layer, ssg_input, KreiselInsert, VarioFoerderer, Gefaellestrecke, export, OmniModulInsert, SSG_LIB_Commands, ssg_blocktools, ssg_texttools, ssg_stueckliste, LagerPruefung, LagerInsert") (princ) ) diff --git a/cfg/example_goh_storage.yaml b/cfg/example_goh_storage.yaml new file mode 100644 index 0000000..22b0b34 --- /dev/null +++ b/cfg/example_goh_storage.yaml @@ -0,0 +1,230 @@ +# ===================================================================================== +# GOH-Regal-Ausschreibung – Projektdaten (Schema v2) +# ===================================================================================== +# Regeln: +# * Feld weglassen -> nicht relevant, erscheint nicht im Dokument +# * Feld = null -> OFFENER PUNKT: erscheint als "to be clarified" (rot, kursiv) und +# zusätzlich in der Tabelle "Open points" am Ende des Dokuments +# * Felder ohne Angabe verwenden die Standardwerte/-texte aus rfq/model.py +# * Zahlen mit Punkt als Dezimaltrennzeichen eingeben (3.10), Ausgabe erfolgt deutsch (3,1) +# * In Freitexten (remarks, scope, ...) sind {floors} und {floors_word} erlaubt +# * "# BSP" = Beispielwert, im Original-Dokument nicht enthalten -> prüfen/ersetzen +# ===================================================================================== + +# ---------------------------------------------------------------- Anfrage / Dokument +doc: + title: Budget request for steel racking scaffolding/platform + subtitle: Steel structure for a four-level garment-on-hanger (GOH) rack # BSP + request_type: budget # budget | binding + reference: RFQ-GOH-Storage-2026 # z. B. RFQ-2026-017 + date: 07.10.2026 # Ausgabedatum, z. B. 07.10.2026 + revision: "0" + contact: + name: Max Mustermann + function: Project Engineering # BSP + phone: +49 xxx xxx + email: xxx@schoenenberger.de + # output_name: SSG_RFP_GOH_racking_Projekt-X + +# ---------------------------------------------------------------- Projekt +project: + name: goh-slo_pol # Projektname, wie er dem Anbieter genannt werden darf + # project_no: P-12345 + # end_customer: ... # weglassen, wenn vertraulich + building_type: a new warehouse + industry: textile logistics + rack_type: static hanging goods rack + additional_purpose: The scaffolding is also used to suspend the conveyor technology. + +# ---------------------------------------------------------------- Termine +deadlines: + questions: 10.10.2026 # Frist für technische Rückfragen + offer: End of October 2026 # Angebotsabgabe + # award: Q1/2027 + approval_drawings: null # Freigabepläne des Anbieters + delivery: null + assembly_start: Mid of 2027 (expected) + assembly_end: null + # handover: ... + +# ---------------------------------------------------------------- Baustelle +site: + location: Slovakia or Poland + address: null + #access_notes: null # Zufahrt, Anlieferung, Entladung, Kranstellfläche + #working_hours: null + hall_available_from: Mid of 2027 # Halle geschlossen/beheizt verfügbar ab + #provided_by_client: # Beistellungen des Auftraggebers + # - Electricity (230/400 V) at one connection point in the hall # BSP + # - Sanitary facilities # BSP + +# ---------------------------------------------------------------- Gebäude / Halle +building: + hall_clear_height_m: 12 # Unterkante Dachtragwerk + sprinkler_clearance_m: null # erforderlicher Freiraum unter Deckensprinklern + column_grid: 12 x 24 m # z. B. "12,0 x 24,0 m" + climate: null # z. B. "closed, heated hall, +5 … +30 °C" + corrosivity_category: C1 # BSP – beheizte Halle + floor_slab: + thickness_mm: null + concrete_class: null + permissible_load_kn_m2: null # bisher "Permissible soil load still to be clarified" + permissible_point_load_kn: null + floor_heating: null # true -> Dübel-Einschränkungen + # anchoring_notes: max. drilling depth 120 mm (sealing layer) + +# ---------------------------------------------------------------- Erdbeben +seismic: + ag_ms2: 0.63 # Bemessungswert der Bodenbeschleunigung (Original: a_b) + ground_type: null # Baugrundklasse A–E + importance_class: null + code: EN 1998-1 | STN EN 1998-1 # BSP + +# ---------------------------------------------------------------- Bemessung / Ausführung +design: + standards: [EN 1990, EN 1991-1-1, EN 1993-1-1, EN 1090-2] # BSP + execution_class: EXC2 # BSP + deflection_general: L/300 # BSP + #deflection_rail_beams: null # wichtig für Trolley-Betrieb, z. B. "L/500, max. 5 mm" + # tolerances: ... + #surface_protection: null # z. B. hot-dip galvanized / powder-coated + # colour: RAL 5010 + fire_protection: null # z. B. "Sprinkler system acc. to FM Global" + #max_escape_route_m: null + # extra: [Further requirement ...] + +# ---------------------------------------------------------------- Konfiguration / Layout +layout: + block_length_m: null + block_width_m: null + # aisles: 40 + # aisle_length_m: 2938.9 # Summe aller Ganglängen je Ebene; sonst aus rack_length_m berechnet + aisle_width_m: 1.00 + rack_rows_per_aisle: 2 + rack_depth_m: 0.7 # Tiefe einer Regalreihe (Bügelbreite) + trolley_rails_per_aisle: 1 + #max_column_spacing_m: null # Vorgabe, falls Gänge/Durchfahrten frei bleiben müssen + storey_height_m: 3.1 # nur Vorgabe, wenn floor_elevation_m fehlt + # column_height_m: 11.85 # sonst berechnet: oberste Belagshöhe + lichte Höhe oben + rail: + type: null # Schienentyp/-profil des Auftraggebers + fixation_spacing_m: 2 + supplied_by: client # client | supplier + suspended_by: client # client | supplier + interface: null # z. B. "threaded rod M12 through cross beam" + +# ---------------------------------------------------------------- Ebenen (von unten nach oben) +# rack_length_m = Laufmeter Hängeschiene je Hängeebene, SUMME ALLER REIHEN der Ebene. +# rail_height_m = Oberkante Schiene über Belag der Ebene, goods_length_m = max. Warenlänge. +# Treppen/Leitern (stairs_down/ladders_down) führen zur Ebene darunter. +levels: + - remark: ground floor + floor_elevation_m: 0.00 + clear_height_m: 2.43 # BSP + hanging_levels: + - {rail_height_m: 2.30, rack_length_m: 5877.8, goods_length_m: 1.20} # Höhen BSP + - {rail_height_m: 1.10, rack_length_m: 5877.8, goods_length_m: 1.10} + + - floor_elevation_m: 3.10 + clear_height_m: 2.95 # BSP + hanging_levels: + - {rail_height_m: 2.80, rack_length_m: 5877.8, goods_length_m: 1.20} + - {rail_height_m: 1.40, rack_length_m: 5877.8, goods_length_m: 1.20} + walkway: {area_m2: 4286, spec: chipboard} + grating: null # Fläche Gitterrost (Absturzsicherung) noch offen + railing_m: 380 + kickplate_m: null + stairs_down: 1 # oder: [{qty: 1, width_m: 1.0, note: straight stair}] + ladders_down: 7 + # openings: [{qty: 4, size: "1,2 x 1,0 m", purpose: conveyor passage}] + # pallet_gates: 2 + # loads: {platform_imposed_kg_m2: 300, conveyor_kg_m: 40} # überschreibt globale Lasten + + - floor_elevation_m: 6.20 + clear_height_m: 2.95 # BSP + hanging_levels: + - {rail_height_m: 2.80, rack_length_m: 5877.8, goods_length_m: 1.20} + - {rail_height_m: 1.40, rack_length_m: 5877.8, goods_length_m: 1.20} + walkway: {area_m2: 4286, spec: chipboard} + grating: null + railing_m: 380 + kickplate_m: null + stairs_down: 1 + ladders_down: 7 + + - floor_elevation_m: 9.30 + clear_height_m: 2.55 # bis Stützenkopf -> Stützenhöhe 11,85 m + hanging_levels: + - {rail_height_m: 2.40, rack_length_m: 5877.8, goods_length_m: 1.10} # BSP + - {rail_height_m: 1.20, rack_length_m: 5877.8, goods_length_m: 1.10} + walkway: {area_m2: 4286, spec: chipboard} + grating: null + railing_m: 380 + kickplate_m: null + stairs_down: 1 + ladders_down: 7 + +# ---------------------------------------------------------------- Beläge und Absturzsicherung +flooring: + walkway: + material: chipboard + thickness_mm: null + fire_class: null + grating: + purpose: fall protection within the rack rows + type: null + mesh: null + sprinkler_permeable: null + kickplate_height_mm: 100 # ersetzt das widersprüchliche "skirting boards min. 5 cm" + railing: + height_m: 1.0 + knee_rail: true + +# ---------------------------------------------------------------- Treppen und Leitern +access: + stairs: + standard: EN ISO 14122-3 + width_m: null + handrails: both sides + ladders: + type: emergency ladder + standard: EN ISO 14122-4 + cage: null + gate: self-closing safety gate at each ladder opening + +# ---------------------------------------------------------------- Lastannahmen (global, je Ebene überschreibbar) +loads: + platform_imposed_kg_m2: 250 + imposed_category: null # Nutzungskategorie nach EN 1991-1-1 / NA SK + point_load_kn: null + railing_horizontal_kn_m: null # z. B. 0,5 kN/m + rail_dead_kg_m: 10 # Trolley-Schiene inkl. Abhängung (ständig) + rail_goods_kg_m: 80 # Trolleys und Hängeware (veränderlich) + hanging_per_level_kg_m: 60 # Hängeware je m Hängeschiene je Hängeebene + grating_kg_m2: 100 + building_services_kg_m2: 25 + conveyor_kg_m: null # abgehängte Fördertechnik (siehe project.additional_purpose) + # conveyor_horizontal_kn: 2 + collision_protection: null # Anfahrschutz im EG, falls Flurförderzeuge verkehren + +# ---------------------------------------------------------------- Kaufmännisches +commercial: + currency: EUR + incoterms: DAP site (Incoterms 2020) # BSP + payment_terms: null + warranty_months: null + offer_validity: 3 months # BSP + offer_language: English + +# ---------------------------------------------------------------- Anlagen +attachments: + - {ref: "1", title: Layout drawing rack block, revision: null} # BSP + - {ref: "2", title: Sections with level heights, revision: null} # BSP + +# Optional überschreibbare Standardtexte (siehe rfq/model.py): +# remarks: [...] +# scope: [...] +# exclusions: [...] +# documentation: [...] +# requested_info: [...] +# client: {description: [...]} diff --git a/cfg/layer.cfg b/cfg/layer.cfg index 3ec6992..a90399b 100644 --- a/cfg/layer.cfg +++ b/cfg/layer.cfg @@ -176,6 +176,21 @@ planquadrate = ZZ_Planquadrate, 8 kettenfoerderer = Omni_Kettenfoerderer, 4 +# ============================================================ +# Lagerplanung (LagerInsert.lsp, Bloecke LG_*) +# ============================================================ +# Wert = BASIS-Layername, Farbe. Der tatsaechliche Layer haengt die +# Geschossnummer an ("LG_Reihe" -> "LG_Reihe_G2"), damit LG_GESCHOSS_AKTIV +# die Layer der anderen Geschosse frieren kann - die 2D-Grundrisse der +# Geschosse liegen uebereinander. Gelesen von lg-layer (LagerInsert.lsp). +[lager] +geschoss = LG_Geschoss, 7 +bereich = LG_Bereich, 4 +reihe = LG_Reihe, 3 +gang = LG_Gang, 8 +goh_rack = LG_Regal, 3 + + # ============================================================ # Farben fuer Layer OHNE Baugruppen-Key # ============================================================ diff --git a/cfg/regelwerk/DE.json b/cfg/regelwerk/DE.json new file mode 100644 index 0000000..4f5e807 --- /dev/null +++ b/cfg/regelwerk/DE.json @@ -0,0 +1,222 @@ +{ + "land": "DE", + "bezeichnung": "Deutschland - ArbStaettV/ASR, DGUV Regel 108-007, EN ISO 14122-3/-4", + "gepflegt": true, + "erbt_von": null, + "hinweis": "Startwerte aus der Planung (doc/plan/lager_rfq.md). verifiziert=false, bis Wert, Abschnitt und Fassung gegen den Originaltext geprueft sind. Die Aufloesung (lg-regel-aufloesen, LagerPruefung.lsp) waehlt je Regel den STRENGSTEN Wert aller Quellen, deren Bedingung zutrifft: art=min -> groesster Wert, art=max -> kleinster Wert.", + "regeln": [ + { + "id": "fluchtweg.laenge_max", + "art": "max", + "einheit": "mm", + "messung": "Luftlinie", + "kommentar": "Textil-Haengeware: erhoehte Brandgefaehrdung pruefen. Ohne Angabe gilt die konservative Annahme erhoeht_ohne_loeschanlage.", + "werte": [ + {"wert": 35000, "quelle": "ASR A2.3", "abschnitt": "5 (2)", "fassung": "", "bedingung": "brandgefaehrdung = normal", "verifiziert": false}, + {"wert": 35000, "quelle": "ASR A2.3", "abschnitt": "5 (2)", "fassung": "", "bedingung": "brandgefaehrdung = erhoeht_mit_loeschanlage", "verifiziert": false}, + {"wert": 25000, "quelle": "ASR A2.3", "abschnitt": "5 (2)", "fassung": "", "bedingung": "brandgefaehrdung = erhoeht_ohne_loeschanlage", "verifiziert": false} + ] + }, + { + "id": "fluchtweg.laufweg_faktor_max", + "art": "max", + "einheit": "faktor", + "kommentar": "tatsaechliche Laufweglaenge <= Faktor x Fluchtweglaenge (Luftlinie). Beide Werte werden geprueft.", + "werte": [ + {"wert": 1.5, "quelle": "ASR A2.3", "abschnitt": "5 (2)", "fassung": "", "verifiziert": false} + ] + }, + { + "id": "fluchtweg.breite_min", + "art": "min", + "einheit": "mm", + "kommentar": "Tabelle nach Personenzahl (PERSONEN am Geschoss). Zeilen [bis_personen, breite_mm]; Minimum 875 mm.", + "werte": [ + {"tabelle": [[5, 875], [20, 1000], [200, 1200], [300, 1800], [400, 2400]], "tabellen_schluessel": "personen", "quelle": "ASR A2.3", "abschnitt": "5 Tabelle 3", "fassung": "", "verifiziert": false} + ] + }, + { + "id": "fluchtweg.leiter_anrechenbar", + "art": "wert", + "einheit": "bool", + "kommentar": "Leitern sind kein Fluchtweg, nur Notausstieg - ladders_down zaehlt nicht fuer die Fluchtwegpruefung.", + "werte": [ + {"wert": 0, "quelle": "ASR A2.3", "abschnitt": "4", "fassung": "", "verifiziert": false} + ] + }, + { + "id": "treppe.steigung_min", + "art": "min", + "einheit": "mm", + "werte": [ + {"wert": 140, "quelle": "ASR A1.8", "abschnitt": "4.5", "fassung": "", "verifiziert": false} + ] + }, + { + "id": "treppe.steigung_max", + "art": "max", + "einheit": "mm", + "werte": [ + {"wert": 190, "quelle": "ASR A1.8", "abschnitt": "4.5", "fassung": "", "verifiziert": false} + ] + }, + { + "id": "treppe.auftritt_min", + "art": "min", + "einheit": "mm", + "werte": [ + {"wert": 260, "quelle": "ASR A1.8", "abschnitt": "4.5", "fassung": "", "verifiziert": false} + ] + }, + { + "id": "treppe.auftritt_max", + "art": "max", + "einheit": "mm", + "werte": [ + {"wert": 320, "quelle": "ASR A1.8", "abschnitt": "4.5", "fassung": "", "verifiziert": false} + ] + }, + { + "id": "treppe.schrittmass_min", + "art": "min", + "einheit": "mm", + "kommentar": "Schrittmass 2 x Steigung + Auftritt; Schnittmenge ASR A1.8 und EN ISO 14122-3.", + "werte": [ + {"wert": 590, "quelle": "ASR A1.8", "abschnitt": "4.5", "fassung": "", "verifiziert": false}, + {"wert": 600, "quelle": "EN ISO 14122-3", "abschnitt": "5", "fassung": "", "verifiziert": false} + ] + }, + { + "id": "treppe.schrittmass_max", + "art": "max", + "einheit": "mm", + "werte": [ + {"wert": 650, "quelle": "ASR A1.8", "abschnitt": "4.5", "fassung": "", "verifiziert": false}, + {"wert": 660, "quelle": "EN ISO 14122-3", "abschnitt": "5", "fassung": "", "verifiziert": false} + ] + }, + { + "id": "treppe.winkel_min", + "art": "min", + "einheit": "grad", + "werte": [ + {"wert": 30, "quelle": "EN ISO 14122-3", "abschnitt": "4", "fassung": "", "verifiziert": false} + ] + }, + { + "id": "treppe.winkel_max", + "art": "max", + "einheit": "grad", + "kommentar": "bevorzugter Bereich 30-38 Grad, zulaessig bis 45 Grad - konservativ 38.", + "werte": [ + {"wert": 38, "quelle": "EN ISO 14122-3", "abschnitt": "4", "fassung": "", "verifiziert": false} + ] + }, + { + "id": "treppe.laufhoehe_max", + "art": "max", + "einheit": "mm", + "kommentar": "Hoehe eines Laufs ohne Podest. Bei 3,10 m Geschosshoehe ist ein Podest noetig (strenger als ASR mit 18 Stufen).", + "werte": [ + {"wert": 3000, "quelle": "EN ISO 14122-3", "abschnitt": "4", "fassung": "", "verifiziert": false} + ] + }, + { + "id": "treppe.stufen_je_lauf_max", + "art": "max", + "einheit": "stueck", + "werte": [ + {"wert": 18, "quelle": "ASR A1.8", "abschnitt": "4.5", "fassung": "", "verifiziert": false} + ] + }, + { + "id": "lichte_hoehe.verkehrsweg_min", + "art": "min", + "einheit": "mm", + "werte": [ + {"wert": 2000, "quelle": "ASR A1.8", "abschnitt": "4.1", "fassung": "", "verifiziert": false} + ] + }, + { + "id": "lichte_hoehe.treppe_min", + "art": "min", + "einheit": "mm", + "kommentar": "Kopfhoehe ueber der Treppe; EN ISO 14122-3 verlangt mehr als die ASR.", + "werte": [ + {"wert": 2000, "quelle": "ASR A1.8", "abschnitt": "4.5", "fassung": "", "verifiziert": false}, + {"wert": 2300, "quelle": "EN ISO 14122-3", "abschnitt": "4", "fassung": "", "verifiziert": false} + ] + }, + { + "id": "gelaender.hoehe_min", + "art": "min", + "einheit": "mm", + "kommentar": "Die Beispiel-YAML hat 1,0 m - die Konfliktliste meldet die Abweichung.", + "werte": [ + {"wert": 1000, "quelle": "ASR A2.1", "abschnitt": "5.2", "fassung": "", "bedingung": "absturzhoehe <= 12000", "verifiziert": false}, + {"wert": 1100, "quelle": "ASR A2.1", "abschnitt": "5.2", "fassung": "", "bedingung": "absturzhoehe > 12000", "verifiziert": false}, + {"wert": 1100, "quelle": "EN ISO 14122-3", "abschnitt": "7", "fassung": "", "verifiziert": false} + ] + }, + { + "id": "gelaender.bordbrett_min", + "art": "min", + "einheit": "mm", + "werte": [ + {"wert": 100, "quelle": "EN ISO 14122-3", "abschnitt": "7", "fassung": "", "verifiziert": false} + ] + }, + { + "id": "gelaender.knieleiste_abstand_max", + "art": "max", + "einheit": "mm", + "werte": [ + {"wert": 500, "quelle": "EN ISO 14122-3", "abschnitt": "7", "fassung": "", "verifiziert": false} + ] + }, + { + "id": "absturz.sicherung_ab", + "art": "max", + "einheit": "mm", + "kommentar": "Absturzsicherung ab dieser Absturzhoehe; konservativ werden alle Buehnenkanten gesichert.", + "werte": [ + {"wert": 1000, "quelle": "ASR A2.1", "abschnitt": "4.1", "fassung": "", "verifiziert": false} + ] + }, + { + "id": "leiter.rueckenschutz_ab", + "art": "max", + "einheit": "mm", + "werte": [ + {"wert": 3000, "quelle": "EN ISO 14122-4", "abschnitt": "4", "fassung": "", "verifiziert": false} + ] + }, + { + "id": "leiter.steighoehe_je_lauf_max", + "art": "max", + "einheit": "mm", + "werte": [ + {"wert": 6000, "quelle": "EN ISO 14122-4", "abschnitt": "4", "fassung": "", "verifiziert": false} + ] + }, + { + "id": "gang.breite_hauptgang_min", + "art": "min", + "einheit": "mm", + "kommentar": "Hauptgang mit Personenverkehr. Wert aus DGUV 108-007 in Phase 0 gegen den Originaltext pruefen.", + "werte": [ + {"wert": 1250, "quelle": "DGUV Regel 108-007", "abschnitt": "", "fassung": "", "verifiziert": false} + ] + }, + { + "id": "gang.breite_nebengang_min", + "art": "min", + "einheit": "mm", + "kommentar": "Regal-Nebengang (Gang zwischen Regalreihen, nur Personen). Wert aus DGUV 108-007 in Phase 0 gegen den Originaltext pruefen.", + "werte": [ + {"wert": 750, "quelle": "DGUV Regel 108-007", "abschnitt": "", "fassung": "", "verifiziert": false}, + {"wert": 875, "quelle": "ASR A1.8", "abschnitt": "4.2", "fassung": "", "bedingung": "gang_ist_fluchtweg = ja", "verifiziert": false} + ] + } + ] +} diff --git a/cfg/regelwerk/PL.json b/cfg/regelwerk/PL.json new file mode 100644 index 0000000..9757a6a --- /dev/null +++ b/cfg/regelwerk/PL.json @@ -0,0 +1,8 @@ +{ + "land": "PL", + "bezeichnung": "Polen - Geruest, erbt alle Werte aus DE", + "gepflegt": false, + "erbt_von": "DE", + "hinweis": "Nicht gepflegtes Laenderprofil: alle Regeln kommen aus DE. Abweichende nationale Werte als eigene Regel-Records (gleiche id) unter regeln eintragen - sie ersetzen dann den DE-Record vollstaendig. Solange gepflegt=false ist, weist der Nachweis eine Warnung aus.", + "regeln": [] +} diff --git a/cfg/regelwerk/SK.json b/cfg/regelwerk/SK.json new file mode 100644 index 0000000..f325150 --- /dev/null +++ b/cfg/regelwerk/SK.json @@ -0,0 +1,8 @@ +{ + "land": "SK", + "bezeichnung": "Slowakei - Geruest, erbt alle Werte aus DE", + "gepflegt": false, + "erbt_von": "DE", + "hinweis": "Nicht gepflegtes Laenderprofil: alle Regeln kommen aus DE. Abweichende nationale Werte als eigene Regel-Records (gleiche id) unter regeln eintragen - sie ersetzen dann den DE-Record vollstaendig. Solange gepflegt=false ist, weist der Nachweis eine Warnung aus.", + "regeln": [] +} diff --git a/data/json/lager.json b/data/json/lager.json new file mode 100644 index 0000000..80b1492 --- /dev/null +++ b/data/json/lager.json @@ -0,0 +1,62 @@ +{ + "version": 1, + "beschreibung": "Vorgaben fuer die Lagerplanung (LagerInsert.lsp). Alle Masse in mm, Winkel in Grad. Die Werte belegen die Dialoge vor; was im Dialog bzw. am Block steht, gewinnt.", + "land": "DE", + + "goh_rack": { + "beschreibung": "Parametrischer BricsCAD-Block, der als Regalreihe mitgezaehlt wird (lg-racks-sammeln in LagerInsert.lsp). param_laenge/param_ebenen = Namen der MCAD-Parameter.", + "blockname": "goh_rack", + "param_laenge": "Laenge", + "param_ebenen": "Ebenen", + "tiefe_mm": 700 + }, + + "geschoss": { + "konstr_hoehe_mm": 250, + "personen": 20, + "brandgefaehrdung": "erhoeht_ohne_loeschanlage", + "belag_art": "spanplatte", + "belag_arten": ["spanplatte", "gitterrost", "hallenboden"] + }, + + "belag_yaml": { + "beschreibung": "Belagsart (Attribut BELAG_ART/BELAG) -> Wert fuer walkway.spec der RFQ-YAML. hallenboden = keine Buehne (kein walkway/grating/railing).", + "spanplatte": "chipboard", + "gitterrost": "grating", + "hallenboden": "" + }, + + "brandgefaehrdungen": ["normal", "erhoeht_mit_loeschanlage", "erhoeht_ohne_loeschanlage"], + + "bereich": { + "gangbreite_mm": 1000, + "regaltiefe_mm": 700, + "reihen_je_gang": 2, + "schienen_je_gang": 1, + "quergang_abstand_mm": 0, + "quergang_breite_mm": 1200, + "min_reihenlaenge_mm": 500, + "min_ganglaenge_mm": 100, + "richtung_grad": 0, + "belag": "spanplatte", + "randgang_mm": {"n": 0, "o": 0, "s": 0, "w": 0} + }, + + "belag_schraffur": { + "beschreibung": "Schraffur der Gangflaechen (LG_GANG_*) je Belag: muster = Schraffurmuster aus der BricsCAD-Musterdatei (metrisch: iso.pat), massstab, winkel in Grad; null = keine Schraffur. ANSI31 x 50 = Linien alle ca. 160 mm, GRID x 8 = Raster ca. 200 mm.", + "spanplatte": {"muster": "ANSI31", "massstab": 50, "winkel": 0}, + "gitterrost": {"muster": "GRID", "massstab": 8, "winkel": 0}, + "hallenboden": null + }, + + "reihe": { + "beschreibung": "haengeebenen = Schienenhoehe ueber Belag / max. Warenlaenge je Haengeebene, durch Semikolon getrennt, oberste zuerst.", + "haengeebenen": "2300/1200;1100/1100", + "schienen": 1 + }, + + "flaechen": { + "beschreibung": "reihen_als_gitterrost: Grundflaeche der Regalreihen (Laenge x Tiefe) auf Buehnen zaehlt als Gitterrost (Absturzsicherung in den Reihen).", + "reihen_als_gitterrost": true + } +} diff --git a/data/omniflo/2D/824331125.dwg b/data/omniflo/2D/824331125.dwg index ac56f1d..5f0647b 100644 Binary files a/data/omniflo/2D/824331125.dwg and b/data/omniflo/2D/824331125.dwg differ diff --git a/dcl/lager.dcl b/dcl/lager.dcl new file mode 100644 index 0000000..df05e72 --- /dev/null +++ b/dcl/lager.dcl @@ -0,0 +1,116 @@ +// ============================================================ +// lager.dcl - Dialoge der Lagerplanung (LagerInsert.lsp) +// +// Die Tile-Keys entsprechen den Feldlisten *lg-felder-geschoss*, +// *lg-felder-bereich*, *lg-felder-reihe*, *lg-felder-gang* und +// *lg-felder-kopie* in LagerInsert.lsp +// (generischer Treiber lg-dialog: popup_list fuer Auswahlfelder, +// edit_box fuer alles andere, Pruefung beim OK in lg-dialog-ok). +// Alle Masse in mm. +// ============================================================ + +lg_geschoss : dialog { + label = "Lager - Geschoss"; + : column { + : row { + : edit_box { key = "nr"; label = "Nummer:"; edit_width = 6; } + : edit_box { key = "bezeichnung"; label = "Bezeichnung:"; edit_width = 24; } + } + : boxed_column { + label = "Hoehen (mm)"; + : edit_box { key = "hoehe"; label = "Belag Oberkante:"; edit_width = 10; } + : edit_box { key = "konstr"; label = "Konstruktionshoehe Belag:"; edit_width = 10; } + : edit_box { key = "lichte"; label = "Lichte Hoehe (leer = berechnet):"; edit_width = 10; } + } + : boxed_column { + label = "Belag und Fluchtweg-Annahmen"; + : popup_list { key = "belag"; label = "Belagsart:"; width = 34; } + : edit_box { key = "personen"; label = "Personen im Geschoss:"; edit_width = 6; } + : popup_list { key = "brand"; label = "Brandgefaehrdung:"; width = 34; } + } + } + ok_cancel; + errtile; +} + +lg_bereich : dialog { + label = "Lager - Lagerbereich (Auto-Fuellung)"; + : column { + : boxed_column { + label = "Reihen und Gaenge (mm)"; + : edit_box { key = "gang"; label = "Gangbreite:"; edit_width = 10; } + : edit_box { key = "tiefe"; label = "Regaltiefe:"; edit_width = 10; } + : popup_list { key = "reihen"; label = "Reihen je Gang:"; width = 20; } + : edit_box { key = "schienen"; label = "Schienen je Gang:"; edit_width = 10; } + : edit_box { key = "richtung"; label = "Reihenrichtung (Grad):"; edit_width = 10; } + } + : boxed_column { + label = "Quergaenge (mm)"; + : edit_box { key = "q_abstand"; label = "Abstand (0 = keine):"; edit_width = 10; } + : edit_box { key = "q_breite"; label = "Breite:"; edit_width = 10; } + } + : boxed_column { + label = "Randgaenge entlang der Kontur (mm, 0 = keiner; Nord = +Y)"; + : row { + : edit_box { key = "rand_n"; label = "Nord:"; edit_width = 7; } + : edit_box { key = "rand_o"; label = "Ost:"; edit_width = 7; } + : edit_box { key = "rand_s"; label = "Sued:"; edit_width = 7; } + : edit_box { key = "rand_w"; label = "West:"; edit_width = 7; } + } + } + : boxed_column { + label = "Haengeware und Belag"; + : edit_box { key = "ebenen"; label = "Haengeebenen (Schiene/Ware;...):"; edit_width = 24; } + : popup_list { key = "belag"; label = "Belag der Gaenge:"; width = 30; } + } + } + ok_cancel; + errtile; +} + +lg_gang : dialog { + label = "Lager - Gang"; + : column { + : edit_box { key = "laenge"; label = "Laenge (mm):"; edit_width = 10; } + : edit_box { key = "breite"; label = "Breite (mm):"; edit_width = 10; } + : popup_list { key = "art"; label = "Art:"; width = 30; } + : popup_list { key = "belag"; label = "Belag:"; width = 30; } + : text { label = "Linie: Gang liegt mittig auf der Linie, Breite wie oben."; } + : text { label = "Option Rechteck: Flaeche mit RECHTECK zeichnen, Lage und Breite daraus."; } + } + ok_cancel; + errtile; +} + +lg_geschoss_kopie : dialog { + label = "Lager - Geschoss kopieren"; + : column { + : popup_list { key = "quelle"; label = "Quellgeschoss (Nr.):"; width = 30; } + : edit_box { key = "anzahl"; label = "Anzahl neuer Geschosse:"; edit_width = 6; } + : edit_box { key = "abstand"; label = "Geschosshoehe je Kopie (mm):"; edit_width = 10; } + : popup_list { key = "belag"; label = "Belag der neuen Geschosse:"; width = 30; } + : boxed_column { + label = "Versatz je Kopie (mm, 0 = deckungsgleich)"; + : row { + : edit_box { key = "versatz_x"; label = "X:"; edit_width = 10; } + : edit_box { key = "versatz_y"; label = "Y:"; edit_width = 10; } + } + } + : text { label = "Kopiert Belag, Bereiche, Reihen, Gaenge und goh_rack 1:1."; } + : text { label = "Belaege gleich dem Quell-Geschossbelag folgen dem neuen Belag."; } + } + ok_cancel; + errtile; +} + +lg_reihe : dialog { + label = "Lager - Regalreihe"; + : column { + : edit_box { key = "laenge"; label = "Laenge (mm):"; edit_width = 10; } + : edit_box { key = "tiefe"; label = "Regaltiefe (mm):"; edit_width = 10; } + : edit_box { key = "ebenen"; label = "Haengeebenen (Schiene/Ware;...):"; edit_width = 24; } + : edit_box { key = "schienen"; label = "Schienen:"; edit_width = 10; } + } + ok_cancel; + errtile; +} diff --git a/lang/de_DE.json b/lang/de_DE.json index aa92df8..e56e22b 100644 --- a/lang/de_DE.json +++ b/lang/de_DE.json @@ -1189,6 +1189,104 @@ "chh-summe": "\n Phase 3: %1 Familie(n), %2 mit Buehne; angepasst: Kreisel %3, Strecken innerhalb %4, zwischen Familien %5; Konflikte %6; einzeln gefragt %7", "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.", + "lg-daten-fehlt": "\n[Lager] Datendatei nicht gefunden: %1 - es gelten die eingebauten Vorgaben.", + "lg-dialog-fehlt": "\n[Lager] Dialog nicht ladbar (%1) - Eingabe ueber die Konsole.", + "lg-einfuegen-fehler": "\n[Lager] Block %1 konnte nicht eingefuegt werden: %2", + "lg-feld-ungueltig": "Ungueltiger Wert: %1", + "lg-wert-ungueltig": "\nUngueltiger Wert - bitte erneut eingeben.", + "lg-wahl-ungueltig": "\nBitte einen der angezeigten Werte eingeben.", + "lg-feld-nr": "\nGeschoss-Nummer", + "lg-feld-bezeichnung": "\nBezeichnung", + "lg-feld-hoehe": "\nBelagshoehe (Oberkante, mm)", + "lg-feld-konstr": "\nKonstruktionshoehe des Belags (mm)", + "lg-feld-lichte": "\nLichte Hoehe (mm, leer = berechnet)", + "lg-feld-belag": "\nBelagsart", + "lg-feld-personen": "\nPersonen im Geschoss", + "lg-feld-brand": "\nBrandgefaehrdung", + "lg-feld-gang": "\nGangbreite (mm)", + "lg-feld-tiefe": "\nRegaltiefe (mm)", + "lg-feld-reihen": "\nReihen je Gang", + "lg-feld-schienen": "\nSchienen je Gang", + "lg-feld-q_abstand": "\nQuergang-Abstand (mm, 0 = keine)", + "lg-feld-q_breite": "\nQuergang-Breite (mm)", + "lg-feld-richtung": "\nReihenrichtung (Grad)", + "lg-feld-ebenen": "\nHaengeebenen Schienenhoehe/Warenlaenge;... (mm)", + "lg-feld-laenge": "\nLaenge (mm)", + "lg-feld-rand_n": "\nRandgang Nord (mm, 0 = keiner)", + "lg-feld-rand_o": "\nRandgang Ost (mm, 0 = keiner)", + "lg-feld-rand_s": "\nRandgang Sued (mm, 0 = keiner)", + "lg-feld-rand_w": "\nRandgang West (mm, 0 = keiner)", + "lg-feld-breite": "\nGangbreite (mm)", + "lg-feld-art": "\nArt des Gangs", + "lg-titel-gang": "Lager: Gaenge einfuegen", + "lg-prompt-gang-start": "\nStartpunkt der Gangmitte oder [%1] : ", + "lg-key-rechteck": "Rechteck", + "lg-rechteck-hinweis": "\nGangflaeche mit RECHTECK zeichnen (alle Optionen wie Abmessungen/Drehung moeglich), Enter = Ende.", + "lg-rechteck-kein-rechteck": "\n[Lager] Keine einfache Rechteckflaeche (4 Ecken, ohne Fase/Rundung) - kein Gang eingefuegt.", + "lg-rechteck-zu-klein": "\n[Lager] Rechteck zu klein (Seite unter 1 mm) - kein Gang eingefuegt.", + "lg-gaenge-eingefuegt": "\n%1 Gang/Gaenge eingefuegt.", + "lg-schraffur-unbekannt": "\n[Lager] Belag %1 hat keinen Eintrag in belag_schraffur (lager.json) - Gaenge ohne Schraffur.", + "lg-schraffur-fehler": "\n[Lager] Schraffur %1 fuer %2 nicht moeglich (%3) - Gang ohne Schraffur.", + "lg-blockdef-ergaenzt": "\n[Lager] Blockdefinition %1 um %2 ergaenzt (Altbestand).", + "lg-titel-kopie": "Lager: Geschoss kopieren", + "lg-feld-quelle": "\nQuellgeschoss (Nr.)", + "lg-feld-anzahl": "\nAnzahl neuer Geschosse", + "lg-feld-abstand": "\nGeschosshoehe je Kopie (mm)", + "lg-feld-versatz_x": "\nVersatz X je Kopie (mm, 0 = deckungsgleich)", + "lg-feld-versatz_y": "\nVersatz Y je Kopie (mm, 0 = deckungsgleich)", + "lg-kopie-abstand-ungueltig": "\n[Lager] Geschosshoehe je Kopie muss groesser 0 sein - nichts kopiert.", + "lg-kopie-hoehe-belegt": "\n[Lager] Auf Belagshoehe %1 mm liegt bereits ein Geschoss - Kopie wird trotzdem angelegt.", + "lg-kopie-racks-mehrdeutig": "\n[Lager] %1 goh_rack ohne eindeutiges Geschoss (Konturen uebereinander, kein Layer _G) - nicht kopiert.", + "lg-kopie-racks-umgehaengt": "\n%1 goh_rack auf den Regal-Layer von Geschoss %2 gelegt (Zuordnung bei deckungsgleichen Geschossen).", + "lg-kopie-fertig": "\nGeschoss %1 kopiert -> Geschoss %2 auf %3 mm: %4 Regalreihe(n), %5 Gang-/Quergangflaeche(n), %6 goh_rack.", + "lg-kopie-hinweis-aktiv": "\nLetzte Kopie ist aktives Geschoss. Anzeige umschalten mit LG_GESCHOSS_AKTIV.", + "lg-regale-keine": "\n[Lager] Keine Bloecke %1 im Modellbereich gefunden.", + "lg-regale-kopf": "\nRegale (goh_rack) je Geschoss: Anzahl, Laenge, Laenge x Ebenen", + "lg-regale-zeile": "\n %1: %2 Regal(e), %3 m, %4 m", + "lg-regale-geschoss": "Geschoss %1", + "lg-regale-ohne-geschoss": "ohne Geschoss", + "lg-regale-summe": "Gesamt", + "lg-regale-ohne-laenge": "\n[Lager] %1 Regal(e) ohne lesbare Laenge - mit 0 gezaehlt.", + "lg-titel-geschoss": "Lager: Geschoss anlegen", + "lg-titel-bereich": "Lager: Lagerbereich anlegen", + "lg-titel-reihe": "Lager: Regalreihen einfuegen", + "lg-titel-edit": "Lager: bearbeiten", + "lg-titel-hand-yaml": "Hand-YAML fuer den Abgleich (leer = keiner)", + "lg-prompt-kontur-geschoss": "\nBelagskontur des Geschosses waehlen (geschlossene Polylinie) : ", + "lg-prompt-kontur-bereich": "\nKontur des Lagerbereichs waehlen (geschlossene Polylinie) : ", + "lg-kontur-zeichnen": "\nKontur als Polylinie zeichnen (Enter beendet, wird geschlossen).", + "lg-keine-kontur": "\nDas ist keine Polylinie mit mindestens 3 Punkten.", + "lg-kontur-boegen": "\nHinweis: Bogensegmente der Kontur werden als Gerade gerechnet.", + "lg-kein-geschoss": "\n[Lager] Noch kein Geschoss angelegt - zuerst LG_GESCHOSS.", + "lg-prompt-geschoss-nr": "\nGeschoss nicht eindeutig - Geschoss-Nummer", + "lg-prompt-geschoss-aktiv": "\nAktives Geschoss (0 = alle anzeigen)", + "lg-geschoss-unbekannt": "\n[Lager] Geschoss %1 gibt es nicht.", + "lg-geschoss-existiert": "\n[Lager] Geschoss %1 gibt es schon - andere Nummer waehlen.", + "lg-geschoss-angelegt": "\nGeschoss %1 angelegt (Belag %2 mm) und aktiv gesetzt.", + "lg-geschoss-aktiv": "\nAktives Geschoss: %1 - die Layer der uebrigen Geschosse sind gefroren.", + "lg-geschoss-alle": "\nAlle Geschosse sichtbar, kein aktives Geschoss.", + "lg-geaendert": "\nGeaendert.", + "lg-bereich-ohne-kontur": "\n[Lager] Die Kontur des Bereichs fehlt - Fuellung nicht moeglich.", + "lg-bereich-beibehalten": "\nBereich %2: %1 von Hand eingefuegte oder geaenderte Reihe(n)/Gaenge bleiben erhalten und werden ausgespart.", + "lg-bereich-gefuellt": "\nBereich %1: %2 Regalreihe(n), %3 Gang-/Quergangflaeche(n), Restbreite %4 mm.", + "lg-prompt-reihe-start": "\nStartpunkt der Regalreihe : ", + "lg-prompt-reihe-ende": "\nEndpunkt oder Laenge (mm): ", + "lg-prompt-reihe-richtung": "\nRichtung: ", + "lg-reihen-eingefuegt": "\n%1 Regalreihe(n) eingefuegt.", + "lg-exp-keine-geschosse": "Keine Geschosse (LG_GESCHOSS) in der Zeichnung - levels bleiben leer.", + "lg-exp-geschoss-ohne-kontur": "Geschoss %1 hat keine Belagskontur mehr.", + "lg-exp-rack-ohne-geschoss": "goh_rack bei X=%1 Y=%2 keinem Geschoss eindeutig zuzuordnen.", + "lg-exp-rack-ohne-laenge": "goh_rack %1 ohne lesbare Laenge.", + "lg-exp-datei-fehler": "\n[Lager] Datei nicht schreibbar: %1", + "lg-exp-raw": "\n[Lager] Rohdaten: %1", + "lg-exp-yaml": "\n[Lager] Overlay-YAML: %1\n[Lager] Nachweis: %2", + "lg-exp-ohne-python": "\n[Lager] Python-Aufruf nicht moeglich (export.lsp/DXFM_LIB fehlt) - YAML von Hand erzeugen: python lib/lager_yaml_export.py ", + "lg-exp-python-fehler": "\n[Lager] lager_yaml_export.py endete mit Code %1 - siehe %2", + "lg-hand-yaml": "\nHand-YAML fuer den Abgleich: %1", + "lg-regel-profil-fehlt": "\n[Lager] Regelprofil %1 nicht gefunden (%2).", + "lg-regel-profil-ungepflegt": "\n[Lager] WARNUNG: Regelprofil %1 ist nicht gepflegt - es gelten die geerbten Werte.", + "lg-regel-unbekannt": "\n[Lager] Regel %1 nicht im Profil.", + "lg-regel-liste-kopf": "\nRegelprofil %1: %2 Regeln (Kontext: erhoehte Brandgefaehrdung ohne Loeschanlage)", "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//).", diff --git a/lang/en_GB.json b/lang/en_GB.json index 4bbc4de..9497ff7 100644 --- a/lang/en_GB.json +++ b/lang/en_GB.json @@ -1189,6 +1189,104 @@ "chh-summe": "\n Phase 3: %1 family(ies), %2 on a platform; adjusted: Kreisel %3, sections within family %4, between families %5; conflicts %6; asked individually %7", "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.", + "lg-daten-fehlt": "\n[Storage] Data file not found: %1 - built-in defaults apply.", + "lg-dialog-fehlt": "\n[Storage] Dialog not loadable (%1) - input via command line.", + "lg-einfuegen-fehler": "\n[Storage] Block %1 could not be inserted: %2", + "lg-feld-ungueltig": "Invalid value: %1", + "lg-wert-ungueltig": "\nInvalid value - please enter again.", + "lg-wahl-ungueltig": "\nPlease enter one of the listed values.", + "lg-feld-nr": "\nLevel number", + "lg-feld-bezeichnung": "\nName", + "lg-feld-hoehe": "\nFloor elevation (top, mm)", + "lg-feld-konstr": "\nFloor construction depth (mm)", + "lg-feld-lichte": "\nClear height (mm, empty = calculated)", + "lg-feld-belag": "\nFloor type", + "lg-feld-personen": "\nPersons on the level", + "lg-feld-brand": "\nFire risk", + "lg-feld-gang": "\nAisle width (mm)", + "lg-feld-tiefe": "\nRack depth (mm)", + "lg-feld-reihen": "\nRows per aisle", + "lg-feld-schienen": "\nRails per aisle", + "lg-feld-q_abstand": "\nCross aisle spacing (mm, 0 = none)", + "lg-feld-q_breite": "\nCross aisle width (mm)", + "lg-feld-richtung": "\nRow direction (degrees)", + "lg-feld-ebenen": "\nHanging levels rail height/goods length;... (mm)", + "lg-feld-laenge": "\nLength (mm)", + "lg-feld-rand_n": "\nPerimeter aisle north (mm, 0 = none)", + "lg-feld-rand_o": "\nPerimeter aisle east (mm, 0 = none)", + "lg-feld-rand_s": "\nPerimeter aisle south (mm, 0 = none)", + "lg-feld-rand_w": "\nPerimeter aisle west (mm, 0 = none)", + "lg-feld-breite": "\nAisle width (mm)", + "lg-feld-art": "\nAisle type", + "lg-titel-gang": "Storage: insert aisles", + "lg-prompt-gang-start": "\nStart point of the aisle centre line or [%1] : ", + "lg-key-rechteck": "Rectangle", + "lg-rechteck-hinweis": "\nDraw the aisle area with RECTANG (all options such as dimensions/rotation available), Enter = finish.", + "lg-rechteck-kein-rechteck": "\n[Storage] Not a plain rectangle (4 corners, no chamfer/fillet) - no aisle inserted.", + "lg-rechteck-zu-klein": "\n[Storage] Rectangle too small (side below 1 mm) - no aisle inserted.", + "lg-gaenge-eingefuegt": "\n%1 aisle(s) inserted.", + "lg-schraffur-unbekannt": "\n[Storage] Floor type %1 has no entry in belag_schraffur (lager.json) - aisles without hatch.", + "lg-schraffur-fehler": "\n[Storage] Hatch %1 for %2 not possible (%3) - aisle without hatch.", + "lg-blockdef-ergaenzt": "\n[Storage] Block definition %1 extended by %2 (existing drawing).", + "lg-titel-kopie": "Storage: copy level", + "lg-feld-quelle": "\nSource level (no.)", + "lg-feld-anzahl": "\nNumber of new levels", + "lg-feld-abstand": "\nLevel height per copy (mm)", + "lg-feld-versatz_x": "\nOffset X per copy (mm, 0 = congruent)", + "lg-feld-versatz_y": "\nOffset Y per copy (mm, 0 = congruent)", + "lg-kopie-abstand-ungueltig": "\n[Storage] Level height per copy must be greater than 0 - nothing copied.", + "lg-kopie-hoehe-belegt": "\n[Storage] A level already exists at floor height %1 mm - copy is created anyway.", + "lg-kopie-racks-mehrdeutig": "\n[Storage] %1 goh_rack without a unique level (stacked contours, no layer _G) - not copied.", + "lg-kopie-racks-umgehaengt": "\n%1 goh_rack moved to the rack layer of level %2 (assignment for congruent levels).", + "lg-kopie-fertig": "\nLevel %1 copied -> level %2 at %3 mm: %4 rack row(s), %5 aisle/cross aisle area(s), %6 goh_rack.", + "lg-kopie-hinweis-aktiv": "\nLast copy is the active level. Switch the display with LG_GESCHOSS_AKTIV.", + "lg-regale-keine": "\n[Storage] No %1 blocks found in model space.", + "lg-regale-kopf": "\nRacks (goh_rack) per level: count, length, length x levels", + "lg-regale-zeile": "\n %1: %2 rack(s), %3 m, %4 m", + "lg-regale-geschoss": "Level %1", + "lg-regale-ohne-geschoss": "no level", + "lg-regale-summe": "Total", + "lg-regale-ohne-laenge": "\n[Storage] %1 rack(s) without readable length - counted as 0.", + "lg-titel-geschoss": "Storage: create level", + "lg-titel-bereich": "Storage: create storage area", + "lg-titel-reihe": "Storage: insert rack rows", + "lg-titel-edit": "Storage: edit", + "lg-titel-hand-yaml": "Manual YAML for comparison (empty = none)", + "lg-prompt-kontur-geschoss": "\nSelect floor contour of the level (closed polyline) : ", + "lg-prompt-kontur-bereich": "\nSelect contour of the storage area (closed polyline) : ", + "lg-kontur-zeichnen": "\nDraw the contour as polyline (Enter finishes, it will be closed).", + "lg-keine-kontur": "\nThis is not a polyline with at least 3 points.", + "lg-kontur-boegen": "\nNote: arc segments of the contour are treated as straight lines.", + "lg-kein-geschoss": "\n[Storage] No level defined yet - run LG_GESCHOSS first.", + "lg-prompt-geschoss-nr": "\nLevel not unique - level number", + "lg-prompt-geschoss-aktiv": "\nActive level (0 = show all)", + "lg-geschoss-unbekannt": "\n[Storage] Level %1 does not exist.", + "lg-geschoss-existiert": "\n[Storage] Level %1 already exists - choose another number.", + "lg-geschoss-angelegt": "\nLevel %1 created (floor %2 mm) and set active.", + "lg-geschoss-aktiv": "\nActive level: %1 - layers of the other levels are frozen.", + "lg-geschoss-alle": "\nAll levels visible, no active level.", + "lg-geaendert": "\nChanged.", + "lg-bereich-ohne-kontur": "\n[Storage] The contour of the area is missing - cannot fill.", + "lg-bereich-beibehalten": "\nArea %2: %1 manually inserted or changed row(s)/aisle(s) are kept and left out.", + "lg-bereich-gefuellt": "\nArea %1: %2 rack row(s), %3 aisle/cross aisle area(s), remaining width %4 mm.", + "lg-prompt-reihe-start": "\nStart point of the rack row : ", + "lg-prompt-reihe-ende": "\nEnd point or length (mm): ", + "lg-prompt-reihe-richtung": "\nDirection: ", + "lg-reihen-eingefuegt": "\n%1 rack row(s) inserted.", + "lg-exp-keine-geschosse": "No levels (LG_GESCHOSS) in the drawing - levels stay empty.", + "lg-exp-geschoss-ohne-kontur": "Level %1 has no floor contour any more.", + "lg-exp-rack-ohne-geschoss": "goh_rack at X=%1 Y=%2 cannot be assigned to a level.", + "lg-exp-rack-ohne-laenge": "goh_rack %1 without readable length.", + "lg-exp-datei-fehler": "\n[Storage] File not writable: %1", + "lg-exp-raw": "\n[Storage] Raw data: %1", + "lg-exp-yaml": "\n[Storage] Overlay YAML: %1\n[Storage] Report: %2", + "lg-exp-ohne-python": "\n[Storage] Cannot call Python (export.lsp/DXFM_LIB missing) - create the YAML manually: python lib/lager_yaml_export.py ", + "lg-exp-python-fehler": "\n[Storage] lager_yaml_export.py ended with code %1 - see %2", + "lg-hand-yaml": "\nManual YAML for comparison: %1", + "lg-regel-profil-fehlt": "\n[Storage] Rule profile %1 not found (%2).", + "lg-regel-profil-ungepflegt": "\n[Storage] WARNING: rule profile %1 is not maintained - inherited values apply.", + "lg-regel-unbekannt": "\n[Storage] Rule %1 not in profile.", + "lg-regel-liste-kopf": "\nRule profile %1: %2 rules (context: increased fire risk without extinguishing system)", "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//).", diff --git a/lib/lager_yaml_export.py b/lib/lager_yaml_export.py new file mode 100644 index 0000000..2543dc1 --- /dev/null +++ b/lib/lager_yaml_export.py @@ -0,0 +1,439 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +lager_yaml_export.py - Lagerplanung aus BricsCAD -> Overlay-YAML fuer das RFQ-Programm. + +Liest die von LAGER_EXPORT (Lisp/LagerInsert.lsp) geschriebene Rohdatei +_lager_raw.json und erzeugt daraus + + _drawing.yaml gleiches Schema wie die Hand-YAML (z.B. + cfg/example_goh_storage.yaml), aber NUR mit den + Feldern, die die Zeichnung liefert, plus + _provenance (Herkunft/Formel je Wert), + _conflicts (Abweichungen zur Hand-YAML) und + _warnings. + _nachweis.html interner Mengen-Nachweis (optional, --nachweis) + +Merge-Vertrag (umgesetzt im RFQ-Repo): Hand-Wert vorhanden und nicht null -> +Hand gewinnt; sonst Zeichnungswert. Abweichungen erscheinen in _conflicts. + +Python aggregiert nur - jede fachliche Regel lebt in LISP +(Lisp/LagerPruefung.lsp, siehe doc/plan/lager_rfq.md). + +Aufruf: + python lager_yaml_export.py + [--hand ] [--nachweis ] +""" + +import argparse +import html +import json +import sys +from collections import Counter, OrderedDict + +import yaml + +# Vergleichstoleranz Hand <-> Zeichnung: relativ 1 % ODER absolut 0,005 +REL_TOL = 0.01 +ABS_TOL = 0.005 + + +# ------------------------------------------------------------------ Einlesen + +def lade_raw(pfad): + """Rohdaten laden. AutoLISP schreibt im ANSI-Codepage -> cp1252-Rueckfall.""" + with open(pfad, "rb") as f: + roh = f.read() + try: + text = roh.decode("utf-8") + except UnicodeDecodeError: + text = roh.decode("cp1252") + return json.loads(text) + + +def _zahl(v, vorgabe=0.0): + return float(v) if isinstance(v, (int, float)) else vorgabe + + +def _m(mm, stellen=3): + return round(_zahl(mm) / 1000.0, stellen) + + +def _m2(mm2, stellen=1): + return round(_zahl(mm2) / 1e6, stellen) + + +# ------------------------------------------------------------------ Aggregation + +class Sammler: + """Werte mit Herkunft sammeln: setze() schreibt in das Overlay und die Provenienz.""" + + def __init__(self): + self.provenance = OrderedDict() + self.warnungen = [] + + def herkunft(self, pfad, wert, quelle, formel): + self.provenance[pfad] = {"wert": wert, "quelle": quelle, "formel": formel} + return wert + + def warnung(self, text): + if text not in self.warnungen: + self.warnungen.append(text) + + +def _haeufigster(werte, pfad, s): + """Haeufigster Wert; bei mehreren verschiedenen Werten eine Warnung.""" + werte = [w for w in werte if w is not None] + if not werte: + return None + zaehlung = Counter(werte) + wert, _ = zaehlung.most_common(1)[0] + if len(zaehlung) > 1: + s.warnung("%s: Bereiche mit unterschiedlichen Werten %s - haeufigster Wert %s uebernommen" + % (pfad, sorted(zaehlung), wert)) + return wert + + +def _haengeebenen(reihen): + """Laenge je Haengeebene (Schienenhoehe, Warenlaenge) summieren. + + Jede Reihe traegt ihre Laenge in jede ihrer Haengeebenen ein - + rack_length_m ist die Summe aller Reihen der Ebene. + """ + summen = OrderedDict() + for r in reihen: + for e in r.get("haengeebenen") or []: + key = (_zahl(e.get("schienenhoehe_mm")), _zahl(e.get("warenlaenge_mm"))) + summen[key] = summen.get(key, 0.0) + _zahl(r.get("laenge_mm")) + return [{"rail_height_m": _m(h), "rack_length_m": round(l / 1000.0, 1), "goods_length_m": _m(w)} + for (h, w), l in sorted(summen.items(), key=lambda kv: -kv[0][0])] + + +def aggregiere(raw): + """Rohdaten -> (overlay, sammler). overlay enthaelt nur Zeichnungswerte.""" + s = Sammler() + for w in raw.get("warnungen") or []: + s.warnung(w) + + belag_yaml = raw.get("belag_yaml") or {} + reihen_als_gitter = bool(raw.get("reihen_als_gitterrost")) + geschosse = sorted(raw.get("geschosse") or [], + key=lambda g: (_zahl(g.get("hoehe_belag_mm")), g.get("nr") or 0)) + bereiche = raw.get("bereiche") or [] + alle_reihen = list(raw.get("reihen") or []) + racks = raw.get("goh_racks") or [] + gaenge = raw.get("gaenge") or [] + + # goh_rack-Bloecke zaehlen wie Regalreihen (Herkunft wird mitgefuehrt) + for r in racks: + alle_reihen.append(dict(r, quelle="goh_rack")) + ohne_geschoss = [r for r in alle_reihen if r.get("geschoss") is None] + if ohne_geschoss and geschosse: + s.warnung("%d Regalreihe(n) ohne Geschoss - nicht in levels enthalten" % len(ohne_geschoss)) + + overlay = OrderedDict() + + # ---------------------------------------------------------- layout + layout = OrderedDict() + if bereiche: + quelle = "LG_BEREICH (%d Bereich(e))" % len(bereiche) + felder = [ + ("aisle_width_m", "gangbreite_mm", lambda v: _m(v), "GANGBREITE_mm / 1000"), + ("rack_rows_per_aisle", "reihen_je_gang", int, "REIHEN_JE_GANG"), + ("rack_depth_m", "regaltiefe_mm", lambda v: _m(v), "REGALTIEFE_mm / 1000"), + ("trolley_rails_per_aisle", "schienen_je_gang", int, "SCHIENEN_JE_GANG"), + ] + for feld, key, conv, formel in felder: + w = _haeufigster([conv(b[key]) for b in bereiche if b.get(key) is not None], + "layout." + feld, s) + if w is not None: + layout[feld] = s.herkunft("layout." + feld, w, quelle, formel) + elif alle_reihen: + w = _haeufigster([_m(r["tiefe_mm"]) for r in alle_reihen if r.get("tiefe_mm") is not None], + "layout.rack_depth_m", s) + if w is not None: + layout["rack_depth_m"] = s.herkunft("layout.rack_depth_m", w, + "Regalreihen (Tiefe)", "haeufigste Reihentiefe") + + # Gaenge je Geschoss: Anzahl und Summe der Laengen (nur Gaenge zwischen den + # Reihen, ART=gang - auch manuelle). Randgaenge entlang der Kontur zaehlen + # nicht als aisle, stecken aber im Belag (walkway) und werden im Detail genannt. + gang_je_gs = {} + rand_je_gs = {} + for g in gaenge: + if g.get("art") == "gang": + n, l = gang_je_gs.get(g.get("geschoss"), (0, 0.0)) + gang_je_gs[g.get("geschoss")] = (n + 1, l + _zahl(g.get("laenge_mm"))) + elif g.get("art") == "randgang": + rand_je_gs[g.get("geschoss")] = rand_je_gs.get(g.get("geschoss"), 0.0) + _zahl(g.get("laenge_mm")) + if gang_je_gs: + gs_max = max(gang_je_gs, key=lambda k: gang_je_gs[k][1]) + detail = ", ".join("G%s: %d / %.1f m" % (k, v[0], v[1] / 1000.0) + for k, v in sorted(gang_je_gs.items(), key=lambda kv: str(kv[0]))) + if rand_je_gs: + detail += "; ohne Randgaenge " + ", ".join( + "G%s: %.1f m" % (k, v / 1000.0) + for k, v in sorted(rand_je_gs.items(), key=lambda kv: str(kv[0]))) + layout["aisles"] = s.herkunft("layout.aisles", gang_je_gs[gs_max][0], + "LG_GANG_* (ART=gang)", + "Anzahl Hauptgaenge, Geschoss mit der groessten Ganglaenge (%s)" % detail) + layout["aisle_length_m"] = s.herkunft("layout.aisle_length_m", + round(gang_je_gs[gs_max][1] / 1000.0, 1), + "LG_GANG_* (ART=gang)", + "Summe Hauptganglaengen je Ebene, groesste (%s)" % detail) + + bboxen = [g["kontur_bbox"] for g in geschosse if g.get("kontur_bbox")] + if bboxen: + xmin = min(b[0] for b in bboxen) + ymin = min(b[1] for b in bboxen) + xmax = max(b[2] for b in bboxen) + ymax = max(b[3] for b in bboxen) + dx, dy = xmax - xmin, ymax - ymin + layout["block_length_m"] = s.herkunft("layout.block_length_m", round(max(dx, dy) / 1000.0, 2), + "Belagskonturen LG_GESCHOSS", + "achsparallele Huelle aller Konturen, laengere Seite") + layout["block_width_m"] = s.herkunft("layout.block_width_m", round(min(dx, dy) / 1000.0, 2), + "Belagskonturen LG_GESCHOSS", + "achsparallele Huelle aller Konturen, kuerzere Seite") + if layout: + overlay["layout"] = layout + + # ---------------------------------------------------------- levels + levels = [] + for i, g in enumerate(geschosse): + p = "levels[%d]" % i + nr = g.get("nr") + lv = OrderedDict() + if g.get("bezeichnung"): + lv["remark"] = s.herkunft(p + ".remark", g["bezeichnung"], "LG_GESCHOSS %s" % nr, "BEZEICHNUNG") + lv["floor_elevation_m"] = s.herkunft(p + ".floor_elevation_m", _m(g.get("hoehe_belag_mm")), + "LG_GESCHOSS %s" % nr, "HOEHE_BELAG_mm / 1000") + if g.get("lichte_hoehe_mm") is not None: + lv["clear_height_m"] = s.herkunft(p + ".clear_height_m", _m(g["lichte_hoehe_mm"]), + "LG_GESCHOSS %s" % nr, "LICHTE_HOEHE_mm (Vorgabe)") + elif i + 1 < len(geschosse): + nx = geschosse[i + 1] + lichte = _zahl(nx.get("hoehe_belag_mm")) - _zahl(nx.get("konstr_hoehe_mm")) \ + - _zahl(g.get("hoehe_belag_mm")) + lv["clear_height_m"] = s.herkunft( + p + ".clear_height_m", _m(lichte), "LG_GESCHOSS %s / %s" % (nr, nx.get("nr")), + "Belag darueber (%s) - Konstruktionshoehe darueber (%s) - eigener Belag (%s), mm" + % (nx.get("hoehe_belag_mm"), nx.get("konstr_hoehe_mm"), g.get("hoehe_belag_mm"))) + else: + s.warnung("Geschoss %s (oberstes): clear_height_m nicht berechenbar - LICHTE_HOEHE_mm setzen" % nr) + + reihen = [r for r in alle_reihen if r.get("geschoss") == nr] + hl = _haengeebenen(reihen) + if hl: + lv["hanging_levels"] = hl + n_rack = sum(1 for r in reihen if r.get("quelle") == "goh_rack") + for j, e in enumerate(hl): + s.herkunft("%s.hanging_levels[%d].rack_length_m" % (p, j), e["rack_length_m"], + "%d Regalreihe(n) Geschoss %s (davon %d goh_rack, Haengeebenen aus Vorgabe)" + % (len(reihen), nr, n_rack), + "Summe LAENGE aller Reihen mit Schienenhoehe %s / Warenlaenge %s" + % (e["rail_height_m"], e["goods_length_m"])) + + spec = belag_yaml.get(g.get("belag_art") or "", g.get("belag_art")) + if spec: # Buehne (kein Hallenboden) + gs_gaenge = [x for x in gaenge if x.get("geschoss") == nr] + belag = sum(_zahl(x.get("laenge_mm")) * _zahl(x.get("breite_mm")) + for x in gs_gaenge if x.get("belag") != "gitterrost") + gitter = sum(_zahl(x.get("laenge_mm")) * _zahl(x.get("breite_mm")) + for x in gs_gaenge if x.get("belag") == "gitterrost") + reihen_fl = sum(_zahl(r.get("laenge_mm")) * _zahl(r.get("tiefe_mm")) for r in reihen) + if reihen_als_gitter: + gitter += reihen_fl + if gs_gaenge: + formel = "Summe LAENGE x BREITE der Gaenge/Quergaenge mit Belag" + quelle = "LG_GANG_* Geschoss %s, %d Flaeche(n)" % (nr, len(gs_gaenge)) + else: + # Ohne Gangflaechen: Belag = Kontur minus Reihengrundflaeche + belag = _zahl(g.get("kontur_flaeche_mm2")) - reihen_fl + formel = "Konturflaeche - Grundflaeche der Regalreihen (keine LG_GANG-Flaechen)" + quelle = "Belagskontur Geschoss %s" % nr + lv["walkway"] = OrderedDict([ + ("area_m2", s.herkunft(p + ".walkway.area_m2", _m2(belag), quelle, formel)), + ("spec", s.herkunft(p + ".walkway.spec", spec, "LG_GESCHOSS %s" % nr, "BELAG_ART")), + ]) + lv["grating"] = s.herkunft( + p + ".grating", _m2(gitter), "Geschoss %s" % nr, + "Gaenge mit Belag gitterrost" + (" + Grundflaeche der Regalreihen (Laenge x Tiefe)" + if reihen_als_gitter else "")) + levels.append(lv) + if levels: + overlay["levels"] = levels + return overlay, s + + +# ------------------------------------------------------------------ Abgleich + +def _gleich(a, b): + if isinstance(a, bool) or isinstance(b, bool): + return a == b + if isinstance(a, (int, float)) and isinstance(b, (int, float)): + return abs(a - b) <= max(ABS_TOL, REL_TOL * max(abs(a), abs(b))) + return str(a).strip() == str(b).strip() + + +def _blaetter(obj, pfad=""): + """(pfad, wert) aller Blattwerte; Listen per Index, _-Schluessel ausgenommen.""" + if isinstance(obj, dict): + for k, v in obj.items(): + if str(k).startswith("_"): + continue + yield from _blaetter(v, "%s.%s" % (pfad, k) if pfad else str(k)) + elif isinstance(obj, list): + for i, v in enumerate(obj): + yield from _blaetter(v, "%s[%d]" % (pfad, i)) + else: + yield pfad, obj + + +def _hole(obj, pfad): + """Wert unter pfad ("levels[1].walkway.area_m2"); (gefunden, wert).""" + teile = [] + for t in pfad.split("."): + while "[" in t: + name, rest = t.split("[", 1) + if name: + teile.append(name) + idx, t = rest.split("]", 1) + teile.append(int(idx)) + if t: + teile.append(t) + cur = obj + for t in teile: + if isinstance(t, int): + if not isinstance(cur, list) or t >= len(cur): + return False, None + cur = cur[t] + else: + if not isinstance(cur, dict) or t not in cur: + return False, None + cur = cur[t] + return True, cur + + +def abgleich(overlay, hand): + """Overlay gegen Hand-YAML: (konflikte, fuellt). + + konflikte = Hand hat einen anderen Wert (Hand gewinnt beim Merge), + fuellt = Hand-Feld fehlt oder ist null (Zeichnung fuellt es). + """ + konflikte, fuellt = [], [] + for pfad, wert in _blaetter(overlay): + gefunden, h = _hole(hand, pfad) + if not gefunden or h is None: + fuellt.append({"pfad": pfad, "zeichnung": wert}) + elif not isinstance(h, (dict, list)) and not _gleich(h, wert): + k = {"pfad": pfad, "hand": h, "zeichnung": wert} + if isinstance(h, (int, float)) and isinstance(wert, (int, float)) and h: + k["abweichung_prozent"] = round((wert - h) / abs(h) * 100.0, 1) + konflikte.append(k) + n_hand = len(hand.get("levels") or []) if isinstance(hand, dict) else 0 + n_zg = len(overlay.get("levels") or []) + if n_hand and n_zg and n_hand != n_zg: + konflikte.append({"pfad": "levels", "hand": "%d Ebenen" % n_hand, + "zeichnung": "%d Ebenen" % n_zg}) + return konflikte, fuellt + + +# ------------------------------------------------------------------ Ausgabe + +def _yaml_text(daten): + def _dict_repr(dumper, d): + return dumper.represent_mapping("tag:yaml.org,2002:map", d.items()) + yaml.SafeDumper.add_representer(OrderedDict, _dict_repr) + return yaml.safe_dump(daten, sort_keys=False, allow_unicode=True, + default_flow_style=False, width=120) + + +def baue_ausgabe(raw, hand=None): + overlay, s = aggregiere(raw) + ausgabe = OrderedDict(overlay) + ausgabe["_provenance"] = s.provenance + konflikte, fuellt = abgleich(overlay, hand) if hand is not None else ([], []) + ausgabe["_conflicts"] = konflikte + ausgabe["_warnings"] = s.warnungen + return ausgabe, fuellt + + +def schreibe_yaml(pfad, ausgabe, raw): + kopf = ("# Overlay aus der Zeichnung - erzeugt von lib/lager_yaml_export.py\n" + "# Zeichnung: %s (%s)\n" + "# Merge im RFQ-Programm: Hand-YAML gewinnt, diese Werte fuellen nur fehlende/null-Felder.\n" + "# _provenance/_conflicts/_warnings sind Zusatzinformation und kein Teil des RFQ-Schemas.\n" + % (raw.get("zeichnung", "?"), raw.get("erzeugt", "?"))) + with open(pfad, "w", encoding="utf-8", newline="\n") as f: + f.write(kopf) + f.write(_yaml_text(ausgabe)) + + +def schreibe_nachweis(pfad, ausgabe, fuellt, raw): + e = html.escape + + def tabelle(kopf, zeilen): + z = "".join("%s" % "".join("%s" % e(str(c)) for c in r) for r in zeilen) + return "%s%s
" % ( + "".join("%s" % e(k) for k in kopf), z or "-") + + teile = [ + "Lager-Nachweis", + "", + "

Lager-Nachweis (Mengen)

", + "

Zeichnung: %s
Erzeugt: %s
Regelprofil: %s

" % ( + e(str(raw.get("zeichnung"))), e(str(raw.get("erzeugt"))), e(str(raw.get("land")))), + "

Interner Nachweis - die Pruefung gegen ASR/DGUV/EN ISO 14122 folgt in Phase 4.

", + "

Warnungen

", + "
    %s
" % ("".join("
  • %s
  • " % e(w) for w in ausgabe["_warnings"]) or "
  • keine
  • "), + "

    Werte aus der Zeichnung

    ", + tabelle(["Feld", "Wert", "Quelle", "Formel"], + [(k, v["wert"], v["quelle"], v["formel"]) for k, v in ausgabe["_provenance"].items()]), + "

    Konflikte mit der Hand-YAML (Hand gewinnt)

    ", + tabelle(["Feld", "Hand", "Zeichnung", "Abweichung %"], + [(k["pfad"], k["hand"], k["zeichnung"], k.get("abweichung_prozent", "")) + for k in ausgabe["_conflicts"]]), + "

    Von der Zeichnung gefuellte Felder (Hand leer/null)

    ", + tabelle(["Feld", "Zeichnung"], [(f["pfad"], f["zeichnung"]) for f in fuellt]), + "", + ] + with open(pfad, "w", encoding="utf-8", newline="\n") as f: + f.write("\n".join(teile)) + + +def main(argv=None): + ap = argparse.ArgumentParser(description="Lager-Rohdaten -> RFQ-Overlay-YAML") + ap.add_argument("raw", help="_lager_raw.json aus LAGER_EXPORT") + ap.add_argument("yaml", help="Ziel: _drawing.yaml") + ap.add_argument("--hand", help="Hand-YAML fuer den Abgleich (Konfliktliste)") + ap.add_argument("--nachweis", help="Ziel: interner Nachweis als HTML") + a = ap.parse_args(argv) + + try: + raw = lade_raw(a.raw) + except (OSError, ValueError) as ex: + print("FEHLER: Rohdaten nicht lesbar: %s" % ex, file=sys.stderr) + return 2 + hand = None + if a.hand: + try: + with open(a.hand, encoding="utf-8") as f: + hand = yaml.safe_load(f) or {} + except (OSError, yaml.YAMLError) as ex: + print("FEHLER: Hand-YAML nicht lesbar: %s" % ex, file=sys.stderr) + return 2 + + ausgabe, fuellt = baue_ausgabe(raw, hand) + schreibe_yaml(a.yaml, ausgabe, raw) + if a.nachweis: + schreibe_nachweis(a.nachweis, ausgabe, fuellt, raw) + print("Overlay: %s (%d Werte, %d Konflikt(e), %d Warnung(en))" + % (a.yaml, len(ausgabe["_provenance"]), len(ausgabe["_conflicts"]), len(ausgabe["_warnings"]))) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/menu/SSG_LIB.cui b/menu/SSG_LIB.cui index f7710f7..dcc5ea0 100644 --- a/menu/SSG_LIB.cui +++ b/menu/SSG_LIB.cui @@ -644,289 +644,355 @@ ^C^C(ssg-ensure "GeruestInsert") GERUEST_TRAEGER_BESCHRIFTEN + + + Geschoss anlegen + ^C^C(ssg-ensure "LagerInsert") LG_GESCHOSS + + + + Aktives Geschoss + ^C^C(ssg-ensure "LagerInsert") LG_GESCHOSS_AKTIV + + + + + Geschoss kopieren + ^C^C(ssg-ensure "LagerInsert") LG_GESCHOSS_KOPIEREN + + + + + Lagerbereich (Auto-Fuellung) + ^C^C(ssg-ensure "LagerInsert") LG_BEREICH + + + + + Regalreihen einzeln + ^C^C(ssg-ensure "LagerInsert") LG_REIHE + + + + + Gaenge einzeln + ^C^C(ssg-ensure "LagerInsert") LG_GANG + + + + + Bearbeiten + ^C^C(ssg-ensure "LagerInsert") LG_EDIT + + + + + Regale zaehlen (goh_rack) + ^C^C(ssg-ensure "LagerInsert") LG_REGALE + + + + + Regelprofil anzeigen + ^C^C(ssg-ensure "LagerPruefung") LG_REGELN + + + + + Hand-YAML fuer Abgleich + ^C^C(ssg-ensure "LagerInsert") LAGER_HAND_YAML + + + + + Export RFQ-YAML + ^C^C(ssg-ensure "LagerInsert") LAGER_EXPORT + + + Marker einfuegen ^C^C(ssg-ensure "Tro_Edit") TRO_INSERT - + Marker bearbeiten ^C^C(ssg-ensure "Tro_Edit") TRO_EDIT - + Bearbeiten (Auto) ^C^C(ssg-ensure "SSG_LIB_Commands") SSG_BLOCKEDIT - + Verbindung einfuegen ^C^C(ssg-ensure "Connection_Edit") CONNECTION_INSERT - + Verbindung bearbeiten ^C^C(ssg-ensure "Connection_Edit") CONNECTION_EDIT - + Eingang ^C^C(ssg-ensure "SSG_LIB_Commands") ILS_EINGANG_INSERT - + Ausgang ^C^C(ssg-ensure "SSG_LIB_Commands") ILS_AUSGANG_INSERT - + SPS Schliessen ^C^C - + Export Sivas ^C^C(ssg-ensure "export") EXPORTSIVAS - + Export CSV ^C^C(ssg-ensure "export") EXPORTCSV - + Umschalten 2D und 3D ^C^C(ssg-ensure "SSG_LIB_Commands") SSG_DIM_SWITCH - + Alle Bloecke auf 2D ^C^C(ssg-ensure "SSG_LIB_Commands") SSG_DIM_ALL_2D - + Alle Bloecke auf 3D ^C^C(ssg-ensure "SSG_LIB_Commands") SSG_DIM_ALL_3D - + Sprache ^C^C(ssg-ensure "SSG_LIB_Commands") SSG_SPRACHE - + Planquadrate an/aus ^C^C(ssg-ensure "DrawPlanquadrate") PLANQUADRATE_TOGGLE - + CSV-Export ^C^C(ssg-ensure "export") CSV_EXPORT_KONFIG - + Bloecke zaehlen ^C^C(ssg-ensure "ssg_blocktools") SSG_BLOCK_COUNT - + Bloecke loeschen ^C^C(ssg-ensure "ssg_blocktools") SSG_BLOCK_DELETE - + Attribute ersetzen ^C^C(ssg-ensure "ssg_blocktools") SSG_ATTR_ERSETZEN - + Text ersetzen ^C^C(ssg-ensure "ssg_texttools") SSG_TEXT_ERSETZEN - + Text parallel beschriften ^C^C(ssg-ensure "ssg_texttools") SSG_TEXT_PARALLEL - + Texthoehe aendern ^C^C(ssg-ensure "ssg_texttools") SSG_TEXT_HOEHE - + Textstil aendern ^C^C(ssg-ensure "ssg_texttools") SSG_TEXT_STIL - + Faehnchen + Stueckliste aus Zeichnung ^C^C(ssg-ensure "ssg_stueckliste") SSG_FAEHNCHEN_STUECKLISTE - + Din Formate ^C^C(ssg-ensure "DinFormate") DIN_FORMATE - + alle Tests ausfuehren und exportieren ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_run_all.lsp")) (c:SSG_RUN_ALL_TESTS_EXPORT) - + Tests ausfuehren und stehen lassen ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_run_all.lsp")) (c:SSG_RUN_ALL_TESTS_OFFEN) - + Export CSV/Sivas ALL ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_export_all.lsp")) (c:TEST_EXPORT_ALL) - + Kreisel ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_kreisel.lsp")) (c:TEST_KREISEL) - + Foerderer ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_foerderer.lsp")) (c:TEST_FOERDERER) - + Foerderer-Linienzug ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_linienzug.lsp")) (c:TEST_LINIENZUG) (c:TEST_LINIENZUG_STUFE2) - + Omniflo ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_omniflo.lsp")) (c:TEST_OMNIFLO) - + Omniflo Strecke ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_omniflo_strecke.lsp")) (c:TEST_OMNIFLO_STRECKE) - + Gefaellestrecke ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_gefaellestrecke.lsp")) (c:TEST_GEFAELLESTRECKE) - + Mubea Gesamtmodell ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_mubea.lsp")) (c:TEST_MUBEA) - + HundM05 Kreisel-Abschnitt ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_hundm05.lsp")) (c:TEST_HUNDM05) - + Dematic/Arvato Flowpuffer (Vorlage) ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_dematic_arvato.lsp")) (c:TEST_DEMATIC_ARVATO) - + ILS 3.0 Showroom (Vorlage) ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_showroom_ils.lsp")) (c:TEST_SHOWROOM_ILS) - + Testanlage test_system ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_test_system.lsp")) (c:TEST_TEST_SYSTEM) - + KS 2D/3D pruefen (CSV) ^C^C(load (strcat (getenv "DXFMAKRO") "/Lisp/ks_pruefung.lsp")) KS_PRUEFEN - + Kreisel bearbeiten (ssg-ensure "KreiselInsert") KreiselEdit - + Eckrad bearbeiten (ssg-ensure "KreiselInsert") EckradEdit - + Omniflo Element bearbeiten (ssg-ensure "OmniModulInsert") OMNI_EDIT - + Gefaellestrecke bearbeiten (ssg-ensure "Gefaellestrecke") GEFAELLESTRECKE_EDIT - + VarioFoerderer bearbeiten (ssg-ensure "VarioFoerderer") VARIOFOERDERER_EDIT - + TRO-Marker bearbeiten (ssg-ensure "Tro_Edit") TRO_EDIT - + Verbindung bearbeiten (ssg-ensure "Connection_Edit") CONNECTION_EDIT - + SSG Bearbeiten (Auto) (ssg-ensure "SSG_LIB_Commands") SSG_BLOCKEDIT @@ -945,10 +1011,11 @@ - - - - + + + + + ILS @@ -1669,327 +1736,398 @@ - SPS - - + Lager - Eingang + Geschoss anlegen - + - Ausgang - - - - - - SPS Schliessen - - - - - - - TRO - - Marker einfuegen + Aktives Geschoss - Marker bearbeiten + Geschoss kopieren + - Bearbeiten (Auto) + Lagerbereich (Auto-Fuellung) - + - - - Connections - Verbindung einfuegen + Regalreihen einzeln - Verbindung bearbeiten + Gaenge einzeln - Bearbeiten (Auto) + Bearbeiten - + - - - Export + - Export Sivas + Regale zaehlen (goh_rack) + + + + + + Regelprofil anzeigen + + + + + + Hand-YAML fuer Abgleich + + + + + + Export RFQ-YAML - - Export CSV - - - - - - Konfiguration + + SPS + + - Umschalten 2D und 3D - - - - - - Alle Bloecke auf 2D - - - - - - Alle Bloecke auf 3D - - - - - - Sprache - - - - - - Planquadrate an/aus - - - - - - CSV-Export - - - - - - - Werkzeuge - - Bloecke zaehlen + Eingang - Bloecke loeschen + Ausgang - Attribute ersetzen + SPS Schliessen + + + TRO + + Marker einfuegen + + + + + + Marker bearbeiten + + + + + + Bearbeiten (Auto) + + + + + + + Connections + + Verbindung einfuegen + + + + + + Verbindung bearbeiten + + + + + + Bearbeiten (Auto) + + + + + + + Export + + Export Sivas + + + + + + Export CSV + + + + + + + Konfiguration + + Umschalten 2D und 3D + + + + + + Alle Bloecke auf 2D + + + + + + Alle Bloecke auf 3D + + + + + + Sprache + + + + + + Planquadrate an/aus + + + + + + CSV-Export + + + + + + + Werkzeuge + + Bloecke zaehlen + + + + + + Bloecke loeschen + + + + + + Attribute ersetzen + + + + Text ersetzen - + Text parallel beschriften - + Texthoehe aendern - + Textstil aendern - + Faehnchen + Stueckliste aus Zeichnung - + Din Formate - + - + Tests alle Tests ausfuehren und exportieren - + Tests ausfuehren und stehen lassen - + Export CSV/Sivas ALL - + Kreisel - + Foerderer - + Foerderer-Linienzug - + Omniflo - + Omniflo Strecke - + Gefaellestrecke - + Mubea Gesamtmodell - + HundM05 Kreisel-Abschnitt - + Dematic/Arvato Flowpuffer (Vorlage) - + ILS 3.0 Showroom (Vorlage) - + Testanlage test_system - + KS 2D/3D pruefen (CSV) - + - + POP501 SSG_LIB Edit Context Kreisel bearbeiten - + Eckrad bearbeiten - + Omniflo Element bearbeiten - + Gefaellestrecke bearbeiten - + VarioFoerderer bearbeiten - + TRO-Marker bearbeiten - + Verbindung bearbeiten - + SSG Bearbeiten (Auto) - + diff --git a/menu/SSG_LIB.mnl b/menu/SSG_LIB.mnl index 7844609..537f85f 100644 --- a/menu/SSG_LIB.mnl +++ b/menu/SSG_LIB.mnl @@ -174,6 +174,9 @@ (ssg-ensure "ssg_blocktools") (ssg-ensure "ssg_texttools") (ssg-ensure "ssg_stueckliste") + ;; Lagerplanung (RFQ-YAML): Regelprofile vor LagerInsert + (ssg-ensure "LagerPruefung") + (ssg-ensure "LagerInsert") ;; Menue einhaengen (menucmd "P13=+SSG_LIB.POP1") diff --git a/menu/SSG_LIB.mnu b/menu/SSG_LIB.mnu index 50ea18e..6bd94a5 100644 --- a/menu/SSG_LIB.mnu +++ b/menu/SSG_LIB.mnu @@ -137,6 +137,20 @@ [Gatterträger]^C^C(ssg-ensure "GeruestInsert") GERUEST [->Funktionen] [<-<-Traeger beschriften]^C^C(ssg-ensure "GeruestInsert") GERUEST_TRAEGER_BESCHRIFTEN +[->Lager] +[Geschoss anlegen]^C^C(ssg-ensure "LagerInsert") LG_GESCHOSS +[Aktives Geschoss]^C^C(ssg-ensure "LagerInsert") LG_GESCHOSS_AKTIV +[Geschoss kopieren]^C^C(ssg-ensure "LagerInsert") LG_GESCHOSS_KOPIEREN +[--] +[Lagerbereich (Auto-Fuellung)]^C^C(ssg-ensure "LagerInsert") LG_BEREICH +[Regalreihen einzeln]^C^C(ssg-ensure "LagerInsert") LG_REIHE +[Gaenge einzeln]^C^C(ssg-ensure "LagerInsert") LG_GANG +[Bearbeiten]^C^C(ssg-ensure "LagerInsert") LG_EDIT +[--] +[Regale zaehlen (goh_rack)]^C^C(ssg-ensure "LagerInsert") LG_REGALE +[Regelprofil anzeigen]^C^C(ssg-ensure "LagerPruefung") LG_REGELN +[Hand-YAML fuer Abgleich]^C^C(ssg-ensure "LagerInsert") LAGER_HAND_YAML +[<-Export RFQ-YAML]^C^C(ssg-ensure "LagerInsert") LAGER_EXPORT [->SPS] [->TRO] [Marker einfuegen]^C^C(ssg-ensure "Tro_Edit") TRO_INSERT diff --git a/requirements.txt b/requirements.txt index a959b49..c6c6b90 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,4 +7,5 @@ pytest==9.0.2 # (lib/export_neighbors.py). Fehlt shapely, faellt die Broad-Phase automatisch # auf das Raster (omniflo_zellgroesse_mm) zurueck. shapely==2.1.2 - +# pyyaml: Overlay-YAML der Lagerplanung (lib/lager_yaml_export.py) +pyyaml==6.0.3 diff --git a/tests/alltests.json b/tests/alltests.json index 32d0ac3..b2ae153 100644 --- a/tests/alltests.json +++ b/tests/alltests.json @@ -5,6 +5,7 @@ { "name": "vfl_schema", "module": "VarioFoerderer" }, { "name": "vf_spec", "module": "VarioFoerderer" }, { "name": "lastent", "module": "GeruestInsert" }, + { "name": "lager", "save": "dxf", "module": "LagerInsert" }, { "name": "omniflo", "save": "dxf", "module": "OmniModulInsert" }, { "name": "omniflo_strecke", "save": "dxf", "module": "OmniModulInsert" }, { "name": "gefaellestrecke", "save": "dwg", "module": "Gefaellestrecke" }, diff --git a/tests/test_lager.lsp b/tests/test_lager.lsp new file mode 100644 index 0000000..81ecb27 --- /dev/null +++ b/tests/test_lager.lsp @@ -0,0 +1,550 @@ +;; ============================================================ +;; test_lager.lsp - Szenario-Test Lagerplanung (LagerInsert.lsp) +;; +;; Baut in der aktuellen (leeren) Zeichnung ohne Dialoge auf: +;; - zwei Geschosse auf derselben 2D-Kontur (EG Hallenboden, OG 3100 mm) +;; - einen Lagerbereich 10000 x 6000 im OG mit Auto-Fuellung +;; und prueft Bloecke, Attribute, Layer, Neuberechnung (keine Verdopplung), +;; den Neuaufbau einer geaenderten Reihe und die Rohdaten von LAGER_EXPORT. +;; Zum Schluss Regression der goh_rack-Erkennung (lg-racks-sammeln) gegen +;; tests/testdata/goh_storage_test.dwg (Referenzwerte aus +;; goh_storage_test_regal_report.txt: 38946.39 mm / 77892.78 mm). +;; +;; Aufruf in BricsCAD: +;; (load (strcat (getenv "DXFMAKRO") "/tests/test_lager.lsp")) +;; TEST_LAGER +;; +;; Speichert tests/output/lager_results.json (Konvention test_run_all) +;; und tests/output/lager_tests_lager_raw.json (Eingabe fuer +;; lib/lager_yaml_export.py). +;; ============================================================ + +(setq *tlg-pass* 0 *tlg-fail* 0 *tlg-fails* '()) + +(defun tlg-eq (name erwartet ist) + (if (equal erwartet ist 1e-3) + (progn (setq *tlg-pass* (1+ *tlg-pass*)) + (princ (strcat "\n [PASS] " name))) + (progn (setq *tlg-fail* (1+ *tlg-fail*)) + (setq *tlg-fails* (cons name *tlg-fails*)) + (princ (strcat "\n [FAIL] " name + "\n erwartet: " (vl-princ-to-string erwartet) + "\n ist: " (vl-princ-to-string ist)))))) + +(defun tlg-rechteck (x0 y0 x1 y1 / ) + (entmakex (list '(0 . "LWPOLYLINE") '(100 . "AcDbEntity") '(8 . "0") + '(100 . "AcDbPolyline") '(90 . 4) '(70 . 1) + (list 10 x0 y0) (list 10 x1 y0) (list 10 x1 y1) (list 10 x0 y1)))) + +(defun tlg-attr (ent tag) (cdr (assoc tag (ssg-attrib-read ent)))) + +(defun tlg-layer-gefroren-p (name / e) + (setq e (tblsearch "LAYER" name)) + (and e (= 1 (logand 1 (cdr (assoc 70 e)))))) + +(defun tlg-summe (texte feld / s) + (setq s 0.0) + (foreach t1 texte (setq s (+ s (cond ((ssg-json-get (ssg-json-parse t1) feld)) (0.0))))) + s) + +;; Bloecke einer Liste, deren Name auf muster passt. +(defun tlg-nur (ents muster) + (vl-remove-if-not '(lambda (e) (wcmatch (cdr (assoc 2 (entget e))) muster)) ents)) + +(defun tlg-anzahl-art (ents art) + (length (vl-remove-if-not '(lambda (e) (= (tlg-attr e "ART") art)) ents))) + +(defun tlg-summe-attr (ents tag / s) + (setq s 0.0) + (foreach e ents (setq s (+ s (cond ((lg-zahl (tlg-attr e tag))) (0.0))))) + s) + +;; Schraffurmuster der (ersten) HATCH einer Blockdefinition oder nil. +(defun tlg-schraffur (bname / h) + (if (tblsearch "BLOCK" bname) + (if (setq h (vl-some '(lambda (ed) (if (= (cdr (assoc 0 ed)) "HATCH") ed)) + (lg-blockdef-objekte bname))) + (cdr (assoc 2 h))))) + +;; Bloecke nach Muster, die zu Geschoss gs (Zahl) gehoeren. +(defun tlg-gs (muster gs) + (vl-remove-if-not '(lambda (e) (= (tlg-attr e "GESCHOSS") (itoa gs))) (lg-alle-bloecke muster))) + +;; Kinderzahl je Bereich eines Geschosses, sortiert. +(defun tlg-kinderzahlen (gs) + (vl-sort (mapcar '(lambda (b) (length (lg-bereich-kinder b))) (tlg-gs "LG_BEREICH" gs)) '<)) + +(defun tlg-anzahl-attr (ents tag wert) + (length (vl-remove-if-not '(lambda (e) (= (tlg-attr e tag) wert)) ents))) + +;; ATTDEF-Tags einer Blockdefinition als Alist (tag . T). +(defun tlg-attdef-tags (bname) + (mapcar '(lambda (ed) (cons (cdr (assoc 2 ed)) T)) + (vl-remove-if-not '(lambda (ed) (= (cdr (assoc 0 ed)) "ATTDEF")) (lg-blockdef-objekte bname)))) + +(defun lager:tests ( / k0 k1 g0 g1 kb bereich reihen gaenge r0 neu raw daten pfad f + racks laengen kap tr kb2 b2 kinder mg mg2 hg alt eb + r1 gg1 n-alt g2 r2 gg2 ids b2k n-kinder bb gr rg grp rk kand zl + b3 rh3 mg3 ge3 ag3 n3 mr3 alt3) + (setvar "CMDECHO" 0) + (ssg-gui-aus) + + ;; --- Geschosse auf derselben Kontur (2D-Grundrisse uebereinander) --- + (setq k0 (tlg-rechteck 0.0 0.0 10000.0 6000.0)) + (setq g0 (lg-geschoss-anlegen k0 + (append (list (cons "HOEHE_BELAG_mm" "0") (cons "BELAG_ART" "hallenboden") + (cons "BEZEICHNUNG" "ground floor") (cons "KONSTR_HOEHE_mm" "0")) + (lg-geschoss-vorgaben 0)))) + (setq g1 (lg-geschoss-anlegen k0 + (append (list (cons "HOEHE_BELAG_mm" "3100") (cons "KONSTR_HOEHE_mm" "150")) + (lg-geschoss-vorgaben 1)))) + (tlg-eq "geschoss 0 angelegt" "INSERT" (if g0 (cdr (assoc 0 (entget g0))))) + (tlg-eq "geschoss 1 angelegt" "INSERT" (if g1 (cdr (assoc 0 (entget g1))))) + (tlg-eq "geschosse gesamt" 2 (length (lg-geschosse))) + (tlg-eq "geschoss 1 layer" "LG_Geschoss_G1" (cdr (assoc 8 (entget g1)))) + (tlg-eq "geschoss 1 hoehe" "3100" (tlg-attr g1 "HOEHE_BELAG_mm")) + ;; die vergebene Kontur wurde fuer Geschoss 1 kopiert + (tlg-eq "kontur kopiert" T (not (equal (lg-xdata-wert g0 "kontur") (lg-xdata-wert g1 "kontur")))) + (tlg-eq "kontur besitzer" (lg-handle g1) + (lg-xdata-wert (lg-ent-aus-handle (lg-xdata-wert g1 "kontur")) "besitzer")) + (tlg-eq "doppelte nummer abgewiesen" nil + (lg-geschoss-anlegen (tlg-rechteck 0.0 0.0 10.0 10.0) (lg-geschoss-vorgaben 1))) + + ;; --- Aktives Geschoss friert die anderen --- + (lg-geschoss-aktivieren 1) + (tlg-eq "aktiv 1" 1 (lg-geschoss-aktiv)) + (tlg-eq "G0 gefroren" T (tlg-layer-gefroren-p "LG_Geschoss_G0")) + (tlg-eq "G1 nicht gefroren" nil (tlg-layer-gefroren-p "LG_Geschoss_G1")) + (tlg-eq "ermitteln = aktiv" 1 (lg-geschoss-ermitteln '(5.0 5.0 0.0))) + (tlg-eq "gefrorene geschosse gefunden" 2 (length (lg-geschosse))) + + ;; --- Bereich mit Auto-Fuellung --- + (setq kb (tlg-rechteck 0.0 0.0 10000.0 6000.0)) + (setq bereich (lg-bereich-anlegen kb (cons (cons "GESCHOSS" "1") + (lg-bereich-vorgaben (ssg-lwpoly-punkte kb))))) + (tlg-eq "bereich angelegt" "INSERT" (if bereich (cdr (assoc 0 (entget bereich))))) + (setq reihen (lg-alle-bloecke "LG_REIHE_*") gaenge (lg-alle-bloecke "LG_GANG_*")) + (tlg-eq "reihen" 4 (length reihen)) + (tlg-eq "gaenge" 2 (length gaenge)) + (tlg-eq "kinder des bereichs" 6 (length (lg-bereich-kinder bereich))) + (setq r0 (car reihen)) + (tlg-eq "reihe laenge" "10000" (tlg-attr r0 "LAENGE")) + (tlg-eq "reihe tiefe" "700" (tlg-attr r0 "TIEFE_mm")) + (tlg-eq "reihe geschoss" "1" (tlg-attr r0 "GESCHOSS")) + (tlg-eq "reihe quelle" "auto" (tlg-attr r0 "QUELLE")) + (tlg-eq "reihe ebenen" "2300/1200;1100/1100" (tlg-attr r0 "HAENGEEBENEN")) + (tlg-eq "reihe layer" "LG_Reihe_G1" (cdr (assoc 8 (entget r0)))) + (tlg-eq "reihe blockdef" "LG_REIHE_10000_700" (cdr (assoc 2 (entget r0)))) + (tlg-eq "bereich rest" "1200" (tlg-attr bereich "RESTBREITE_mm")) + (tlg-eq "gang belag" "spanplatte" (tlg-attr (car gaenge) "BELAG")) + + ;; --- Neuberechnung ersetzt statt verdoppelt --- + (lg-bereich-fuellen bereich) + (tlg-eq "neu gefuellt: reihen" 4 (length (lg-alle-bloecke "LG_REIHE_*"))) + (tlg-eq "neu gefuellt: gaenge" 2 (length (lg-alle-bloecke "LG_GANG_*"))) + + ;; --- Bereichsparameter aendern: Gang 1200 -> Modul 2600, 2 Module --- + (lg-attribute-setzen bereich (list (cons "GANGBREITE_mm" "1200") + (cons "QUERGANG_ABSTAND_mm" "4000"))) + (lg-bereich-fuellen bereich) + (tlg-eq "quergaenge: reihenstuecke" 8 (length (lg-alle-bloecke "LG_REIHE_*"))) + (tlg-eq "bereich rest neu" "800" (tlg-attr bereich "RESTBREITE_mm")) + + ;; --- Einzelne Reihe aendern (Neuaufbau, Kennzeichnung) --- + (setq r0 (car (lg-alle-bloecke "LG_REIHE_*"))) + (setq neu (lg-linie-neu-aufbauen r0 (list (cons "LAENGE" "2000") (cons "TIEFE_mm" "700") + (cons "HAENGEEBENEN" "2300/1200") (cons "SCHIENEN" "1") + (cons "GESCHOSS" "1") (cons "BEREICH" "1") + (cons "QUELLE" "auto_geaendert")))) + (tlg-eq "reihe neu aufgebaut" "LG_REIHE_2000_700" (if neu (cdr (assoc 2 (entget neu))))) + (tlg-eq "alte reihe weg" nil (entget r0)) + (tlg-eq "reihenzahl gleich" 8 (length (lg-alle-bloecke "LG_REIHE_*"))) + (tlg-eq "geaenderte reihe bleibt kind" T (if (member neu (lg-bereich-kinder bereich)) T nil)) + + ;; --- Rohdaten fuer LAGER_EXPORT --- + (setq raw (lg-export-json)) + (setq daten (ssg-json-parse raw)) + (tlg-eq "export geschosse" 2 (length (ssg-json-get daten "geschosse"))) + (tlg-eq "export bereiche" 1 (length (ssg-json-get daten "bereiche"))) + (tlg-eq "export reihen" 8 (length (ssg-json-get daten "reihen"))) + (tlg-eq "export land" "DE" (ssg-json-get daten "land")) + (tlg-eq "export konturflaeche" 6.0e7 + (ssg-json-get (car (vl-remove-if-not '(lambda (g) (= (ssg-json-get g "nr") 1)) + (ssg-json-get daten "geschosse"))) + "kontur_flaeche_mm2")) + (tlg-eq "export haengeebenen" + '((("schienenhoehe_mm" . 2300) ("warenlaenge_mm" . 1200)) + (("schienenhoehe_mm" . 1100) ("warenlaenge_mm" . 1100))) + (ssg-json-get (vl-some '(lambda (r) (if (= (ssg-json-get r "laenge_mm") 4000) r)) + (ssg-json-get daten "reihen")) + "haengeebenen")) + + ;; --- Randgaenge Nord + Sued, Schraffur je Belag --- + (setq kb2 (tlg-rechteck 20000.0 0.0 30000.0 6000.0)) + (setq b2 (lg-bereich-anlegen kb2 (append (list (cons "GESCHOSS" "1") + (cons "RANDGANG_N_mm" "1000") + (cons "RANDGANG_S_mm" "1000")) + (lg-bereich-vorgaben (ssg-lwpoly-punkte kb2))))) + (tlg-eq "randgang: attribut" "1000" (if b2 (tlg-attr b2 "RANDGANG_N_mm"))) + (setq kinder (lg-bereich-kinder b2)) + (tlg-eq "randgang: anzahl" 2 (tlg-anzahl-art kinder "randgang")) + (tlg-eq "randgang: reihen" 2 (length (tlg-nur kinder "LG_REIHE_*"))) + ;; Stapel 1000..5000 -> ein Modul, Rest 1600 + (tlg-eq "randgang: rest" "1600" (tlg-attr b2 "RESTBREITE_mm")) + (tlg-eq "randgang: quelle auto" "auto" (tlg-attr (car (tlg-nur kinder "LG_GANG_*")) "QUELLE")) + (tlg-eq "schraffur spanplatte" "ANSI31" (tlg-schraffur "LG_GANG_10000_1000_SPANPLATTE")) + + ;; --- Manueller Gang quer durch den Bereich spart die Reihen aus --- + (setq mg (lg-gang-einfuegen '(25000.0 1000.0 0.0) '(25000.0 5000.0 0.0) + (list (cons "GESCHOSS" "1") (cons "BREITE_mm" "1000") (cons "ART" "gang") + (cons "BELAG" "gitterrost")))) + (tlg-eq "manuell: eingefuegt" "LG_GANG_4000_1000_GITTERROST" (if mg (cdr (assoc 2 (entget mg))))) + (tlg-eq "manuell: quelle" "manuell" (tlg-attr mg "QUELLE")) + (tlg-eq "manuell: bereich" (tlg-attr b2 "NR") (tlg-attr mg "BEREICH")) + (tlg-eq "schraffur gitterrost" "GRID" (tlg-schraffur "LG_GANG_4000_1000_GITTERROST")) + (setq kinder (lg-bereich-kinder b2)) + (tlg-eq "manuell: reihen unterbrochen" 4 (length (tlg-nur kinder "LG_REIHE_*"))) + (tlg-eq "manuell: reihenlaenge" 18000.0 (tlg-summe-attr (tlg-nur kinder "LG_REIHE_*") "LAENGE")) + ;; Neuaufbau: manueller Gang bleibt, Reihen bleiben ausgespart + (lg-bereich-fuellen b2) + (setq kinder (lg-bereich-kinder b2)) + (tlg-eq "manuell: bleibt beim neuaufbau" T (if (entget mg) T nil)) + (tlg-eq "manuell: weiter ausgespart" 4 (length (tlg-nur kinder "LG_REIHE_*"))) + ;; Belag aendern -> neuer Blockname/neue Schraffur + (setq mg2 (lg-linie-neu-aufbauen mg (lg-wert-setzen (ssg-attrib-read mg) "BELAG" "spanplatte"))) + (tlg-eq "manuell: belag geaendert" "LG_GANG_4000_1000_SPANPLATTE" (if mg2 (cdr (assoc 2 (entget mg2))))) + (tlg-eq "manuell: bleibt kind" T (if (member mg2 (lg-bereich-kinder b2)) T nil)) + ;; Hallenboden: keine Schraffur; ausserhalb jedes Bereichs: nicht verknuepft + (setq hg (lg-gang-einfuegen '(40000.0 0.0 0.0) '(42000.0 0.0 0.0) + (list (cons "GESCHOSS" "1") (cons "BREITE_mm" "1500") (cons "ART" "gang") + (cons "BELAG" "hallenboden")))) + (tlg-eq "hallenboden: ohne schraffur" nil (tlg-schraffur "LG_GANG_2000_1500_HALLENBODEN")) + (tlg-eq "hallenboden: kein bereich" "" (tlg-attr hg "BEREICH")) + + ;; --- Altbestand: Blockdefinition um ein neues ATTDEF ergaenzen --- + (lg-blockdef "TLG_ALT" (list (lg-geom-linie '(0.0 0.0 0.0) '(100.0 0.0 0.0)) + (lg-geom-attdef "A" "" '(0.0 0.0 0.0) 50.0 nil))) + (setq alt (lg-block-einfuegen "TLG_ALT" '(0.0 -5000.0 0.0) 0.0 "0" '(("A" . "1")))) + (lg-blockdef "TLG_ALT" (list (lg-geom-linie '(0.0 0.0 0.0) '(100.0 0.0 0.0)) + (lg-geom-attdef "A" "" '(0.0 0.0 0.0) 50.0 nil) + (lg-geom-attdef "B" "" '(0.0 -100.0 0.0) 50.0 nil))) + (tlg-eq "altbestand: neues ATTDEF" T (if (assoc "B" (tlg-attdef-tags "TLG_ALT")) T nil)) + (tlg-eq "altbestand: referenz hat neuen tag" T (if (assoc "B" (ssg-attrib-read alt)) T nil)) + (tlg-eq "altbestand: alter wert bleibt" "1" (tlg-attr alt "A")) + + ;; --- Export mit Randgaengen und manuellen Gaengen --- + (setq raw (lg-export-json) daten (ssg-json-parse raw)) + (setq eb (vl-some '(lambda (b) (if (= (ssg-json-get b "nr") (atoi (tlg-attr b2 "NR"))) b)) + (ssg-json-get daten "bereiche"))) + (tlg-eq "export randgang_mm" '(("n" . 1000) ("o" . 0) ("s" . 1000) ("w" . 0)) + (ssg-json-get eb "randgang_mm")) + (tlg-eq "export randgaenge" 2 + (length (vl-remove-if-not '(lambda (g) (= (ssg-json-get g "art") "randgang")) + (ssg-json-get daten "gaenge")))) + (tlg-eq "export manuelle gaenge" 2 + (length (vl-remove-if-not '(lambda (g) (= (ssg-json-get g "quelle") "manuell")) + (ssg-json-get daten "gaenge")))) + + (setq pfad (strcat (getenv "DXFMAKRO") "/tests/output/lager_tests_lager_raw.json")) + (if (setq f (open pfad "w")) (progn (princ raw f) (close f))) + + ;; --- Geschoss kopieren: G1 (3100, Spanplatte) -> G2, G3 mit Gitterrost --- + (setq r1 (tlg-gs "LG_REIHE_*" 1) gg1 (tlg-gs "LG_GANG_*" 1) + n-alt (+ (length r1) (length gg1))) + (setq neu (lg-geschosse-kopieren + (list (cons "QUELLE" "1") (cons "ANZAHL" "2") (cons "ABSTAND_mm" "3000") + (cons "BELAG_ART" "gitterrost") (cons "VERSATZ_X_mm" "0") (cons "VERSATZ_Y_mm" "0")))) + (tlg-eq "kopie: neue geschosse" '(2 3) neu) + (tlg-eq "kopie: geschosse gesamt" 4 (length (lg-geschosse))) + (setq g2 (car (lg-geschoss-finden 2))) + (tlg-eq "kopie: G2 hoehe" "6100" (tlg-attr g2 "HOEHE_BELAG_mm")) + (tlg-eq "kopie: G3 hoehe" "9100" (tlg-attr (car (lg-geschoss-finden 3)) "HOEHE_BELAG_mm")) + (tlg-eq "kopie: G2 belag" "gitterrost" (tlg-attr g2 "BELAG_ART")) + (tlg-eq "kopie: G2 bezeichnung" "Level 2" (tlg-attr g2 "BEZEICHNUNG")) + (tlg-eq "kopie: G2 konstr. von quelle" "150" (tlg-attr g2 "KONSTR_HOEHE_mm")) + (tlg-eq "kopie: G2 layer" "LG_Geschoss_G2" (cdr (assoc 8 (entget g2)))) + (tlg-eq "kopie: G2 eigene kontur" T + (and (lg-xdata-wert g2 "kontur") + (not (equal (lg-xdata-wert g2 "kontur") (lg-xdata-wert g1 "kontur"))))) + (foreach nr '(2 3) + (tlg-eq (strcat "kopie: G" (itoa nr) " reihen") (length r1) (length (tlg-gs "LG_REIHE_*" nr))) + (tlg-eq (strcat "kopie: G" (itoa nr) " gaenge") (length gg1) (length (tlg-gs "LG_GANG_*" nr))) + (tlg-eq (strcat "kopie: G" (itoa nr) " reihenlaenge") + (tlg-summe-attr r1 "LAENGE") (tlg-summe-attr (tlg-gs "LG_REIHE_*" nr) "LAENGE")) + (tlg-eq (strcat "kopie: G" (itoa nr) " kinder je bereich") (tlg-kinderzahlen 1) (tlg-kinderzahlen nr))) + (tlg-eq "kopie: quelle unveraendert" n-alt + (+ (length (tlg-gs "LG_REIHE_*" 1)) (length (tlg-gs "LG_GANG_*" 1)))) + (setq r2 (tlg-gs "LG_REIHE_*" 2) gg2 (tlg-gs "LG_GANG_*" 2)) + (tlg-eq "kopie: reihen-layer G2" T + (vl-every '(lambda (e) (= (cdr (assoc 8 (entget e))) "LG_Reihe_G2")) r2)) + (setq ids (mapcar '(lambda (e) (tlg-attr e "ID")) + (append (lg-alle-bloecke "LG_REIHE_*") (lg-alle-bloecke "LG_GANG_*")))) + (tlg-eq "kopie: IDs eindeutig" (length ids) + (length (vl-remove-if '(lambda (x) (member x (cdr (member x ids)))) ids))) + (tlg-eq "kopie: auto_geaendert bleibt" (tlg-anzahl-attr r1 "QUELLE" "auto_geaendert") + (tlg-anzahl-attr r2 "QUELLE" "auto_geaendert")) + (tlg-eq "kopie: manuelle gaenge" 2 (tlg-anzahl-attr gg2 "QUELLE" "manuell")) + ;; Kinder zeigen auf den neuen Bereich (Nummer und XDATA) + (tlg-eq "kopie: kinder verknuepft" T + (vl-every '(lambda (b / nr) + (setq nr (tlg-attr b "NR")) + (vl-every '(lambda (k) (= (tlg-attr k "BEREICH") nr)) (lg-bereich-kinder b))) + (tlg-gs "LG_BEREICH" 2))) + ;; Belag folgt dem Geschoss (Spanplatte -> Gitterrost), Hallenboden-Gang bleibt + (tlg-eq "kopie: belag folgt" 0 (tlg-anzahl-attr gg2 "BELAG" "spanplatte")) + (tlg-eq "kopie: manueller gang gitterrost" 1 + (length (tlg-nur (vl-remove-if-not '(lambda (e) (= (tlg-attr e "QUELLE") "manuell")) gg2) + "LG_GANG_4000_1000_GITTERROST"))) + (tlg-eq "kopie: hallenboden bleibt" 1 (length (tlg-nur gg2 "LG_GANG_2000_1500_HALLENBODEN"))) + (tlg-eq "kopie: bereich belag" T + (vl-every '(lambda (b) (= (tlg-attr b "BELAG") "gitterrost")) (tlg-gs "LG_BEREICH" 2))) + ;; Neufuellen einer Kopie laesst die Quelle in Ruhe, manueller Gang bleibt + (setq b2k (vl-some '(lambda (b) (if (= (tlg-attr b "RANDGANG_N_mm") "1000") b)) (tlg-gs "LG_BEREICH" 2))) + (setq n-kinder (length (lg-bereich-kinder b2k))) + (lg-bereich-fuellen b2k) + (tlg-eq "kopie neu gefuellt: kinder" n-kinder (length (lg-bereich-kinder b2k))) + (tlg-eq "kopie neu gefuellt: quelle unveraendert" n-alt + (+ (length (tlg-gs "LG_REIHE_*" 1)) (length (tlg-gs "LG_GANG_*" 1)))) + ;; Nachtraeglich erweitern: G3 -> G4 + (tlg-eq "kopie: G3 -> G4" '(4) + (lg-geschosse-kopieren (list (cons "QUELLE" "3") (cons "ANZAHL" "1") (cons "ABSTAND_mm" "3000") + (cons "BELAG_ART" "gitterrost")))) + (tlg-eq "kopie: G4 hoehe" "12100" (tlg-attr (car (lg-geschoss-finden 4)) "HOEHE_BELAG_mm")) + ;; Versatz in Y + (setq neu (lg-geschosse-kopieren (list (cons "QUELLE" "1") (cons "ANZAHL" "1") (cons "ABSTAND_mm" "3100") + (cons "BELAG_ART" "spanplatte") (cons "VERSATZ_Y_mm" "20000")))) + (tlg-eq "kopie versatz: G5" '(5) neu) + (tlg-eq "kopie versatz: kontur" '((0.0 20000.0) (10000.0 26000.0)) + (mapcar '(lambda (p) (list (car p) (cadr p))) (ssg-poly-bbox (cadddr (lg-geschoss-finden 5))))) + (tlg-eq "kopie versatz: reihen verschoben" T + (vl-every '(lambda (e) (>= (cadr (cdr (assoc 10 (entget e)))) 20000.0)) (tlg-gs "LG_REIHE_*" 5))) + (tlg-eq "kopie: abstand 0 abgewiesen" nil + (lg-geschosse-kopieren (list (cons "QUELLE" "1") (cons "ANZAHL" "1") (cons "ABSTAND_mm" "0")))) + (setq daten (ssg-json-parse (lg-export-json))) + (tlg-eq "kopie: export geschosse" 6 (length (ssg-json-get daten "geschosse"))) + + ;; --- Gang per Rechteck quer durch den kopierten Randgang-Bereich (G2) --- + (setq rg (lg-gang-rechteck-einfuegen + '((26500.0 0.0 0.0) (27500.0 0.0 0.0) (27500.0 6000.0 0.0) (26500.0 6000.0 0.0)) + (list (cons "GESCHOSS" "2") (cons "BREITE_mm" "1000") (cons "ART" "gang") + (cons "BELAG" "gitterrost")))) + (tlg-eq "rechteck: blockname" "LG_GANG_6000_1000_GITTERROST" (if rg (cdr (assoc 2 (entget rg))))) + (tlg-eq "rechteck: mittellinie" '(27000.0 0.0) (if rg (list (car (cdr (assoc 10 (entget rg)))) + (cadr (cdr (assoc 10 (entget rg))))))) + (tlg-eq "rechteck: quelle" "manuell" (tlg-attr rg "QUELLE")) + (tlg-eq "rechteck: bereich" (tlg-attr b2k "NR") (tlg-attr rg "BEREICH")) + (tlg-eq "rechteck: kind" T (if (member rg (lg-bereich-kinder b2k)) T nil)) + ;; Breite aus dem Rechteck, nicht aus werte + (setq rg (lg-gang-rechteck-einfuegen + '((40000.0 10000.0 0.0) (44000.0 10000.0 0.0) (44000.0 11500.0 0.0) (40000.0 11500.0 0.0)) + (list (cons "GESCHOSS" "2") (cons "BREITE_mm" "1000") (cons "ART" "gang") + (cons "BELAG" "spanplatte")))) + (tlg-eq "rechteck: breite aus flaeche" "1500" (tlg-attr rg "BREITE_mm")) + (tlg-eq "rechteck: ohne bereich" "" (tlg-attr rg "BEREICH")) + (tlg-eq "rechteck: zu klein" nil + (lg-gang-rechteck-einfuegen '((0.0 0.0) (10.0 0.0) (10.0 0.0) (0.0 0.0)) + (list (cons "GESCHOSS" "2") (cons "ART" "gang") (cons "BELAG" "spanplatte")))) + ;; Polylinie aus dem BricsCAD-Befehl RECHTECK auswerten + (command "_.RECTANG" "_non" '(700000.0 0.0) "_non" '(704000.0 1200.0)) + (setq rk (lg-rechteck-ecken-aus-poly (entlast))) + (entdel (entlast)) + (tlg-eq "rechteck-befehl: 4 ecken" 4 (if (listp rk) (length rk))) + (tlg-eq "rechteck-befehl: gang" 1200.0 (caddr (lg-gang-aus-ecken rk))) + ;; um 30 Grad gedrehtes Rechteck 5000 x 800 (wie RECHTECK mit Option Drehung) + (setq rk (mapcar '(lambda (p) (polar '(0.0 0.0) (+ (angle '(0.0 0.0) p) (/ pi 6.0)) (distance '(0.0 0.0) p))) + '((0.0 0.0) (5000.0 0.0) (5000.0 800.0) (0.0 800.0)))) + (entmake (append (list '(0 . "LWPOLYLINE") '(100 . "AcDbEntity") '(100 . "AcDbPolyline") '(90 . 4) '(70 . 1)) + (mapcar '(lambda (p) (cons 10 p)) rk))) + (setq rk (lg-rechteck-ecken-aus-poly (entlast))) + (entdel (entlast)) + (tlg-eq "rechteck-befehl: gedreht breite" 800.0 (caddr (lg-gang-aus-ecken rk))) + (tlg-eq "rechteck-befehl: gedreht laenge" 5000.0 + (distance (car (lg-gang-aus-ecken rk)) (cadr (lg-gang-aus-ecken rk)))) + ;; Fuenfeck (z.B. Fase) -> kein Rechteck + (entmake (append (list '(0 . "LWPOLYLINE") '(100 . "AcDbEntity") '(100 . "AcDbPolyline") '(90 . 5) '(70 . 1)) + (mapcar '(lambda (p) (cons 10 p)) '((0.0 0.0) (900.0 0.0) (1000.0 100.0) (1000.0 500.0) (0.0 500.0))))) + (tlg-eq "rechteck-befehl: fase abgelehnt" 'kein-rechteck (lg-rechteck-ecken-aus-poly (entlast))) + (entdel (entlast)) + + ;; --- Rack-Erkennung: direkt, verschachtelt + gedreht, MINSERT --- + ;; eigener Testblock (Parameter als Attribut bzw. ATTDEF-Vorgabe), damit + ;; die Suche nach goh_rack davon unberuehrt bleibt + (lg-blockdef "TLG_RACK" (list (lg-geom-linie '(0.0 0.0 0.0) '(1000.0 0.0 0.0)) + (lg-geom-attdef "Laenge" "500" '(0.0 0.0 0.0) 50.0 nil) + (lg-geom-attdef "Ebenen" "3" '(0.0 0.0 0.0) 50.0 nil))) + (lg-blockdef "TLG_GRUPPE" (list '((0 . "INSERT") (8 . "0") (2 . "TLG_RACK") (10 0.0 0.0 0.0)) + '((0 . "INSERT") (8 . "0") (2 . "TLG_RACK") (10 0.0 2000.0 0.0)))) + (lg-block-einfuegen "TLG_RACK" '(-100000.0 0.0 0.0) 0.0 "0" '(("Laenge" . "100 cm") ("Ebenen" . "2"))) + (setq grp (lg-block-einfuegen "TLG_GRUPPE" '(-200000.0 0.0 0.0) (/ pi 2.0) "0" nil)) + (vla-AddMInsertBlock (lg-modelspace) (vlax-3d-point '(-300000.0 0.0 0.0)) "TLG_RACK" + 1.0 1.0 1.0 0.0 1 3 1000.0 3000.0) + (setq rk (lg-racks-finden "TLG_RACK" "Laenge" "Ebenen")) + (tlg-eq "racks: anzahl" 6 (length rk)) + (tlg-eq "racks: laenge (attribut mit einheit + vorgaben)" 3500.0 + (apply '+ (mapcar '(lambda (r) (cond ((lg-rk-laenge r)) (0.0))) rk))) + (tlg-eq "racks: ebenen" 17.0 (apply '+ (mapcar '(lambda (r) (cond ((lg-rk-ebenen r)) (0.0))) rk))) + (tlg-eq "racks: verschachtelt top" 2 + (length (vl-remove-if-not '(lambda (r) (equal (lg-rk-top r) grp)) rk))) + ;; Gruppe um 90 Grad gedreht: zweites Rack (lokal y = 2000) liegt bei x = -202000 + (tlg-eq "racks: verschachtelt gedreht" T + (equal (vl-sort (mapcar '(lambda (r) (car (lg-rk-mitte r))) + (vl-remove-if-not '(lambda (r) (equal (lg-rk-top r) grp)) rk)) + '<) + '(-202000.0 -200000.0) 100.0)) + ;; MINSERT 1 Zeile x 3 Spalten, Abstand 3000: Mitten = Einfuegepunkte + (tlg-eq "racks: minsert" '(-300000.0 -297000.0 -294000.0) + (vl-sort (mapcar '(lambda (r) (car (lg-rk-mitte r))) + (vl-remove-if-not '(lambda (r) (< (car (lg-rk-mitte r)) -250000.0)) rk)) + '<)) + + ;; --- goh_rack-Regression (Referenzwerte goh_storage_test_regal_report.txt) --- + (setq pfad (strcat (getenv "DXFMAKRO") "/tests/testdata/goh_storage_test.dwg")) + (if (findfile pfad) + (progn + ;; "*" = aufgeloest einfuegen: die goh_rack-Bloecke liegen danach + ;; direkt im Modellbereich wie in der Originalzeichnung + (command "_.-INSERT" (strcat "*" pfad) '(50000.0 0.0 0.0) 1 0) + (setq racks (lg-export-goh-racks nil)) + (tlg-eq "goh_rack anzahl" 4 (length (car racks))) + (setq laengen (tlg-summe (car racks) "laenge_mm")) + (setq kap 0.0) + (foreach t1 (car racks) + (setq tr (ssg-json-parse t1)) + (setq kap (+ kap (* (ssg-json-get tr "laenge_mm") (ssg-json-get tr "ebenen"))))) + (tlg-eq "goh_rack laenge gesamt" T (equal laengen 38946.39 0.1)) + (tlg-eq "goh_rack laenge x ebenen" T (equal kap 77892.78 0.1)) + (princ (strcat "\n goh_rack: " (rtos laengen 2 2) " mm / " (rtos kap 2 2) " mm")) + + ;; --- Geschoss mit goh_racks kopieren (deckungsgleich, 2D) --- + ;; Geschosskontur um die Racks legen (Mitten aus lg-racks-sammeln + Rand) + (setq bb (ssg-poly-bbox (mapcar '(lambda (p) (list (car p) (cadr p))) + (mapcar 'lg-rk-mitte (lg-racks-sammeln))))) + (setq gr (lg-geschoss-naechste-nr)) + (lg-geschoss-anlegen (tlg-rechteck (- (car (car bb)) 20000.0) (- (cadr (car bb)) 20000.0) + (+ (car (cadr bb)) 20000.0) (+ (cadr (cadr bb)) 20000.0)) + (append (list (cons "HOEHE_BELAG_mm" "20000")) (lg-geschoss-vorgaben gr))) + (setq neu (lg-geschosse-kopieren (list (cons "QUELLE" (itoa gr)) (cons "ANZAHL" "1") + (cons "ABSTAND_mm" "3000") (cons "BELAG_ART" "spanplatte")))) + (setq racks (lg-export-goh-racks (lg-geschoss-kandidaten))) + (tlg-eq "rack-kopie: anzahl" 8 (length (car racks))) + (tlg-eq "rack-kopie: quelle" 4 + (length (vl-remove-if-not '(lambda (t1) (equal (ssg-json-get (ssg-json-parse t1) "geschoss") gr)) + (car racks)))) + (tlg-eq "rack-kopie: kopie" 4 + (length (vl-remove-if-not '(lambda (t1) (equal (ssg-json-get (ssg-json-parse t1) "geschoss") (car neu))) + (car racks)))) + (tlg-eq "rack-kopie: keine warnung" nil (cadr racks)) + (tlg-eq "rack-kopie: laenge kopie" T + (equal (tlg-summe (car racks) "laenge_mm") (* 2 38946.39) 0.2)) + (tlg-eq "rack-kopie: layer" T (if (tblsearch "LAYER" (strcat "LG_Regal_G" (itoa (car neu)))) T nil)) + ;; Auswertung je Geschoss wie LG_REGALE: je 4 Racks in Quelle und Kopie + (setq kand (lg-geschoss-kandidaten)) + (setq zl (lg-racks-auswerten + (mapcar '(lambda (r) (list (lg-rack-geschoss (lg-ent-layer (lg-rk-top r)) (lg-rk-mitte r) kand) + (lg-rk-laenge r) (lg-rk-ebenen r))) + (lg-racks-sammeln)))) + (tlg-eq "lg_regale: geschosse" (list gr (car neu)) (mapcar 'car zl)) + (tlg-eq "lg_regale: anzahl" '(4 4) (mapcar 'cadr zl)) + (tlg-eq "lg_regale: laenge x ebenen" T (equal (cadddr (car zl)) 77892.78 0.2)) + (tlg-eq "lg_regale: befehl laeuft" nil (vl-catch-all-error-p (vl-catch-all-apply 'c:LG_REGALE nil)))) + (tlg-eq "goh_storage_test.dwg vorhanden" T nil)) + + ;; --- Von Hand Gesetztes bleibt: Einfuegen loest keine Neuberechnung aus --- + (setq kb (tlg-rechteck 600000.0 0.0 610000.0 6000.0)) + (setq b3 (lg-bereich-anlegen kb (append (list (cons "GESCHOSS" "1")) + (lg-bereich-vorgaben (ssg-lwpoly-punkte kb))))) + (setq rh3 (tlg-nur (lg-bereich-kinder b3) "LG_REIHE_*")) + (tlg-eq "von hand: auto reihen" 4 (length rh3)) + ;; Anwender loescht eine automatisch erzeugte Reihe ... + (entdel (car rh3)) + ;; ... und setzt einen Gang quer durch den Bereich + (setq mg3 (lg-gang-einfuegen '(605000.0 0.0 0.0) '(605000.0 6000.0 0.0) + (list (cons "GESCHOSS" "1") (cons "BREITE_mm" "1000") (cons "ART" "gang") + (cons "BELAG" "spanplatte")))) + (setq rh3 (tlg-nur (lg-bereich-kinder b3) "LG_REIHE_*")) + (tlg-eq "von hand: geloeschte reihe bleibt weg" 6 (length rh3)) + (tlg-eq "von hand: reihen zugeschnitten" 27000.0 (tlg-summe-attr rh3 "LAENGE")) + (tlg-eq "von hand: reste bleiben auto" 6 (tlg-anzahl-attr rh3 "QUELLE" "auto")) + ;; Reihe von Hand aendern (auto -> auto_geaendert) + (setq ge3 (lg-linie-neu-aufbauen-sperre (car rh3) + (lg-wert-setzen (lg-wert-setzen (ssg-attrib-read (car rh3)) "QUELLE" "auto_geaendert") + "LAENGE" "3000"))) + ;; Regalreihe von Hand quer in einen automatisch erzeugten Gang setzen + (setq ag3 (vl-some '(lambda (g) (if (and (= (tlg-attr g "QUELLE") "auto") (= (tlg-attr g "ART") "gang") + (< (car (cdr (assoc 10 (entget g)))) 601000.0)) + g)) + (tlg-nur (lg-bereich-kinder b3) "LG_GANG_*"))) + (setq n3 (length (tlg-nur (lg-bereich-kinder b3) "LG_GANG_*"))) + (setq mr3 (lg-manuell-einfuegen "reihe" + (list 601000.0 (cadr (cdr (assoc 10 (entget ag3)))) 0.0) + (list 603000.0 (cadr (cdr (assoc 10 (entget ag3)))) 0.0) + (list (cons "GESCHOSS" "1") (cons "TIEFE_mm" "700") (cons "BEREICH" "")))) + (tlg-eq "von hand: reihe verknuepft" (tlg-attr b3 "NR") (tlg-attr mr3 "BEREICH")) + (tlg-eq "von hand: reihe quelle" "manuell" (tlg-attr mr3 "QUELLE")) + (tlg-eq "von hand: gang zerteilt" (1+ n3) (length (tlg-nur (lg-bereich-kinder b3) "LG_GANG_*"))) + (tlg-eq "von hand: manueller gang unberuehrt" T (if (entget mg3) T nil)) + ;; unverknuepfte manuelle Reihe (Altbestand) in der Kontur, im Streifen + ;; des manuellen Gangs (dort sicher frei von automatischen Objekten) + (setq alt3 (lg-linie-einfuegen "reihe" '(605000.0 500.0 0.0) '(605000.0 2500.0 0.0) + (list (cons "GESCHOSS" "1") (cons "TIEFE_mm" "700") (cons "QUELLE" "manuell")) nil)) + ;; ausdrueckliche Neuberechnung (Bereich bearbeiten): Handarbeit bleibt + (lg-bereich-fuellen b3) + (tlg-eq "neuberechnung: manueller gang bleibt" T (if (entget mg3) T nil)) + (tlg-eq "neuberechnung: manuelle reihe bleibt" T (if (entget mr3) T nil)) + (tlg-eq "neuberechnung: geaenderte reihe bleibt" T (if (and ge3 (entget ge3)) T nil)) + (tlg-eq "neuberechnung: altbestand bleibt" T (if (entget alt3) T nil)) + ;; nichts Automatisches ueberdeckt mehr die Handarbeit + (tlg-eq "neuberechnung: nichts zu schneiden" '(0 0 0 0) + (mapcar 'lg-sperre-anwenden (list mg3 mr3 ge3 alt3))) + ;; Gang-Rechteck buendig an eine Reihe gezeichnet, krumme Breite 999.6: + ;; die Rundung darf die Ansetzkante nicht verschieben, die Reihe bleibt + (setq rh3 (vl-remove-if-not '(lambda (r) (= (tlg-attr r "QUELLE") "auto")) + (tlg-nur (lg-bereich-kinder b3) "LG_REIHE_*"))) + (setq bb (lg-linie-polygon (car rh3)) + kb (mapcar '(lambda (q p) (/ (- q p) (distance (car bb) (cadddr bb)))) (cadddr bb) (car bb))) + (setq rg (lg-gang-rechteck-einfuegen + (list (cadddr bb) (caddr bb) + (mapcar '+ (caddr bb) (mapcar '(lambda (x) (* x 999.6)) kb)) + (mapcar '+ (cadddr bb) (mapcar '(lambda (x) (* x 999.6)) kb))) + (list (cons "GESCHOSS" "1") (cons "ART" "gang") (cons "BELAG" "spanplatte")))) + (tlg-eq "rechteck buendig: gang breite" "1000" (tlg-attr rg "BREITE_mm")) + (tlg-eq "rechteck buendig: reihe bleibt" T (if (entget (car rh3)) T nil)) + + (ssg-gui-an) + (princ)) + + +(defun lager:export-results (out-dir / f pfad) + (setq pfad (strcat out-dir "/lager_results.json")) + (setq f (open pfad "w")) + (if f + (progn + (write-line "{" f) + (write-line (strcat " \"pass\": " (itoa *tlg-pass*) ",") f) + (write-line (strcat " \"fail\": " (itoa *tlg-fail*) ",") f) + (write-line (strcat " \"status\": \"" + (if (= *tlg-fail* 0) "OK" "FEHLER") "\"") f) + (write-line "}" f) + (close f) + (princ (strcat "\n Ergebnis: " pfad)))) + (princ)) + + +(defun c:TEST_LAGER ( / ) + (setq *tlg-pass* 0 *tlg-fail* 0 *tlg-fails* '()) + (princ "\n================================================================") + (princ "\n TEST_LAGER - Lagerplanung (LagerInsert.lsp)") + (princ "\n================================================================") + (lager:tests) + (princ "\n\n----------------------------------------------------------------") + (princ (strcat "\n Ergebnis: " (itoa *tlg-pass*) " PASS, " + (itoa *tlg-fail*) " FAIL")) + (if (> *tlg-fail* 0) + (progn + (princ "\n Fehlgeschlagen:") + (foreach n (reverse *tlg-fails*) (princ (strcat "\n - " n))))) + (princ "\n================================================================") + (princ)) + +(princ "\nTEST_LAGER geladen. Aufruf: TEST_LAGER") +(princ) diff --git a/tests/test_lager_yaml.py b/tests/test_lager_yaml.py new file mode 100644 index 0000000..d13364c --- /dev/null +++ b/tests/test_lager_yaml.py @@ -0,0 +1,208 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +test_lager_yaml.py - lib/lager_yaml_export.py (Lagerplanung -> RFQ-Overlay-YAML). + +Laeuft ohne BricsCAD: die Rohdaten sind von Hand nachgestellt, im Format von +LAGER_EXPORT (Lisp/LagerInsert.lsp, lg-export-json). Geprueft wird: + + 1. levels: Belagshoehen, lichte Hoehe aus dem Geschoss darueber, + Haengeebenen (Summe aller Reihen je Schienenhoehe/Warenlaenge, inkl. + goh_rack-Bloecke), Belag/Gitterrost nur auf Buehnen. + 2. layout: Bereichsparameter, Ganganzahl/-laenge, Huelle. + 3. Provenienz: jeder Overlay-Wert hat einen Herkunfts-Eintrag. + 4. Abgleich gegen cfg/example_goh_storage.yaml: Abweichungen -> Konflikte, + null/fehlend -> gefuellt. + 5. CLI schreibt YAML (wieder einlesbar) und Nachweis-HTML. +""" + +import json +import os +import sys + +import pytest +import yaml + +sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "lib")) +import lager_yaml_export as lye # noqa: E402 + +BEISPIEL_YAML = os.path.join(os.path.dirname(os.path.abspath(__file__)), + "..", "cfg", "example_goh_storage.yaml") + +EBENEN_EG = [{"schienenhoehe_mm": 2300, "warenlaenge_mm": 1200}, + {"schienenhoehe_mm": 1100, "warenlaenge_mm": 1100}] +EBENEN_OG = [{"schienenhoehe_mm": 2800, "warenlaenge_mm": 1200}, + {"schienenhoehe_mm": 1400, "warenlaenge_mm": 1200}] + + +def _reihe(gs, laenge, ebenen, tiefe=700): + return {"geschoss": gs, "laenge_mm": laenge, "tiefe_mm": tiefe, "haengeebenen": ebenen, + "schienen": 1, "quelle": "auto", "bereich": 1} + + +def _gang(gs, laenge, breite=1000, art="gang", belag="spanplatte"): + return {"geschoss": gs, "laenge_mm": laenge, "breite_mm": breite, "art": art, + "belag": belag, "bereich": 1} + + +@pytest.fixture +def raw(): + """Zwei Geschosse: Hallenboden (EG) + Buehne auf 3100 mm, je ein 2-Reihen-Modul.""" + return { + "version": 1, "zeichnung": "test.dwg", "erzeugt": "20261021120000", "einheit": "mm", + "land": "DE", + "belag_yaml": {"spanplatte": "chipboard", "gitterrost": "grating", "hallenboden": ""}, + "reihen_als_gitterrost": True, + "geschosse": [ + # bewusst nicht nach Hoehe sortiert - der Export sortiert selbst + {"nr": 1, "bezeichnung": "Level 1", "hoehe_belag_mm": 3100, "konstr_hoehe_mm": 150, + "lichte_hoehe_mm": None, "belag_art": "spanplatte", "personen": 20, + "kontur_flaeche_mm2": 60e6, "kontur_bbox": [0, 0, 10000, 6000]}, + {"nr": 0, "bezeichnung": "ground floor", "hoehe_belag_mm": 0, "konstr_hoehe_mm": 0, + "lichte_hoehe_mm": None, "belag_art": "hallenboden", "personen": 20, + "kontur_flaeche_mm2": 60e6, "kontur_bbox": [0, 0, 10000, 6000]}, + ], + "bereiche": [ + {"nr": 1, "geschoss": 0, "gangbreite_mm": 1000, "regaltiefe_mm": 700, + "reihen_je_gang": 2, "schienen_je_gang": 1}, + {"nr": 2, "geschoss": 1, "gangbreite_mm": 1000, "regaltiefe_mm": 700, + "reihen_je_gang": 2, "schienen_je_gang": 1}, + ], + "reihen": [_reihe(0, 10000, EBENEN_EG), _reihe(0, 10000, EBENEN_EG), + _reihe(1, 10000, EBENEN_OG), _reihe(1, 10000, EBENEN_OG)], + "gaenge": [_gang(0, 10000), _gang(1, 10000), + _gang(1, 700, breite=1200, art="quergang"), + _gang(1, 500, breite=1200, art="quergang", belag="gitterrost")], + "goh_racks": [{"geschoss": 1, "name": "goh_rack", "laenge_mm": 5000, "ebenen": 2, + "tiefe_mm": 700, "haengeebenen": EBENEN_OG, "haengeebenen_quelle": "vorgabe"}], + "warnungen": ["Testwarnung"], + } + + +def test_levels_sortiert_und_hoehen(raw): + overlay, _ = lye.aggregiere(raw) + lv = overlay["levels"] + assert [l["floor_elevation_m"] for l in lv] == [0.0, 3.1] + assert lv[0]["remark"] == "ground floor" + # lichte Hoehe EG = 3100 - 150 (Konstruktion darueber) - 0 + assert lv[0]["clear_height_m"] == pytest.approx(2.95) + # oberstes Geschoss ohne LICHTE_HOEHE_mm -> nicht berechenbar + assert "clear_height_m" not in lv[1] + + +def test_haengeebenen_summe_inkl_goh_rack(raw): + overlay, _ = lye.aggregiere(raw) + eg, og = overlay["levels"] + assert eg["hanging_levels"] == [ + {"rail_height_m": 2.3, "rack_length_m": 20.0, "goods_length_m": 1.2}, + {"rail_height_m": 1.1, "rack_length_m": 20.0, "goods_length_m": 1.1}, + ] + # 2 Reihen a 10 m + goh_rack 5 m, oberste Schiene zuerst + assert [h["rack_length_m"] for h in og["hanging_levels"]] == [25.0, 25.0] + assert og["hanging_levels"][0]["rail_height_m"] == 2.8 + + +def test_belag_nur_auf_buehne(raw): + overlay, _ = lye.aggregiere(raw) + eg, og = overlay["levels"] + assert "walkway" not in eg and "grating" not in eg + # Belag: Hauptgang 10 m2 + Quergang Spanplatte 0,84 m2 + assert og["walkway"]["area_m2"] == pytest.approx(10.8) + assert og["walkway"]["spec"] == "chipboard" + # Gitterrost: Quergang 0,6 m2 + Reihen 2 x 7 m2 + goh_rack 3,5 m2 + assert og["grating"] == pytest.approx(18.1) + + +def test_belag_ohne_gaenge_aus_kontur(raw): + raw["gaenge"] = [] + overlay, s = lye.aggregiere(raw) + og = overlay["levels"][1] + # 60 m2 Kontur - (2 x 7 + 3,5) m2 Reihen + assert og["walkway"]["area_m2"] == pytest.approx(42.5) + assert "Konturflaeche" in s.provenance["levels[1].walkway.area_m2"]["formel"] + + +def test_layout(raw): + overlay, _ = lye.aggregiere(raw) + lay = overlay["layout"] + assert lay["aisle_width_m"] == 1.0 + assert lay["rack_rows_per_aisle"] == 2 + assert lay["rack_depth_m"] == 0.7 + assert lay["trolley_rails_per_aisle"] == 1 + assert lay["aisles"] == 1 # Quergaenge zaehlen nicht + assert lay["aisle_length_m"] == 10.0 + assert lay["block_length_m"] == 10.0 + assert lay["block_width_m"] == 6.0 + + +def test_randgang_im_belag_nicht_in_aisles(raw): + # Randgang 10 m x 1 m (Spanplatte) im OG: Belag waechst, aisles bleibt + raw["gaenge"].append(_gang(1, 10000, art="randgang")) + overlay, s = lye.aggregiere(raw) + og = overlay["levels"][1] + assert og["walkway"]["area_m2"] == pytest.approx(20.8) + assert overlay["layout"]["aisles"] == 1 + assert overlay["layout"]["aisle_length_m"] == 10.0 + assert "Randgaenge G1: 10.0 m" in s.provenance["layout.aisles"]["formel"] + + +def test_unterschiedliche_bereiche_warnen(raw): + raw["bereiche"][1]["gangbreite_mm"] = 1200 + raw["bereiche"].append(dict(raw["bereiche"][1])) + overlay, s = lye.aggregiere(raw) + assert overlay["layout"]["aisle_width_m"] == 1.2 + assert any("aisle_width_m" in w for w in s.warnungen) + + +def test_provenienz_vollstaendig(raw): + ausgabe, _ = lye.baue_ausgabe(raw) + # hanging_levels: Provenienz je rack_length_m; Schienenhoehe/Warenlaenge + # sind der Gruppenschluessel derselben Zeile und stehen in deren Formel. + fehlend = [p for p, _ in lye._blaetter(ausgabe) + if p not in ausgabe["_provenance"] + and not (".hanging_levels[" in p and not p.endswith(".rack_length_m"))] + assert fehlend == [] + assert ausgabe["_warnings"][0] == "Testwarnung" + + +def test_abgleich_mit_beispiel_yaml(raw): + with open(BEISPIEL_YAML, encoding="utf-8") as f: + hand = yaml.safe_load(f) + konflikte, fuellt = lye.abgleich(lye.aggregiere(raw)[0], hand) + pfade = {k["pfad"]: k for k in konflikte} + # Hand 4 Ebenen, Zeichnung 2 + assert "levels" in pfade + # Hand 1,0 m Gangbreite = Zeichnung -> kein Konflikt + assert "layout.aisle_width_m" not in pfade + # Hand 4286 m2, Zeichnung 10,8 m2 + assert pfade["levels[1].walkway.area_m2"]["hand"] == 4286 + assert pfade["levels[1].walkway.area_m2"]["abweichung_prozent"] < -99 + # Hand null -> Zeichnung fuellt + gef = {f["pfad"] for f in fuellt} + assert "layout.block_length_m" in gef + assert "levels[1].grating" in gef + + +def test_hole_pfad(): + obj = {"levels": [{"a": 1}, {"walkway": {"area_m2": 5}}]} + assert lye._hole(obj, "levels[1].walkway.area_m2") == (True, 5) + assert lye._hole(obj, "levels[2].walkway") == (False, None) + assert lye._hole(obj, "layout.x") == (False, None) + + +def test_cli(raw, tmp_path): + rp = tmp_path / "x_lager_raw.json" + rp.write_text(json.dumps(raw), encoding="cp1252") + yp = tmp_path / "x_drawing.yaml" + hp = tmp_path / "x_nachweis.html" + rc = lye.main([str(rp), str(yp), "--hand", BEISPIEL_YAML, "--nachweis", str(hp)]) + assert rc == 0 + daten = yaml.safe_load(yp.read_text(encoding="utf-8")) + assert daten["levels"][1]["floor_elevation_m"] == 3.1 + assert daten["_conflicts"] + assert "levels[0].floor_elevation_m" in daten["_provenance"] + assert "Lager-Nachweis" in hp.read_text(encoding="utf-8") + + +def test_cli_fehlende_datei(tmp_path): + assert lye.main([str(tmp_path / "fehlt.json"), str(tmp_path / "o.yaml")]) == 2 diff --git a/tests/test_unit.lsp b/tests/test_unit.lsp index 9c2fd50..c948006 100644 --- a/tests/test_unit.lsp +++ b/tests/test_unit.lsp @@ -3066,6 +3066,441 @@ +;; ------------------------------------------------------------ +;; TESTS: ssg_core.lsp - Polygon-Helfer (reine 2D-Berechnung) +;; ------------------------------------------------------------ +(defun tu-tests-poly ( / _q _u _l) + (princ "\n[ssg_core Polygon]") + (setq _q '((0.0 0.0) (10.0 0.0) (10.0 5.0) (0.0 5.0))) + (tu-eqf "ssg-poly-flaeche/rechteck" 50.0 (ssg-poly-flaeche _q)) + (tu-eqf "ssg-poly-flaeche/gegen-uhrzeiger" 50.0 (ssg-poly-flaeche (reverse _q))) + (tu-eqf "ssg-poly-umfang" 30.0 (ssg-poly-umfang _q)) + (tu-eq "ssg-poly-kanten/anzahl" 4 (length (ssg-poly-kanten _q))) + (tu-eq "ssg-poly-kanten/doppelter-schlusspunkt" 4 + (length (ssg-poly-kanten (append _q (list (car _q)))))) + (tu-eqf "ssg-poly-bbox" '((0.0 0.0) (10.0 5.0)) (ssg-poly-bbox _q)) + (tu-true "ssg-punkt-in-poly-p/innen" (ssg-punkt-in-poly-p '(5.0 2.0) _q)) + (tu-nil "ssg-punkt-in-poly-p/aussen" (ssg-punkt-in-poly-p '(11.0 2.0) _q)) + ;; U-Form: Schnitt durch beide Schenkel + (setq _u '((0.0 0.0) (10.0 0.0) (10.0 10.0) (7.0 10.0) (7.0 3.0) (3.0 3.0) (3.0 10.0) (0.0 10.0))) + (tu-eqf "ssg-poly-flaeche/u-form" 72.0 (ssg-poly-flaeche _u)) + (tu-nil "ssg-punkt-in-poly-p/u-luecke" (ssg-punkt-in-poly-p '(5.0 5.0) _u)) + (tu-eqf "ssg-poly-schnitt-y/u-schenkel" '((0.0 3.0) (7.0 10.0)) (ssg-poly-schnitt-y _u 5.0)) + (tu-eqf "ssg-poly-schnitt-y/u-boden" '((0.0 10.0)) (ssg-poly-schnitt-y _u 1.0)) + (tu-eqf "ssg-poly-schnitt-y/unterkante" '((0.0 10.0)) (ssg-poly-schnitt-y _q 0.0)) + (tu-nil "ssg-poly-schnitt-y/oberkante" (ssg-poly-schnitt-y _q 5.0)) + (tu-eqf "ssg-intervalle-schnitt" '((2.0 3.0) (5.0 10.0)) + (ssg-intervalle-schnitt '((0.0 10.0)) '((2.0 3.0) (5.0 12.0)))) + (tu-nil "ssg-intervalle-schnitt/leer" (ssg-intervalle-schnitt '((0.0 1.0)) '((2.0 3.0)))) + (tu-eqf "ssg-intervalle-minus" '((0.0 2.0) (3.0 5.0) (6.0 10.0)) + (ssg-intervalle-minus '((0.0 10.0)) '((2.0 3.0) (5.0 6.0)))) + (tu-eqf "ssg-intervalle-minus/ohne-b" '((0.0 10.0)) (ssg-intervalle-minus '((0.0 10.0)) nil)) + (tu-eqf "ssg-intervalle-minus/ueberdeckt" nil (ssg-intervalle-minus '((2.0 3.0)) '((0.0 10.0)))) + (tu-eqf "ssg-punkt-drehen/90" '(0.0 1.0) (ssg-punkt-drehen '(1.0 0.0) (/ pi 2.0) '(0.0 0.0))) + (tu-eqf "ssg-punkt-drehen/z-bleibt" '(1.0 2.0 7.0) (ssg-punkt-drehen '(1.0 2.0 7.0) 0.0 '(5.0 5.0))) + ;; Umlaufsinn + (tu-eqf "ssg-poly-flaeche-signiert/ccw" 50.0 (ssg-poly-flaeche-signiert _q)) + (tu-eqf "ssg-poly-flaeche-signiert/cw" -50.0 (ssg-poly-flaeche-signiert (reverse _q))) + ;; Band-Clip: x-Ausdehnung des Polygonteils zwischen zwei Waagrechten + (tu-eqf "ssg-poly-band-x/rechteck" '(0.0 10.0) (ssg-poly-band-x _q 1.0 2.0)) + (tu-eqf "ssg-poly-band-x/dreieck-unten" '(0.0 10.0) (ssg-poly-band-x '((0.0 0.0) (10.0 0.0) (0.0 10.0)) 0.0 5.0)) + (tu-eqf "ssg-poly-band-x/dreieck-oben" '(0.0 5.0) (ssg-poly-band-x '((0.0 0.0) (10.0 0.0) (0.0 10.0)) 5.0 10.0)) + (tu-nil "ssg-poly-band-x/ausserhalb" (ssg-poly-band-x _q 6.0 7.0)) + ;; nur an der Kante beruehrt -> sperrt nicht + (tu-nil "ssg-poly-band-x/kante" (ssg-poly-band-x _q 5.0 6.0)) +) + +;; ------------------------------------------------------------ +;; TESTS: LagerInsert.lsp / LagerPruefung.lsp - reine Funktionen +;; (Geschoss-/Bereichsbloecke, Export und Dialoge brauchen eine Zeichnung: +;; tests/test_lager.lsp) +;; ------------------------------------------------------------ +(defun tu-lg-anzahl (recs typ) + (length (vl-remove-if-not '(lambda (r) (= (car r) typ)) recs)) +) +(defun tu-lg-summe (recs typ / s) + (setq s 0.0) + (foreach r recs + (if (= (car r) typ) (setq s (+ s (distance (cadr r) (caddr r)))))) + s +) + +(defun tu-tests-lager ( / _q _erg _l _kand _kand2 _sq0 _sq3 _regel _prof _rand _rs _tab _w) + (princ "\n[LagerInsert / LagerPruefung]") + (if (not (and (car (atoms-family 1 '("LG-FUELLUNG-BERECHNEN"))) + (car (atoms-family 1 '("LG-REGEL-AUFLOESEN"))))) + (tu-record nil "lager/geladen" "LagerInsert/LagerPruefung nicht geladen (ssg_load.lsp)") + (progn + ;; --- Zahlen/Texte --- + (tu-eq "lg-mm-str/ganz" "2300" (lg-mm-str 2300.0)) + (tu-eq "lg-mm-str/halb" "2.5" (lg-mm-str 2.5)) + (tu-eq "lg-mm-str/negativ" "-1" (lg-mm-str -1.0)) + (tu-eq "lg-mm-str/nil" "" (lg-mm-str nil)) + (tu-eqf "lg-zahl/komma" 1.5 (lg-zahl "1,5")) + (tu-nil "lg-zahl/leer" (lg-zahl "")) + (tu-nil "lg-zahl/text" (lg-zahl "abc")) + (tu-eqf "lg-haengeebenen-parse" '((2300.0 1200.0) (1100.0 1100.0)) + (lg-haengeebenen-parse "2300/1200;1100/1100")) + (tu-eqf "lg-haengeebenen-parse/ohne-ware" '((2300.0 0.0)) (lg-haengeebenen-parse "2300")) + (tu-nil "lg-haengeebenen-parse/ungueltig" (lg-haengeebenen-parse "x")) + (tu-eq "lg-haengeebenen-string" "2300/1200;1100/1100" + (lg-haengeebenen-string '((2300.0 1200.0) (1100.0 1100.0)))) + (tu-eq "lg-haengeebenen-n/1" "2300/1200" (lg-haengeebenen-n "2300/1200;1100/1100" 1)) + (tu-eq "lg-haengeebenen-n/zuviel" "2300/1200;1100/1100" (lg-haengeebenen-n "2300/1200;1100/1100" 5)) + (tu-eq "lg-feld-pruefen/mm" "1000" (lg-feld-pruefen "mm" " 1000 ")) + (tu-nil "lg-feld-pruefen/mm-neg" (lg-feld-pruefen "mm" "-5")) + (tu-nil "lg-feld-pruefen/mm-leer" (lg-feld-pruefen "mm" "")) + (tu-eq "lg-feld-pruefen/mm_leer" "" (lg-feld-pruefen "mm_leer" "")) + (tu-eq "lg-feld-pruefen/int" "2" (lg-feld-pruefen "int" "2")) + (tu-nil "lg-feld-pruefen/int-0" (lg-feld-pruefen "int" "0")) + (tu-eq "lg-feld-pruefen/int0-0" "0" (lg-feld-pruefen "int0" "0")) + (tu-nil "lg-feld-pruefen/int-bruch" (lg-feld-pruefen "int" "1.5")) + (tu-eq "lg-feld-pruefen/grad-neg" "-90" (lg-feld-pruefen "grad" "-90")) + (tu-eq "lg-feld-pruefen/ebenen" "2300/1200;1100/1100" (lg-feld-pruefen "ebenen" "2300/1200; 1100/1100")) + (tu-nil "lg-feld-pruefen/ebenen-leer" (lg-feld-pruefen "ebenen" "")) + (tu-eq "lg-layer-name/geschoss" "LG_Reihe_G2" (lg-layer-name "LG_Reihe" "2")) + (tu-eq "lg-layer-name/int" "LG_Reihe_G3" (lg-layer-name "LG_Reihe" 3)) + (tu-eq "lg-layer-name/ohne" "LG_Reihe" (lg-layer-name "LG_Reihe" nil)) + + ;; --- Auto-Fuellung: Rechteck 10000 x 6000, Gang 1000, Tiefe 700 --- + (setq _q '((0.0 0.0) (10000.0 0.0) (10000.0 6000.0) (0.0 6000.0))) + ;; 2 Reihen je Gang: Modul 700+1000+700 = 2400 -> 2 Module, Rest 1200 + (setq _erg (lg-fuellung-berechnen _q (list (cons "gang" 1000.0) (cons "tiefe" 700.0) (cons "reihen" 2)))) + (tu-eq "lg-fuellung/2je-reihen" 4 (tu-lg-anzahl (car _erg) "reihe")) + (tu-eq "lg-fuellung/2je-gaenge" 2 (tu-lg-anzahl (car _erg) "gang")) + (tu-eq "lg-fuellung/2je-module" 2 (caddr _erg)) + (tu-eqf "lg-fuellung/2je-rest" 1200.0 (cadr _erg)) + (tu-eqf "lg-fuellung/2je-reihenlaenge" 40000.0 (tu-lg-summe (car _erg) "reihe")) + (tu-eqf "lg-fuellung/erste-reihe" '("reihe" (0.0 350.0) (10000.0 350.0) 700.0) (car (car _erg))) + (tu-eqf "lg-fuellung/erster-gang" '("gang" (0.0 1200.0) (10000.0 1200.0) 1000.0) (cadr (car _erg))) + ;; Ruecken an Ruecken: 3. Reihe (Modul 1) direkt nach der 2. Reihe + (tu-eqf "lg-fuellung/ruecken-an-ruecken" '(0.0 2050.0) (cadr (nth 2 (car _erg)))) + (tu-eqf "lg-fuellung/zweites-modul" '(0.0 2750.0) (cadr (nth 3 (car _erg)))) + ;; 1 Reihe je Gang: Modul 1700 -> 3 Module, Rest 900 + (setq _erg (lg-fuellung-berechnen _q (list (cons "gang" 1000.0) (cons "tiefe" 700.0) (cons "reihen" 1)))) + (tu-eq "lg-fuellung/1je-reihen" 3 (tu-lg-anzahl (car _erg) "reihe")) + (tu-eq "lg-fuellung/1je-gaenge" 3 (tu-lg-anzahl (car _erg) "gang")) + (tu-eqf "lg-fuellung/1je-rest" 900.0 (cadr _erg)) + ;; Quergaenge alle 4000 mm, 1000 breit, ein Modul (Hoehe 2400) + (tu-eqf "lg-quergang-luecken" '((4000.0 5000.0) (9000.0 10000.0)) + (lg-quergang-luecken 0.0 10000.0 4000.0 1000.0 0.0)) + ;; keine Luecke am Reihenende, wenn dahinter kein Mindeststueck passt + (tu-eqf "lg-quergang-luecken/reihenende" '((4000.0 5000.0)) + (lg-quergang-luecken 0.0 10000.0 4000.0 1000.0 500.0)) + (tu-nil "lg-quergang-luecken/aus" (lg-quergang-luecken 0.0 10000.0 0.0 1000.0 500.0)) + (setq _erg (lg-fuellung-berechnen '((0.0 0.0) (10000.0 0.0) (10000.0 2400.0) (0.0 2400.0)) + (list (cons "gang" 1000.0) (cons "tiefe" 700.0) (cons "reihen" 2) + (cons "q_abstand" 4000.0) (cons "q_breite" 1000.0)))) + ;; Luecke 4000-5000; die zweite (9000-10000) entfaellt am Reihenende + (tu-eq "lg-fuellung/quer-reihenstuecke" 4 (tu-lg-anzahl (car _erg) "reihe")) + (tu-eqf "lg-fuellung/quer-reihenlaenge" 18000.0 (tu-lg-summe (car _erg) "reihe")) + (tu-eq "lg-fuellung/quer-hauptgang-durch" 1 (tu-lg-anzahl (car _erg) "gang")) + (tu-eqf "lg-fuellung/quer-hauptganglaenge" 10000.0 (tu-lg-summe (car _erg) "gang")) + ;; zwei Stuecke (unter und ueber dem Hauptgang), je 700 lang + (tu-eq "lg-fuellung/quergaenge" 2 (tu-lg-anzahl (car _erg) "quergang")) + (tu-eqf "lg-fuellung/quergang-laenge" 1400.0 (tu-lg-summe (car _erg) "quergang")) + ;; L-Form: oberes Modul nur halb so lang + (setq _l '((0.0 0.0) (10000.0 0.0) (10000.0 2400.0) (5000.0 2400.0) (5000.0 4800.0) (0.0 4800.0))) + (setq _erg (lg-fuellung-berechnen _l (list (cons "gang" 1000.0) (cons "tiefe" 700.0) (cons "reihen" 2)))) + (tu-eqf "lg-fuellung/l-form-reihenlaenge" 30000.0 (tu-lg-summe (car _erg) "reihe")) + (tu-eqf "lg-fuellung/l-form-ganglaenge" 15000.0 (tu-lg-summe (car _erg) "gang")) + ;; Kontur kleiner als ein Modul -> nichts, Rest = ganze Breite + (setq _erg (lg-fuellung-berechnen '((0.0 0.0) (5000.0 0.0) (5000.0 2000.0) (0.0 2000.0)) + (list (cons "gang" 1000.0) (cons "tiefe" 700.0) (cons "reihen" 2)))) + (tu-nil "lg-fuellung/zu-schmal" (car _erg)) + (tu-eqf "lg-fuellung/zu-schmal-rest" 2000.0 (cadr _erg)) + (tu-eqf "lg-kontur-richtung/waagrecht" 0.0 (lg-kontur-richtung _q)) + (tu-eqf "lg-kontur-richtung/senkrecht" 90.0 + (lg-kontur-richtung '((0.0 0.0) (1000.0 0.0) (1000.0 8000.0) (0.0 8000.0)))) + + ;; --- Randgaenge (Himmelsrichtung, entlang der Konturkanten) --- + (tu-eq "lg-kompass/ost" "O" (lg-kompass 0.0)) + (tu-eq "lg-kompass/nord" "N" (lg-kompass (/ pi 2.0))) + (tu-eq "lg-kompass/west" "W" (lg-kompass pi)) + (tu-eq "lg-kompass/sued" "S" (lg-kompass (/ pi -2.0))) + (tu-eq "lg-kompass/diagonal-ns" "N" (lg-kompass (/ pi 4.0))) + (setq _rand '(("N" . 1000.0) ("O" . 1000.0) ("S" . 1000.0) ("W" . 1000.0))) + ;; Rechteck, alle vier Seiten: N/S volle Laenge, O/W um 2 x 1000 gekuerzt + (setq _rs (lg-randgang-streifen _q 0.0 _rand 100.0)) + (tu-eq "lg-randgang/rechteck-anzahl" 4 (length _rs)) + (tu-eq "lg-randgang/rechteck-sued" "S" (car (car _rs))) + (tu-eqf "lg-randgang/rechteck-sued-mitte" '((0.0 500.0) (10000.0 500.0)) + (list (cadr (car _rs)) (caddr (car _rs)))) + (tu-eqf "lg-randgang/rechteck-ost-gekuerzt" '((9500.0 1000.0) (9500.0 5000.0)) + (list (cadr (cadr _rs)) (caddr (cadr _rs)))) + (setq _erg (lg-fuellung-berechnen _q (list (cons "gang" 1000.0) (cons "tiefe" 700.0) (cons "reihen" 2) + (cons "rand" _rand)))) + (tu-eqf "lg-fuellung/rand-laenge" 28000.0 (tu-lg-summe (car _erg) "randgang")) + ;; Stapel 1000..5000 -> ein Modul, Rest 1600; Reihen/Gang 1000..9000 + (tu-eq "lg-fuellung/rand-module" 1 (caddr _erg)) + (tu-eqf "lg-fuellung/rand-rest" 1600.0 (cadr _erg)) + (tu-eqf "lg-fuellung/rand-erste-reihe" '("reihe" (1000.0 1350.0) (9000.0 1350.0) 700.0) (car (car _erg))) + (tu-eqf "lg-fuellung/rand-reihenlaenge" 16000.0 (tu-lg-summe (car _erg) "reihe")) + (tu-eqf "lg-fuellung/rand-ganglaenge" 8000.0 (tu-lg-summe (car _erg) "gang")) + (tu-eq "lg-fuellung/rand-am-ende" "randgang" (car (last (car _erg)))) + ;; RICHTUNG 90 Grad: Nord liegt auf der lokalen rechten Kante (Reihenende) + (setq _rs (lg-randgang-streifen _q (/ pi 2.0) '(("N" . 1000.0)) 100.0)) + (tu-eq "lg-randgang/gedreht-anzahl" 1 (length _rs)) + (tu-eqf "lg-randgang/gedreht-lage" '((9500.0 0.0) (9500.0 6000.0)) + (list (cadr (car _rs)) (caddr (car _rs)))) + (setq _erg (lg-fuellung-berechnen _q (list (cons "gang" 1000.0) (cons "tiefe" 700.0) (cons "reihen" 2) + (cons "rand" '(("N" . 1000.0))) (cons "richtung" (/ pi 2.0))))) + (tu-eqf "lg-fuellung/gedreht-reihenlaenge" 36000.0 (tu-lg-summe (car _erg) "reihe")) + (tu-eqf "lg-fuellung/gedreht-rest" 1200.0 (cadr _erg)) + ;; L-Form, Nord + Ost: Stufenkante bekommt den Nord-Randgang und wird + ;; an der Innenecke um die Ost-Breite verlaengert; Ost-Gaenge an den + ;; konvexen Ecken gekuerzt + (setq _rs (lg-randgang-streifen _l 0.0 '(("N" . 1000.0) ("O" . 1000.0)) 100.0)) + (tu-eq "lg-randgang/l-anzahl" 4 (length _rs)) + (tu-eq "lg-randgang/l-nordkanten" 2 + (length (vl-remove-if-not '(lambda (s) (= (car s) "N")) _rs))) + (tu-eqf "lg-randgang/l-stufe-verlaengert" '((10000.0 1900.0) (4000.0 1900.0)) + (list (cadr (nth 1 _rs)) (caddr (nth 1 _rs)))) + (setq _erg (lg-fuellung-berechnen _l (list (cons "gang" 1000.0) (cons "tiefe" 700.0) (cons "reihen" 2) + (cons "rand" '(("N" . 1000.0) ("O" . 1000.0)))))) + (tu-eqf "lg-fuellung/l-rand-laenge" 13800.0 (tu-lg-summe (car _erg) "randgang")) + (tu-eqf "lg-fuellung/l-reihenlaenge" 13000.0 (tu-lg-summe (car _erg) "reihe")) + (tu-eqf "lg-fuellung/l-ganglaenge" 4000.0 (tu-lg-summe (car _erg) "gang")) + ;; Sperrrechteck (manueller Gang) quer durch die Reihen + (setq _erg (lg-fuellung-berechnen _q (list (cons "gang" 1000.0) (cons "tiefe" 700.0) (cons "reihen" 2) + (cons "sperr" '(((4000.0 0.0) (5000.0 0.0) (5000.0 6000.0) (4000.0 6000.0))))))) + (tu-eq "lg-fuellung/sperr-reihenstuecke" 8 (tu-lg-anzahl (car _erg) "reihe")) + (tu-eqf "lg-fuellung/sperr-reihenlaenge" 36000.0 (tu-lg-summe (car _erg) "reihe")) + (tu-eqf "lg-fuellung/sperr-ganglaenge" 18000.0 (tu-lg-summe (car _erg) "gang")) + ;; Reststuecke unter min_reihe fallen weg, Gangstuecke ab min_gang bleiben + (setq _erg (lg-fuellung-berechnen _q (list (cons "gang" 1000.0) (cons "tiefe" 700.0) (cons "reihen" 2) + (cons "sperr" '(((0.0 0.0) (9700.0 0.0) (9700.0 6000.0) (0.0 6000.0))))))) + (tu-eq "lg-fuellung/sperr-reststueck" 0 (tu-lg-anzahl (car _erg) "reihe")) + (tu-eq "lg-fuellung/sperr-gangstueck" 2 (tu-lg-anzahl (car _erg) "gang")) + + ;; --- Schraffur je Belag, Gang-Blockname --- + (setq _tab (ssg-json-parse + "{\"spanplatte\": {\"muster\": \"ANSI31\", \"massstab\": 50}, \"hallenboden\": null}")) + (tu-eqf "lg-schraffur-vorgabe/spanplatte" '("ANSI31" 50.0 0.0) (lg-schraffur-vorgabe "spanplatte" _tab)) + (tu-nil "lg-schraffur-vorgabe/hallenboden" (lg-schraffur-vorgabe "hallenboden" _tab)) + (tu-nil "lg-schraffur-vorgabe/unbekannt" (lg-schraffur-vorgabe "beton" _tab)) + (tu-nil "lg-schraffur-vorgabe/nil" (lg-schraffur-vorgabe nil _tab)) + (tu-eq "lg-gang-blockname/belag" "LG_GANG_12000_1000_SPANPLATTE" + (lg-gang-blockname 12000.0 1000.0 "spanplatte")) + (tu-eq "lg-gang-blockname/ohne" "LG_GANG_12000_1000" (lg-gang-blockname 12000.0 1000.0 "")) + + ;; --- Geschosszuordnung --- + (setq _sq0 '((0.0 0.0 0.0) (100.0 0.0 0.0) (100.0 100.0 0.0) (0.0 100.0 0.0)) + _sq3 '((0.0 0.0 3100.0) (100.0 0.0 3100.0) (100.0 100.0 3100.0) (0.0 100.0 3100.0))) + (tu-eq "lg-geschoss-punkt/einzeln" 1 + (lg-geschoss-fuer-punkt-kern '(50.0 50.0 0.0) (list (list 1 0.0 _sq0)))) + (tu-nil "lg-geschoss-punkt/aussen" + (lg-geschoss-fuer-punkt-kern '(150.0 50.0 0.0) (list (list 1 0.0 _sq0)))) + ;; 2D: beide Konturen auf Z=0 uebereinander -> nicht eindeutig + (tu-nil "lg-geschoss-punkt/2d-mehrdeutig" + (lg-geschoss-fuer-punkt-kern '(50.0 50.0 0.0) (list (list 1 0.0 _sq0) (list 2 3100.0 _sq0)))) + ;; 3D: Konturen auf ihrer Belagshoehe -> Z entscheidet + (setq _kand (list (list 1 0.0 _sq0) (list 2 3100.0 _sq3))) + (tu-eq "lg-geschoss-punkt/3d-oben" 2 (lg-geschoss-fuer-punkt-kern '(50.0 50.0 3200.0) _kand)) + (tu-eq "lg-geschoss-punkt/3d-unten" 1 (lg-geschoss-fuer-punkt-kern '(50.0 50.0 100.0) _kand)) + + ;; --- Geschoss kopieren --- + (tu-eq "lg-geschoss-aus-layer/regal" 2 (lg-geschoss-aus-layer "LG_Regal_G2")) + (tu-eq "lg-geschoss-aus-layer/12" 12 (lg-geschoss-aus-layer "LG_Reihe_G12")) + (tu-nil "lg-geschoss-aus-layer/ohne" (lg-geschoss-aus-layer "LG_Reihe")) + (tu-nil "lg-geschoss-aus-layer/0" (lg-geschoss-aus-layer "0")) + (tu-nil "lg-geschoss-aus-layer/suffix" (lg-geschoss-aus-layer "LG_X_G2a")) + (tu-nil "lg-geschoss-aus-layer/nil" (lg-geschoss-aus-layer nil)) + ;; 2D uebereinander: Kontur mehrdeutig, Layer entscheidet + (setq _kand2 (list (list 1 0.0 _sq0) (list 2 3100.0 _sq0))) + (tu-eq "lg-rack-geschoss/layer" 2 (lg-rack-geschoss "LG_Regal_G2" '(50.0 50.0 0.0) _kand2)) + (tu-nil "lg-rack-geschoss/mehrdeutig" (lg-rack-geschoss "0" '(50.0 50.0 0.0) _kand2)) + (tu-eq "lg-rack-geschoss/kontur" 1 (lg-rack-geschoss "0" '(50.0 50.0 0.0) (list (list 1 0.0 _sq0)))) + ;; Layer eines nicht vorhandenen Geschosses zaehlt nicht + (tu-eq "lg-rack-geschoss/layer-fremd" 1 (lg-rack-geschoss "LG_Regal_G7" '(50.0 50.0 0.0) (list (list 1 0.0 _sq0)))) + (tu-eq "lg-kopie-belag/folgt" "spanplatte" (lg-kopie-belag "hallenboden" "hallenboden" "spanplatte")) + (tu-eq "lg-kopie-belag/eigen" "gitterrost" (lg-kopie-belag "gitterrost" "hallenboden" "spanplatte")) + (tu-nil "lg-kopie-belag/nil" (lg-kopie-belag nil "hallenboden" "spanplatte")) + (setq _w (lg-kopie-werte '(("ID" . "2A") ("GESCHOSS" . "1") ("LAYER" . "LG_Gang_G1") ("LAENGE" . "9000") + ("BREITE_mm" . "1000") ("ART" . "gang") ("BELAG" . "hallenboden") + ("BEREICH" . "3") ("QUELLE" . "manuell")) + 2 5 "hallenboden" "spanplatte")) + (tu-eq "lg-kopie-werte/gang" + '(("GESCHOSS" . "2") ("BEREICH" . "5") ("BREITE_mm" . "1000") ("ART" . "gang") + ("BELAG" . "spanplatte") ("QUELLE" . "manuell")) + _w) + (tu-eq "lg-kopie-werte/ohne-bereich" "" (cdr (assoc "BEREICH" (lg-kopie-werte '(("BEREICH" . "3")) 2 nil nil nil)))) + (tu-eqf "lg-kopie-plan/2d" + '((1 3000.0 (0.0 0.0 0.0)) (2 6000.0 (0.0 0.0 0.0))) + (lg-kopie-plan 0.0 3000.0 2 0.0 0.0 nil)) + (tu-eqf "lg-kopie-plan/versatz-3d" + '((1 6100.0 (20000.0 0.0 3000.0)) (2 9100.0 (40000.0 0.0 6000.0))) + (lg-kopie-plan 3100.0 3000.0 2 20000.0 0.0 T)) + (tu-eqf "lg-kopie-abstand/unten" 3100.0 (lg-kopie-abstand-vorgabe 6200.0 3100.0 nil nil)) + (tu-eqf "lg-kopie-abstand/lichte" 3000.0 (lg-kopie-abstand-vorgabe 0.0 nil 2750.0 250.0)) + (tu-nil "lg-kopie-abstand/nichts" (lg-kopie-abstand-vorgabe 0.0 nil nil 250.0)) + (tu-nil "lg-kopie-3d-p/2d" (lg-kopie-3d-p _kand2)) + (tu-eq "lg-kopie-3d-p/3d" T (lg-kopie-3d-p _kand)) + (tu-eq "lg-feld-pruefen/mm_vz" "-1500" (lg-feld-pruefen "mm_vz" "-1500")) + (tu-nil "lg-feld-pruefen/mm_vz-x" (lg-feld-pruefen "mm_vz" "abc")) + (tu-nil "lg-feld-pruefen/mm_vz-leer" (lg-feld-pruefen "mm_vz" "")) + + ;; --- Gang aus Rechteck --- + (tu-eqf "lg-gang-aus-ecken/quer" + '((0.0 500.0 0.0) (4000.0 500.0 0.0) 1000.0) + (lg-gang-aus-ecken '((0.0 0.0 0.0) (4000.0 0.0 0.0) (4000.0 1000.0 0.0) (0.0 1000.0 0.0)))) + ;; hochkant, Ecken gegen den Uhrzeigersinn ab oben rechts, Z uebernommen + (tu-eqf "lg-gang-aus-ecken/hoch" + '((600.0 5000.0 3100.0) (600.0 0.0 3100.0) 1200.0) + (lg-gang-aus-ecken '((1200.0 5000.0 3100.0) (0.0 5000.0 3100.0) (0.0 0.0 3100.0) (1200.0 0.0 3100.0)))) + ;; Breite auf ganze mm + (tu-eqf "lg-gang-aus-ecken/runden" 900.0 + (caddr (lg-gang-aus-ecken '((0.0 0.0) (3000.0 0.0) (3000.0 899.6) (0.0 899.6))))) + ;; Rundung verschiebt nur die Seite gegenueber der ersten Ecke + (tu-eqf "lg-gang-aus-ecken/rundung-erste-ecke-unten" + '((0.0 450.0 0.0) (3000.0 450.0 0.0) 900.0) + (lg-gang-aus-ecken '((0.0 0.0) (3000.0 0.0) (3000.0 899.6) (0.0 899.6)))) + (tu-eqf "lg-gang-aus-ecken/rundung-erste-ecke-oben" + '((0.0 449.6 0.0) (3000.0 449.6 0.0) 900.0) + (lg-gang-aus-ecken '((0.0 899.6) (3000.0 899.6) (3000.0 0.0) (0.0 0.0)))) + ;; erste Ecke rechts: Linie beginnt an der rechten Querseite + (tu-eqf "lg-gang-aus-ecken/start-an-erster-ecke" + '((3000.0 450.0 0.0) (0.0 450.0 0.0) 900.0) + (lg-gang-aus-ecken '((3000.0 0.0) (0.0 0.0) (0.0 899.6) (3000.0 899.6)))) + ;; gedreht (UCS 90 Grad): laengere Seite bestimmt die Richtung + (tu-eqf "lg-gang-aus-ecken/gedreht" + '((-500.0 0.0 0.0) (-500.0 4000.0 0.0) 1000.0) + (lg-gang-aus-ecken '((0.0 0.0 0.0) (0.0 4000.0 0.0) (-1000.0 4000.0 0.0) (-1000.0 0.0 0.0)))) + ;; --- Zuschnitt durch von Hand gesetzte Objekte --- + ;; Reihe 10000 x 700, Gang 1000 breit quer darueber -> zwei Reste + (tu-eqf "lg-linie-zuschnitt/quer" '((0.0 4500.0) (5500.0 10000.0)) + (lg-linie-zuschnitt 10000.0 700.0 + '(((4500.0 -2000.0) (5500.0 -2000.0) (5500.0 2000.0) (4500.0 2000.0))) 500.0)) + ;; Gang liegt nur an der Laengsseite an -> nichts zu schneiden + (tu-eq "lg-linie-zuschnitt/anliegend" 'unveraendert + (lg-linie-zuschnitt 10000.0 700.0 + '(((0.0 350.0) (10000.0 350.0) (10000.0 1350.0) (0.0 1350.0))) 500.0)) + (tu-eq "lg-linie-zuschnitt/weit-weg" 'unveraendert + (lg-linie-zuschnitt 10000.0 700.0 + '(((20000.0 -500.0) (21000.0 -500.0) (21000.0 500.0) (20000.0 500.0))) 500.0)) + (tu-nil "lg-linie-zuschnitt/ganz-ueberdeckt" + (lg-linie-zuschnitt 4000.0 700.0 + '(((-100.0 -500.0) (4100.0 -500.0) (4100.0 500.0) (-100.0 500.0))) 500.0)) + ;; Rest 300 < Mindestlaenge 500 faellt weg + (tu-nil "lg-linie-zuschnitt/rest-zu-kurz" + (lg-linie-zuschnitt 10000.0 700.0 + '(((300.0 -500.0) (10000.0 -500.0) (10000.0 500.0) (300.0 500.0))) 500.0)) + (tu-eq "lg-quelle-rang" '(2 1 0 0) + (mapcar 'lg-quelle-rang '("manuell" "auto_geaendert" "auto" nil))) + ;; --- goh_rack-Erkennung: Parameterwerte, Namen, Transformationen --- + (tu-eqf "lg-zahl-einheit/mm" 700.0 (lg-zahl-einheit "700 mm")) + (tu-eqf "lg-zahl-einheit/cm" 718.752 (lg-zahl-einheit "71.8752 cm")) + (tu-eqf "lg-zahl-einheit/m" 12900.0 (lg-zahl-einheit "12,9 m")) + (tu-eqf "lg-zahl-einheit/ohne" 2.0 (lg-zahl-einheit "2")) + (tu-eqf "lg-zahl-einheit/zahl" 3.0 (lg-zahl-einheit 3)) + (tu-nil "lg-zahl-einheit/km" (lg-zahl-einheit "5 km")) + (tu-nil "lg-zahl-einheit/text" (lg-zahl-einheit "abc")) + (tu-nil "lg-zahl-einheit/leer" (lg-zahl-einheit "")) + (tu-nil "lg-zahl-einheit/nil" (lg-zahl-einheit nil)) + (tu-eqf "lg-racks-auswerten" + '((1 2 3000.0 6000.0) (2 1 500.0 1500.0) (nil 2 700.0 1400.0)) + (lg-racks-auswerten '((2 500.0 3.0) (nil 700.0 2.0) (1 1000.0 2.0) + (1 2000.0 2.0) (nil nil 2.0)))) + (tu-nil "lg-racks-auswerten/leer" (lg-racks-auswerten nil)) + (tu-eq "lg-name-brauchbar-p/name" T (lg-name-brauchbar-p "goh_rack")) + (tu-nil "lg-name-brauchbar-p/anonym" (lg-name-brauchbar-p "*U29")) + (tu-nil "lg-name-brauchbar-p/leer" (lg-name-brauchbar-p "")) + ;; Einfuegung bei (100,0), 90 Grad, x-Skalierung 2: lokales (1,0,0) -> (100,2,0) + (tu-eqf "lg-insert-tf/drehung-skalierung" '(100.0 2.0 0.0) + (lg-tf-pkt (lg-insert-tf (list '(2 . "TU_KEIN_BLOCK") '(10 100.0 0.0 0.0) + (cons 50 (/ pi 2.0)) '(41 . 2.0)) + 0 0) + '(1.0 0.0 0.0))) + ;; MINSERT-Spalte 2 bei Spaltenabstand 3000, gedreht um 90 Grad -> entlang +Y + (tu-eqf "lg-insert-tf/minsert" '(0.0 6000.0 0.0) + (car (lg-insert-tf (list '(2 . "TU_KEIN_BLOCK") '(10 0.0 0.0 0.0) (cons 50 (/ pi 2.0)) + '(70 . 3) '(44 . 3000.0)) + 2 0))) + ;; verschachtelt: Container bei (1000,0) um 90 Grad, Kind bei lokal (0,2000) + (tu-eqf "lg-tf-verketten" '(-1000.0 0.0 0.0) + (car (lg-tf-verketten + (lg-insert-tf (list '(2 . "TU_KEIN_BLOCK") '(10 1000.0 0.0 0.0) (cons 50 (/ pi 2.0))) 0 0) + (lg-insert-tf (list '(2 . "TU_KEIN_BLOCK") '(10 0.0 2000.0 0.0)) 0 0)))) + + (tu-nil "lg-gang-aus-ecken/entartet" + (lg-gang-aus-ecken '((0.0 0.0) (4000.0 0.0) (4000.0 0.0) (0.0 0.0)))) + + ;; --- JSON-Schreiber --- + (tu-eq "lg-json-wert/nil" "null" (lg-json-wert nil)) + (tu-eq "lg-json-wert/t" "true" (lg-json-wert T)) + (tu-eq "lg-json-wert/int" "3" (lg-json-wert 3)) + (tu-eq "lg-json-wert/real" "2.5" (lg-json-wert 2.5)) + (tu-eq "lg-json-wert/rund" "1000" (lg-json-wert 1000.0)) + (tu-eq "lg-json-wert/str" "\"a\\\"b\"" (lg-json-wert "a\"b")) + (tu-eq "lg-json-wert/roh" "[1]" (lg-json-wert (list 'ROH "[1]"))) + (tu-eq "lg-json-obj" "{\"a\": 1, \"b\": \"x\"}" (lg-json-obj '(("a" . 1) ("b" . "x")))) + (tu-eq "lg-json-ebenen" "[{\"schienenhoehe_mm\": 2300, \"warenlaenge_mm\": 1200}]" + (cadr (lg-json-ebenen "2300/1200"))) + + ;; --- Regelprofil-Aufloesung --- + (tu-eq "lg-bedingung/leer" 'ja (lg-bedingung-pruefen nil nil)) + (tu-eq "lg-bedingung/gleich" 'ja (lg-bedingung-pruefen "brandgefaehrdung = normal" + '(("brandgefaehrdung" . "normal")))) + (tu-eq "lg-bedingung/ungleich" 'nein (lg-bedingung-pruefen "brandgefaehrdung = normal" + '(("brandgefaehrdung" . "erhoeht_ohne_loeschanlage")))) + (tu-eq "lg-bedingung/fehlt" 'unbekannt (lg-bedingung-pruefen "brandgefaehrdung = normal" nil)) + (tu-eq "lg-bedingung/groesser" 'ja (lg-bedingung-pruefen "absturzhoehe > 12000" '(("absturzhoehe" . 13000)))) + (tu-eq "lg-bedingung/kleinergl" 'nein (lg-bedingung-pruefen "absturzhoehe <= 12000" '(("absturzhoehe" . "13000")))) + ;; min: strengster = groesster, max: strengster = kleinster + (setq _regel (ssg-json-parse + "{\"id\": \"s\", \"art\": \"min\", \"werte\": [{\"wert\": 590, \"quelle\": \"A\"}, {\"wert\": 600, \"quelle\": \"B\"}]}")) + (tu-eq "lg-regel/min-strengster" 600 (car (lg-regel-aufloesen _regel nil))) + (tu-eq "lg-regel/min-gewinner" "B" (ssg-json-get (cadr (lg-regel-aufloesen _regel nil)) "quelle")) + (setq _regel (ssg-json-parse + "{\"id\": \"s\", \"art\": \"max\", \"werte\": [{\"wert\": 650, \"quelle\": \"A\"}, {\"wert\": 660, \"quelle\": \"B\"}]}")) + (tu-eq "lg-regel/max-strengster" 650 (car (lg-regel-aufloesen _regel nil))) + (setq _regel (ssg-json-parse (strcat + "{\"id\": \"f\", \"art\": \"max\", \"werte\": [" + "{\"wert\": 35000, \"bedingung\": \"brandgefaehrdung = normal\"}," + "{\"wert\": 25000, \"bedingung\": \"brandgefaehrdung = erhoeht_ohne_loeschanlage\"}]}"))) + (tu-eq "lg-regel/bedingung-normal" 35000 + (car (lg-regel-aufloesen _regel '(("brandgefaehrdung" . "normal"))))) + (tu-eq "lg-regel/bedingung-erhoeht" 25000 + (car (lg-regel-aufloesen _regel '(("brandgefaehrdung" . "erhoeht_ohne_loeschanlage"))))) + (tu-eq "lg-regel/bedingung-fehlt-konservativ" 25000 (car (lg-regel-aufloesen _regel nil))) + (tu-nil "lg-regel/nichts-zutreffend" + (lg-regel-aufloesen (ssg-json-parse + "{\"id\": \"x\", \"art\": \"max\", \"werte\": [{\"wert\": 1, \"bedingung\": \"a = b\"}]}") + '(("a" . "c")))) + (setq _regel (ssg-json-parse (strcat + "{\"id\": \"b\", \"art\": \"min\", \"werte\": [{\"tabelle\": [[5, 875], [20, 1000], [200, 1200]]," + " \"tabellen_schluessel\": \"personen\"}]}"))) + (tu-eq "lg-regel/tabelle-15" 1000 (car (lg-regel-aufloesen _regel '(("personen" . 15))))) + (tu-eq "lg-regel/tabelle-5" 875 (car (lg-regel-aufloesen _regel '(("personen" . 5))))) + (tu-eq "lg-regel/tabelle-ueber" 1200 (car (lg-regel-aufloesen _regel '(("personen" . 500))))) + (tu-eq "lg-regel/tabelle-fehlt" 1200 (car (lg-regel-aufloesen _regel nil))) + ;; Vererbung: Kind ersetzt Regel b, ergaenzt c, Kopf vom Kind + (setq _prof (lg-profil-mischen + (ssg-json-parse "{\"land\": \"DE\", \"regeln\": [{\"id\": \"a\", \"x\": 1}, {\"id\": \"b\", \"x\": 1}]}") + (ssg-json-parse "{\"land\": \"SK\", \"regeln\": [{\"id\": \"b\", \"x\": 2}, {\"id\": \"c\", \"x\": 3}]}"))) + (tu-eq "lg-profil-mischen/ids" '("a" "b" "c") + (mapcar '(lambda (r) (ssg-json-get r "id")) (ssg-json-get _prof "regeln"))) + (tu-eq "lg-profil-mischen/ersetzt" 2 (ssg-json-get (lg-regel-finden _prof "b") "x")) + (tu-eq "lg-profil-mischen/kopf" "SK" (ssg-json-get _prof "land")) + ;; echte Profile (wie tu-tests-json gegen die Geruest-Daten): SK erbt DE + (if (and (lg-regelwerk-pfad "SK") (findfile (lg-regelwerk-pfad "SK"))) + (progn + (setq _prof (lg-profil-laden-roh "SK" nil)) + (tu-eq "lg-profil/sk-erbt-de" 190 + (car (lg-regel-aufloesen (lg-regel-finden _prof "treppe.steigung_max") nil))) + (tu-eq "lg-profil/laufhoehe" 3000 + (car (lg-regel-aufloesen (lg-regel-finden _prof "treppe.laufhoehe_max") nil))) + (tu-eq "lg-profil/schrittmass-schnittmenge" 600 + (car (lg-regel-aufloesen (lg-regel-finden _prof "treppe.schrittmass_min") nil))) + (tu-eq "lg-profil/gelaender-14122" 1100 + (car (lg-regel-aufloesen (lg-regel-finden _prof "gelaender.hoehe_min") + '(("absturzhoehe" . 3100))))) + (tu-nil "lg-profil/sk-ungepflegt" (ssg-json-get _prof "gepflegt"))) + (tu-record nil "lg-profil/dateien" "cfg/regelwerk/SK.json nicht gefunden (DXFM_CFG?)")) + ) + ) +) + (defun c:TEST_UNIT ( / out-dir report) (tu-reset) (princ "\n================================================================") @@ -3078,7 +3513,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-kf" "tu-tests-hoehen-fam") + "tu-tests-werkzeuge" "tu-tests-poly" "tu-tests-lager" "tu-tests-kf" "tu-tests-hoehen-fam") (tu-run-block fn)) (princ "\n\n----------------------------------------------------------------")