[CHANGE] Lager-Export nur noch als JSON mit Kennwerten, Gitterkorb unter Regalreihen

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Simon HenschandClaude Opus 5.5 committed 2026-11-03 15:37:49 +01:00
1 parent 3c729ea604
commit d2729140ff
22 files changed
+1068 -1089

No files matched your search

+4 -5
View File
@@ -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_<winkel>` -> `KF_LAENGE` -> `KF_AN_<winkel>` (je KS_AUS auf KS_EIN) plus der Draufsicht `KF_2D` EINEN Block `KF_<n>`. 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_<n>` 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_<n>` (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_<Ziffern>`. 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_<n>` 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 `<dwg>_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/<dwg>_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_<L>_<T>`/`LG_GANG_<L>_<B>_<BELAG>` (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<nr>`; `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 `<dwg>_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 neuLine truncated
| `LagerPruefung.lsp` | LG_REGELN | Regelprofile der Lagerplanung: `cfg/regelwerk/<LAND>.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_<L>_<T>`/`LG_GANG_<L>_<B>_<BELAG>` (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<nr>`; `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 `<dwg>_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 Line truncated
| `LagerPruefung.lsp` | LG_REGELN | Regelprofile der Lagerplanung: `cfg/stahlbau/regelwerk/<LAND>.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 <dxf> <output.json> [--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 <export.csv> <out_HW.dxf> [--layer-cfg ...]` |
| `lager_yaml_export.py` | Lagerplanung: `<dwg>_lager_raw.json` (LAGER_EXPORT) -> `<dwg>_drawing.yaml` (Overlay im Schema der RFQ-Hand-YAML, nur Zeichnungsfelder, plus `_provenance`/`_conflicts`/`_warnings`) und optional `--nachweis` HTML. `--hand <yaml>` 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 <raw.json> <out.yaml> [--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 <dxf> <data_dir> <output.json> [--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
+566 -143
View File
@@ -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 (<dwg>_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 <dwg>_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)
)
+2 -2
View File
@@ -1,7 +1,7 @@
;; ============================================================
;; LagerPruefung.lsp - Regelprofile der Lagerplanung (Phase 0)
;;
;; Laedt das Laenderprofil cfg/regelwerk/<LAND>.json, loest die Vererbung
;; Laedt das Laenderprofil cfg/stahlbau/regelwerk/<LAND>.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
+9 -10
View File
@@ -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<n>` 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<n>` und IDs sind neu |
| `c:LG_BEREICH` | Lagerbereich-Kontur + Dialog `lg_bereich`; Auto-Fuellung mit `LG_REIHE_<L>_<T>` / `LG_GANG_<L>_<B>_<BELAG>`, 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_<L>_<T>` / `LG_GANG_<L>_<B>_<BELAG>`, 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` | `<dwg>_lager_raw.json` + Python -> `<dwg>_drawing.yaml` und `<dwg>_nachweis.html` in `DXFM_RESULTS` |
| `c:LAGER_HAND_YAML` | Hand-YAML fuer den Abgleich (Konfliktliste) festlegen |
| `c:LG_REGELN` | Regelprofil (`cfg/regelwerk/<Land>.json`) mit aufgeloesten Werten und Quellen anzeigen |
| `c:LAGER_EXPORT` | `<dwg>_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/<Land>.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,
-230
View File
@@ -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: [...]}
File renamed without changes.
File renamed without changes.
File renamed without changes.
+7 -13
View File
@@ -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
}
}
+5
View File
@@ -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;
+274
View File
@@ -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/<dwg>_lager.json --> Dokument
^ Regelprofil cfg/stahlbau/regelwerk/<Land>.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{<kennwert>: {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_<L>_<T>` | 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_<L>_<B>_<BELAG>` | 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<nr>`
(`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/<LAND>.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`: `<schluessel> <op> <wert>` (`=`, `<=`, `>=`, `<`, `>`) 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`.
+14 -2
View File
@@ -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/<LAND>.json` je Geschoss (Kontext Brandgefaehrdung,
Personen, Absturzhoehe) mit Quelle und `verifiziert` in `<dwg>_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`:
+6 -6
View File
@@ -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] <Enter = Ende>: ",
"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) <Enter = zeichnen>: ",
"lg-prompt-kontur-bereich": "\nKontur des Lagerbereichs waehlen (geschlossene Polylinie) <Enter = zeichnen>: ",
"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 <raw.json> <out.yaml>",
"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.",
+6 -6
View File
@@ -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] <Enter = finish>: ",
"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) <Enter = draw>: ",
"lg-prompt-kontur-bereich": "\nSelect contour of the storage area (closed polyline) <Enter = draw>: ",
"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 <raw.json> <out.yaml>",
"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.",
-433
View File
@@ -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
<zeichnung>_lager_raw.json und erzeugt daraus
<projekt>_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.
<projekt>_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 <lager_raw.json> <out_drawing.yaml>
[--hand <hand.yaml>] [--nachweis <out.html>]
"""
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("<tr>%s</tr>" % "".join("<td>%s</td>" % e(str(c)) for c in r) for r in zeilen)
return "<table><thead><tr>%s</tr></thead><tbody>%s</tbody></table>" % (
"".join("<th>%s</th>" % e(k) for k in kopf), z or "<tr><td colspan=9>-</td></tr>")
teile = [
"<!doctype html><html lang='de'><head><meta charset='utf-8'><title>Lager-Nachweis</title>",
"<style>body{font-family:Segoe UI,Arial,sans-serif;margin:24px;color:#222}"
"table{border-collapse:collapse;margin:8px 0 24px}td,th{border:1px solid #bbb;padding:4px 8px;"
"text-align:left;vertical-align:top}th{background:#eee}.w{color:#a40}</style></head><body>",
"<h1>Lager-Nachweis (Mengen)</h1>",
"<p>Zeichnung: %s<br>Erzeugt: %s<br>Regelprofil: %s</p>" % (
e(str(raw.get("zeichnung"))), e(str(raw.get("erzeugt"))), e(str(raw.get("land")))),
"<p class='w'>Interner Nachweis - die Pruefung gegen ASR/DGUV/EN ISO 14122 folgt in Phase 4.</p>",
"<h2>Warnungen</h2>",
"<ul>%s</ul>" % ("".join("<li class='w'>%s</li>" % e(w) for w in ausgabe["_warnings"]) or "<li>keine</li>"),
"<h2>Werte aus der Zeichnung</h2>",
tabelle(["Feld", "Wert", "Quelle", "Formel"],
[(k, v["wert"], v["quelle"], v["formel"]) for k, v in ausgabe["_provenance"].items()]),
"<h2>Konflikte mit der Hand-YAML (Hand gewinnt)</h2>",
tabelle(["Feld", "Hand", "Zeichnung", "Abweichung %"],
[(k["pfad"], k["hand"], k["zeichnung"], k.get("abweichung_prozent", ""))
for k in ausgabe["_conflicts"]]),
"<h2>Von der Zeichnung gefuellte Felder (Hand leer/null)</h2>",
tabelle(["Feld", "Zeichnung"], [(f["pfad"], f["zeichnung"]) for f in fuellt]),
"</body></html>",
]
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="<zeichnung>_lager_raw.json aus LAGER_EXPORT")
ap.add_argument("yaml", help="Ziel: <projekt>_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())
+2 -14
View File
@@ -680,15 +680,9 @@
<Command>^C^C(ssg-ensure "LagerPruefung") LG_REGELN</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00152">
<Macro>
<Name>Hand-YAML fuer Abgleich</Name>
<Command>^C^C(ssg-ensure "LagerInsert") LAGER_HAND_YAML</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00153">
<Macro>
<Name>Export RFQ-YAML</Name>
<Name>Export Lager-JSON</Name>
<Command>^C^C(ssg-ensure "LagerInsert") LAGER_EXPORT</Command>
</Macro>
</MenuMacro>
@@ -1773,13 +1767,7 @@
</MenuItem>
</PopMenuItem>
<PopMenuItem>
<NameRef>Hand-YAML fuer Abgleich</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00152"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem>
<NameRef>Export RFQ-YAML</NameRef>
<NameRef>Export Lager-JSON</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00153"/>
</MenuItem>
+1 -1
View File
@@ -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")
+1 -2
View File
@@ -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
-2
View File
@@ -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
+50 -11
View File
@@ -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)))
-206
View File
@@ -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
+121 -3
View File
@@ -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?)"))
)
)
)