diff --git a/CLAUDE.md b/CLAUDE.md index dfae364..0bf1c17 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -25,7 +25,7 @@ AutoLISP-Makros (SSG_LIB) fuer BricsCAD Pro zur automatisierten Erstellung von F | `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); `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/stahlbau/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 | @@ -81,7 +81,7 @@ Die `start_briscad.bat` ruft `setenv.bat` auf und startet BricsCAD. | Omniflo (komplett) | `OmniModulInsert` + `GeruestInsert` | OMNI_APB_*, OMNI_W*_*, 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 | +| Lager | `LagerInsert` + `LagerPruefung` | LG_GESCHOSS, LG_GESCHOSS_AKTIV, LG_GESCHOSS_KOPIEREN, LG_BEREICH, LG_REIHE, LG_GANG, LG_EDIT, LG_REGALE, LAGER_EXPORT, LG_REGELN | | HW Planung | `HwPlanung` | HW_MATERIALFLUSS (Materialfluss erzeugen), HW_SYMBOLE (Symbole erzeugen, noch Platzhalter) | | 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 | @@ -125,8 +125,8 @@ Detaillierte Dokumentation aller Funktionen und Befehle: siehe `Lisp/README.md`. | `KettenFoerderer.lsp` | KETTENFOERDERER, KF_EDIT, KF_KS_PRUEFEN | Kettenfoerderer/Schraegfoerderer Stahl: baut aus `KF_UM_` -> `KF_LAENGE` -> `KF_AN_` (je KS_AUS auf KS_EIN) plus der Draufsicht `KF_2D` EINEN Block `KF_`. Daten `data/json/kettenfoerderer.json`, Dialog `dcl/kettenfoerderer.dcl`. **Geometrie**: SEITENANSICHT in der XY-Ebene (Hoehe = Y), ungedreht und nicht aufgerichtet, Einfuegepunkt = KS_EIN der Umlenkung; jeder Baustein bekommt nur den aus den KS gemessenen Verkettungswinkel (`insert-block-by-ks`/`insert-block-ks-skaliert`). Die Neigung (26/30 Grad) ist KEIN Rechenparameter: sie steckt in den Varianten-Bloecken, der Einbauwinkel jedes Folgebausteins wird zur Laufzeit aus der RICHTUNG des KS_AUS seines Vorgaengers gemessen (`ks-xy-winkel`/`kf-hz-folge`) - keine gepflegte Winkeltabelle. `KF_LAENGE` wird in X skaliert, die Einbauhoehe H bestimmt seine Laenge ueber die Hoehenbilanz (`kf-laenge-berechnen`). **Hauptblock + Hilfsblock**: `KF_` traegt Beschriftung (`KF_1 26 Grad H=..mm L_horizontal=..mm`, Layer `kettenfoerderer_beschriftung`) und ein Hoehenmass (ActiveX `AddDimRotated`, scheitert es, wird ohne Mass gebaut) und ist der EINZIGE exportierte Block. Die Draufsicht `KF_2D` ist ein getrennter Hilfsblock `KF2D_` (kein Objekt, nur zum Anschliessen von Schienen): flach, auf die PROJIZIERTE Laenge skaliert (`kf-waagerecht-laenge`), beim ersten Einfuegen `ansicht_2d.versatz_y` (-1000) mm unter dem Hauptblock, danach frei verschieb- und loeschbar, gleiche Beschriftung. Beide Bloecke tragen denselben Schluessel als XDATA `SSG_KF_EDIT` (`kf-xdata-key`); KF_EDIT findet damit den - auch verschobenen - Hilfsblock (`kf-hilfsblock-finden`), behaelt dessen Lage und erzeugt ihn bei jedem Edit neu (auch wenn er geloescht war). Der Hilfsblock passt nicht aufs Export-Muster `KF_*`; `export_sivas.py` nimmt ausserdem nur `KF_`. Eine von Hand gesetzte Drehung des Hauptblocks (DXF 50) wird beim Edit wieder aufgesetzt. **Attribut-Schema** `*kf-attrib-defs-liste*` ist EIGENSTAENDIG und bewusst NICHT von `ssg-strecke-attrib-defs` abgeleitet (sonst ~24 geerbte VF-Tags am Block); die Reihenfolge folgt dem Sivas-Merkmalstamm. FOERDERERTYP = Bauart im Klartext, WINKEL = nackte Zahl 26/30 (der Klartext mit Gradzeichen entsteht erst ueber `[kf_typ_anzeige]` in cfg/sivas.cfg, damit Attributwerte ASCII bleiben); kein TEILEART (Festwert `KF_TEILEART` im Export). `L_KF_mm` ist die WAAGERECHTE Laenge des ganzen Foerderers (der Draufsicht `KF_2D`, `kf-waagerecht-laenge`), nicht die Laenge des schraegen Mittelstuecks; sie geht NICHT in die Sivas-Zeile (Spalte Laenge bleibt leer) - Sivas berechnet ZL, EL und Kettenlaenge selbst, uebernommen wird nur die Hoehe (360). Alle Auswahllisten stehen mit ihren Sivas-Optionstexten in der JSON. **Das `KF_`-Praefix ist wie `GR_` nicht kosmetisch**: `SSG_BLOCKEDIT` haelt JEDEN Block mit TEILEART-Attribut fuer ein Omniflo-Element, darum steht ein `KF_*`-Zweig VOR dem Omniflo-Zweig (Doppelklick -> KF_EDIT, der die vorgewaehlte Auswahl nutzt und nur echte `KF_` zulaesst - die Bausteine `KF_UM_26`, `KF_2D` usw. tragen dasselbe Praefix). **Die Blocknummer wird ueber die Blocktabelle vergeben** (`kf-naechste-nummer` ueberspringt jede Nummer mit vorhandener Block-DEFINITION, wie bei `GF_n`/`VF_n`): nur ueber die Referenzen im Modell zu zaehlen war ein realer Fehler - `KF_EDIT` loescht den alten Block, die Definition lebt aber weiter, `_.-BLOCK` traf auf den vorhandenen Namen, die "Neu definieren?"-Abfrage verschluckte die Folge-Eingaben, und die Einzelteile blieben lose liegen ("Block aufgeloest"). **Sivas-Export** (`[kettenfoerderer]` in cfg/sivas.cfg, `build_kettenfoerderer_details` in export_sivas.py): baugleiche Foerderer werden zu EINER Zeile zusammengefasst (`kettenfoerderer_signature`), die Gruppengroesse traegt Merkmal 353 (entsteht erst aus der Gruppierung, daher Code); Farbe (354) und Antriebsmotor (355) sind ZWEI Merkmale, und jedes merkmalbildende Attribut steht auch in der Signatur, sonst verschmelzen unterschiedlich ausgefuehrte Foerderer. Reine Kerne in `tests/test_unit.lsp` (`tu-tests-kf`); das Modul wird in der MNL vorgeladen, Laufzeit-Messung ueber den dbg-Schalter `kf-zeit` | | `HwPlanung.lsp` | HW_MATERIALFLUSS, HW_SYMBOLE | Menue "HW Planung" (zwischen Lager und SPS). HW_MATERIALFLUSS ruft EXPORTCSV auf (alte `_export.csv` wird vorher geloescht, damit kein veralteter Stand gezeichnet wird) und danach `lib/hw_materialfluss.py` ueber `export:run-python` (Log `DXFM_LOG/hw_materialfluss.log`); Ergebnis ist eine NEUE Datei `DXFM_RESULTS/_HW.dxf` (`hw-dxf-name`, getestet), die offene Zeichnung bleibt unveraendert. HW_SYMBOLE ist noch ein Platzhalter (Hinweis) | | `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-*`; Geschosse, Bereiche, Reihen, Gaenge) und ruft `lib/lager_yaml_export.py` ueber `export:run-python`. Befehl `LG_REGALE` (Menue Lager -> Regale zaehlen) zaehlt die Regalreihen `LG_REIHE_*` (Anzahl, Laenge, Laenge x Haengeebenen je Geschoss aus den Attributen GESCHOSS/LAENGE/HAENGEEBENEN, reine Kerne `lg-reihe-eintrag`/`lg-reihen-auswerten`). **Der parametrische BricsCAD-Block `goh_rack` wird nicht mehr unterstuetzt** (veraltet, 2026-10-30 aus Zaehlung, Export und Geschoss-Kopie entfernt). 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`). 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` | +| `LagerInsert.lsp` | LG_GESCHOSS, LG_GESCHOSS_AKTIV, LG_GESCHOSS_KOPIEREN, LG_BEREICH, LG_REIHE, LG_GANG, LG_EDIT, LG_REGALE, LAGER_EXPORT | Lagerplanung GOH-Buehnenlager -> technische Daten als JSON fuer die RFQ-Dokumentation (Phase 0+1: Mengen, 2D; Konzept/Schema/Kennwerte 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 NUR `_lager.json` nach `DXFM_RESULTS` (Format `ssg-lager` Version 2, kein Python-Schritt; das RFQ-Projekt uebernimmt Mapping und Hand-Abgleich): alle Geschosse, Bereiche, Reihen und Gaenge mit Attributen und voller Geometrie (Konturpunkte, Start/Ende/Mitte, 4 Ecken) plus `kennwerte` je Geschoss und `lager` gesamt (lichte Hoehe, Regal-/Schienenlaenge, Haengeebenen, Gaenge je Art, Belags-/Gitterrostflaeche, Gitterkorb), je Geschoss-Kennwert `herkunft` (Formel + Quelle). Eigener JSON-Schreiber `lg-json-*` ueber getaggte Werte `(OBJ ...)`/`(ARR ...)` (`lg-json-text` rueckt ein, Nicht-ASCII als `\uXXXX`), Kennwerte in reinen Kernen (`lg-kennwerte-geschoss`/`-lager`, `lg-haengeebenen-summen`, `lg-gaenge-je-art`, `lg-reihen-offene-kanten`, `lg-reihen-gitterkorb-berechnen`). **Absturzsicherung unter den Reihen** je Bereich (`UNTER_REIHE` = `gitterrost`/`gitterkorb`/`keine`, Vorgabe `lager.json bereich.unter_reihe`, nur auf Buehnen wirksam): Gitterrost zaehlt die Reihengrundflaeche zur Gitterrostflaeche, Gitterkorb liefert Boden (Laenge x Tiefe) und Seitenflaeche = OFFENE Kantenlaenge x `GITTERKORB_HOEHE_mm` (Kanten an einer direkt anliegenden Nachbarreihe desselben Geschosses, z.B. Ruecken an Ruecken, zaehlen nicht; die Korbhoehe soll spaeter aus den Attributen berechnet werden). Regelwerk-Werte stehen noch nicht im Export (`doc/todo.md`). Befehl `LG_REGALE` (Menue Lager -> Regale zaehlen) zaehlt die Regalreihen `LG_REIHE_*` (Anzahl, Laenge, Laenge x Haengeebenen je Geschoss aus den Attributen GESCHOSS/LAENGE/HAENGEEBENEN, reine Kerne `lg-reihe-eintrag`/`lg-reihen-auswerten`). **Der parametrische BricsCAD-Block `goh_rack` wird nicht mehr unterstuetzt** (veraltet, 2026-10-30 aus Zaehlung, Export und Geschoss-Kopie entfernt). 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`). Tests: `tu-tests-lager` (test_unit) und `tests/test_lager.lsp` | +| `LagerPruefung.lsp` | LG_REGELN | Regelprofile der Lagerplanung: `cfg/stahlbau/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). | @@ -223,7 +223,6 @@ Abhaengigkeiten: `ezdxf`, Standard-Library. Benoetigen Umgebungsvariablen (`DXFM | `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` | | (`io_zuordnung_pruefen.py`) | **Nicht hier**: die Zuordnung E-Geraete (Sensoren/Aktoren/Motoren) -> Anlagenbauteile (Separator, AS, Kreisel AN8/SP8, Vario-Stationen, Scanner) liegt im Kabeltool-Repo `kabellaengen`: Pruefskript `lib/io_zuordnung_pruefen.py` (`bin\io_zuordnung_pruefen.bat`), Konverter alter `io`-Symbole `lib/altZuNeu.py` (`bin\altZuNeu.bat`), gemeinsames Regelwerk `cfg/io_regeln.json`, Doku `doc/Anwenderdoku_altZuNeu.md`. Die dort erkannten Mechanik-Bloecke sind die dxfmakros-Bloecke (`Separator_SP*`, `AS_Element*`, `AN8_*`/`SP8_*`, `Vario_Motorstation*`/`Vario_Umlenkstation*`, `Scanner*`) - wer sie umbenennt, muss `bauteile` in `io_regeln.json` mitziehen | | `hw_materialfluss.py` | HW-Planung: Materialfluss-Skizze aus dem CSV-Export (EXPORTCSV) als neue DXF, flach in XY. Kreisel/Eckrad = geschlossenes Rechteck (Position/Boundingbox, Merkmal `Drehung`; schraeg: Abstand + 800 mm), Punktfolge in Drehrichtung (UZS = Uhrzeigersinn) plus Pfeile; Strecke/GF = Polylinie AS -> Separatoren -> ES ueber `TrackIds` (ES vorn -> umgedreht) plus Endpfeil; `ILS Weiche` = kleiner Kreis; BTMT Beladung = grosser Kreis K1 + kleiner Kreis K2 + Linie, Entladung = kleiner Kreis K1 + grosser Kreis K2 + Linie (K1/K2 per `decode_trans` aus `export_graph.py`, dessen `load_elements` auch die CSV liest). Layer/Farben aus `cfg/layer.cfg` `[hw_planung]` (eigener Mini-Parser, Vorgaben im Code). Test `tests/test_hw_materialfluss.py`. Aufruf: `python hw_materialfluss.py [--layer-cfg ...]` | -| `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 index 1f0ab3c..6b36b27 100644 --- a/Lisp/LagerInsert.lsp +++ b/Lisp/LagerInsert.lsp @@ -2,8 +2,8 @@ ;; 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): +;; Haengewarenlager und exportiert daraus die technischen Daten als JSON +;; fuer die RFQ-Dokumentation (LAGER_EXPORT, siehe doc/plan/lager_rfq.md): ;; ;; LG_GESCHOSS Geschoss-Kopfsymbol, verknuepft mit seiner Belagskontur ;; LG_BEREICH Lagerbereich (Kontur + Parameter), fuellt sich selbst @@ -13,7 +13,7 @@ ;; je Belag aus lager.json belag_schraffur) ;; ;; Befehle: LG_GESCHOSS, LG_GESCHOSS_AKTIV, LG_GESCHOSS_KOPIEREN, LG_BEREICH, -;; LG_REIHE, LG_GANG, LG_EDIT, LG_REGALE, LAGER_EXPORT, LAGER_HAND_YAML +;; LG_REIHE, LG_GANG, LG_EDIT, LG_REGALE, LAGER_EXPORT ;; ;; Grundsaetze (CLAUDE.md): ;; - Alle Werte selbstbeschreibend in mm/Grad, keine Index-Codes. @@ -379,6 +379,7 @@ ("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") + ("UNTER_REIHE" . "") ("GITTERKORB_HOEHE_mm" . "") ("RESTBREITE_mm" . ""))) (setq *lg-schema-reihe* @@ -621,6 +622,8 @@ ("richtung" "RICHTUNG" "grad" nil) ("ebenen" "HAENGEEBENEN" "ebenen" nil) ("belag" "BELAG" "wahl" belag) + ("unter" "UNTER_REIHE" "wahl" unter) + ("korbhoehe" "GITTERKORB_HOEHE_mm" "mm" nil) ("rand_n" "RANDGANG_N_mm" "mm" nil) ("rand_o" "RANDGANG_O_mm" "mm" nil) ("rand_s" "RANDGANG_S_mm" "mm" nil) @@ -653,6 +656,7 @@ ((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 'unter) *lg-unter-reihe-werte*) ((eq sym 'geschosse) (mapcar '(lambda (g) (itoa (cadr g))) (lg-geschosse))) ) ) @@ -1174,12 +1178,36 @@ (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 "UNTER_REIHE" (lg-unter-reihe-vorgabe)) + (cons "GITTERKORB_HOEHE_mm" (lg-mm-str (lg-zahl (lg-vorgabe '("bereich" "gitterkorb_hoehe_mm") 500)))) (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))))) ) +;; Absturzsicherung unter den Regalreihen (Attribut UNTER_REIHE am Bereich): +;; gitterrost = Grundflaeche der Reihe als Gitterrost, gitterkorb = Korb mit +;; Boden UND Seitenwaenden zum Geschoss darunter, keine = offen. +(setq *lg-unter-reihe-werte* '("gitterrost" "gitterkorb" "keine")) + +(defun lg-unter-reihe-vorgabe ( / v) + (setq v (lg-vorgabe '("bereich" "unter_reihe") "gitterrost")) + (if (member v *lg-unter-reihe-werte*) v "gitterrost") +) + +;; Reiner Kern: wirksamer Wert unter einer Reihe. wert = UNTER_REIHE des +;; Bereichs (nil/leer/unbekannt -> vorgabe), buehne-p = nil auf Hallenboden +;; (dort gibt es nichts abzusichern -> "keine"). +(defun lg-unter-reihe-wert (wert vorgabe buehne-p) + (cond + ((not buehne-p) "keine") + ((member wert *lg-unter-reihe-werte*) wert) + ((member vorgabe *lg-unter-reihe-werte*) vorgabe) + (T "gitterrost") + ) +) + ;; Randgang-Breiten eines Bereichs (Attribute) fuer lg-fuellung-berechnen. ;; Fehlt ein Attribut (Altbestand), gilt 0. (defun lg-bereich-rand (a) @@ -2011,11 +2039,21 @@ ) ;; ============================================================ -;; EXPORT (lager_raw.json) +;; EXPORT (_lager.json) ;; ============================================================ +;; Eine JSON-Datei mit den technischen Daten des Lagers fuer die +;; RFQ-Dokumentation (eigenes Projekt): alle Objekte mit Attributen und +;; voller Geometrie (WCS, mm) plus Kennwerte je Geschoss und fuer das ganze +;; Lager, jeder Geschoss-Kennwert mit Formel und Quelle (herkunft). +;; Schema und Formeln: doc/plan/lager_rfq.md. Die Kennwerte entstehen hier +;; in reinen Kernen (getestet) - fachliche Regeln leben in LISP. +;; +;; Datenform: Objekte als (OBJ (schluessel . wert) ...), Arrays als +;; (ARR wert ...); lg-j liest ein Feld, lg-j-setzen setzt es. ;; --- JSON-Schreiber (rein, getestet) --- -;; Werte: nil -> null, T -> true, String, Ganz-/Fliesszahl, (ROH "json"). +;; Werte: nil -> null, T -> true, FALSE -> false, String, Ganz-/Fliesszahl, +;; (ROH "json"), (OBJ ...) -> Objekt, (ARR ...) -> Array. (defun lg-json-zahl (w / s) (setq s (rtos w 2 3)) (if (vl-string-search "." s) @@ -2025,141 +2063,564 @@ (if (= s "-0") "0" s) ) +(defun lg-hex4 (n / h erg) + (setq h "0123456789abcdef" erg "") + (repeat 4 + (setq erg (strcat (substr h (1+ (rem n 16)) 1) erg) n (/ n 16))) + erg +) + +;; String als JSON-Text. Zeichen ausserhalb ASCII (Umlaute in einer +;; Bezeichnung) als \uXXXX, damit die Datei unabhaengig von der Codepage +;; gueltiges UTF-8 bleibt. +(defun lg-json-str (s / roh i c erg) + (setq roh (ssg-json-escape s) erg "" i 1) + (while (<= i (strlen roh)) + (setq c (substr roh i 1)) + (setq erg (strcat erg (if (> (ascii c) 127) (strcat "\\u" (lg-hex4 (ascii c))) c)) + i (1+ i))) + (strcat "\"" erg "\"") +) + (defun lg-json-wert (v) (cond ((null v) "null") ((eq v T) "true") - ((= (type v) 'STR) (strcat "\"" (ssg-json-escape v) "\"")) + ((eq v 'FALSE) "false") + ((= (type v) 'STR) (lg-json-str 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)) "\"")) + ((and (listp v) (eq (car v) 'OBJ)) (lg-json-obj (cdr v))) + ((and (listp v) (eq (car v) 'ARR)) + (strcat "[" (ssg-join (mapcar 'lg-json-wert (cdr v)) ", ") "]")) + (T (lg-json-str (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) + (ssg-join (mapcar '(lambda (p) (strcat (lg-json-str (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-container-p (v) + (and v (listp v) (member (car v) '(OBJ ARR))) ) -(defun lg-json-zahlen (zahlen) - (list 'ROH (strcat "[" (ssg-join (mapcar 'lg-json-wert zahlen) ", ") "]")) +;; Anzahl Blattwerte eines Werts (Mass fuer "passt in eine Zeile"). +(defun lg-json-blaetter (v) + (cond + ((not (lg-json-container-p v)) 1) + ((eq (car v) 'OBJ) (apply '+ (cons 0 (mapcar '(lambda (p) (lg-json-blaetter (cdr p))) (cdr v))))) + (T (apply '+ (cons 0 (mapcar 'lg-json-blaetter (cdr v))))) + ) ) -(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)) - ", ") - "]")) +;; Zahl, null oder Array nur daraus (Punkte, Punktlisten, bbox). +(defun lg-json-zahlen-p (v) + (cond + ((or (null v) (numberp v)) T) + ((and (listp v) (eq (car v) 'ARR)) (vl-every 'lg-json-zahlen-p (cdr v))) + (T nil) + ) +) + +;; Eingerueckter JSON-Text. Zahlenreihen (Punkte, Konturen) und kleine +;; Container (bis *lg-json-inline-max* Blattwerte) bleiben einzeilig, alles +;; andere bekommt je Eintrag eine Zeile. +(setq *lg-json-inline-max* 12) + +(defun lg-json-text (v einr / innen zeilen obj-p) + (if (or (not (lg-json-container-p v)) + (lg-json-zahlen-p v) + (<= (lg-json-blaetter v) *lg-json-inline-max*)) + (lg-json-wert v) + (progn + (setq innen (strcat einr " ") obj-p (eq (car v) 'OBJ)) + (setq zeilen + (if obj-p + (mapcar '(lambda (p) (strcat innen (lg-json-str (car p)) ": " (lg-json-text (cdr p) innen))) (cdr v)) + (mapcar '(lambda (x) (strcat innen (lg-json-text x innen))) (cdr v)))) + (if zeilen + (strcat (if obj-p "{" "[") "\n" (ssg-join zeilen ",\n") "\n" einr (if obj-p "}" "]")) + (if obj-p "{}" "[]"))) + ) +) + +;; --- Zugriff auf OBJ-Records --- +(defun lg-j (obj key) (cdr (assoc key (cdr obj)))) + +(defun lg-j-setzen (obj key wert / alt) + (if (setq alt (assoc key (cdr obj))) + (cons 'OBJ (subst (cons key wert) alt (cdr obj))) + (cons 'OBJ (append (cdr obj) (list (cons key wert))))) +) + +(defun lg-j-pkte (pts) (cons 'ARR (mapcar '(lambda (p) (cons 'ARR p)) pts))) +(defun lg-j-bbox (bb) (if bb (cons 'ARR (append (car bb) (cadr bb))))) +(defun lg-n (v) (if (numberp v) (float v) 0.0)) + +(defun lg-j-ebenen (s) + (cons 'ARR + (mapcar '(lambda (e) (list 'OBJ (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))) +;; Buehne = Belag vorhanden und kein Hallenboden. Rein. +(defun lg-buehne-p (belag) + (and (= (type belag) 'STR) (/= belag "") (/= (strcase belag T) "hallenboden")) ) -(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)))) +;; --- Reine Kerne der Kennwerte (getestet) --- + +;; Regallaenge je Haengeebene. reihen = Reihen-Records mit laenge_mm und +;; haengeebenen; jede Reihe traegt ihre Laenge in jede ihrer Ebenen ein. +;; Rueckgabe (ARR (OBJ schienenhoehe_mm warenlaenge_mm reihen regallaenge_mm) +;; ...), nach Schienenhoehe absteigend. +(defun lg-haengeebenen-summen (reihen / erg key alt l) + (setq erg nil) + (foreach r reihen + (setq l (lg-n (lg-j r "laenge_mm"))) + (foreach e (cdr (lg-j r "haengeebenen")) + (setq key (list (lg-n (lg-j e "schienenhoehe_mm")) (lg-n (lg-j e "warenlaenge_mm"))) + alt (assoc key erg)) + (setq erg (if alt + (subst (list key (1+ (cadr alt)) (+ (caddr alt) l)) alt erg) + (cons (list key 1 l) erg))))) + (cons 'ARR + (mapcar '(lambda (x) (list 'OBJ (cons "schienenhoehe_mm" (car (car x))) + (cons "warenlaenge_mm" (cadr (car x))) + (cons "reihen" (cadr x)) + (cons "regallaenge_mm" (caddr x)))) + (vl-sort erg '(lambda (a b) (if (equal (car (car a)) (car (car b)) 1e-6) + (> (cadr (car a)) (cadr (car b))) + (> (car (car a)) (car (car b)))))))) ) -(defun lg-export-linie (e art / a ed) - (setq a (ssg-attrib-read e) ed (entget e)) - (lg-json-obj +;; Gaenge je ART: (OBJ ("gang" . (OBJ anzahl laenge_mm flaeche_mm2)) ...), +;; gang/quergang/randgang immer vorhanden, weitere Arten dahinter. +(defun lg-gaenge-je-art (gaenge / erg art x l f) + (setq erg (mapcar '(lambda (a) (list a 0 0.0 0.0)) '("gang" "quergang" "randgang"))) + (foreach g gaenge + (setq art (cond ((lg-j g "art")) ("unbekannt")) + x (assoc art erg) + l (lg-n (lg-j g "laenge_mm")) + f (* l (lg-n (lg-j g "breite_mm")))) + (setq erg (if x + (subst (list art (1+ (cadr x)) (+ (caddr x) l) (+ (cadddr x) f)) x erg) + (append erg (list (list art 1 l f)))))) + (cons 'OBJ + (mapcar '(lambda (x) (cons (car x) (list 'OBJ (cons "anzahl" (cadr x)) + (cons "laenge_mm" (caddr x)) + (cons "flaeche_mm2" (cadddr x))))) + erg)) +) + +;; Die vier Kanten eines Rechtecks aus lg-linie-polygon, in fester +;; Reihenfolge: 1-2 laengs, 2-3 stirn, 3-4 laengs, 4-1 stirn. +(defun lg-rechteck-kanten (ecken) + (list (list (nth 0 ecken) (nth 1 ecken)) (list (nth 1 ecken) (nth 2 ecken)) + (list (nth 2 ecken) (nth 3 ecken)) (list (nth 3 ecken) (nth 0 ecken))) +) + +(defun lg-bbox-beruehrt-p (a b tol) + (and a b + (<= (car (car b)) (+ (car (cadr a)) tol)) (<= (car (car a)) (+ (car (cadr b)) tol)) + (<= (cadr (car b)) (+ (cadr (cadr a)) tol)) (<= (cadr (car a)) (+ (cadr (cadr b)) tol))) +) + +;; Offener Teil der Kante (p q): Laenge minus die Abschnitte, auf denen eine +;; Kante eines Nachbar-Rechtecks kollinear (Abstand < tol) aufliegt. +(defun lg-kante-offen (kante nachbarn tol / p q len ux uy zu c d dx dy dl t1 t2 lo hi) + (setq p (car kante) q (cadr kante) zu nil + len (distance (list (car p) (cadr p)) (list (car q) (cadr q)))) + (if (< len 1e-9) + 0.0 + (progn + (setq ux (/ (- (car q) (car p)) len) uy (/ (- (cadr q) (cadr p)) len)) + (foreach f nachbarn + (foreach k (lg-rechteck-kanten f) + (setq c (car k) d (cadr k) + dx (- (car d) (car c)) dy (- (cadr d) (cadr c)) + dl (sqrt (+ (* dx dx) (* dy dy)))) + (if (and (> dl 1e-9) + (< (abs (- (* ux dy) (* uy dx))) (* 1e-4 dl)) ; parallel + (< (abs (- (* ux (- (cadr c) (cadr p))) (* uy (- (car c) (car p))))) tol)) ; gleiche Linie + (progn + (setq t1 (+ (* ux (- (car c) (car p))) (* uy (- (cadr c) (cadr p)))) + t2 (+ (* ux (- (car d) (car p))) (* uy (- (cadr d) (cadr p)))) + lo (max 0.0 (min t1 t2)) + hi (min len (max t1 t2))) + (if (> (- hi lo) tol) (setq zu (cons (list lo hi) zu))))))) + (apply '+ (cons 0.0 (mapcar '(lambda (i) (- (cadr i) (car i))) + (ssg-intervalle-minus (list (list 0.0 len)) zu)))))) +) + +;; Offene Kantenlaenge je Reihe. ecken-liste = je Reihe die 4 Ecken (2D) +;; aus lg-linie-polygon. Eine Kante ist dort zu, wo eine Kante einer ANDEREN +;; Reihe kollinear darauf liegt (z.B. Ruecken an Ruecken). Rueckgabe je +;; Reihe (offen-laengs offen-stirn) in mm. +(defun lg-reihen-offene-kanten (ecken-liste tol / bbs erg i j k bb nachbarn l s o) + (setq bbs (mapcar 'ssg-poly-bbox ecken-liste) erg nil i 0) + (foreach e ecken-liste + (setq bb (nth i bbs) nachbarn nil j 0 l 0.0 s 0.0 k 0) + (foreach f ecken-liste + (if (and (/= i j) (lg-bbox-beruehrt-p bb (nth j bbs) tol)) + (setq nachbarn (cons f nachbarn))) + (setq j (1+ j))) + (foreach kante (lg-rechteck-kanten e) + (setq o (lg-kante-offen kante nachbarn tol)) + (if (= 0 (rem k 2)) (setq l (+ l o)) (setq s (+ s o))) + (setq k (1+ k))) + (setq erg (cons (list l s) erg) i (1+ i))) + (reverse erg) +) + +;; Gitterkorb der Reihen EINES Geschosses vervollstaendigen: Reihen mit +;; gitterkorb (OBJ hoehe_mm) bekommen Boden (Laenge x Tiefe), offene Kanten +;; (gegen alle Reihen des Geschosses) und Seitenflaeche (offen x Hoehe). +(defun lg-reihen-gitterkorb-berechnen (reihen tol / offen) + (if (vl-some '(lambda (r) (lg-j r "gitterkorb")) reihen) + (progn + (setq offen (lg-reihen-offene-kanten + (mapcar '(lambda (r) (mapcar 'cdr (cdr (lg-j r "ecken")))) reihen) tol)) + (mapcar '(lambda (r o / gk h l b) + (if (setq gk (lg-j r "gitterkorb")) + (progn + (setq h (lg-n (lg-j gk "hoehe_mm")) + l (lg-n (lg-j r "laenge_mm")) + b (lg-n (lg-j r "tiefe_mm"))) + (lg-j-setzen r "gitterkorb" + (list 'OBJ (cons "hoehe_mm" (lg-j gk "hoehe_mm")) + (cons "bodenflaeche_mm2" (* l b)) + (cons "offen_laengs_mm" (car o)) + (cons "offen_stirn_mm" (cadr o)) + (cons "seitenflaeche_mm2" (* h (+ (car o) (cadr o))))))) + r)) + reihen offen)) + reihen) +) + +(defun lg-herkunft (key formel quelle) + (cons key (list 'OBJ (cons "formel" formel) (cons "quelle" quelle))) +) + +;; Kennwerte eines Geschosses. g = Geschoss-Record, nx = Record des +;; naechsthoeheren Geschosses (nil = oberstes), reihen/gaenge = Records +;; dieses Geschosses (Reihen mit unter_reihe/gitterkorb), n-bereiche = +;; Anzahl Bereiche. Rueckgabe (kennwerte herkunft), beide OBJ. +(defun lg-kennwerte-geschoss (g nx reihen gaenge n-bereiche / nrs q qr buehne hk lichte regal grund + schienen gitter-r korb gk l f belag-g gitter-g belag gitter) + (setq nrs (vl-princ-to-string (lg-j g "nr")) + q (strcat "LG_GESCHOSS " nrs) + qr (strcat (itoa (length reihen)) " Regalreihe(n) LG_REIHE_* Geschoss " nrs) + buehne (lg-buehne-p (lg-j g "belag_art")) + hk nil) + ;; lichte Hoehe: Vorgabe am Geschoss, sonst aus dem Geschoss darueber + (cond + ((numberp (lg-j g "lichte_hoehe_mm")) + (setq lichte (lg-j g "lichte_hoehe_mm") + hk (cons (lg-herkunft "lichte_hoehe_mm" "LICHTE_HOEHE_mm (Vorgabe am Geschoss)" q) hk))) + (nx + (setq lichte (- (lg-n (lg-j nx "hoehe_belag_mm")) (lg-n (lg-j nx "konstr_hoehe_mm")) + (lg-n (lg-j g "hoehe_belag_mm"))) + hk (cons (lg-herkunft "lichte_hoehe_mm" + "HOEHE_BELAG_mm darueber - KONSTR_HOEHE_mm darueber - eigene HOEHE_BELAG_mm" + (strcat q " / " (vl-princ-to-string (lg-j nx "nr")))) + hk))) + (T (setq lichte nil)) + ) + ;; Regalreihen + (setq regal 0.0 grund 0.0 schienen 0.0 gitter-r 0.0 korb (list 0 0.0 0.0 0.0 0.0)) + (foreach r reihen + (setq l (lg-n (lg-j r "laenge_mm")) + f (* l (lg-n (lg-j r "tiefe_mm")))) + (setq regal (+ regal l) grund (+ grund f) + schienen (+ schienen (* l (if (numberp (lg-j r "schienen")) (lg-j r "schienen") 1)))) + (cond + ((equal (lg-j r "unter_reihe") "gitterrost") (setq gitter-r (+ gitter-r f))) + ((setq gk (lg-j r "gitterkorb")) + (setq korb (list (1+ (nth 0 korb)) + (+ (nth 1 korb) (lg-n (lg-j gk "bodenflaeche_mm2"))) + (+ (nth 2 korb) (lg-n (lg-j gk "seitenflaeche_mm2"))) + (+ (nth 3 korb) (lg-n (lg-j gk "offen_laengs_mm"))) + (+ (nth 4 korb) (lg-n (lg-j gk "offen_stirn_mm"))))))) + ) + (setq hk (append (reverse + (list (lg-herkunft "regallaenge_mm" "Summe LAENGE der Regalreihen" qr) + (lg-herkunft "regalgrundflaeche_mm2" "Summe LAENGE x TIEFE_mm der Regalreihen" qr) + (lg-herkunft "schienenlaenge_mm" "Summe LAENGE x SCHIENEN der Regalreihen" qr) + (lg-herkunft "haengeebenen" "je Schienenhoehe/Warenlaenge: Summe LAENGE aller Reihen mit dieser Haengeebene" qr) + (lg-herkunft "gaenge" "je ART: Anzahl, Summe LAENGE, Summe LAENGE x BREITE_mm" + (strcat (itoa (length gaenge)) " Flaeche(n) LG_GANG_* Geschoss " nrs)))) + hk)) + ;; Belag und Gitterrost (nur Buehne) + (setq belag-g 0.0 gitter-g 0.0) + (foreach x gaenge + (setq f (* (lg-n (lg-j x "laenge_mm")) (lg-n (lg-j x "breite_mm")))) + (if (equal (lg-j x "belag") "gitterrost") + (setq gitter-g (+ gitter-g f)) + (setq belag-g (+ belag-g f)))) + (if buehne + (progn + (if gaenge + (setq belag belag-g + hk (cons (lg-herkunft "belagsflaeche_mm2" + "Summe LAENGE x BREITE_mm der Gaenge mit BELAG ungleich gitterrost" + (strcat "LG_GANG_* Geschoss " nrs)) hk)) + (setq belag (- (lg-n (lg-j (lg-j g "kontur") "flaeche_mm2")) grund) + hk (cons (lg-herkunft "belagsflaeche_mm2" + "Konturflaeche - Grundflaeche der Regalreihen (keine LG_GANG-Flaechen)" + (strcat "Belagskontur Geschoss " nrs)) hk))) + (setq gitter (+ gitter-g gitter-r) + hk (cons (lg-herkunft "gitterrostflaeche_mm2" + (strcat "Summe LAENGE x BREITE_mm der Gaenge mit BELAG gitterrost" + " + LAENGE x TIEFE_mm der Reihen mit UNTER_REIHE gitterrost") + (strcat "LG_GANG_*/LG_REIHE_* Geschoss " nrs)) hk)) + (setq hk (cons (lg-herkunft "gitterkorb" + (strcat "Reihen mit UNTER_REIHE gitterkorb: Boden = LAENGE x TIEFE_mm," + " Seiten = offene Kantenlaenge x GITTERKORB_HOEHE_mm" + " (Kanten an einer direkt anliegenden Nachbarreihe zaehlen nicht)") + qr) hk))) + (setq belag nil gitter nil + hk (cons (lg-herkunft "buehne" "BELAG_ART hallenboden (oder leer): keine Buehne, kein Belag/Gitterrost/Gitterkorb" q) hk)) + ) + (list + (list 'OBJ + (cons "lichte_hoehe_mm" lichte) + (cons "buehne" (if buehne T 'FALSE)) + (cons "bereiche" n-bereiche) + (cons "reihen" (length reihen)) + (cons "regallaenge_mm" regal) + (cons "regalgrundflaeche_mm2" grund) + (cons "schienenlaenge_mm" schienen) + (cons "haengeebenen" (lg-haengeebenen-summen reihen)) + (cons "gaenge" (lg-gaenge-je-art gaenge)) + (cons "belagsflaeche_mm2" belag) + (cons "gitterrostflaeche_mm2" gitter) + (cons "gitterkorb" (list 'OBJ (cons "reihen" (nth 0 korb)) + (cons "bodenflaeche_mm2" (nth 1 korb)) + (cons "seitenflaeche_mm2" (nth 2 korb)) + (cons "offen_laengs_mm" (nth 3 korb)) + (cons "offen_stirn_mm" (nth 4 korb))))) + (cons 'OBJ (reverse hk))) +) + +(defun lg-summe-feld (objs key) + (apply '+ (cons 0.0 (mapcar '(lambda (o) (lg-n (lg-j o key))) objs))) +) + +;; Kennwerte des ganzen Lagers. geschosse = Geschoss-Records mit kennwerte +;; und kontur, reihen/gaenge = alle einem Geschoss zugeordneten Records. +(defun lg-kennwerte-lager (geschosse reihen gaenge n-bereiche / kws korb bbs xmin ymin xmax ymax dx dy) + (setq kws (mapcar '(lambda (g) (lg-j g "kennwerte")) geschosse) + korb (mapcar '(lambda (k) (lg-j k "gitterkorb")) kws) + bbs (vl-remove nil (mapcar '(lambda (g) (cdr (lg-j (lg-j g "kontur") "bbox"))) geschosse))) + (if bbs + (setq xmin (apply 'min (mapcar 'car bbs)) ymin (apply 'min (mapcar 'cadr bbs)) + xmax (apply 'max (mapcar 'caddr bbs)) ymax (apply 'max (mapcar 'cadddr bbs)) + dx (- xmax xmin) dy (- ymax ymin))) + (list 'OBJ + (cons "geschosse" (length geschosse)) + (cons "bereiche" n-bereiche) + (cons "reihen" (length reihen)) + (cons "regallaenge_mm" (lg-summe-feld kws "regallaenge_mm")) + (cons "regalgrundflaeche_mm2" (lg-summe-feld kws "regalgrundflaeche_mm2")) + (cons "schienenlaenge_mm" (lg-summe-feld kws "schienenlaenge_mm")) + (cons "haengeebenen" (lg-haengeebenen-summen reihen)) + (cons "gaenge" (lg-gaenge-je-art gaenge)) + (cons "belagsflaeche_mm2" (lg-summe-feld kws "belagsflaeche_mm2")) + (cons "gitterrostflaeche_mm2" (lg-summe-feld kws "gitterrostflaeche_mm2")) + (cons "gitterkorb" (list 'OBJ (cons "reihen" (fix (lg-summe-feld korb "reihen"))) + (cons "bodenflaeche_mm2" (lg-summe-feld korb "bodenflaeche_mm2")) + (cons "seitenflaeche_mm2" (lg-summe-feld korb "seitenflaeche_mm2")) + (cons "offen_laengs_mm" (lg-summe-feld korb "offen_laengs_mm")) + (cons "offen_stirn_mm" (lg-summe-feld korb "offen_stirn_mm")))) + (cons "huelle" (if bbs + (list 'OBJ (cons "bbox" (list 'ARR xmin ymin xmax ymax)) + (cons "laenge_mm" (max dx dy)) + (cons "breite_mm" (min dx dy)))))) +) + +;; --- Records aus der Zeichnung --- + +(defun lg-ent-layer (e) (cdr (assoc 8 (entget e)))) + +(defun lg-export-kontur (k / pts) + (if (and k (setq pts (ssg-lwpoly-punkte k))) + (list 'OBJ + (cons "handle" (lg-handle k)) + (cons "z" (cond ((caddr (car pts))) (0.0))) + (cons "punkte" (lg-j-pkte (mapcar '(lambda (p) (list (car p) (cadr p))) pts))) + (cons "flaeche_mm2" (ssg-poly-flaeche pts)) + (cons "umfang_mm" (ssg-poly-umfang pts)) + (cons "bbox" (lg-j-bbox (ssg-poly-bbox pts))))) +) + +(defun lg-export-geschoss-rec (g / a) + (setq a (caddr g)) + (list 'OBJ + (cons "nr" (cadr g)) + (cons "bezeichnung" (cdr (assoc "BEZEICHNUNG" a))) + (cons "id" (cdr (assoc "ID" a))) + (cons "handle" (lg-handle (car g))) + (cons "layer" (lg-ent-layer (car g))) + (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" (lg-export-kontur (lg-ent-aus-handle (lg-xdata-wert (car g) "kontur")))))) + +(defun lg-export-bereich-rec (e / a kinder) + (setq a (ssg-attrib-read e) kinder (lg-bereich-kinder e)) + (list 'OBJ + (cons "nr" (lg-a-int a "NR")) + (cons "id" (cdr (assoc "ID" a))) + (cons "handle" (lg-handle e)) + (cons "layer" (lg-ent-layer e)) + (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-j-ebenen (cdr (assoc "HAENGEEBENEN" a)))) + (cons "belag" (cdr (assoc "BELAG" a))) + (cons "unter_reihe" (lg-unter-reihe-wert (cdr (assoc "UNTER_REIHE" a)) (lg-unter-reihe-vorgabe) T)) + (cons "gitterkorb_hoehe_mm" (cond ((lg-a-zahl a "GITTERKORB_HOEHE_mm")) + ((lg-zahl (lg-vorgabe '("bereich" "gitterkorb_hoehe_mm") nil))))) + (cons "randgang_mm" (cons '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 "anzahl_reihen" (length (vl-remove-if-not '(lambda (k) (wcmatch (strcase (lg-blockname k)) "LG_REIHE_*")) kinder))) + (cons "anzahl_gaenge" (length (vl-remove-if-not '(lambda (k) (wcmatch (strcase (lg-blockname k)) "LG_GANG_*")) kinder))) + (cons "kontur" (lg-export-kontur (lg-ent-aus-handle (lg-xdata-wert e "kontur")))))) + +;; Reihe oder Gang: Attribute plus Geometrie (Start/Ende/Mitte in WCS mit Z, +;; Ecken des Grundrisses 2D aus lg-linie-polygon). +(defun lg-export-linie-rec (e art / a ed p1 z rot l b p2 quelle) + (setq a (ssg-attrib-read e) ed (entget e) + p1 (cdr (assoc 10 ed)) z (cond ((caddr p1)) (0.0)) + rot (cond ((cdr (assoc 50 ed))) (0.0)) + l (lg-a-zahl a "LAENGE") + b (lg-a-zahl a (if (= art "reihe") "TIEFE_mm" "BREITE_mm")) + p2 (polar (list (car p1) (cadr p1)) rot (lg-n l)) + quelle (cdr (assoc "QUELLE" a))) + (if (member quelle '(nil "")) (setq quelle "auto")) + (cons 'OBJ (append - (list (cons "geschoss" (lg-a-int a "GESCHOSS")) - (cons "laenge_mm" (lg-a-zahl a "LAENGE"))) + (list (cons "id" (cdr (assoc "ID" a))) + (cons "handle" (lg-handle e)) + (cons "layer" (lg-ent-layer e)) + (cons "geschoss" (lg-a-int a "GESCHOSS")) + (cons "bereich" (lg-a-int a "BEREICH")) + (cons "bereich_handle" (lg-xdata-wert e "bereich")) + (cons "quelle" quelle) + (cons "laenge_mm" l)) (if (= art "reihe") - (list (cons "tiefe_mm" (lg-a-zahl a "TIEFE_mm")) - (cons "haengeebenen" (lg-json-ebenen (cdr (assoc "HAENGEEBENEN" a)))) + (list (cons "tiefe_mm" b) (cons "schienen" (lg-a-int a "SCHIENEN")) - (cons "quelle" (cdr (assoc "QUELLE" a)))) - (list (cons "breite_mm" (lg-a-zahl a "BREITE_mm")) + (cons "haengeebenen" (lg-j-ebenen (cdr (assoc "HAENGEEBENEN" a))))) + (list (cons "breite_mm" b) (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))))) + (cons "belag" (cdr (assoc "BELAG" a))))) + (list (cons "start" (list 'ARR (car p1) (cadr p1) z)) + (cons "ende" (list 'ARR (car p2) (cadr p2) z)) + (cons "mitte" (list 'ARR (/ (+ (car p1) (car p2)) 2.0) (/ (+ (cadr p1) (cadr p2)) 2.0) z)) + (cons "winkel_grad" (* (/ rot pi) 180.0)) + (cons "ecken" (lg-j-pkte (lg-linie-polygon e))) + (cons "flaeche_mm2" (* (lg-n l) (lg-n b)))))) ) -;; Alle Daten als JSON-Text. -(defun lg-export-json ( / gs warn) - (setq gs (lg-geschosse) warn nil) +;; unter_reihe je Reihe aus ihrem Bereich (sonst Vorgabe), auf Hallenboden +;; "keine"; Reihen mit Gitterkorb bekommen (OBJ hoehe_mm) als Startwert. +(defun lg-reihen-unter-setzen (reihen bereiche geschosse vorgabe / br gr u) + (mapcar + '(lambda (r / br gr u) + (setq br (if (lg-j r "bereich_handle") + (vl-some '(lambda (b) (if (equal (lg-j b "handle") (lg-j r "bereich_handle")) b)) bereiche)) + gr (vl-some '(lambda (g) (if (equal (lg-j g "nr") (lg-j r "geschoss")) g)) geschosse) + u (lg-unter-reihe-wert (lg-j br "unter_reihe") vorgabe (lg-buehne-p (lg-j gr "belag_art")))) + (lg-j-setzen + (lg-j-setzen r "unter_reihe" u) + "gitterkorb" + (if (= u "gitterkorb") + (list 'OBJ (cons "hoehe_mm" (cond ((lg-j br "gitterkorb_hoehe_mm")) + ((lg-zahl (lg-vorgabe '("bereich" "gitterkorb_hoehe_mm") nil))))))))) + reihen) +) + +;; Alle Daten des Lagers als OBJ (Grundlage von lg-export-json). +(defun lg-export-daten ( / warn gs bereiche reihen gaenge g-recs vorgabe g-neu r-neu r-gs g-gs i g nx nr nrs kw) + (setq gs (lg-geschosse) warn nil vorgabe (lg-unter-reihe-vorgabe)) (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" - " \"warnungen\": " (lg-json-arr (mapcar 'lg-json-wert warn) " ") "\n" - "}\n") + (setq bereiche (mapcar 'lg-export-bereich-rec (lg-alle-bloecke "LG_BEREICH")) + reihen (mapcar '(lambda (e) (lg-export-linie-rec e "reihe")) (lg-alle-bloecke "LG_REIHE_*")) + gaenge (mapcar '(lambda (e) (lg-export-linie-rec e "gang")) (lg-alle-bloecke "LG_GANG_*")) + ;; Geschosse von unten nach oben (Belagshoehe, dann Nr) + g-recs (vl-sort (mapcar 'lg-export-geschoss-rec gs) + '(lambda (a b / ha hb) + (setq ha (lg-n (lg-j a "hoehe_belag_mm")) hb (lg-n (lg-j b "hoehe_belag_mm"))) + (if (equal ha hb 1e-6) (< (lg-j a "nr") (lg-j b "nr")) (< ha hb))))) + (setq reihen (lg-reihen-unter-setzen reihen bereiche g-recs vorgabe)) + (setq g-neu nil r-neu nil r-gs nil g-gs nil i 0) + (foreach g g-recs + (setq nr (lg-j g "nr") + nx (nth (1+ i) g-recs) + r-gs (lg-reihen-gitterkorb-berechnen + (vl-remove-if-not '(lambda (r) (equal (lg-j r "geschoss") nr)) reihen) 1.0) + g-gs (vl-remove-if-not '(lambda (x) (equal (lg-j x "geschoss") nr)) gaenge) + kw (lg-kennwerte-geschoss g nx r-gs g-gs + (length (vl-remove-if-not '(lambda (b) (equal (lg-j b "geschoss") nr)) bereiche)))) + (if (null (lg-j (car kw) "lichte_hoehe_mm")) + (setq warn (cons (ssg-textf "lg-exp-lichte-hoehe-oben" (list (itoa nr))) warn))) + (if (not (lg-buehne-p (lg-j g "belag_art"))) + (foreach b bereiche + (if (and (equal (lg-j b "geschoss") nr) (= (lg-j b "unter_reihe") "gitterkorb")) + (setq warn (cons (ssg-textf "lg-exp-korb-hallenboden" + (list (vl-princ-to-string (lg-j b "nr")) (itoa nr))) warn))))) + (setq g-neu (cons (lg-j-setzen (lg-j-setzen g "kennwerte" (car kw)) "herkunft" (cadr kw)) g-neu) + r-neu (append r-neu r-gs) + i (1+ i))) + (setq g-neu (reverse g-neu) + nrs (mapcar '(lambda (g) (lg-j g "nr")) g-recs) + r-gs (vl-remove-if '(lambda (r) (member (lg-j r "geschoss") nrs)) reihen)) + (if r-gs + (setq warn (cons (ssg-textf "lg-exp-reihen-ohne-geschoss" (list (itoa (length r-gs)))) warn))) + (list 'OBJ + (cons "format" "ssg-lager") + (cons "version" 2) + (cons "zeichnung" (strcat (getvar "DWGPREFIX") (getvar "DWGNAME"))) + (cons "erzeugt" (ssg-timestamp)) + (cons "land" (lg-land)) + (cons "einheiten" (list 'OBJ (cons "laenge" "mm") (cons "flaeche" "mm2") (cons "winkel" "grad"))) + (cons "koordinaten" "WCS, Nord = +Y") + (cons "lager" (lg-kennwerte-lager g-neu r-neu + (vl-remove-if-not '(lambda (x) (member (lg-j x "geschoss") nrs)) gaenge) + (length bereiche))) + (cons "geschosse" (cons 'ARR g-neu)) + (cons "bereiche" (cons 'ARR bereiche)) + (cons "reihen" (cons 'ARR (append r-neu r-gs))) + (cons "gaenge" (cons 'ARR gaenge)) + (cons "warnungen" (cons 'ARR (reverse warn)))) +) + +;; Alle Daten als JSON-Text. +(defun lg-export-json ( / ) + (strcat (lg-json-text (lg-export-daten) "") "\n") ) (defun lg-ausgabe-verzeichnis ( / d) @@ -2180,49 +2641,11 @@ (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 "-")))) +;; Technische Daten des Lagers als _lager.json nach DXFM_RESULTS. +(defun c:LAGER_EXPORT ( / pfad) + (setq pfad (strcat (lg-ausgabe-verzeichnis) "/" (lg-dwg-basis) "_lager.json")) + (if (lg-export-schreiben pfad) + (princ (ssg-textf "lg-exp-json" (list pfad)))) (princ) ) diff --git a/Lisp/LagerPruefung.lsp b/Lisp/LagerPruefung.lsp index 63b1ebf..da654e3 100644 --- a/Lisp/LagerPruefung.lsp +++ b/Lisp/LagerPruefung.lsp @@ -1,7 +1,7 @@ ;; ============================================================ ;; LagerPruefung.lsp - Regelprofile der Lagerplanung (Phase 0) ;; -;; Laedt das Laenderprofil cfg/regelwerk/.json, loest die Vererbung +;; Laedt das Laenderprofil cfg/stahlbau/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 @@ -35,7 +35,7 @@ (setq cfg (getenv "DXFM_CFG")) (if cfg (strcat (vl-string-right-trim "/\\" (vl-string-translate "\\" "/" cfg)) - "/regelwerk/" (strcase land) ".json")) + "/stahlbau/regelwerk/" (strcase land) ".json")) ) ;; Zwei Profile mischen: Regeln von kind ersetzen gleichnamige (id) von diff --git a/Lisp/README.md b/Lisp/README.md index ec80196..8fcc64a 100644 --- a/Lisp/README.md +++ b/Lisp/README.md @@ -867,27 +867,26 @@ Das fertige Raster (Linien + Zaehl-Texte) wird zu einem einzigen Block --- -### `LagerInsert.lsp` / `LagerPruefung.lsp` – Lagerplanung (GOH-Buehnenlager -> RFQ-YAML) +### `LagerInsert.lsp` / `LagerPruefung.lsp` – Lagerplanung (GOH-Buehnenlager -> Lager-JSON) -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). +Parametrische Attributbloecke fuer ein mehrgeschossiges Haengewarenlager; +`LAGER_EXPORT` schreibt die technischen Daten als eine JSON-Datei fuer die +RFQ-Dokumentation (eigenes Projekt). Konzept, Datenmodell, JSON-Schema und +Kennwert-Formeln: [`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 und Gaengen 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_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) und Absturzsicherung unter den Reihen (`UNTER_REIHE` gitterrost/gitterkorb/keine, `GITTERKORB_HOEHE_mm`) | | `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` | Regalreihen `LG_REIHE_*` zaehlen (`lg-reihe-eintrag` + `lg-reihen-auswerten`): Anzahl, Laenge und Laenge x Haengeebenen je Geschoss 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 | +| `c:LAGER_EXPORT` | `_lager.json` in `DXFM_RESULTS`: alle Geschosse, Bereiche, Reihen und Gaenge mit Attributen und Geometrie, Kennwerte je Geschoss und gesamt (inkl. Gitterkorb mit offenen Kanten), Herkunft je Kennwert, Warnungen. Kein Python-Schritt | +| `c:LG_REGELN` | Regelprofil (`cfg/stahlbau/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, diff --git a/cfg/example_goh_storage.yaml b/cfg/example_goh_storage.yaml deleted file mode 100644 index 22b0b34..0000000 --- a/cfg/example_goh_storage.yaml +++ /dev/null @@ -1,230 +0,0 @@ -# ===================================================================================== -# 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/regelwerk/DE.json b/cfg/stahlbau/regelwerk/DE.json similarity index 100% rename from cfg/regelwerk/DE.json rename to cfg/stahlbau/regelwerk/DE.json diff --git a/cfg/regelwerk/PL.json b/cfg/stahlbau/regelwerk/PL.json similarity index 100% rename from cfg/regelwerk/PL.json rename to cfg/stahlbau/regelwerk/PL.json diff --git a/cfg/regelwerk/SK.json b/cfg/stahlbau/regelwerk/SK.json similarity index 100% rename from cfg/regelwerk/SK.json rename to cfg/stahlbau/regelwerk/SK.json diff --git a/data/json/lager.json b/data/json/lager.json index 31ced3a..ff67684 100644 --- a/data/json/lager.json +++ b/data/json/lager.json @@ -11,13 +11,6 @@ "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": { @@ -31,7 +24,13 @@ "min_ganglaenge_mm": 100, "richtung_grad": 0, "belag": "spanplatte", - "randgang_mm": {"n": 0, "o": 0, "s": 0, "w": 0} + "randgang_mm": {"n": 0, "o": 0, "s": 0, "w": 0}, + "unter_reihe": "gitterrost", + "gitterkorb_hoehe_mm": 500 + }, + + "unter_reihe": { + "beschreibung": "Absturzsicherung unter den Regalreihen auf Buehnen (Attribut UNTER_REIHE am Bereich, Vorgabe bereich.unter_reihe): gitterrost = Grundflaeche der Reihe (Laenge x Tiefe) zaehlt als Gitterrost; gitterkorb = Korb mit Boden (Laenge x Tiefe) und Seitenwaenden (offene Kantenlaenge x GITTERKORB_HOEHE_mm), Kanten an einer direkt anliegenden Nachbarreihe zaehlen nicht; keine = offen. Auf hallenboden wirkt der Wert nicht. gitterkorb_hoehe_mm ist ein Platzhalter, bis die Hoehe aus den Attributen berechnet wird." }, "belag_schraffur": { @@ -45,10 +44,5 @@ "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/dcl/lager.dcl b/dcl/lager.dcl index f21d286..bc2c2d1 100644 --- a/dcl/lager.dcl +++ b/dcl/lager.dcl @@ -63,6 +63,11 @@ lg_bereich : dialog { : edit_box { key = "ebenen"; label = "Haengeebenen (Schiene/Ware;...):"; edit_width = 24; } : popup_list { key = "belag"; label = "Belag der Gaenge:"; width = 30; } } + : boxed_column { + label = "Absturzsicherung unter den Reihen (nur auf Buehnen)"; + : popup_list { key = "unter"; label = "Unter den Reihen:"; width = 30; } + : edit_box { key = "korbhoehe"; label = "Gitterkorb-Hoehe (mm):"; edit_width = 10; } + } } ok_cancel; errtile; diff --git a/doc/plan/lager_rfq.md b/doc/plan/lager_rfq.md new file mode 100644 index 0000000..3a6d92b --- /dev/null +++ b/doc/plan/lager_rfq.md @@ -0,0 +1,274 @@ +# Lagerplanung in BricsCAD -> Lager-JSON (GOH-Buehnenlager) + +Schema und Schnittstelle der Lagerplanung. Code: `Lisp/LagerInsert.lsp`, +`Lisp/LagerPruefung.lsp`. Daten: `data/json/lager.json`, +`cfg/stahlbau/regelwerk/*.json`, `cfg/layer.cfg` Abschnitt `[lager]`, Dialoge `dcl/lager.dcl`. + +## Ziel und Datenfluss + +Das RFQ-Programm (eigenes Repo) erzeugt das Ausschreibungsdokument. Die Makros +liefern ihm die technischen Daten des Lagers aus der Zeichnung, als EINE +JSON-Datei. Mapping auf das RFQ-Schema, Abgleich mit Handwerten und Darstellung +sind Sache des RFQ-Projekts. + +``` +BricsCAD (LISP) RFQ-Repo +LG_GESCHOSS / LG_BEREICH / LG_REIHE / LG_GANG + --LAGER_EXPORT--> DXFM_RESULTS/_lager.json --> Dokument + ^ Regelprofil cfg/stahlbau/regelwerk/.json (LagerPruefung.lsp, noch nicht im Export) +``` + +Grundsatz: jede fachliche Regel und jeder Kennwert wird genau einmal +implementiert, in LISP (`LagerInsert.lsp` Abschnitt EXPORT, Regeln in +`LagerPruefung.lsp`). Es gibt keinen Python-Schritt mehr (die fruehere +Overlay-YAML `lib/lager_yaml_export.py` und `LAGER_HAND_YAML` sind entfernt). + +## JSON-Schnittstelle (`format` = `ssg-lager`, `version` = 2) + +Einheiten mm, mm2, Grad; Koordinaten WCS, Nord = +Y. Werte stehen +selbstbeschreibend drin (`"belag_art": "spanplatte"`, keine Index-Codes). Fehlende +Werte sind `null`. Nicht-ASCII-Zeichen in Texten werden als `\uXXXX` geschrieben. + +``` +{ + "format": "ssg-lager", "version": 2, "zeichnung", "erzeugt", "land", + "einheiten": {"laenge": "mm", "flaeche": "mm2", "winkel": "grad"}, + "koordinaten": "WCS, Nord = +Y", + "lager": { geschosse, bereiche, reihen, regallaenge_mm, regalgrundflaeche_mm2, + schienenlaenge_mm, haengeebenen[], gaenge{}, belagsflaeche_mm2, + gitterrostflaeche_mm2, gitterkorb{}, huelle{bbox, laenge_mm, breite_mm} }, + "geschosse": [ { nr, bezeichnung, id, handle, layer, hoehe_belag_mm, konstr_hoehe_mm, + lichte_hoehe_mm (Attribut), belag_art, personen, brandgefaehrdung, + kontur{handle, z, punkte[[x,y]...], flaeche_mm2, umfang_mm, bbox}, + kennwerte{...}, herkunft{: {formel, quelle}} } ], + "bereiche": [ { nr, id, handle, layer, geschoss, gangbreite_mm, regaltiefe_mm, + reihen_je_gang, schienen_je_gang, quergang_abstand_mm, quergang_breite_mm, + richtung_grad, haengeebenen[], belag, unter_reihe, gitterkorb_hoehe_mm, + randgang_mm{n,o,s,w}, restbreite_mm, anzahl_reihen, anzahl_gaenge, kontur{} } ], + "reihen": [ { id, handle, layer, geschoss, bereich, bereich_handle, quelle, laenge_mm, + tiefe_mm, schienen, haengeebenen[], start, ende, mitte, winkel_grad, + ecken[4], flaeche_mm2, unter_reihe, + gitterkorb{hoehe_mm, bodenflaeche_mm2, offen_laengs_mm, offen_stirn_mm, + seitenflaeche_mm2} | null } ], + "gaenge": [ { id, handle, layer, geschoss, bereich, bereich_handle, quelle, laenge_mm, + breite_mm, art, belag, start, ende, mitte, winkel_grad, ecken[4], flaeche_mm2 } ], + "warnungen": [ ... ] +} +``` + +`geschosse` ist nach Belagshoehe sortiert (von unten nach oben). `start`/`ende`/ +`mitte` sind 3D (Z der Einfuegung), `ecken` sind die vier Ecken des Grundrisses +(2D), Kante 1-2 und 3-4 laengs, 2-3 und 4-1 an den Stirnseiten. +`kennwerte` je Geschoss: `lichte_hoehe_mm`, `buehne` (true/false), `bereiche`, +`reihen`, `regallaenge_mm`, `regalgrundflaeche_mm2`, `schienenlaenge_mm`, +`haengeebenen` (je Schienenhoehe/Warenlaenge: `reihen`, `regallaenge_mm`), +`gaenge` (je Art gang/quergang/randgang: `anzahl`, `laenge_mm`, `flaeche_mm2`), +`belagsflaeche_mm2`, `gitterrostflaeche_mm2`, `gitterkorb` (Summen). Formeln: +Abschnitt "Kennwerte <- Zeichnung". + +Erzeugt von `LAGER_EXPORT` (Menue Lager -> Export Lager-JSON), Beispiel: +`tests/output/lager_tests_lager.json` nach `TEST_LAGER`. + +## Bloecke und Attribute + +Alle Bloecke tragen `ID`, `GESCHOSS`, `LAYER`. Masse in mm, Winkel in Grad, +Werte selbstbeschreibend (keine Index-Codes). Verknuepfungen als XDATA +(App `SSG_LG`, Paare `1000 schluessel` + `1005 handle`/`1000 text`). + +| Block | Geometrie | Attribute | XDATA | +|---|---|---|---| +| `LG_GESCHOSS` | Kopfsymbol unten links an der Belagskontur | `NR`, `BEZEICHNUNG`, `HOEHE_BELAG_mm`, `KONSTR_HOEHE_mm`, `LICHTE_HOEHE_mm` (leer = berechnet), `BELAG_ART` (`spanplatte`/`gitterrost`/`hallenboden`), `PERSONEN`, `BRANDGEFAEHRDUNG` | `kontur` -> Polylinie; Kontur: `besitzer`, `rolle` | +| `LG_BEREICH` | Symbol mit Pfeil in Reihenrichtung | `NR`, `GANGBREITE_mm`, `REGALTIEFE_mm`, `REIHEN_JE_GANG` (1/2), `SCHIENEN_JE_GANG`, `QUERGANG_ABSTAND_mm` (0 = keine), `QUERGANG_BREITE_mm`, `RICHTUNG`, `HAENGEEBENEN`, `BELAG`, `RANDGANG_N_mm`/`_O_`/`_S_`/`_W_` (0 = keiner), `UNTER_REIHE` (`gitterrost`/`gitterkorb`/`keine`, Absturzsicherung unter den Reihen, nur auf Buehnen), `GITTERKORB_HOEHE_mm`, `RESTBREITE_mm` (Ergebnis) | `kontur` -> Polylinie | +| `LG_REIHE__` | Rechteck L x T um die Schienenlinie, Einfuegepunkt = Reihenanfang | `LAENGE`, `TIEFE_mm`, `HAENGEEBENEN`, `SCHIENEN`, `BEREICH`, `QUELLE` (`auto`/`auto_geaendert`/`manuell`) | `bereich` -> LG_BEREICH (nur Auto-Fuellung) | +| `LG_GANG___` | Rechteck L x B um die Gangachse + Schraffur je Belag (`lager.json belag_schraffur`, Layer 0/VONBLOCK; `hallenboden` ohne) | `LAENGE`, `BREITE_mm`, `ART` (`gang`/`quergang`/`randgang`), `BELAG`, `BEREICH`, `QUELLE` (`auto`/`auto_geaendert`/`manuell`) | `bereich` (Auto-Fuellung und manuelle Gaenge im Bereich) | + +`HAENGEEBENEN` = `Schienenhoehe/Warenlaenge;...` ueber Belag, oberste zuerst, +z.B. `2300/1200;1100/1100`. Die Definitionen `LG_REIHE_*`/`LG_GANG_*` werden je +Geometrie wiederverwendet; die Werte stehen in den Attributen der Referenz. + +### Geschosse und Layer + +Layer je Geschoss: Basis aus `cfg/layer.cfg [lager]` + `_G` +(`LG_Reihe_G2`). `LG_GESCHOSS_AKTIV` friert die Layer der uebrigen Geschosse, +weil die 2D-Grundrisse uebereinander liegen. Neue Objekte bekommen das aktive +Geschoss; ohne aktives Geschoss entscheidet die Kontur, in der sie liegen. In +2D (alle Konturen auf Z = 0) ist eine Lage in mehreren Konturen nicht +eindeutig -> Rueckfrage bzw. Export-Warnung; in 3D entscheidet Z. + +Jede Kontur gehoert genau einem Block. Wird eine schon vergebene Kontur +gewaehlt (z.B. dieselbe Halle fuer mehrere Geschosse), wird sie kopiert. + +Der parametrische BricsCAD-Block `goh_rack` (frueher als Bestand mitgezaehlt) +wird nicht mehr unterstuetzt: Regale gibt es nur noch als `LG_REIHE_*`. + +### Geschoss kopieren (`LG_GESCHOSS_KOPIEREN`) + +Obergeschosse gleichen meist dem Geschoss darunter. Dialog `lg_geschoss_kopie`: + +- Quelle: Vorgabe ist das oberste Geschoss. Damit wird ein weiteres Geschoss + nachtraeglich angehaengt, z.B. G3 -> G4. +- Anzahl. +- Geschosshoehe je Kopie: Vorgabe ist der Abstand zum Geschoss darunter, sonst + lichte Hoehe + Konstruktion. +- Belag. +- Versatz X/Y je Kopie: 0 = deckungsgleich. In einer 3D-Zeichnung (Konturen auf + Belagshoehe) wandert die Kopie zusaetzlich in Z. + +Ein BricsCAD-KOPIEREN taugt nicht. Die XDATA-Verweise `kontur`/`besitzer`/`bereich` +zeigten dann weiter auf die Originale, und GESCHOSS, Layer und IDs blieben die alten. +Stattdessen entsteht jedes Objekt ueber die normalen Einfuegefunktionen neu: + +- Geschoss und Bereiche mit kopierter Kontur; die Bereiche ohne Fuellung + (`lg-bereich-block-anlegen`). +- Reihen und Gaenge 1:1 aus der Quelle (`lg-linie-einfuegen`). Es wird nicht neu + gefuellt, damit Handaenderungen (`auto_geaendert`, manuelle Reihen/Gaenge) erhalten + bleiben. +- Belaege, die dem Geschossbelag der Quelle entsprachen, folgen dem neuen Belag, + inklusive Schraffur. Bewusst abweichende bleiben. + +### Auto-Fuellung (`lg-fuellung-berechnen`) + +Die Kontur wird in ein lokales System gedreht (Reihen entlang +X, `RICHTUNG`; +Vorgabe = Richtung der laengsten Konturkante). Quer dazu wird ab Unterkante ein +Modul nach dem anderen gelegt, nur volle Module: + +- 2 Reihen je Gang: `Reihe | Gang | Reihe`, benachbarte Module Ruecken an Ruecken +- 1 Reihe je Gang: `Reihe | Gang` + +Ein Streifen ist dort belegbar, wo Unter-, Mittel- und Oberkante in der Kontur +liegen (konkave Konturen werden korrekt beschnitten). Quergaenge: Luecken der +Breite `QUERGANG_BREITE_mm` nach je `QUERGANG_ABSTAND_mm`, ueber alle Reihen +ausgerichtet; keine Luecke am Reihenende, wenn dahinter kein Mindeststueck mehr +passt. Hauptgaenge laufen durch. Der Rest quer zu den Reihen steht in +`RESTBREITE_mm`. Edit des Bereichs = Neuaufbau aller Kinder (Sektions-Neuaufbau, +kein Feld-Splice) - aber nur der automatisch erzeugten (`QUELLE=auto`). Von Hand +eingefuegte (`manuell`) und von Hand geaenderte (`auto_geaendert`) Reihen und Gaenge +bleiben stehen und werden ausgespart, ebenso unverknuepfte manuelle Objekte, deren +Mitte in der Kontur liegt; die Anzahl wird gemeldet. Von Hand geloeschte +automatische Objekte entstehen bei dieser ausdruecklichen Neuberechnung neu. + +**Randgaenge** (`RANDGANG_N/O/S/W_mm`, Nord = +Y Welt): jede Konturkante bekommt +den Randgang ihrer Himmelsrichtung (Aussennormale, auf N/O/S/W eingerastet, +45 Grad zaehlt zu N/S) als Rechteck von der Kante nach innen - auch bei L-Form +entlang der echten Kanten (dort bekommt z.B. auch die Stufenkante den +Nord-Randgang; Ausnahmen per `LG_GANG`). Ecken: N/S-Randgaenge besitzen sie; +an konvexen Ecken wird der O/W-Randgang gekuerzt, an einspringenden Ecken der +N/S-Randgang verlaengert (sonst bliebe ein freies Quadrat). Randgaenge an der +Unter-/Oberkante der Huelle verschieben den Modulstapel, an der linken/rechten +den Bezug der Quergaenge; Stufenkanten mitten im Stapel kappen nur die +betroffenen Reihen. Nicht rechtwinklige Ecken sind eine Naeherung. + +**Sperrflaechen**: Randgaenge sowie von Hand eingefuegte und geaenderte Reihen und +Gaenge werden aus Reihen, Haupt- und Quergaengen ausgeschnitten - exakt ueber den +Band-Clip des Streifens (`ssg-poly-band-x`), Reststuecke unter der +Mindestlaenge fallen weg. Eine von Hand eingefuegte Reihe (`LG_REIHE`) oder ein +Gang (`LG_GANG`), dessen Mitte in einem Bereich liegt, wird mit ihm verknuepft. + +**Von Hand Gesetztes loest KEINE Neuberechnung aus.** Beim Einfuegen und +Bearbeiten (`LG_REIHE`, `LG_GANG`, `LG_EDIT` einer Reihe/eines Gangs) schneidet das +Objekt nur seine Flaeche aus den ueberdeckten Objekten heraus +(`lg-sperre-anwenden`, Kern `lg-linie-zuschnitt`); die Reste behalten ihre Attribute. +Geschnitten wird nur, was rangniedriger ist (`lg-quelle-rang`: `auto` < +`auto_geaendert` < `manuell`) - ein manuelles Objekt schneidet aus automatischen und +geaenderten, ein geaendertes nur aus automatischen, manuelle untereinander nie. Alles +andere bleibt, wie der Anwender es hinterlassen hat: von Hand geloeschte Objekte +kommen nicht zurueck. Wird ein manueller Gang verkleinert oder geloescht, bleibt die +Luecke bis zur naechsten ausdruecklichen Neuberechnung (Doppelklick auf den +Bereich, OK). + +`LG_GANG` zeichnet einen Gang entweder als Linie (Gangmitte, Breite aus dem +Dialog) oder ueber die Option `Rechteck` als Flaeche. Beim Rechteck +ergeben sich Lage und Breite aus der Flaeche, man muss sie also vorher nicht +kennen: die laengere Seite ist die Gangrichtung, die kuerzere die Breite (auf +ganze mm). Die Rundung verschiebt nur die Laengsseite gegenueber der ersten +gezeichneten Ecke - die Kante, an der man ansetzt (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 der Gang an +der Querseite der ersten Ecke (`lg-gang-aus-ecken`). Gezeichnet wird mit dem echten BricsCAD-Befehl RECHTECK +(`lg-rechteck-zeichnen`) - mit allen seinen Optionen (Abmessungen, Flaeche, +Drehung, Objektfang); ein gedrehtes Rechteck ergibt einen schraeg liegenden Gang. +Die gezeichnete Polylinie wird ausgewertet (`lg-rechteck-ecken-aus-poly`) und +wieder geloescht; mit Fase oder Rundung gezeichnete Flaechen werden abgelehnt. +Nach jedem Rechteck startet RECHTECK erneut, Enter ohne Rechteck beendet. Im Block +entsteht derselbe `LG_GANG_*` wie bei der Linie. + +**Schraffur**: steckt in der Gang-Blockdefinition (Belag im Blocknamen), Muster +aus `lager.json belag_schraffur` - `spanplatte` ANSI31, `gitterrost` GRID, +`hallenboden` keine. Unbekannter Belag/Muster -> Meldung, Gang ohne Schraffur. +Reihen bleiben unschraffiert. Altbestand: fehlen einer vorhandenen +Blockdefinition ATTDEFs des Schemas, definiert `lg-blockdef` sie neu und +uebertraegt sie per `_.ATTSYNC` auf die Referenzen. + +## Kennwerte <- Zeichnung + +Reine Kerne in `LagerInsert.lsp` (getestet in `tu-tests-lager`). Jeder +Geschoss-Kennwert steht mit Formel und Quelle unter `herkunft`. + +| Kennwert | Quelle / Formel | +|---|---| +| `lichte_hoehe_mm` | `LICHTE_HOEHE_mm`, sonst Belag darueber - Konstruktionshoehe darueber - eigener Belag; oberstes Geschoss nur mit Vorgabe (sonst `null` + Warnung) | +| `buehne` | `BELAG_ART` gesetzt und nicht `hallenboden`. Ohne Buehne: kein Belag, kein Gitterrost, kein Gitterkorb | +| `regallaenge_mm`, `regalgrundflaeche_mm2`, `schienenlaenge_mm` | Summe `LAENGE`, `LAENGE x TIEFE_mm`, `LAENGE x SCHIENEN` der Reihen des Geschosses | +| `haengeebenen[]` | je (Schienenhoehe, Warenlaenge): Anzahl Reihen und Summe `LAENGE`, nach Schienenhoehe absteigend (`lg-haengeebenen-summen`) | +| `gaenge` | je `ART` (gang/quergang/randgang, auch manuelle): Anzahl, Summe `LAENGE`, Summe `LAENGE x BREITE_mm` (`lg-gaenge-je-art`) | +| `belagsflaeche_mm2` | Summe `LAENGE x BREITE_mm` der Gaenge mit Belag != gitterrost; ohne Gangflaechen: Konturflaeche - Reihengrundflaeche | +| `gitterrostflaeche_mm2` | Gaenge mit Belag gitterrost + Grundflaeche der Reihen mit `UNTER_REIHE = gitterrost` | +| `gitterkorb` (je Reihe und Summe) | Reihen mit `UNTER_REIHE = gitterkorb`: Boden = `LAENGE x TIEFE_mm`; Seiten = offene Kantenlaenge x `GITTERKORB_HOEHE_mm`, getrennt nach Laengs- und Stirnseiten. Offen ist eine Kante, soweit keine Kante einer ANDEREN Reihe desselben Geschosses kollinear darauf liegt (Abstand < 1 mm, z.B. Ruecken an Ruecken; `lg-reihen-offene-kanten`) | +| `lager.huelle` | achsparallele Huelle aller Geschosskonturen, `laenge_mm` = laengere Seite | +| `lager.*` sonst | Summen der Geschoss-Kennwerte; `haengeebenen`/`gaenge` ueber alle zugeordneten Reihen/Gaenge | + +`UNTER_REIHE` kommt aus dem verknuepften Bereich; Reihen ohne Bereich (und +Bereiche ohne Attribut, Altbestand) nehmen die Vorgabe `lager.json +bereich.unter_reihe`. `GITTERKORB_HOEHE_mm` ist vorerst ein Attribut am Bereich +und soll spaeter aus den Attributen berechnet werden. + +Spaetere Phasen: Gelaender/Bordbrett, Oeffnungen, Treppen, Leitern, Zugaenge +(Phase 2), Schnitte (Phase 3), Fluchtwege (Phase 4/5), Regelwerk-Werte je +Geschoss im Export. + +## Regelprofil + +`cfg/stahlbau/regelwerk/.json`, aufgeloest von `LagerPruefung.lsp`: + +```json +{"id": "treppe.schrittmass_min", "art": "min", "einheit": "mm", + "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}]} +``` + +- `art`: `min` (strengster = groesster), `max` (strengster = kleinster), `wert`. +- `bedingung`: ` ` (`=`, `<=`, `>=`, `<`, `>`) gegen einen + Kontext (z.B. `brandgefaehrdung`, `absturzhoehe`, `personen`). Fehlt der + Schluessel im Kontext, wird der Eintrag konservativ einbezogen. +- `tabelle` + `tabellen_schluessel`: Wert der ersten Zeile `[bis, wert]`, die den + Kontextwert abdeckt (Fluchtwegbreite nach Personen). +- `erbt_von`: Laenderprofil erbt alle Regeln, eigene Records mit gleicher `id` + ersetzen sie. `SK.json`/`PL.json` sind Gerueste (`gepflegt: false` -> Warnung). +- `LG_REGELN` zeigt alle Regeln mit Ergebnis und Protokoll (welche Quelle gewann). + +**Alle Startwerte stehen auf `verifiziert: false`.** Wert, Abschnitt und +Fassung muessen gegen die Originaltexte (ASR A1.8, A2.1, A2.3, DGUV Regel +108-007, EN ISO 14122-3/-4) geprueft werden; besonders unsicher sind die +DGUV-Gangbreiten und die Abschnittsnummern. + +## Stand + +| Phase | Inhalt | Stand | +|---|---|---| +| 0 | Regelprofil + Resolver, Schema (dieses Dokument), Layer, Sprach-Keys, Menue, Laden | erledigt, Regelwerte unverifiziert | +| 1 | Geschoss, aktives Geschoss, Bereich + Auto-Fuellung, Einzelreihe, Edit, `LAGER_EXPORT` als Lager-JSON mit Kennwerten und Herkunft, Gitterkorb/Gitterrost unter den Reihen | erledigt (2D) | +| 1a | Regelwerk-Werte (aufgeloest je Geschoss) im Lager-JSON; Gitterkorb-Hoehe aus den Attributen berechnen | offen | +| 2 | Gelaender/Bordbrett aus Kontur, Kanten-Ausnahmen, Oeffnungen, Treppe (Auslegung nach Profil), Leiter | offen | +| 3 | Schema-Schnitt, 3D-Varianten | offen | +| 4 | `LAGER_PRUEFEN`, Fluchtweg (gezeichnet), Verstoss-Marker, Nachweis mit Regelpruefung | offen | +| 5 | automatische Fluchtweganalyse | offen | +| 6 | SK/PL-Profile, Kapazitaet, Kopplung Geruest, Stuecklisten-Klassen | offen | + +## Tests + +- `tests/test_unit.lsp`: `tu-tests-poly` (Polygon-Helfer in `ssg_core.lsp`), + `tu-tests-lager` (Fuellung, Geschosszuordnung, JSON-Schreiber, Kennwerte, + offene Kanten/Gitterkorb, Feldpruefung, Regelprofil). +- `tests/test_lager.lsp` (`TEST_LAGER`, `alltests.json`): Ablauf in einer + Zeichnung inkl. Neuberechnung, Reihen-Edit, Export (Schema, Geometrie, + Kennwerte, Gitterkorb), Geschoss-Kopie und `LG_REGALE`. diff --git a/doc/todo.md b/doc/todo.md index c7813c1..79890fd 100644 --- a/doc/todo.md +++ b/doc/todo.md @@ -47,7 +47,19 @@ Stand: 2026-10-26. Wird woechentlich (freitags) mit der Doku geprueft, siehe innerhalb der Streckengruppe (Vario-Kurve, GF-Bogen); die frueheren Dummy-Befehle `ILS_K*` sind entfernt. -## 6. Tests +## 6. Lager + +- **Regelwerk-Werte im Lager-JSON-Export**: die aufgeloesten Regeln aus + `cfg/stahlbau/regelwerk/.json` je Geschoss (Kontext Brandgefaehrdung, + Personen, Absturzhoehe) mit Quelle und `verifiziert` in `_lager.json` + aufnehmen (`LagerPruefung.lsp`, `lg-regel-aufloesen`). +- **Gitterkorb-Hoehe berechnen**: `GITTERKORB_HOEHE_mm` steht bisher als + Attribut am Bereich (Vorgabe `lager.json bereich.gitterkorb_hoehe_mm`, + Platzhalter 500); spaeter aus den Attributen ableiten. +- Weitere Phasen (Gelaender, Treppen, Fluchtweg ...): `doc/plan/lager_rfq.md` + Abschnitt "Stand". + +## 7. Tests - **`tests/test_vf_geometrie.py`** braucht BricsCAD-erzeugte DXF-Referenzdaten, die headless fehlen -> schlaegt ohne vorherigen CAD-Lauf fehl. @@ -55,7 +67,7 @@ Stand: 2026-10-26. Wird woechentlich (freitags) mit der Doku geprueft, siehe gegen `tests/output/hundm05_results.json` (Beweis "Spec-Pfad == Journal-Pfad" auf Geometrie-Ebene). -> `doc/todo/plan-vf-interactive.md` -## 7. Spaetere Meilensteine (Entwicklungsplan) +## 8. Spaetere Meilensteine (Entwicklungsplan) Nur Ueberschriften, Details in `doc/plan/Entwicklungsplan.md`: diff --git a/lang/de_DE.json b/lang/de_DE.json index 57797c4..d73de5d 100644 --- a/lang/de_DE.json +++ b/lang/de_DE.json @@ -1215,6 +1215,8 @@ "lg-feld-rand_w": "\nRandgang West (mm, 0 = keiner)", "lg-feld-breite": "\nGangbreite (mm)", "lg-feld-art": "\nArt des Gangs", + "lg-feld-unter": "\nUnter den Reihen (gitterrost/gitterkorb/keine)", + "lg-feld-korbhoehe": "\nGitterkorb-Hoehe (mm)", "lg-titel-gang": "Lager: Gaenge einfuegen", "lg-prompt-gang-start": "\nStartpunkt der Gangmitte oder [%1] : ", "lg-key-rechteck": "Rechteck", @@ -1245,7 +1247,6 @@ "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).", @@ -1270,11 +1271,10 @@ "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-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-exp-json": "\n[Lager] Technische Daten: %1", + "lg-exp-lichte-hoehe-oben": "Geschoss %1 (oberstes): lichte Hoehe nicht berechenbar - LICHTE_HOEHE_mm setzen.", + "lg-exp-korb-hallenboden": "Bereich %1 hat Gitterkorb unter den Reihen, Geschoss %2 ist aber Hallenboden - nicht gezaehlt.", + "lg-exp-reihen-ohne-geschoss": "%1 Regalreihe(n) ohne bekanntes Geschoss - nicht in den Kennwerten enthalten.", "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.", diff --git a/lang/en_GB.json b/lang/en_GB.json index 65e6153..06c1968 100644 --- a/lang/en_GB.json +++ b/lang/en_GB.json @@ -1215,6 +1215,8 @@ "lg-feld-rand_w": "\nPerimeter aisle west (mm, 0 = none)", "lg-feld-breite": "\nAisle width (mm)", "lg-feld-art": "\nAisle type", + "lg-feld-unter": "\nBelow the rows (gitterrost/gitterkorb/keine)", + "lg-feld-korbhoehe": "\nMesh basket height (mm)", "lg-titel-gang": "Storage: insert aisles", "lg-prompt-gang-start": "\nStart point of the aisle centre line or [%1] : ", "lg-key-rechteck": "Rectangle", @@ -1245,7 +1247,6 @@ "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).", @@ -1270,11 +1271,10 @@ "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-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-exp-json": "\n[Storage] Technical data: %1", + "lg-exp-lichte-hoehe-oben": "Level %1 (top): clear height cannot be calculated - set LICHTE_HOEHE_mm.", + "lg-exp-korb-hallenboden": "Area %1 has a mesh basket below the rows, but level %2 is a hall floor - not counted.", + "lg-exp-reihen-ohne-geschoss": "%1 rack row(s) without a known level - not included in the key figures.", "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.", diff --git a/lib/lager_yaml_export.py b/lib/lager_yaml_export.py deleted file mode 100644 index 8f5a2a3..0000000 --- a/lib/lager_yaml_export.py +++ /dev/null @@ -1,433 +0,0 @@ -#!/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 []) - gaenge = raw.get("gaenge") or [] - - 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 - 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" % (len(reihen), nr), - "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 b3e6655..0913cbf 100644 --- a/menu/SSG_LIB.cui +++ b/menu/SSG_LIB.cui @@ -680,15 +680,9 @@ ^C^C(ssg-ensure "LagerPruefung") LG_REGELN - - - Hand-YAML fuer Abgleich - ^C^C(ssg-ensure "LagerInsert") LAGER_HAND_YAML - - - Export RFQ-YAML + Export Lager-JSON ^C^C(ssg-ensure "LagerInsert") LAGER_EXPORT @@ -1773,13 +1767,7 @@ - Hand-YAML fuer Abgleich - - - - - - Export RFQ-YAML + Export Lager-JSON diff --git a/menu/SSG_LIB.mnl b/menu/SSG_LIB.mnl index 537f85f..c80d638 100644 --- a/menu/SSG_LIB.mnl +++ b/menu/SSG_LIB.mnl @@ -174,7 +174,7 @@ (ssg-ensure "ssg_blocktools") (ssg-ensure "ssg_texttools") (ssg-ensure "ssg_stueckliste") - ;; Lagerplanung (RFQ-YAML): Regelprofile vor LagerInsert + ;; Lagerplanung (Export Lager-JSON): Regelprofile vor LagerInsert (ssg-ensure "LagerPruefung") (ssg-ensure "LagerInsert") diff --git a/menu/SSG_LIB.mnu b/menu/SSG_LIB.mnu index 0bcfc51..78f2285 100644 --- a/menu/SSG_LIB.mnu +++ b/menu/SSG_LIB.mnu @@ -145,8 +145,7 @@ [--] [Regale zaehlen]^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 +[<-Export Lager-JSON]^C^C(ssg-ensure "LagerInsert") LAGER_EXPORT [->HW Planung] [Materialfluss erzeugen]^C^C(ssg-ensure "HwPlanung") HW_MATERIALFLUSS [<-Symbole erzeugen]^C^C(ssg-ensure "HwPlanung") HW_SYMBOLE diff --git a/requirements.txt b/requirements.txt index c6c6b90..9073fa6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,5 +7,3 @@ 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/test_lager.lsp b/tests/test_lager.lsp index cd3923b..b849d5a 100644 --- a/tests/test_lager.lsp +++ b/tests/test_lager.lsp @@ -5,16 +5,15 @@ ;; - 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, die Rohdaten von LAGER_EXPORT und -;; die Zaehlung von LG_REGALE. +;; den Neuaufbau einer geaenderten Reihe, die JSON-Daten von LAGER_EXPORT +;; (inkl. Kennwerte und Gitterkorb) und die Zaehlung von LG_REGALE. ;; ;; 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). +;; und tests/output/lager_tests_lager.json (Beispiel der Export-Datei). ;; ============================================================ (setq *tlg-pass* 0 *tlg-fail* 0 *tlg-fails* '()) @@ -80,7 +79,11 @@ (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 +;; Geschoss-Objekt Nr. nr aus den geparsten Export-Daten. +(defun tlg-export-geschoss (daten nr) + (vl-some '(lambda (g) (if (= (ssg-json-get g "nr") nr) g)) (ssg-json-get daten "geschosse"))) + +(defun lager:tests ( / k0 k1 g0 g1 kb bereich reihen gaenge r0 neu raw daten pfad f eg0 eg1 kr kb2 b2 kinder mg mg2 hg alt eb r1 gg1 n-alt g2 r2 gg2 ids b2k n-kinder bb rg rk zl b3 rh3 mg3 ge3 ag3 n3 mr3 alt3) @@ -159,23 +162,36 @@ (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 --- + ;; --- JSON-Daten von LAGER_EXPORT --- (setq raw (lg-export-json)) (setq daten (ssg-json-parse raw)) + (tlg-eq "export format" '("ssg-lager" 2) (list (ssg-json-get daten "format") (ssg-json-get daten "version"))) (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")) + (setq eg1 (tlg-export-geschoss daten 1) eg0 (tlg-export-geschoss daten 0)) + (tlg-eq "export konturflaeche" 6.0e7 (ssg-json-get (ssg-json-get eg1 "kontur") "flaeche_mm2")) + (tlg-eq "export konturpunkte" 4 (length (ssg-json-get (ssg-json-get eg1 "kontur") "punkte"))) (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")) + (tlg-eq "export ecken" 4 (length (ssg-json-get (car (ssg-json-get daten "reihen")) "ecken"))) + (tlg-eq "export ende" T (if (ssg-json-get (car (ssg-json-get daten "reihen")) "ende") T)) + ;; Kennwerte: Regallaenge = Summe der Reihen, lichte Hoehe EG = 3100 - 150 - 0 + (tlg-eq "export regallaenge G1" (tlg-summe-attr (tlg-gs "LG_REIHE_*" 1) "LAENGE") + (ssg-json-get (ssg-json-get eg1 "kennwerte") "regallaenge_mm")) + (tlg-eq "export haengeebenen G1" 2 (length (ssg-json-get (ssg-json-get eg1 "kennwerte") "haengeebenen"))) + (tlg-eq "export lichte G0" 2950 (ssg-json-get (ssg-json-get eg0 "kennwerte") "lichte_hoehe_mm")) + (tlg-eq "export G0 keine buehne" nil (ssg-json-get (ssg-json-get eg0 "kennwerte") "buehne")) + (tlg-eq "export G1 herkunft" T (if (ssg-json-get (ssg-json-get eg1 "herkunft") "gitterrostflaeche_mm2") T)) + (tlg-eq "export G1 unter gitterrost" T + (vl-every '(lambda (r) (= (ssg-json-get r "unter_reihe") "gitterrost")) (ssg-json-get daten "reihen"))) + (tlg-eq "export lager regallaenge" (ssg-json-get (ssg-json-get eg1 "kennwerte") "regallaenge_mm") + (ssg-json-get (ssg-json-get daten "lager") "regallaenge_mm")) ;; --- Randgaenge Nord + Sued, Schraffur je Belag --- (setq kb2 (tlg-rechteck 20000.0 0.0 30000.0 6000.0)) @@ -243,7 +259,29 @@ (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")) + ;; --- Gitterkorb unter den Reihen des ersten Bereichs (G1) --- + (lg-attribute-setzen bereich (list (cons "UNTER_REIHE" "gitterkorb") (cons "GITTERKORB_HOEHE_mm" "500"))) + (setq raw (lg-export-json) daten (ssg-json-parse raw)) + (setq kr (vl-remove-if-not '(lambda (r) (= (ssg-json-get r "unter_reihe") "gitterkorb")) + (ssg-json-get daten "reihen"))) + (tlg-eq "gitterkorb: reihen" (length (vl-remove-if-not '(lambda (k) (wcmatch (cdr (assoc 2 (entget k))) "LG_REIHE_*")) + (lg-bereich-kinder bereich))) + (length kr)) + (tlg-eq "gitterkorb: seitenflaeche > 0" T + (vl-every '(lambda (r) (> (ssg-json-get (ssg-json-get r "gitterkorb") "seitenflaeche_mm2") 0)) kr)) + ;; Ruecken an Ruecken: es sind nicht alle Laengsseiten offen + (tlg-eq "gitterkorb: anliegende kanten zu" T + (< (apply '+ (mapcar '(lambda (r) (ssg-json-get (ssg-json-get r "gitterkorb") "offen_laengs_mm")) kr)) + (* 2.0 (apply '+ (mapcar '(lambda (r) (ssg-json-get r "laenge_mm")) kr))))) + (setq eg1 (tlg-export-geschoss daten 1)) + (tlg-eq "gitterkorb: kennwerte G1" (length kr) + (ssg-json-get (ssg-json-get (ssg-json-get eg1 "kennwerte") "gitterkorb") "reihen")) + (tlg-eq "gitterkorb: bereich" "gitterkorb" + (ssg-json-get (vl-some '(lambda (b) (if (= (ssg-json-get b "nr") (atoi (tlg-attr bereich "NR"))) b)) + (ssg-json-get daten "bereiche")) + "unter_reihe")) + + (setq pfad (strcat (getenv "DXFMAKRO") "/tests/output/lager_tests_lager.json")) (if (setq f (open pfad "w")) (progn (princ raw f) (close f))) ;; --- Geschoss kopieren: G1 (3100, Spanplatte) -> G2, G3 mit Gitterrost --- @@ -296,6 +334,7 @@ (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))) + (tlg-eq "kopie: unter_reihe gitterkorb" 1 (tlg-anzahl-attr (tlg-gs "LG_BEREICH" 2) "UNTER_REIHE" "gitterkorb")) ;; 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))) diff --git a/tests/test_lager_yaml.py b/tests/test_lager_yaml.py deleted file mode 100644 index 254b936..0000000 --- a/tests/test_lager_yaml.py +++ /dev/null @@ -1,206 +0,0 @@ -#!/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), - 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")], - "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(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, oberste Schiene zuerst - assert [h["rack_length_m"] for h in og["hanging_levels"]] == [20.0, 20.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 - assert og["grating"] == pytest.approx(14.6) - - -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 m2 Reihen - assert og["walkway"]["area_m2"] == pytest.approx(46.0) - 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 dfb12ef..c2becc6 100644 --- a/tests/test_unit.lsp +++ b/tests/test_unit.lsp @@ -3409,9 +3409,127 @@ (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-wert/false" "false" (lg-json-wert 'FALSE)) + (tu-eq "lg-json-wert/umlaut" "\"G\\u00e4\"" (lg-json-wert (strcat "G" (chr 228)))) (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"))) + (tu-eq "lg-json-wert/obj-arr" "{\"p\": [1, 2.5], \"o\": {\"x\": null}}" + (lg-json-wert (list 'OBJ (cons "p" (list 'ARR 1 2.5)) (cons "o" (list 'OBJ (cons "x" nil)))))) + (tu-eq "lg-json-wert/leer" "[]" (lg-json-wert (list 'ARR))) + (tu-eq "lg-j-ebenen" "[{\"schienenhoehe_mm\": 2300, \"warenlaenge_mm\": 1200}]" + (lg-json-wert (lg-j-ebenen "2300/1200"))) + ;; kleine Container einzeilig, grosse je Eintrag eine Zeile + (tu-eq "lg-json-text/klein" "{\"a\": 1}" (lg-json-text (list 'OBJ (cons "a" 1)) "")) + (setq _gross (cons 'OBJ (mapcar '(lambda (i) (cons (itoa i) i)) '(1 2 3 4 5 6 7 8 9 10 11 12 13)))) + (tu-eq "lg-json-text/gross-zeilen" 15 + (length (ssg-sepliste-split-feld (lg-json-text _gross "") "\n"))) + (tu-eq "lg-json-text/punkte-einzeilig" 1 + (length (ssg-sepliste-split-feld + (lg-json-text (lg-j-pkte '((0 0) (1 0) (1 1) (0 1) (0 2) (2 2) (2 0))) "") "\n"))) + (tu-eq "lg-j-setzen/neu" 2 (lg-j (lg-j-setzen (list 'OBJ (cons "a" 1)) "b" 2) "b")) + (tu-eq "lg-j-setzen/ersetzen" '(OBJ ("a" . 3)) (lg-j-setzen (list 'OBJ (cons "a" 1)) "a" 3)) + + ;; --- Kennwerte (Export) --- + (tu-eq "lg-buehne-p" '(T nil nil nil) + (mapcar 'lg-buehne-p '("spanplatte" "hallenboden" "" nil))) + (tu-eq "lg-unter-reihe-wert" '("gitterkorb" "gitterrost" "keine" "keine" "gitterrost") + (list (lg-unter-reihe-wert "gitterkorb" "gitterrost" T) + (lg-unter-reihe-wert "" "gitterrost" T) + (lg-unter-reihe-wert "keine" "gitterrost" T) + (lg-unter-reihe-wert "gitterkorb" "gitterrost" nil) + (lg-unter-reihe-wert "quatsch" "unbekannt" T))) + (setq _r1 (list 'OBJ (cons "laenge_mm" 4000.0) (cons "tiefe_mm" 700.0) (cons "schienen" 1) + (cons "haengeebenen" (lg-j-ebenen "2300/1200;1100/1100")) + (cons "unter_reihe" "gitterrost")) + _r2 (list 'OBJ (cons "laenge_mm" 2000.0) (cons "tiefe_mm" 700.0) (cons "schienen" 2) + (cons "haengeebenen" (lg-j-ebenen "2300/1200")) + (cons "unter_reihe" "gitterrost"))) + (tu-eqf "lg-haengeebenen-summen" + '(ARR (OBJ ("schienenhoehe_mm" . 2300.0) ("warenlaenge_mm" . 1200.0) ("reihen" . 2) ("regallaenge_mm" . 6000.0)) + (OBJ ("schienenhoehe_mm" . 1100.0) ("warenlaenge_mm" . 1100.0) ("reihen" . 1) ("regallaenge_mm" . 4000.0))) + (lg-haengeebenen-summen (list _r1 _r2))) + (setq _ga (list (list 'OBJ (cons "art" "gang") (cons "belag" "spanplatte") (cons "laenge_mm" 10000.0) (cons "breite_mm" 1000.0)) + (list 'OBJ (cons "art" "randgang") (cons "belag" "gitterrost") (cons "laenge_mm" 5000.0) (cons "breite_mm" 800.0)) + (list 'OBJ (cons "art" "gang") (cons "belag" "spanplatte") (cons "laenge_mm" 2000.0) (cons "breite_mm" 1000.0)))) + (tu-eqf "lg-gaenge-je-art" + '(OBJ ("gang" OBJ ("anzahl" . 2) ("laenge_mm" . 12000.0) ("flaeche_mm2" . 1.2e7)) + ("quergang" OBJ ("anzahl" . 0) ("laenge_mm" . 0.0) ("flaeche_mm2" . 0.0)) + ("randgang" OBJ ("anzahl" . 1) ("laenge_mm" . 5000.0) ("flaeche_mm2" . 4.0e6))) + (lg-gaenge-je-art _ga)) + ;; Geschoss 1 (Spanplatte, 3100) unter Geschoss 2 (6100, Konstr. 250) + (setq _g1 (list 'OBJ (cons "nr" 1) (cons "hoehe_belag_mm" 3100.0) (cons "konstr_hoehe_mm" 250.0) + (cons "lichte_hoehe_mm" nil) (cons "belag_art" "spanplatte") + (cons "kontur" (list 'OBJ (cons "flaeche_mm2" 6.0e7)))) + _g2 (list 'OBJ (cons "nr" 2) (cons "hoehe_belag_mm" 6100.0) (cons "konstr_hoehe_mm" 250.0) + (cons "lichte_hoehe_mm" nil) (cons "belag_art" "spanplatte"))) + (setq _kw (car (lg-kennwerte-geschoss _g1 _g2 (list _r1 _r2) _ga 1))) + (tu-eqf "lg-kennwerte/lichte-berechnet" 2750.0 (lg-j _kw "lichte_hoehe_mm")) + (tu-eqf "lg-kennwerte/regallaenge" 6000.0 (lg-j _kw "regallaenge_mm")) + (tu-eqf "lg-kennwerte/grundflaeche" 4.2e6 (lg-j _kw "regalgrundflaeche_mm2")) + (tu-eqf "lg-kennwerte/schienen" 8000.0 (lg-j _kw "schienenlaenge_mm")) + (tu-eqf "lg-kennwerte/belag-gaenge" 1.2e7 (lg-j _kw "belagsflaeche_mm2")) + ;; Gitterrost = Randgang gitterrost 4e6 + Reihen 4.2e6 + (tu-eqf "lg-kennwerte/gitterrost" 8.2e6 (lg-j _kw "gitterrostflaeche_mm2")) + (tu-eq "lg-kennwerte/buehne" T (lg-j _kw "buehne")) + (tu-eq "lg-kennwerte/herkunft" T + (if (lg-j (cadr (lg-kennwerte-geschoss _g1 _g2 (list _r1 _r2) _ga 1)) "belagsflaeche_mm2") T)) + ;; ohne Gaenge: Konturflaeche - Reihengrundflaeche; oberstes ohne Vorgabe -> nil + (setq _kw (car (lg-kennwerte-geschoss _g1 nil (list _r1 _r2) nil 1))) + (tu-eqf "lg-kennwerte/belag-kontur" (- 6.0e7 4.2e6) (lg-j _kw "belagsflaeche_mm2")) + (tu-nil "lg-kennwerte/lichte-oben" (lg-j _kw "lichte_hoehe_mm")) + (tu-eqf "lg-kennwerte/lichte-vorgabe" 2800.0 + (lg-j (car (lg-kennwerte-geschoss (lg-j-setzen _g1 "lichte_hoehe_mm" 2800.0) _g2 nil nil 0)) + "lichte_hoehe_mm")) + ;; Hallenboden: keine Buehne, keine Flaechen + (setq _kw (car (lg-kennwerte-geschoss (lg-j-setzen _g1 "belag_art" "hallenboden") _g2 (list _r1) _ga 1))) + (tu-eq "lg-kennwerte/hallenboden" 'FALSE (lg-j _kw "buehne")) + (tu-nil "lg-kennwerte/hallenboden-belag" (lg-j _kw "belagsflaeche_mm2")) + (tu-nil "lg-kennwerte/hallenboden-gitter" (lg-j _kw "gitterrostflaeche_mm2")) + + ;; --- Gitterkorb: offene Kanten --- + ;; Einzelreihe 4000 x 700: alles offen + (setq _e1 '((0.0 -350.0) (4000.0 -350.0) (4000.0 350.0) (0.0 350.0)) + _e2 '((0.0 350.0) (4000.0 350.0) (4000.0 1050.0) (0.0 1050.0)) + _e3 '((2000.0 1050.0) (6000.0 1050.0) (6000.0 1750.0) (2000.0 1750.0))) + (tu-eqf "lg-offene-kanten/einzeln" '((8000.0 1400.0)) (lg-reihen-offene-kanten (list _e1) 1.0)) + ;; Ruecken an Ruecken: je eine Laengsseite zu + (tu-eqf "lg-offene-kanten/ruecken" '((4000.0 1400.0) (4000.0 1400.0)) + (lg-reihen-offene-kanten (list _e1 _e2) 1.0)) + ;; Teilueberlappung: _e3 liegt nur auf 2000 mm an _e2 an (_e2 unten ganz an _e1) + (tu-eqf "lg-offene-kanten/teilweise" '((2000.0 1400.0) (6000.0 1400.0)) + (cdr (lg-reihen-offene-kanten (list _e1 _e2 _e3) 1.0))) + ;; Abstand > Toleranz: nicht anliegend + (tu-eqf "lg-offene-kanten/abstand" '((8000.0 1400.0) (8000.0 1400.0)) + (lg-reihen-offene-kanten + (list _e1 '((0.0 360.0) (4000.0 360.0) (4000.0 1060.0) (0.0 1060.0))) 1.0)) + ;; Gitterkorb vervollstaendigen: Boden, offene Kanten, Seitenflaeche + (setq _rk (lg-reihen-gitterkorb-berechnen + (list (list 'OBJ (cons "laenge_mm" 4000.0) (cons "tiefe_mm" 700.0) + (cons "ecken" (lg-j-pkte _e1)) + (cons "gitterkorb" (list 'OBJ (cons "hoehe_mm" 500.0)))) + (list 'OBJ (cons "laenge_mm" 4000.0) (cons "tiefe_mm" 700.0) + (cons "ecken" (lg-j-pkte _e2)) (cons "gitterkorb" nil))) + 1.0)) + (setq _gk (lg-j (car _rk) "gitterkorb")) + (tu-eqf "lg-gitterkorb/boden" 2.8e6 (lg-j _gk "bodenflaeche_mm2")) + (tu-eqf "lg-gitterkorb/laengs" 4000.0 (lg-j _gk "offen_laengs_mm")) + (tu-eqf "lg-gitterkorb/seite" (* 500.0 5400.0) (lg-j _gk "seitenflaeche_mm2")) + (tu-nil "lg-gitterkorb/ohne" (lg-j (cadr _rk) "gitterkorb")) + ;; Kennwerte summieren den Korb, Grundflaeche zaehlt dann nicht als Gitterrost + (setq _kw (car (lg-kennwerte-geschoss _g1 _g2 + (list (lg-j-setzen (car _rk) "unter_reihe" "gitterkorb")) nil 1))) + (tu-eqf "lg-kennwerte/korb-seite" (* 500.0 5400.0) (lg-j (lg-j _kw "gitterkorb") "seitenflaeche_mm2")) + (tu-eq "lg-kennwerte/korb-reihen" 1 (lg-j (lg-j _kw "gitterkorb") "reihen")) + (tu-eqf "lg-kennwerte/korb-kein-gitterrost" 0.0 (lg-j _kw "gitterrostflaeche_mm2")) + ;; Lager-Summen ueber zwei Geschosse + (setq _kl (lg-kennwerte-lager + (list (lg-j-setzen (lg-j-setzen _g1 "kennwerte" (car (lg-kennwerte-geschoss _g1 _g2 (list _r1) nil 1))) + "kontur" (list 'OBJ (cons "bbox" (list 'ARR 0.0 0.0 10000.0 6000.0)))) + (lg-j-setzen (lg-j-setzen _g2 "kennwerte" (car (lg-kennwerte-geschoss _g2 nil (list _r2) nil 1))) + "kontur" (list 'OBJ (cons "bbox" (list 'ARR 0.0 0.0 12000.0 5000.0))))) + (list _r1 _r2) nil 2)) + (tu-eqf "lg-kennwerte-lager/regal" 6000.0 (lg-j _kl "regallaenge_mm")) + (tu-eqf "lg-kennwerte-lager/huelle" '(12000.0 6000.0) + (list (lg-j (lg-j _kl "huelle") "laenge_mm") (lg-j (lg-j _kl "huelle") "breite_mm"))) ;; --- Regelprofil-Aufloesung --- (tu-eq "lg-bedingung/leer" 'ja (lg-bedingung-pruefen nil nil)) @@ -3472,7 +3590,7 @@ (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?)")) + (tu-record nil "lg-profil/dateien" "cfg/stahlbau/regelwerk/SK.json nicht gefunden (DXFM_CFG?)")) ) ) )