This commit is contained in:
Simon Hensch committed 2026-11-04 07:50:01 +01:00
commit 57b01ad516
19 files changed
+4089 -183

No files matched your search

+12 -4
View File
@@ -81,8 +81,9 @@ 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_EXPORT, LG_REGELN |
| HW Planung | `HwPlanung` | HW_MATERIALFLUSS (Materialfluss erzeugen), HW_SYMBOLE (Symbole erzeugen, noch Platzhalter) |
| 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 |
| HW Planung | `HwPlanung` | HW_MATERIALFLUSS (Materialfluss erzeugen), HW_SYMBOLE (Symbole erzeugen), HW_NUMMERIEREN (Symbole nummerieren). Einzelbefehle HW_SYM_VENTILE/_SENSOREN/_MOTOREN/_MOTORSCHUTZ/_ENCODER/_SCANNER und HW_SYM_LOESCHEN bewusst NICHT im Menue. Ablauf: `doc/hw_planung.md` |
| Export | `export` | EXPORTSIVAS, EXPORTCSV, CALLPYTHON |
| Werkzeuge | `ssg_blocktools`, `ssg_texttools`, `ssg_stueckliste`, `DinFormate` | SSG_BLOCK_COUNT, SSG_BLOCK_DELETE, SSG_ATTR_ERSETZEN, SSG_TEXT_ERSETZEN, SSG_TEXT_PARALLEL, SSG_TEXT_HOEHE, SSG_TEXT_STIL, SSG_FAEHNCHEN_STUECKLISTE, DIN_FORMATE |
| Konfiguration | `SSG_LIB_Commands` + `DrawPlanquadrate` | SSG_DIM_SWITCH, SSG_SPRACHE, PLANQUADRATE_TOGGLE (DRAW_PLANQUADRATE/DELETE_PLANQUADRATE) |
@@ -123,7 +124,7 @@ Detaillierte Dokumentation aller Funktionen und Befehle: siehe `Lisp/README.md`.
| `TEFInsert.lsp` | TEF_FOERDERER_LINIE | Linie zwischen zwei Punkten zeichnen und mit 1000-mm-TEF-Geraden (`0_B10030`) belegen; Rest ueber ein zurueckversetztes Element, damit der Foerderer exakt am Linienende endet; geneigte Linien per Rz*Ry wie `ssg_ks_insert.lsp` |
| `GeruestInsert.lsp` | GERUEST, GERUEST_SAEULEN, GERUEST_ROHRABHAENGER, GERUEST_WANDBEFESTIGUNG, GERUEST_TRAEGER, GERUEST_TRAEGER_BESCHRIFTEN, OMNI_FAHRSTRECKE, OMNI_FAHRSTRECKE_BESCHRIFTEN | Geruest-Baugruppen aus `data/json/geruest.json` konfigurieren und einfuegen. **Ein Dialog je Bereich**: Rohrabhaenger / Saeulen / Wandbefestigung oeffnen NUR ueber ihren eigenen Menuebefehl (`geruest-starten id`, Dialogtitel aus dem ASCII-Feld `titel`); eine Umschaltung im Dialog gibt es nicht (DCL hat kein Reiter-Tile), `GERUEST` ohne Bereich zeigt nur einen Hinweis. Der Dialogstand beginnt je Aufruf frisch. 1"- und 2"-Saeulen liegen in EINER Baugruppe `Saeulen` mit gemeinsamer Anbauteil-Liste; **`teileart` und `dummy` duerfen je Geometrie-Variante ueberschrieben werden** (`geruest-variante-wert`, Variante gewinnt vor Baugruppe; fuer `dummy` ueber `geruest-dummy-wahl`: fehlt die DWG der Variante, Meldung + Rueckfall auf den Baugruppen-Dummy; ist der Dummy zugleich eine Teile-DWG, wird er per `geruest-ohne-pfad` nicht doppelt eingefuegt) - so liefert `R 1"` weiterhin TEILEART `Geruest Saeule 1 Zoll` und die `R 2"`-Varianten `Geruest Saeule 2 Zoll`, die Groessen bleiben im Export also unterscheidbar. **Das Layout steht als Vorlage in `dcl/geruest.dcl`** (bzw. `dcl/geruest_traeger.dcl`); nur die datenabhaengigen Bloecke - ein `radio_button` je Variante (Key `v<i>`, `geruest-radio-key`/`-index`), Mengenfeld + Ankreuzfeld je Anbauteil - werden zur Laufzeit an Markerzeilen `//@NAME@` eingesetzt (`geruest-dcl-ersetzungen` -> `ssg-dcl-vorlage-fuellen` in ssg_core, Temp-Datei, nach `unload_dialog` geloescht). Grund: DCL kann Tiles weder zur Laufzeit erzeugen noch ausblenden, Radiobutton-Labels lassen sich per `set_tile` nicht aendern, und eine `list_box` kann keine Eingabefelder enthalten. Varianten bewusst als Radiobuttons statt `list_box`: BricsCAD zeichnet die Listenspalte nur halb so breit, und ein `tabs`-Wert passt nur schrift-/skalierungsabhaengig. Inhalt: Basis-Variante aus `geometrie_varianten` (Radiobuttons, siehe oben), **Winkel Platte** (0/90 Grad, `*geruest-winkel-werte*`): Ausrichtung der Anbauteile zur Basis - die Teile-DWGs aus oben/unten/sonstiges werden um diesen Winkel um Z gedreht in den Block eingefuegt, Basis und Dummy bleiben ungedreht (`geruest-teile-dwgs-rot`; in Konfigurationen als echter Wert `winkel_platte`, kein Block-Attribut), Laenge (gegen `attribute.Laenge` min/max geprueft, Retry-Schleife), Farbe (`attribute.Teilefarbe`, Freitext bei `frei_option`) und **eine Befestigungsspalte** "Befestigung oben und unten" mit allen `anbaugruppen.befestigung` - die Anzahl ist die GESAMTanzahl je Teil (oben und unten zusammen; Tile-Keys `b<i>`/`bm<i>`, Zustand `*geruest-dlg-bef*`). Unter jedem Teil (auch in Sonstiges) zeigt eine Textzeile "Art.-Nr. ..." die Artikelnummer(n) (`geruest-artnr-anzeige`: Einzelteil -> `sivas_id`, Gruppe -> eindeutige Nummern der Teile; DCL kennt keine Schriftgroesse, darum normale Groesse in eigener Zeile). `anbaugruppen` ist ein Objekt mit zwei Listen: `befestigung` (Befestigungsspalte) und `sonstiges` (Teile, die weder oben noch unten sitzen, z.B. die Saeulen-Haltearme) - letztere bekommen eine eigene Zeile darunter (nur wenn nicht leer), in ARTINR/BEZEICHNUNG nach "unten" als "sonstiges" (`geruest-anbau-liste`; ein altes flaches Array gilt als `befestigung`). **Zwei Mengenfelder im JSON, bewusst verschieden benannt**: `stueckzahl` = wie oft ein Teil TATSAECHLICH geschrieben wird (je Variante bzw. je Satz/Anbauteil, fehlt = 1), `anzahl_vorgabe` = NUR die Vorbelegung des Anzahl-Feldes im Dialog (Anbaugruppen, fehlt = 1); ein Anbauteil wird Anzahl x `stueckzahl` mal geschrieben, bei einer Gruppe (`teile`) zaehlt die Anzahl die Saetze (`geruest-stueckzahl`/`geruest-anzahl-vorgabe`). In Konfigurationen (`befestigung`/`sonstiges`, dazu `winkel_platte`) heisst der gespeicherte Dialogwert `anzahl`. Aeltere Konfigurationen mit getrennten `oben`/`unten`-Listen laden weiter: deren Mengen werden zur Gesamtanzahl addiert (`geruest-konfig-befestigung`), Sonstiges-Teile darin still in die Sonstiges-Zeile umgehaengt (`geruest-konfig-sonstiges`); beim naechsten Speichern steht alles im neuen Format. Baugruppen ohne `attribute`-Block (Wandbefestigung) bekommen Laenge/Farbe gesperrt, Baugruppen ohne Anbauteile bekommen gar keine Spalten (ein leerer DCL-Cluster ist unzulaessig). Im generierten Label wird `"` zu `''` ersetzt (`geruest-dcl-label`) - fuenf Artikelnamen enthalten ein Anfuehrungszeichen, das das DCL-Label sonst vorzeitig beendet und die Datei zerstoert. **Konfigurationen**: gepflegte Bibliothek je Baugruppe unter `konfigurationen` in `geruest.json` (schreibgeschuetzt) plus eigene in `cfg/geruest_user.json` (gitignored, `geruest-user-schreiben`; getrennte Datei, damit Benutzerdaten und versionierte Bibliothek nicht kollidieren). Eigene Eintraege sind in der Liste mit `*` markiert und nur diese loeschbar. **Geaenderte Konfiguration**: beim Anwenden wird der Dialogstand als VergleichsbasLine truncated
| `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) |
| `HwPlanung.lsp` | HW_MATERIALFLUSS, HW_SYMBOLE, HW_SYM_VENTILE, HW_SYM_SENSOREN, HW_SYM_MOTOREN, HW_SYM_MOTORSCHUTZ, HW_SYM_ENCODER, HW_SYM_SCANNER, HW_SYM_LOESCHEN, HW_NUMMERIEREN | Menue "HW Planung" (zwischen Lager und SPS), Ablauf und Renamer-Regeln in `doc/hw_planung.md`. **Ausgabeziel per Dialog** `dcl/hw_ausgabe.dcl` vor jedem Erzeugungsbefehl (`hw-ausgabe-waehlen`, letzte Wahl je Sitzung in `*hw-ausgabe-letzt*`, ohne GUI Standarddatei `<dwg>_HW.dxf`): **aktuelle Zeichnung** (Python schreibt eine EINHEITENLOSE Zwischendatei `<dwg>_HW_tmp.dxf` (`--einheitenlos`, sonst skaliert BricsCAD beim Einfuegen), `hw-in-aktuelle-zeichnung` fuegt sie per `vla-InsertBlock` bei 0,0 ein, loest sie per `vla-Explode` auf und loescht ERST DANACH die fruehere Ausgabe - `hw-alte-ausgabe` ueber XDATA `SSG_HW_MF` (Materialfluss) bzw. `SSG_HW_SYM` je Praefix), **bestehende Datei ergaenzen** (getfiled ueber `done_dialog 2`, da getfiled aus einem action_tile nicht geht) oder **neue Datei** (Name, Ordner DXFM_RESULTS; existiert sie, bleibt der Dialog mit Hinweis offen). Reine Kerne `hw-name-ohne-dxf`/`hw-name-gueltig-p`/`hw-neu-pfad`/`hw-ausgabe-pruefen` getestet. In einer Datei ersetzt jeder Befehl nur seinen eigenen Anteil (Materialfluss = XDATA `SSG_HW_MF` bzw. HW_*-Layer, Symbole = XDATA `SSG_HW_SYM` je Praefix) - vom Anwender Gezeichnetes (Renamer-Bereiche) bleibt; die Ursprungsdatei auf der Platte wird nie geaendert. Python-Aufrufe zentral ueber `hw-python` (Log je Befehl in DXFM_LOG, `ok-codes` fuer create_numbers' Exit 1 bei Planungsfehlern). HW_MATERIALFLUSS: EXPORTCSV (alte CSV vorher geloescht) -> `lib/hw_materialfluss.py`. **Symbole**: Regelwerk `cfg/io_regeln.json` (reduzierte Kopie aus kabellaengen: Rollen mit Praefix/Symbolblock, Bauteile, `io_vorlage`; Erkennungs-Regex/Radien/Nummernpaare/altZuNeu entfernt). **Aussehen und Attribute der Symbole stehen in `cfg/hw_symbole.json`** (Vorlagen mit Rahmen/Schraffur + Attribut-Lage/-Hoehe/-Stil, je Symbol Layer/Farbe/`versatz`/Abweichungen je Tag/Werte) - es gibt KEINE Symbol-Bibliothek als Referenzdokument. Die Symbole tragen DIESELBEN Blocknamen wie die ILS-Katalogsymbole in kabellaengen und sind ihnen exakt nachgebaut (inkl. Einfuegepunkt: Eingaenge/Motoren haengen nach unten) - `test_symbole_gleichen_dem_katalog` prueft das gegen `kabellaengen/doc/Symbole/ILS-Symbole.dxf`; aendert sich dort ein Symbol, faellt der Test und die Konfiguration wird nachgezogen. `hw-sym-bauteile-sammeln` findet die Mechanik-Bloecke der Ursprungszeichnung top-level UND verschachtelt in VF_n/GF_n/KREISEL_n (Weltlage ueber `csv:wrapper-welt-records`), `hw-sym-plan` legt je Rolle Symbolblock, flachen ZIELPUNKT (Bauteil-Einfuegepunkt, `hw-sym-ziel`) und ID-VORLAGE (`hw-sym-io-vorlage`, z.B. `MB1@@@`) fest; bei Alternativen in `benoetigt` die erste. `hw-sym-plan->json` (Zahlen ueber `hw-json-zahl`, DIMZIN-unabhaengig) -> `<dwg>_hw_symbole.json` -> `lib/hw_symbole.py`; der Plan ist IMMER vollstaendig (alle Praefixe), `praefixe` sagt nur, was eingefuegt wird - so haengt die Lage nicht vom Einzelbefehl ab. Die LAGE rechnet Python (kennt die Symbolgeometrie), nicht LISP. Die Nummern vergibt NICHT das Erzeugen, sondern HW_NUMMERIEREN in der geoeffneten _HW.dxf: QSAVE, `lib/create_numbers.py --dryrun --info`, dann `hw-num-anwenden` (Zuordnung je Attribut mit `@` ueber Wert + Einfuegepunkt, `hw-num-eintrag-p`) in die offene Zeichnung. Reine Kerne in `tu-tests-hw` |
| `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 | 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 und Breite, Belags-/Gitterrostflaeche, Gitterkorb, Regalrohr = Laenge x Haengeebenen), je Geschoss-Kennwert `herkunft` (Formel + Quelle). **`null` = fehlt (offener Punkt), nicht Zutreffendes wird weggelassen** (Marker `'ENTFAELLT`, `lg-json-ohne-entfallend`). Plausibilitaetswarnungen je Geschoss (`lg-plausibilitaet`, Grenzwerte aus dem Regelprofil). Das Reihen-Attribut `SCHIENEN` ist entfallen; `SCHIENEN_JE_GANG` am Bereich (Trolley) wird noch nicht ausgewertet. 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):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` |
@@ -152,6 +153,7 @@ Detaillierte Dokumentation aller Funktionen und Befehle: siehe `Lisp/README.md`.
| `fahrstrecke.dcl` | VORLAGE fuer OMNI_FAHRSTRECKE (GeruestInsert.lsp, `geruest-traeger-dialog`), gleiche Tile-Keys wie `geruest_traeger.dcl`, Platzhalter `//@VARIANTEN@` |
| `kettenfoerderer.dcl` | KETTENFOERDERER/KF_EDIT (KettenFoerderer.lsp, `kf-dialog`); VORLAGE mit Markerzeile `//@VARIANTEN@` (Typ als Radiobuttons, gefuellt per `ssg-dcl-vorlage-fuellen`), die uebrigen Auswahllisten sind `popup_list`-Tiles, die per `start_list`/`add_list` aus `data/json/kettenfoerderer.json` gefuellt werden. Der Knopf `hoehe_messen` schliesst den Dialog per `done_dialog 2` (Hoehe aus 2 Punkten messen, danach erneut zeigen) |
| `dinformate.dcl` | DIN_FORMATE (DinFormate.lsp) |
| `hw_ausgabe.dcl` | Ausgabeziel vor HW_MATERIALFLUSS/HW_SYMBOLE/HW_SYM_* (HwPlanung.lsp, `hw-ausgabe-waehlen`): Radiogruppe `modus` (`aktuell`/`ergaenzen`/`neu`), Dateiauswahl `datei` + Knopf `waehlen` (`done_dialog 2`), Name `name`, Fehlerzeile `error` |
| `lager.dcl` | LG_GESCHOSS/LG_GESCHOSS_KOPIEREN/LG_BEREICH/LG_REIHE/LG_GANG + Edit (LagerInsert.lsp, generischer Treiber `lg-dialog`); Dialoge `lg_geschoss`, `lg_geschoss_kopie`, `lg_bereich` (inkl. Randgaenge N/O/S/W), `lg_reihe`, `lg_gang`, Tile-Keys = Feldlisten `*lg-felder-*` |
| `vfl_edit.dcl` | Linienzug-Editieren/Fortsetzen (vf_linienzug.lsp, `vfl-dlg-position`, aufgerufen aus `vfl-edit-ent`) |
| `vfl_edit_modus.dcl` | Linienzug-Edit: Auswahl des Edit-Modus (vf_linienzug.lsp) |
@@ -221,8 +223,14 @@ Abhaengigkeiten: `ezdxf`, Standard-Library. Benoetigen Umgebungsvariablen (`DXFM
| `dxf_vf_abbild.py` | Abbild der `VF_`/`GF_`-Wrapperbloecke einer Testzeichnung: Attribute UND Bausteinzusammensetzung je Kette, Ketten ueber den Einfuegepunkt der Spec zugeordnet (der Blockname taugt nicht - die VF-Nummer beginnt in jeder Zeichnung neu bei 1). Regressionsnetz fuer Umbauten am Bau-Ablauf: `--referenz` vergleicht gegen `tests/reference/hundm05_attribute.json` (Exit 1 bei Abweichung), `--schreibe-referenz` frischt sie nach einem GEPRUEFTEN Lauf auf. Faengt genau das, was der Ergebnis-Record nicht sieht - eine vertauschte `vfl-acc-*`-Reihenfolge (Komma-Listen `L_VF_m`/`L_GF_m`/`GF_WINKEL`, in der Zeichnung unsichtbar, im Sivas-Export falsch) und eine falsche Blockvariante. Liest ASCII-DXF streamend; binaere DXF kann `dxf_scan_components.py`. Genutzt von `tests/test_vf_geometrie.py` |
| `vf_spec_export.py` | Uebersetzt ein Eingabe-Journal in eine **Spec** und zurueck (`spec_aus_journal` / `journal_aus_spec`) - Gegenstueck zu `vsp-journal` in `Lisp/vf_spec.lsp`, beide muessen dieselbe Tokenfolge liefern. Der Rundlauf `journal_aus_spec(spec_aus_journal(tok)) == tok` laeuft in `tests/test_vf_spec.py` gegen die 5 echten HundM-Ketten und beweist, dass die Spec das Journal verlustfrei abbildet. Schreibt das flache Spec-JSON (vom LISP-Parser lesbar). Objekte der Ziel-Datei, die keine Kette beschreiben (von Hand ergaenzte Anlagenteile mit `function`, z.B. Kreisel), werden beim Neuschreiben unveraendert ans Dateiende uebernommen - sonst waeren sie beim naechsten Lauf weg. Aufruf: `python vf_spec_export.py <protokoll.json> <spec.json>` — erzeugt `tests/testdata/hundm05.json` (Spec, Eingabe fuer `TEST_HUNDM05`/`VF_SPEC_BAU`) aus `tests/testdata/hm_recformat.json` (Aufzeichnung) |
| `dxf_anlage_export.py` | Gegenstueck zum Scanner: liest eine mit unseren Makros GEBAUTE (und von Hand nachbearbeitete) Anlage aus der Zeichnung zurueck in Nachbau-Testdaten - je Wrapper genau die Parameter, die auch dessen Edit-Befehl liest (KREISEL_n: Einfuegepunkt, `abstand` exakt aus der Blockdefinition, `drehrichtung` aus DREHRICHTUNG; GF_n: SSG_GF_EDIT + SEITE_AS/ES, endet die GF auf einer Kreisel-Kette, wird dessen Hoehe `hoehe_ende_mm`; VF_n `standard`/`etage`: mubea-Schema wie `vfs-edit-basis-lesen`; VF_n `linienzug`: Journal UNVERAENDERT als `"typ": "linienzug_journal"` - bewusst keine Spec, weil der Bau-Dialog neuer ist als die Spec-Grammatik; uebrige Bloecke als Einzel-Insert ohne `_2D`/`_3D`-Suffix). Reihenfolge Kreisel, VF, GF, Einzelbloecke (VF vor GF wie mubea). Aufruf: `python dxf_anlage_export.py <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 ...]` |
| (`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 - in BEIDEN Repos: dxfmakros fuehrt fuer `HW_SYMBOLE` (HwPlanung.lsp) eine reduzierte Kopie `cfg/io_regeln.json`. Die Symbol-NUMMERIERUNG (`create_numbers.py`, `cfg/enumerate.cfg`) liegt dagegen seit 2026-11-03 nur noch hier |
| `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). Alle erzeugten Objekte tragen XDATA `SSG_HW_MF`; ein Folgelauf ersetzt nur diese (bzw. bei Altdateien die HW_*-Layer). `--einheitenlos` setzt `$INSUNITS = 0` fuer die Zwischendatei der aktuellen Zeichnung (gilt ebenso fuer `hw_symbole.py`). Test `tests/test_hw_materialfluss.py`. Aufruf: `python hw_materialfluss.py <export.csv> <out_HW.dxf> [--layer-cfg ...]` |
<<<<<<< Updated upstream
=======
| `hw_symbole.py` | HW-Planung: setzt die E-Geraete-Symbole aus dem Plan `<dwg>_hw_symbole.json` (HW_SYMBOLE, HwPlanung.lsp) in die `_HW.dxf` (`hw_materialfluss.hw_dokument_oeffnen`: vorhandene Datei ergaenzen statt ueberschreiben). Entfernt vorher nur die erzeugten Symbole der Praefixe des Plans (XDATA `SSG_HW_SYM`), baut die Blockdefinitionen bei jedem Lauf neu aus `cfg/hw_symbole.json` (`block_aufbauen`: Rahmen, ATTDEFs, Textstil, Layer), **Lage** per `platzieren`: Marke `REALE_POSITION` mit x genau auf dem Zielpunkt, Kasten daneben, Symbole eines Bauteils luekenlos uebereinander (Reihenfolge `benoetigt`), Gruppe um das Ziel zentriert; Ueberlappung mit schon gesetzten Rahmen (2D-Bounding-Boxen, `platzierung` in hw_symbole.json) wird NUR in y ausgewichen, x bleibt auf dem Ziel; Plan immer vollstaendig, eingefuegt nur `praefixe`. IO = Vorlage, Platzhalter-ID in VERW/BEZEICHNUNG ersetzt (`attribut_werte`). Test `tests/test_hw_symbole.py` (inkl. Abdeckung io_regeln -> hw_symbole und Kette Symbole -> Renamer-Polylinie -> create_numbers). Aufruf: `python hw_symbole.py <plan.json> <out_HW.dxf> --symbole cfg/hw_symbole.json` |
| `create_numbers.py` | Nummerierung der Symbol-Vorlagen (`@`) entlang von Renamer-Bereichen (Rechteck mit Richtung oder `POLYLINE_PATH`), Layer/Parameter aus `cfg/enumerate.cfg`; Fehler/Warnungen als zwei einfache Listen `(art, text)` (`neue_meldungen`/`meldungen_als_dict`, Format der Fehlerdatei unveraendert) - KEINE Kopie von kabellaengens `error_collector.py`/`utils.py`; die wenigen Helfer daraus stehen einmalig im Modul. 2026-11-03 aus kabellaengen umgezogen (dort entfernt), die benoetigten Helfer aus kabellaengen/lib/utils.py stehen direkt im Modul, `--all-examples` entfallen. Nummer wird OKTAL gebildet (`oct(counter)`, so gewollt - nicht auf dezimal "korrigieren"). Genutzt von HW_NUMMERIEREN. Test `tests/test_create_numbers.py`. Aufruf: `python create_numbers.py --filename <dxf> [--config ...] [--info ...] [--errorfile ...] [--dryrun\|--overwrite]` |
| `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 ...]` |
>>>>>>> Stashed changes
| `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
+576 -43
View File
@@ -1,40 +1,426 @@
;; ============================================================
;; HwPlanung.lsp - Hardware-Planung (Menue "HW Planung")
;;
;; HW_MATERIALFLUSS ("Materialfluss erzeugen"):
;; 1. exportiert das aktuelle Hardware-Layout per EXPORTCSV
;; (csv:run-export, export.lsp) nach DXFM_RESULTS/<dwg>_export.csv
;; 2. ruft lib/hw_materialfluss.py auf, das daraus die Materialfluss-
;; Skizze (Kreisel-Rechtecke in Drehrichtung, Strecken-Polylinien
;; AS -> Separatoren -> ES, Weichen, Be-/Entladungen) zeichnet
;; 3. Ergebnis: DXFM_RESULTS/<dwg>_HW.dxf (neue Datei, die offene
;; Zeichnung bleibt unveraendert)
;; Ziel der Ausgabe: vor jedem Erzeugungsbefehl fragt der Dialog
;; dcl/hw_ausgabe.dcl (hw-ausgabe-waehlen), wohin:
;; - aktuelle Zeichnung: Python schreibt eine einheitenlose Zwischendatei
;; <dwg>_HW_tmp.dxf, die als Block bei 0,0 eingefuegt und aufgeloest wird
;; (hw-in-aktuelle-zeichnung); die fruehere Ausgabe (XDATA SSG_HW_MF bzw.
;; SSG_HW_SYM) wird danach entfernt
;; - bestehende Datei ergaenzen (Dateiauswahl)
;; - neue Datei (Name, Ordner DXFM_RESULTS)
;; In einer Datei ersetzt jeder Befehl nur seinen eigenen Anteil - was der
;; Anwender dort ergaenzt (Renamer-Rahmen, Polylinien), bleibt erhalten.
;;
;; HW_SYMBOLE ("Symbole erzeugen"): noch Platzhalter, zeigt nur einen Hinweis.
;; HW_MATERIALFLUSS ("Materialfluss erzeugen", in der Ursprungszeichnung):
;; EXPORTCSV -> lib/hw_materialfluss.py zeichnet Kreisel-Rechtecke in
;; Drehrichtung, Strecken-Polylinien AS -> Separatoren -> ES, Weichen und
;; Be-/Entladungen auf die HW_*-Layer der _HW.dxf.
;;
;; HW_SYMBOLE ("Symbole erzeugen", in der Ursprungszeichnung): sucht die
;; Mechanik-Bauteile nach cfg/io_regeln.json (auch verschachtelt in
;; VF_n/GF_n/KREISEL_n), schreibt den Symbolplan als JSON und laesst
;; lib/hw_symbole.py die Symbole in die _HW.dxf setzen - deren Aussehen und
;; Attribute stehen in cfg/hw_symbole.json (keine Symbol-Bibliothek). Die Geraete-ID ist noch eine VORLAGE (z.B. MB1@@@) -
;; nummeriert wird erst mit HW_NUMMERIEREN, entlang der Rahmen/Polylinien,
;; die der Anwender in der _HW.dxf zieht. Einzelbefehle HW_SYM_VENTILE usw.
;;
;; HW_NUMMERIEREN ("Symbole nummerieren", in der geoeffneten _HW.dxf):
;; speichert die Zeichnung, laesst lib/create_numbers.py (Renamer-Bereiche,
;; cfg/enumerate.cfg) die Nummern berechnen und traegt sie in die offene
;; Zeichnung ein.
;;
;; Setzt export.lsp voraus (laedt die MNL vor).
;; ============================================================
(setq *hw-skript* "hw_materialfluss.py")
(setq *hw-sym-skript* "hw_symbole.py")
(setq *hw-num-skript* "create_numbers.py")
;; Name der Ausgabedatei aus dem Zeichnungsnamen (reiner Kern, getestet):
;; "Mubea.dwg" -> "Mubea_HW.dxf"
;; XDATA der erzeugten Objekte (gesetzt von lib/hw_materialfluss.py bzw.
;; lib/hw_symbole.py) - daran erkennt hw-alte-ausgabe die fruehere Ausgabe
(setq *hw-mf-app* "SSG_HW_MF")
(setq *hw-sym-app* "SSG_HW_SYM")
;; Letzte Wahl im Ausgabe-Dialog (modus datei name), je Sitzung
(if (not (boundp '*hw-ausgabe-letzt*)) (setq *hw-ausgabe-letzt* nil))
;; Geraeteart -> Praefix (Einzelbefehle unten, noch nicht im Menue)
(setq *hw-sym-arten*
'(("ventile" "MB") ("sensoren" "BG") ("motoren" "MA")
("motorschutz" "FC") ("encoder" "GF") ("scanner" "BX")))
;; ------------------------------------------------------------
;; Reine Kerne (tests/test_unit.lsp, tu-tests-hw)
;; ------------------------------------------------------------
;; Name der Ausgabedatei aus dem Zeichnungsnamen: "Mubea.dwg" -> "Mubea_HW.dxf"
(defun hw-dxf-name (dwgname)
(strcat (vl-filename-base dwgname) "_HW.dxf")
)
(defun c:HW_MATERIALFLUSS ( / label out-dir csv-pfad dxf-pfad py-skript
py-exe log-pfad rc)
(setq label "HW_MATERIALFLUSS")
(if (null (getenv "DXFM_LIB"))
(princ (ssg-textf "exp-dxfm-lib-missing" (list label)))
;; Zahl fuer JSON, unabhaengig von DIMZIN/LUPREC (rtos wuerde bei DIMZIN-
;; Unterdrueckung der fuehrenden Null ".5" liefern - kein gueltiges JSON).
;; Auf 0.001 gerundet: (hw-json-zahl -12.5) -> "-12.500"
(defun hw-json-zahl (x / n r s)
(setq n (fix (+ (* (abs x) 1000.0) 0.5))
r (itoa (rem n 1000)))
(while (< (strlen r) 3) (setq r (strcat "0" r)))
(setq s (strcat (itoa (/ n 1000)) "." r))
(if (and (< x 0.0) (> n 0)) (strcat "-" s) s)
)
(defun hw-json-text (s)
(strcat "\"" (ssg-json-escape s) "\"")
)
;; Rollen-Objekt zum Rollennamen
(defun hw-sym-rolle (regeln name / erg)
(foreach r (ssg-json-get regeln "rollen")
(if (and (null erg) (= (ssg-json-get r "rolle") name)) (setq erg r)))
erg
)
;; Rollennamen eines Bauteils - je Eintrag in "benoetigt" die erste Alternative
(defun hw-sym-bauteil-rollen (bauteil)
(mapcar 'car (ssg-json-get bauteil "benoetigt"))
)
;; wcmatch-Muster aller Mechanik-Bloecke (gross, kommagetrennt)
(defun hw-sym-muster (regeln / muster)
(foreach b (ssg-json-get regeln "bauteile")
(setq muster (append muster (ssg-json-get b "bloecke"))))
(strcase (ssg-join muster ","))
)
;; Bauteil-Objekt zum Blocknamen (erster Treffer) oder nil
(defun hw-sym-bauteil-fuer-block (regeln blockname / erg)
(foreach b (ssg-json-get regeln "bauteile")
(if (and (null erg)
(wcmatch (strcase blockname)
(strcase (ssg-join (ssg-json-get b "bloecke") ","))))
(setq erg b)))
erg
)
;; Symbol (Blockname) einer Rolle, ggf. abhaengig vom Blocknamen des Bauteils
;; (block_nach_name, z.B. Vario-Motorstation rechts -> Motor VARIO rechts)
(defun hw-sym-block-fuer (rolle bauteil-blockname / erg)
(foreach v (ssg-json-get rolle "block_nach_name")
(if (and (null erg)
(wcmatch (strcase bauteil-blockname) (strcase (ssg-json-get v "muster"))))
(setq erg (ssg-json-get v "block"))))
(cond (erg) ((ssg-json-get rolle "block")))
)
;; IO-Vorlage eines Symbols: Praefix + io_vorlage, z.B. "MB" + "1@@@" -> "MB1@@@"
(defun hw-sym-io-vorlage (regeln praefix)
(strcat praefix (cond ((ssg-json-get regeln "io_vorlage")) ("1@@@")))
)
;; Zielpunkt eines Bauteils in der _HW.dxf: flach (Z = 0) - die _HW.dxf ist
;; ein Grundriss, die Nummerierung arbeitet in XY.
(defun hw-sym-ziel (pt)
(list (car pt) (cadr pt) 0.0)
)
;; Plan aus Bauteil-Instanzen. instanz = (bauteil blockname pt key).
;; Rueckgabe: Liste von Records, je Rolle in der Reihenfolge von "benoetigt"
;; (= Stapelfolge der Symbole am Bauteil, erstes oben):
;; (praefix io-vorlage symbolblock zielpunkt rolle key bauteilname)
;; Den Einfuegepunkt rechnet lib/hw_symbole.py aus dem Zielpunkt und der
;; Symbolgeometrie (Marke REALE_POSITION auf dem Ziel, ohne Ueberlappung).
(defun hw-sym-plan (regeln instanzen / erg rolle)
(foreach ins instanzen
(foreach name (hw-sym-bauteil-rollen (car ins))
(if (setq rolle (hw-sym-rolle regeln name))
(setq erg (cons (list (ssg-json-get rolle "praefix")
(hw-sym-io-vorlage regeln (ssg-json-get rolle "praefix"))
(hw-sym-block-fuer rolle (nth 1 ins))
(hw-sym-ziel (nth 2 ins))
name
(nth 3 ins)
(ssg-json-get (car ins) "bauteil"))
erg)))))
(reverse erg)
)
;; Sortierung der Instanzen: Bauteil-Reihenfolge im Regelwerk, dann x, dann y
(defun hw-sym-instanzen-sortieren (regeln instanzen / bauteile)
(setq bauteile (mapcar '(lambda (b) (ssg-json-get b "bauteil"))
(ssg-json-get regeln "bauteile")))
;; Rang vorab anhaengen (rang . instanz), sortieren, wieder abnehmen
(mapcar 'cdr
(vl-sort
(mapcar '(lambda (ins)
(cons (vl-position (ssg-json-get (car ins) "bauteil") bauteile) ins))
instanzen)
'(lambda (a b)
(cond ((/= (car a) (car b)) (< (car a) (car b)))
((not (equal (car (nth 3 a)) (car (nth 3 b)) 0.5))
(< (car (nth 3 a)) (car (nth 3 b))))
(t (< (cadr (nth 3 a)) (cadr (nth 3 b))))))))
)
;; Symbolplan als JSON-Zeilen fuer lib/hw_symbole.py. praefixe = die
;; Geraetearten, die dieser Lauf einfuegt bzw. ersetzt; der Plan selbst ist
;; immer vollstaendig, damit die Lage nicht davon abhaengt.
(defun hw-sym-plan->json (praefixe plan / zeilen)
(setq zeilen
(mapcar
'(lambda (r)
(strcat " {\"praefix\": " (hw-json-text (nth 0 r))
", \"io\": " (hw-json-text (nth 1 r))
", \"block\": " (hw-json-text (nth 2 r))
", \"x\": " (hw-json-zahl (car (nth 3 r)))
", \"y\": " (hw-json-zahl (cadr (nth 3 r)))
", \"rolle\": " (hw-json-text (nth 4 r))
", \"key\": " (hw-json-text (nth 5 r))
", \"bauteil\": " (hw-json-text (nth 6 r)) "}"))
plan))
(append
(list "{"
(strcat " \"praefixe\": [" (ssg-join (mapcar 'hw-json-text praefixe) ", ") "],")
" \"symbole\": [")
(if zeilen
(append (mapcar '(lambda (z) (strcat z ",")) (reverse (cdr (reverse zeilen))))
(list (last zeilen))))
(list " ]" "}"))
)
;; Name einer neuen Ausgabedatei: Leerzeichen am Rand und eine Endung .dxf
;; werden entfernt (" Anlage_HW.DXF " -> "Anlage_HW")
(defun hw-name-ohne-dxf (name / n)
(setq n (vl-string-trim " " (cond (name) (""))))
(if (wcmatch (strcase n) "*.DXF")
(substr n 1 (- (strlen n) 4))
n)
)
;; Gueltiger Dateiname: nicht leer, keine Pfad- oder Sonderzeichen
(defun hw-name-gueltig-p (name / n)
(setq n (hw-name-ohne-dxf name))
(and (> (strlen n) 0)
(not (wcmatch n "*[\\/:`*?\"<>|]*")))
)
(defun hw-neu-pfad (ordner name)
(strcat ordner "/" (hw-name-ohne-dxf name) ".dxf")
)
;; Eingaben des Ausgabe-Dialogs pruefen. datei-da / neu-da = ob die gewaehlte
;; bzw. die neue Datei schon existiert. Rueckgabe: nil oder ssg-text-Key.
(defun hw-ausgabe-pruefen (modus datei-da name neu-da)
(cond
((= modus "ergaenzen") (if datei-da nil "hw-aus-datei-fehlt"))
((= modus "neu")
(cond ((not (hw-name-gueltig-p name)) "hw-aus-name-ungueltig")
(neu-da "hw-aus-name-existiert")))
((= modus "aktuell") nil)
(t "hw-aus-datei-fehlt"))
)
;; Nummerierung uebertragen: passt ein Eintrag aus der create_numbers-Info
;; (old_value + Einfuegepunkt) auf ein Attribut? pos/wert aus der Zeichnung.
(defun hw-num-eintrag-p (eintrag wert pos / p)
(setq p (ssg-json-get eintrag "position"))
(and (= (ssg-json-get eintrag "old_value") wert)
(equal (ssg-json-get p "x") (car pos) 0.01)
(equal (ssg-json-get p "y") (cadr pos) 0.01))
)
;; ------------------------------------------------------------
;; Gemeinsam: Ausgabeordner und Python-Aufruf
;; ------------------------------------------------------------
;; DXFM_RESULTS, ausser waehrend TEST_EXPORT_ALL (*export-test-override*)
(defun hw-ausgabe-ordner ( / d)
(setq d (if *export-test-override* *export-test-override* (getenv "DXFM_RESULTS")))
(if (and d (not (vl-file-directory-p d))) (vl-mkdir d))
d
)
;; Python-Skript aus DXFM_LIB synchron ausfuehren, Log nach DXFM_LOG.
;; Rueckgabe: Exitcode (0 = ok) oder nil, wenn gar nicht gestartet.
;; ok-codes = Exitcodes, die KEIN Python-Fehler sind (create_numbers endet
;; mit 1, wenn es Planungsfehler meldet - die stehen in seiner Fehlerdatei).
(defun hw-python (label skript args log-name ok-codes / log-pfad rc)
(setq log-pfad (strcat (cond ((getenv "DXFM_LOG")) ((hw-ausgabe-ordner))) "/" log-name))
(setq rc (export:run-python (export:python-exe)
(strcat (getenv "DXFM_LIB") "/" skript) args log-pfad))
(if (null rc) (princ (ssg-textf "exp-python-call-failed" (list label))))
(export:log-ausgeben log-pfad 15)
(if (and rc (not (member rc (cons 0 ok-codes))))
(princ (ssg-textf "exp-python-error" (list label rc log-pfad))))
rc
)
(defun hw-umgebung-ok-p (label)
(if (getenv "DXFM_LIB")
T
(progn (princ (ssg-textf "exp-dxfm-lib-missing" (list label))) nil))
)
;; ------------------------------------------------------------
;; Ausgabeziel: Dialog dcl/hw_ausgabe.dcl
;; ------------------------------------------------------------
(defun hw-ausgabe-modus-tiles (modus)
(mode_tile "datei" (if (= modus "ergaenzen") 0 1))
(mode_tile "waehlen" (if (= modus "ergaenzen") 0 1))
(mode_tile "name" (if (= modus "neu") 0 1))
)
;; OK-Knopf: Eingaben pruefen, bei Fehler im Dialog bleiben. Arbeitet auf den
;; Variablen von hw-ausgabe-waehlen (modus datei name ordner pfad - AutoLISP
;; bindet dynamisch, der Rueckruf laeuft waehrend dessen start_dialog).
(defun hw-ausgabe-ok ( / fehler)
(setq modus (get_tile "modus")
datei (get_tile "datei")
name (get_tile "name"))
(setq fehler (hw-ausgabe-pruefen modus
(if (and (/= datei "") (findfile datei)) T)
name
(if (findfile (hw-neu-pfad ordner name)) T)))
(if fehler
(set_tile "error" (ssg-text fehler))
(progn
(setq out-dir (if *export-test-override*
*export-test-override*
(getenv "DXFM_RESULTS")))
(setq pfad (cond ((= modus "aktuell")
(strcat ordner "/" (vl-filename-base (getvar "DWGNAME")) "_HW_tmp.dxf"))
((= modus "ergaenzen") datei)
(t (hw-neu-pfad ordner name))))
(done_dialog 1)))
)
;; Ziel der Ausgabe erfragen. aktion = Text oben im Dialog.
;; Rueckgabe: (modus pfad) mit modus "aktuell"/"ergaenzen"/"neu" - bei
;; "aktuell" ist pfad die Zwischendatei - oder nil bei Abbruch. Ohne GUI
;; (automatische Tests): Standarddatei <dwg>_HW.dxf, anlegen oder ergaenzen.
(defun hw-ausgabe-waehlen (aktion / ordner modus datei name pfad dcl dat r f erg weiter)
(setq ordner (hw-ausgabe-ordner))
(cond
((not (ssg-gui-p))
(list "ergaenzen" (strcat ordner "/" (hw-dxf-name (getvar "DWGNAME")))))
((or (null (getenv "DXFM_DCL"))
(not (findfile (setq dcl (strcat (getenv "DXFM_DCL") "/hw_ausgabe.dcl"))))
(< (setq dat (load_dialog dcl)) 0))
(princ (ssg-textf "hw-aus-dcl-fehlt" (list (cond (dcl) ("hw_ausgabe.dcl")))))
nil)
(t
(setq modus (cond ((car *hw-ausgabe-letzt*)) ("neu"))
datei (cond ((cadr *hw-ausgabe-letzt*))
((strcat ordner "/" (hw-dxf-name (getvar "DWGNAME")))))
name (cond ((caddr *hw-ausgabe-letzt*))
((vl-filename-base (hw-dxf-name (getvar "DWGNAME")))))
weiter T)
(while weiter
(if (not (new_dialog "hw_ausgabe" dat))
(setq weiter nil)
(progn
(set_tile "aktion" aktion)
(set_tile "modus" modus)
(set_tile "datei" datei)
(set_tile "name" name)
(set_tile "ordner" (ssg-textf "hw-aus-ordner" (list ordner)))
(hw-ausgabe-modus-tiles modus)
(action_tile "modus" "(setq modus $value) (hw-ausgabe-modus-tiles modus) (set_tile \"error\" \"\")")
;; getfiled geht nicht aus einem Rueckruf: Dialog schliessen,
;; waehlen, Dialog neu zeigen
(action_tile "waehlen"
"(setq datei (get_tile \"datei\") name (get_tile \"name\")) (done_dialog 2)")
(action_tile "accept" "(hw-ausgabe-ok)")
(action_tile "cancel" "(done_dialog 0)")
(setq r (start_dialog))
(cond
((= r 2)
(if (setq f (getfiled (ssg-text "hw-aus-waehlen")
(if (and (/= datei "") (findfile datei)) datei (strcat ordner "/"))
"dxf" 0))
(setq datei f modus "ergaenzen")))
((= r 1) (setq weiter nil erg (list modus pfad)))
(t (setq weiter nil))))))
(unload_dialog dat)
(if erg (setq *hw-ausgabe-letzt* (list modus datei name)))
erg))
)
;; Zieldatei fuer Python vorbereiten: die Zwischendatei der aktuellen
;; Zeichnung wird jedesmal frisch angelegt. Rueckgabe: Pfad.
(defun hw-ziel-pfad (ziel)
(if (and (= (car ziel) "aktuell") (findfile (cadr ziel)))
(vl-file-delete (cadr ziel)))
(cadr ziel)
)
;; Zusatzargumente fuer Python: Zwischendatei ohne Einheit (sonst skaliert
;; BricsCAD sie beim Einfuegen in eine Zeichnung mit anderer Einheit)
(defun hw-zusatz-args (ziel)
(if (= (car ziel) "aktuell") '("--einheitenlos"))
)
;; Fruehere Ausgabe in der aktuellen Zeichnung: alle Objekte mit XDATA app,
;; bei praefixe nur die, deren erster 1000er-Wert (Praefix) darin steht.
(defun hw-alte-ausgabe (app praefixe / ss i e xd erg)
(if (setq ss (ssget "_X" (list (list -3 (list app)))))
(progn
(setq i 0)
(while (setq e (ssname ss i))
(setq xd (cdr (assoc app (cdr (assoc -3 (entget e (list app)))))))
(if (or (null praefixe) (member (cdr (assoc 1000 xd)) praefixe))
(setq erg (cons e erg)))
(setq i (1+ i)))))
erg
)
;; Zwischendatei als Block bei 0,0 einfuegen und aufloesen; erst wenn das
;; geklappt hat, die fruehere Ausgabe (alte) loeschen. Rueckgabe: T/nil.
(defun hw-in-aktuelle-zeichnung (pfad alte / doc obj teile blk leer)
(setq doc (vla-get-ActiveDocument (vlax-get-acad-object)))
(setq obj (vl-catch-all-apply 'vla-InsertBlock
(list (vla-get-ModelSpace doc) (vlax-3D-point '(0 0 0)) pfad 1.0 1.0 1.0 0.0)))
(if (vl-catch-all-error-p obj)
nil
(progn
;; Leere Zwischendatei (HW_SYM_LOESCHEN, keine Bauteile): nichts
;; aufzuloesen - ein Explode wuerde scheitern, gilt aber als Erfolg
(setq blk (vl-catch-all-apply 'vla-Item (list (vla-get-Blocks doc) (vl-filename-base pfad))))
(setq leer (and (not (vl-catch-all-error-p blk)) (= (vla-get-Count blk) 0)))
(if (not leer) (setq teile (vl-catch-all-apply 'vla-Explode (list obj))))
(vla-Delete obj)
(if (not (vl-catch-all-error-p blk)) (vl-catch-all-apply 'vla-Delete (list blk)))
(if (vl-catch-all-error-p teile)
nil
(progn
(foreach e alte (if (entget e) (entdel e)))
(vl-file-delete pfad)
T))))
)
;; Ergebnis eines erfolgreichen Python-Laufs melden bzw. in die aktuelle
;; Zeichnung uebernehmen. app/praefixe: welche fruehere Ausgabe ersetzt wird.
(defun hw-ergebnis-uebernehmen (ziel app praefixe meldung / alte)
(if (= (car ziel) "aktuell")
(progn
(setq alte (hw-alte-ausgabe app praefixe))
(if (hw-in-aktuelle-zeichnung (cadr ziel) alte)
(princ (ssg-textf "hw-fertig-aktuell" (list (itoa (length alte)))))
(princ (ssg-textf "hw-aktuell-fehler" (list (cadr ziel))))))
(princ (ssg-textf meldung (list (cadr ziel)))))
)
;; ------------------------------------------------------------
;; MATERIALFLUSS
;; ------------------------------------------------------------
(defun c:HW_MATERIALFLUSS ( / label ziel out-dir csv-pfad dxf-pfad)
(setq label "HW_MATERIALFLUSS")
(if (and (hw-umgebung-ok-p label)
(setq ziel (hw-ausgabe-waehlen (ssg-text "hw-aus-materialfluss"))))
(progn
(setq out-dir (hw-ausgabe-ordner))
(setq csv-pfad (strcat out-dir "/" (export:dwg-praefix)
(export:filename "csv_csv")))
(setq dxf-pfad (strcat out-dir "/" (hw-dxf-name (getvar "DWGNAME"))))
(setq dxf-pfad (hw-ziel-pfad ziel))
;; Alte CSV entfernen - sonst wuerde nach einem gescheiterten Export
;; still die Skizze aus dem vorigen Stand gezeichnet.
(if (findfile csv-pfad) (vl-file-delete csv-pfad))
@@ -43,32 +429,179 @@
(if (not (findfile csv-pfad))
(princ (ssg-textf "hw-csv-fehlt" (list csv-pfad)))
(progn
(setq py-skript (strcat (getenv "DXFM_LIB") "/" *hw-skript*))
(setq py-exe (export:python-exe))
(setq log-pfad (strcat (cond ((getenv "DXFM_LOG")) (out-dir))
"/hw_materialfluss.log"))
(setq rc (export:run-python py-exe py-skript
(list csv-pfad dxf-pfad) log-pfad))
(cond
((null rc)
(princ (ssg-textf "exp-python-call-failed" (list label))))
((/= rc 0)
(princ (ssg-textf "exp-python-error" (list label rc log-pfad)))
(export:log-ausgeben log-pfad 12))
(t
(export:log-ausgeben log-pfad 12)
(princ (ssg-textf "hw-fertig" (list dxf-pfad))))
)
)
)
)
)
(if (equal 0 (hw-python label *hw-skript*
(append (list csv-pfad dxf-pfad) (hw-zusatz-args ziel))
"hw_materialfluss.log" nil))
(hw-ergebnis-uebernehmen ziel *hw-mf-app* nil "hw-fertig")))))
(princ)
)
(defun c:HW_SYMBOLE ()
(alert (ssg-text "hw-symbole-offen"))
;; ------------------------------------------------------------
;; SYMBOLE ERZEUGEN (HW_SYMBOLE + Einzelbefehle je Geraeteart)
;; ------------------------------------------------------------
(defun hw-sym-regeln-laden ( / pfad regeln)
(setq pfad (strcat (cond ((getenv "DXFM_CFG")) ("")) "/io_regeln.json"))
(setq regeln (if (findfile pfad) (ssg-json-laden pfad)))
(if (null regeln) (princ (ssg-textf "hw-sym-regeln-fehlt" (list pfad))))
regeln
)
;; Alle Bauteile der Ursprungszeichnung: top-level INSERTs und in VF_n/GF_n/
;; KREISEL_n verschachtelte Mechanik-Bloecke (Weltlage ueber
;; csv:wrapper-welt-records, export.lsp). Katalogsymbole (ILS_*) werden
;; nicht durchsucht. Rueckgabe: sortierte Liste (bauteil blockname pt key).
(defun hw-sym-bauteile-sammeln (regeln / muster ss i ename ed name bt erg)
(setq muster (hw-sym-muster regeln))
(if (setq ss (ssget "_X" '((0 . "INSERT") (410 . "Model"))))
(progn
(setq i 0)
(while (setq ename (ssname ss i))
(setq ed (entget ename)
name (cdr (assoc 2 ed)))
(cond
((wcmatch (strcase name) muster)
(if (setq bt (hw-sym-bauteil-fuer-block regeln name))
(setq erg (cons (list bt name (trans (cdr (assoc 10 ed)) ename 0)
(cdr (assoc 5 ed)))
erg))))
((not (wcmatch (strcase name) "ILS_*,`**"))
(foreach rec (csv:wrapper-welt-records ename muster)
(if (setq bt (hw-sym-bauteil-fuer-block regeln (nth 2 rec)))
(setq erg (cons (list bt (nth 2 rec) (nth 3 rec)
(strcat (cdr (assoc 5 ed)) "/"
(cdr (assoc 5 (entget (nth 1 rec))))))
erg))))))
(setq i (1+ i)))))
(hw-sym-instanzen-sortieren regeln erg)
)
(defun hw-sym-zaehlen (schluessel alist / alt)
(setq alt (assoc schluessel alist))
(if alt
(subst (cons schluessel (1+ (cdr alt))) alt alist)
(cons (cons schluessel 1) alist))
)
;; Symbolplan als JSON schreiben und hw_symbole.py die Symbole der
;; angegebenen Praefixe ins gewaehlte Ziel setzen lassen (nil = alle Praefixe
;; des Regelwerks). mit-bauteilen = nil -> leerer Plan (nur entfernen).
(defun hw-sym-erzeugen (praefixe mit-bauteilen / label regeln instanzen plan out-dir
json-pfad dxf-pfad sym-pfad fh bt-zahl ziel)
(setq label "HW_SYMBOLE")
(if (and (hw-umgebung-ok-p label)
(setq regeln (hw-sym-regeln-laden))
(setq ziel (hw-ausgabe-waehlen (ssg-text "hw-aus-symbole"))))
(progn
(if (null praefixe)
(foreach r (ssg-json-get regeln "rollen")
(if (not (member (ssg-json-get r "praefix") praefixe))
(setq praefixe (append praefixe (list (ssg-json-get r "praefix")))))))
(if mit-bauteilen
(progn
(setq instanzen (hw-sym-bauteile-sammeln regeln))
(if (null instanzen) (princ (ssg-text "hw-sym-keine-bauteile")))
(foreach ins instanzen
(setq bt-zahl (hw-sym-zaehlen (ssg-json-get (car ins) "bauteil") bt-zahl)))
(foreach z (reverse bt-zahl)
(princ (ssg-textf "hw-sym-bauteile" (list (car z) (itoa (cdr z))))))
(setq plan (hw-sym-plan regeln instanzen))))
(setq out-dir (hw-ausgabe-ordner)
json-pfad (strcat out-dir "/" (vl-filename-base (getvar "DWGNAME")) "_hw_symbole.json")
dxf-pfad (hw-ziel-pfad ziel)
sym-pfad (strcat (cond ((getenv "DXFM_CFG")) ("")) "/hw_symbole.json"))
(if (not (setq fh (open json-pfad "w")))
(princ (ssg-textf "exp-file-open-error" (list label json-pfad)))
(progn
(foreach z (hw-sym-plan->json praefixe plan) (write-line z fh))
(close fh)
(if (equal 0 (hw-python label *hw-sym-skript*
(append (list json-pfad dxf-pfad "--symbole" sym-pfad)
(hw-zusatz-args ziel))
"hw_symbole.log" nil))
(hw-ergebnis-uebernehmen ziel *hw-sym-app* praefixe "hw-fertig-symbole"))))))
(princ)
)
(defun hw-sym-art (art)
(hw-sym-erzeugen (cdr (assoc art *hw-sym-arten*)) T)
)
;; Menue: HW Planung -> Symbole erzeugen
(defun c:HW_SYMBOLE () (hw-sym-erzeugen nil T))
;; Einzelbefehle je Geraeteart - noch nicht im Menue, zum schrittweisen
;; Erzeugen und Pruefen. Jeder ersetzt in der _HW.dxf nur die Symbole
;; seiner Art.
(defun c:HW_SYM_VENTILE () (hw-sym-art "ventile"))
(defun c:HW_SYM_SENSOREN () (hw-sym-art "sensoren"))
(defun c:HW_SYM_MOTOREN () (hw-sym-art "motoren"))
(defun c:HW_SYM_MOTORSCHUTZ () (hw-sym-art "motorschutz"))
(defun c:HW_SYM_ENCODER () (hw-sym-art "encoder"))
(defun c:HW_SYM_SCANNER () (hw-sym-art "scanner"))
;; Alle erzeugten Symbole aus dem gewaehlten Ziel entfernen
(defun c:HW_SYM_LOESCHEN () (hw-sym-erzeugen nil nil))
;; ------------------------------------------------------------
;; NUMMERIEREN (in der geoeffneten _HW.dxf)
;; ------------------------------------------------------------
;; Nummern aus der create_numbers-Info in die offene Zeichnung eintragen:
;; jedes Attribut mit '@', dessen Wert und Einfuegepunkt zu einem Eintrag
;; passen, bekommt new_value. Rueckgabe: Anzahl geaenderter Attribute.
(defun hw-num-anwenden (eintraege / ss i e pos obj ed wert treffer n)
(setq n 0)
(if (setq ss (ssget "_X" '((0 . "INSERT") (66 . 1) (410 . "Model"))))
(progn
(setq i 0)
(while (setq e (ssname ss i))
(setq pos (cdr (assoc 10 (entget e))))
(setq obj (entnext e))
(while (and obj (= (cdr (assoc 0 (setq ed (entget obj)))) "ATTRIB"))
(setq wert (cdr (assoc 1 ed)))
(if (vl-string-search "@" wert)
(progn
(setq treffer nil)
(foreach z eintraege
(if (and (null treffer) (hw-num-eintrag-p z wert pos)) (setq treffer z)))
(if treffer
(progn
(entmod (subst (cons 1 (ssg-json-get treffer "new_value")) (assoc 1 ed) ed))
(setq n (1+ n))))))
(setq obj (entnext obj)))
(entupd e)
(setq i (1+ i)))))
n
)
(defun c:HW_NUMMERIEREN ( / label pfad basis info-pfad fehler-pfad rc info n)
(setq label "HW_NUMMERIEREN")
(setq pfad (strcat (getvar "DWGPREFIX") (getvar "DWGNAME")))
(cond
((not (hw-umgebung-ok-p label)))
((or (= (getvar "DWGTITLED") 0)
(/= (strcase (vl-filename-extension pfad)) ".DXF"))
(princ (ssg-textf "hw-num-kein-dxf" (list (getvar "DWGNAME")))))
(t
;; create_numbers liest die Datei von der Platte - erst speichern
(command "_.QSAVE")
(setq basis (strcat (getvar "DWGPREFIX") (vl-filename-base pfad))
info-pfad (strcat basis "_nummern.json")
fehler-pfad (strcat basis "_nummern_fehler.json"))
(foreach f (list info-pfad fehler-pfad) (if (findfile f) (vl-file-delete f)))
;; --dryrun: create_numbers schreibt keine DXF, nur die Info - die
;; Nummern werden hier in die OFFENE Zeichnung eingetragen.
(setq rc (hw-python label *hw-num-skript*
(list "--filename" pfad "--dryrun"
"--info" info-pfad "--errorfile" fehler-pfad)
"hw_nummerieren.log" '(1)))
(if (and rc (setq info (if (findfile info-pfad) (ssg-json-laden info-pfad))))
(progn
(setq n (hw-num-anwenden (ssg-json-get info "renamed_symbols")))
(princ (ssg-textf "hw-num-ergebnis" (list (itoa n))))))
(if (findfile fehler-pfad)
(princ (ssg-textf "hw-num-fehler" (list fehler-pfad))))))
(princ)
)
+16
View File
@@ -926,6 +926,22 @@ Details (aufgerichtete Geometrie, Attribut-Schema, Sivas-Export): `../CLAUDE.md`
---
### `HwPlanung.lsp` – HW-Planung (Materialfluss, Symbole, Nummerierung)
| Befehl | Beschreibung |
| --- | --- |
| `c:HW_MATERIALFLUSS` | In der Ursprungszeichnung: EXPORTCSV -> `lib/hw_materialfluss.py`, Materialfluss-Polylinien in `<dwg>_HW.dxf` |
| `c:HW_SYMBOLE` | In der Ursprungszeichnung: Bauteile nach `cfg/io_regeln.json` suchen, `lib/hw_symbole.py` setzt die Symbole (Aussehen aus `cfg/hw_symbole.json`, IO-Vorlage `MB1@@@` usw.) in `<dwg>_HW.dxf` |
| `c:HW_SYM_VENTILE` / `_SENSOREN` / `_MOTOREN` / `_MOTORSCHUTZ` / `_ENCODER` / `_SCANNER` | Nur eine Geraeteart (MB/BG/MA/FC/GF/BX) erzeugen bzw. ersetzen (nicht im Menue) |
| `c:HW_SYM_LOESCHEN` | Alle erzeugten Symbole aus `<dwg>_HW.dxf` entfernen (nicht im Menue) |
| `c:HW_NUMMERIEREN` | In der geoeffneten `_HW.dxf`: speichern, `lib/create_numbers.py` nummeriert entlang der Renamer-Bereiche, Nummern werden in die offene Zeichnung eingetragen |
Vor HW_MATERIALFLUSS, HW_SYMBOLE und HW_SYM_* fragt `dcl/hw_ausgabe.dcl`, wohin: aktuelle Zeichnung, bestehende Datei ergaenzen oder neue Datei.
Ablauf, Renamer-Bereiche und Regelwerk: `../doc/hw_planung.md`.
---
### Werkzeuge: `ssg_stueckliste.lsp`, `DinFormate.lsp`
| Befehl | Beschreibung |
+8
View File
@@ -0,0 +1,8 @@
[Layers]
ILS_RENAMER
Omniflo_RENAMER
[Parameters]
# Maximale Distanz (in DXF-Einheiten) eines Symbols zur Polylinie bei POLYLINE_PATH
# Symbole die weiter entfernt sind, werden nicht zur Nummerierung berücksichtigt
polyline_max_distance = 500.0
+220
View File
@@ -0,0 +1,220 @@
{
"_kommentar": "Symboldefinitionen der HW-Planung. lib/hw_symbole.py baut daraus die Blockdefinitionen in der <dwg>_HW.dxf selbst - es gibt keine Symbol-Bibliothek als Referenzdokument. Abgeleitet aus den ILS-Katalogsymbolen in kabellaengen/doc/Symbole/ILS-Symbole.dxf (Stand 2026-11-03); aendert sich dort ein Symbol, hier nachziehen. Welches Symbol an welchem Bauteil sitzt, steht in cfg/io_regeln.json (rollen -> block). Bei jedem Lauf wird die Blockdefinition neu aus dieser Datei aufgebaut.",
"_textstile": "Textstile der Attribute (Name -> Schriftdatei); werden in der _HW.dxf angelegt, falls sie fehlen. Ohne \"stil\" am Attribut gilt standard.",
"textstile": {
"standard": "ARIAL",
"schriften": {"ARIAL": "arial.ttf", "MtXpl_Arial_Narrow": "arialn.ttf"}
},
"_platzierung": "Lage der Symbole in der _HW.dxf (lib/hw_symbole.py, platzieren): die Marke REALE_POSITION jedes Symbols liegt mit ihrem x genau auf dem Bauteil (Sensor, AS/ES-Element, Motor-/Kreiselzentrum), der Kasten daneben; mehrere Symbole eines Bauteils stehen uebereinander (Reihenfolge = benoetigt in cfg/io_regeln.json), als Gruppe um das Bauteil zentriert. Ueberlappt eine Gruppe mit einer schon gesetzten (Rahmen mit Mindestabstand abstand), wird sie in y um +-schritt, +-2*schritt ... bis max_verschiebung verschoben (mm).",
"platzierung": {"abstand": 20.0, "schritt": 50.0, "max_verschiebung": 3000.0},
"_vorlagen": "Gemeinsames Aussehen mehrerer Symbole, uebernommen aus je einem Katalogsymbol (io_750: ILS_Separator-Ausgang, geraet_800: ILS_829624040-Encoder-800). rahmen = [Breite, Hoehe] ab Rahmen-Ecke links unten; attribute = Reihenfolge, Lage (x, y relativ zur Rahmen-Ecke), Texthoehe. Optional je Attribut: unsichtbar, farbe (Zahl; \"symbol\" = Farbe des Symbols, fehlt = VONLAYER), layer (fehlt = Layer des Symbols), ausrichtung (C/R = Grundlinie mittig/rechts, ML/MC/MR = Mitte links/mittig/rechts; fehlt = links), stil, breite (Breitenfaktor), wert (Vorgabe). schraffur = Fuellmuster im Rahmen.",
"vorlagen": {
"io_750": {
"rahmen": [750.0, 270.0],
"attribute": [
{"tag": "SPS", "x": 0.0, "y": 130.4, "hoehe": 125.0, "farbe": "symbol"},
{"tag": "IO", "x": 103.9, "y": 129.2, "hoehe": 125.0, "farbe": "symbol"},
{"tag": "VERW", "x": 27.7, "y": 20.8, "hoehe": 70.0, "farbe": "symbol"},
{"tag": "BEZEICHNUNG", "x": 1.1, "y": -83.8, "hoehe": 60.0, "unsichtbar": true, "farbe": 7},
{"tag": "KENNZEICHNUNG", "x": 1.1, "y": -163.8, "hoehe": 60.0, "unsichtbar": true, "farbe": 7},
{"tag": "ARTINR", "x": 1.1, "y": -248.6, "hoehe": 60.0, "unsichtbar": true, "farbe": 7},
{"tag": "BESCHR", "x": 1.1, "y": -336.9, "hoehe": 60.0, "unsichtbar": true, "farbe": 7},
{"tag": "TEXT-D", "x": 780.4, "y": 190.4, "hoehe": 60.0, "layer": "ILS_Text-D"},
{"tag": "TEXT-E", "x": 772.9, "y": 110.4, "hoehe": 60.0, "layer": "ILS_Text-E"},
{"tag": "TEXT-ES", "x": 780.4, "y": 30.4, "hoehe": 60.0, "layer": "ILS_Text-ES"},
{"tag": "TEXT-F", "x": 780.4, "y": 30.4, "hoehe": 60.0, "layer": "ILS_Text-F"},
{"tag": "TEXT-IT", "x": 780.4, "y": 30.4, "hoehe": 60.0, "layer": "ILS_Text-IT"},
{"tag": "TEXT-CS", "x": 780.4, "y": 30.4, "hoehe": 60.0, "layer": "ILS_Text-CS"},
{"tag": "ID", "x": 371.2, "y": 0.0, "hoehe": 80.0, "unsichtbar": true, "farbe": 7},
{"tag": "REALE_POSITION", "x": 750.0, "y": 135.0, "hoehe": 50.0, "farbe": 7, "ausrichtung": "MR", "wert": "x"}
]
},
"geraet_800": {
"rahmen": [800.0, 350.0],
"attribute": [
{"tag": "SPS", "x": 0.0, "y": 244.1, "hoehe": 140.0, "farbe": "symbol", "ausrichtung": "ML", "stil": "MtXpl_Arial_Narrow"},
{"tag": "IO", "x": 400.0, "y": 244.1, "hoehe": 140.0, "farbe": "symbol", "ausrichtung": "MC", "stil": "MtXpl_Arial_Narrow"},
{"tag": "VERW", "x": 400.0, "y": 77.2, "hoehe": 100.0, "farbe": "symbol", "ausrichtung": "MC", "breite": 0.8},
{"tag": "KENNZEICHNUNG", "x": 10.3, "y": -253.8, "hoehe": 60.0, "unsichtbar": true, "farbe": 7},
{"tag": "ARTINR", "x": 10.3, "y": -88.9, "hoehe": 60.0, "unsichtbar": true, "farbe": 7},
{"tag": "BESCHR", "x": 10.3, "y": -173.8, "hoehe": 60.0, "unsichtbar": true, "farbe": 7},
{"tag": "ID", "x": 0.0, "y": 0.0, "hoehe": 80.0, "unsichtbar": true, "farbe": 7},
{"tag": "REALE_POSITION", "x": 800.0, "y": 175.0, "hoehe": 50.0, "farbe": 7, "ausrichtung": "MR", "wert": "x"}
],
"schraffur": {"muster": "INSULATION", "massstab": 0.25}
}
},
"_symbole": "Blockname -> vorlage, layer, farbe (fuer Attribute mit farbe \"symbol\"), versatz (Lage der Rahmen-Ecke zum Einfuegepunkt: Eingaenge/Motoren haengen im Katalog nach unten, [0, -270]), attribute (Abweichungen von der Vorlage je Tag), ohne (Vorlagen-Attribute, die dieses Symbol nicht hat), schraffur_layer/-farbe, werte. IO traegt die Platzhalter-ID (z.B. MB0): hw_symbole.py ersetzt sie in IO, VERW und BEZEICHNUNG durch die ID-Vorlage aus cfg/io_regeln.json (z.B. MB1@@@). Umlaute als \\u-Escape, damit die Datei ASCII bleibt.",
"symbole": {
"ILS_Separator-Ausgang": {
"vorlage": "io_750",
"layer": "ILS_Ausgang",
"farbe": 3,
"werte": {"SPS": "1", "IO": "MB0", "VERW": "Sep", "BEZEICHNUNG": "Separator ILS-VARIO (MA0)", "KENNZEICHNUNG": "=A01+UC0000-KF1DQ1", "ARTINR": "929012603", "BESCHR": "WEGEVENT.5/2 MONOSTABIL SY5120-5WAOU-C6F-Q"}
},
"ILS_Weiche-Abzweig-Ausgang": {
"vorlage": "io_750",
"layer": "ILS_Ausgang",
"farbe": 3,
"versatz": [0.0, -0.7],
"attribute": {
"IO": {"y": 130.4},
"VERW": {"x": 27.0},
"BEZEICHNUNG": {"x": 2.0, "y": -91.2},
"KENNZEICHNUNG": {"x": 2.0, "y": -171.2},
"ARTINR": {"x": 2.0, "y": -247.8},
"BESCHR": {"x": 2.0, "y": -327.8},
"TEXT-E": {"y": 111.2},
"TEXT-IT": {"layer": "TEXT-IT"},
"ID": {"x": 0.0, "y": 0.7}
},
"werte": {"SPS": "1", "IO": "MB0", "VERW": "Weiche Abzweig", "BEZEICHNUNG": "Weiche Abzweig ILS-VARIO", "KENNZEICHNUNG": "=A01+UC0000-KF1DQ1", "ARTINR": "929012603", "BESCHR": "WEGEVENT.5/2 MONOSTABIL SY5120-5WAOU-C6F-Q"}
},
"ILS_720002003-Separator": {
"vorlage": "io_750",
"layer": "ILS_Eingang",
"farbe": 6,
"versatz": [0.0, -270.0],
"attribute": {
"VERW": {"x": 17.0},
"BEZEICHNUNG": {"x": -5.7, "y": -83.0},
"KENNZEICHNUNG": {"x": -5.7, "y": -171.3},
"ARTINR": {"x": -5.7, "y": -258.1},
"BESCHR": {"x": -5.7, "y": -338.1}
},
"werte": {"SPS": "1", "IO": "BG0", "VERW": "In Sep", "BEZEICHNUNG": "in Separator ILS-VARIO (MA0)", "KENNZEICHNUNG": "=A01+UC0000-KF1DI1", "ARTINR": "720002003", "BESCHR": "Sensor Induk. M18x1, SA=12mm N-B\u00dc/NO, Kabel-150mm"}
},
"ILS_720002011-Rad dreht": {
"vorlage": "io_750",
"layer": "ILS_Eingang",
"farbe": 6,
"attribute": {
"IO": {"y": 130.4},
"VERW": {"x": 23.0},
"BEZEICHNUNG": {"x": 0.0, "y": -67.0},
"KENNZEICHNUNG": {"x": 0.0, "y": -147.0},
"ARTINR": {"x": 0.0, "y": -227.0},
"BESCHR": {"x": 0.0, "y": -307.0},
"TEXT-IT": {"farbe": 7},
"ID": {"x": 0.0, "y": 0.7}
},
"werte": {"SPS": "1", "IO": "BG0", "VERW": "Rad dreht", "BEZEICHNUNG": "Rad dreht ILS-KR (MA0)", "KENNZEICHNUNG": "=A01+UC0000-KF1DI1", "ARTINR": "720002011", "BESCHR": "Sensor Induk. M18 8mm b\u00fcndig/PNP-NO/ST M12"}
},
"ILS_200000375-Finger in Position": {
"vorlage": "io_750",
"layer": "ILS_Eingang",
"farbe": 6,
"attribute": {
"SPS": {"x": 10.0},
"IO": {"y": 130.4},
"VERW": {"x": 29.3, "breite": 0.85},
"BEZEICHNUNG": {"x": -1.0, "y": -67.0},
"KENNZEICHNUNG": {"x": -1.0, "y": -149.7},
"ARTINR": {"x": -1.0, "y": -239.4},
"BESCHR": {"x": -1.0, "y": -319.4},
"TEXT-F": {"layer": "TEXT-F"},
"TEXT-IT": {"farbe": 7},
"ID": {"x": 400.0, "ausrichtung": "R"}
},
"werte": {"SPS": "1", "IO": "BG0", "VERW": "Finger in Position", "BEZEICHNUNG": "Finger in Position", "KENNZEICHNUNG": "=A01+UC0000-KF1DI1", "ARTINR": "200000375", "BESCHR": "Halter mit Sensor f\u00fcr Abfrage VARIO Finger"}
},
"ILS_200000248-PC in Position": {
"vorlage": "io_750",
"layer": "ILS_Eingang",
"farbe": 6,
"attribute": {
"VERW": {"x": 18.4},
"BEZEICHNUNG": {"x": 0.0, "y": -67.0},
"KENNZEICHNUNG": {"x": 0.0, "y": -147.0},
"ARTINR": {"x": 0.0, "y": -227.0},
"BESCHR": {"x": 0.0, "y": -307.0},
"ID": {"x": 375.0}
},
"werte": {"SPS": "1", "IO": "BG0", "VERW": "PC in Position", "BEZEICHNUNG": "PC in Position (MA0)", "KENNZEICHNUNG": "=A01+UC0000-KF1DI1", "ARTINR": "200000248", "BESCHR": "Lichttaster LP+SP als Staumelder"}
},
"ILS_Motorschutz": {
"vorlage": "io_750",
"layer": "ILS_Eingang",
"ohne": ["ARTINR", "BESCHR"],
"attribute": {
"IO": {"y": 130.4},
"VERW": {"x": 21.4},
"BEZEICHNUNG": {"x": -10.1, "y": -67.0},
"KENNZEICHNUNG": {"x": -10.1, "y": -155.3},
"TEXT-E": {"x": 780.4},
"ID": {"x": 375.0}
},
"werte": {"SPS": "1", "IO": "FC0", "VERW": "MS FC0", "BEZEICHNUNG": "MSS ausgel\u00f6st FC0", "KENNZEICHNUNG": "=A01+UH01-KF1DI1"}
},
"ILS_Motor-400102811-Kreisel": {
"vorlage": "io_750",
"layer": "ILS_Motor",
"versatz": [0.0, -270.0],
"attribute": {
"IO": {"y": 130.4},
"VERW": {"x": 6.9, "breite": 0.9},
"BEZEICHNUNG": {"x": 0.0, "y": -79.3},
"KENNZEICHNUNG": {"x": 0.0, "y": -171.6},
"ARTINR": {"x": 0.0, "y": -250.7},
"BESCHR": {"x": 0.0, "y": -334.5},
"ID": {"x": 375.0}
},
"werte": {"SPS": "1", "IO": "MA0", "VERW": "ILS-KR-MA0", "BEZEICHNUNG": "Motor ILS-KR-MA0", "KENNZEICHNUNG": "=A01+UH01-KF1FDQ1", "ARTINR": "400102811", "BESCHR": "Kegelradgetriebemotor KF47 DRN80MK4/ASE1/Z Pos.1"}
},
"ILS_Motor-200000020-VARIO-links": {
"vorlage": "io_750",
"layer": "ILS_Motor",
"versatz": [0.0, -270.0],
"attribute": {
"IO": {"y": 130.4},
"VERW": {"x": 6.9, "breite": 0.85},
"BEZEICHNUNG": {"x": 0.0, "y": -90.5},
"KENNZEICHNUNG": {"x": 0.0, "y": -182.8},
"ARTINR": {"x": 0.0, "y": -278.8},
"BESCHR": {"x": 0.0, "y": -362.6},
"TEXT-E": {"x": 780.4},
"ID": {"x": 375.0}
},
"werte": {"SPS": "1", "IO": "MA0", "VERW": "ILS-VARIO-0,55kW", "BEZEICHNUNG": "Motor MA0", "KENNZEICHNUNG": "=A01+UH01-KF1FDQ1", "ARTINR": "200000020", "BESCHR": "Schneckengetriebem. SA57 DRN80MK4/ASE1 VARIO LI 0,55 KW, v=29 bei 50Hz, links"}
},
"ILS_Motor-200000021-VARIO-rechts": {
"vorlage": "io_750",
"layer": "ILS_Motor",
"versatz": [0.0, -270.0],
"attribute": {
"IO": {"y": 130.4},
"VERW": {"x": 6.9, "breite": 0.85},
"BEZEICHNUNG": {"x": 0.0, "y": -90.5},
"KENNZEICHNUNG": {"x": 0.0, "y": -182.8},
"ARTINR": {"x": 0.0, "y": -278.8},
"BESCHR": {"x": 0.0, "y": -362.6},
"TEXT-E": {"x": 780.4},
"ID": {"x": 375.0}
},
"werte": {"SPS": "1", "IO": "MA0", "VERW": "ILS-VARIO-0,55kW", "BEZEICHNUNG": "Motor MA0", "KENNZEICHNUNG": "=A01+UH01-KF1FDQ1", "ARTINR": "200000021", "BESCHR": "Schneckengetriebem. SA57 DRN80MK4/ASE1 VARIO RE 0,55 KW, v=29 bei 50Hz, rechts"}
},
"ILS_829624040-Encoder-800": {
"vorlage": "geraet_800",
"layer": "ILS_Encoder-800",
"farbe": 1,
"werte": {"IO": "GF0", "VERW": "192.168.1.100", "KENNZEICHNUNG": "=A01+UH01-KF1DQ1", "ARTINR": "829624040", "BESCHR": "Encoder f\u00fcr ILS 2.0 Rad-800"}
},
"ILS_Scanner-829534306": {
"vorlage": "geraet_800",
"layer": "ILS_Scanner",
"schraffur_layer": "0",
"schraffur_farbe": 40,
"attribute": {
"SPS": {"y": 254.4, "hoehe": 125.0, "stil": null},
"IO": {"y": 254.4, "hoehe": 150.0},
"VERW": {"y": 81.3},
"KENNZEICHNUNG": {"x": -1.0, "y": -245.7},
"ARTINR": {"x": -1.0, "y": -85.7},
"BESCHR": {"x": -1.0, "y": -165.7},
"ID": {"x": 450.0, "ausrichtung": "R"},
"REALE_POSITION": {"y": 135.0}
},
"werte": {"IO": "BX0", "VERW": "192.168.1.100", "KENNZEICHNUNG": "=A01+UH01-KF1FDQ1", "ARTINR": "829534306", "BESCHR": "Camera DM280 m. Halter (ILS) - BG"}
}
}
}
+42
View File
@@ -0,0 +1,42 @@
{
"_kommentar": "Regelwerk fuer die Symbolerzeugung der HW-Planung (Lisp/HwPlanung.lsp, HW_SYMBOLE): welche E-Geraete (Sensoren, Aktoren, Motoren) an welchem Anlagenbauteil sitzen und mit welchem Katalogsymbol sie gezeichnet werden. Uebernommen aus kabellaengen/cfg/io_regeln.json (dort Grundlage der Pruefung io_zuordnung_pruefen.py, abgeleitet aus Mubea, Stand 2026-10-30); hier nur noch die Teile, die zum Erzeugen noetig sind. Erkennungs-Regex, Suchradien, Nummernpaare und der Konverter-Abschnitt altZuNeu sind entfallen - die Nummern vergibt create_numbers.py entlang der Renamer-Bereiche.",
"_io_vorlage": "Geraete-ID beim Erzeugen = praefix + io_vorlage (z.B. MB1@@@). Die '@' ersetzt erst die Nummerierung (HW_NUMMERIEREN, lib/create_numbers.py) entlang der Renamer-Bereiche; die Anzahl '@' muss zum NAME-Muster des Renamer-Blocks passen (z.B. BG-1@@@).",
"io_vorlage": "1@@@",
"_rollen": "rolle -> Geraete-Praefix und Symbol (Blockname; Aussehen, Layer und Attribute stehen in cfg/hw_symbole.json). block_nach_name: wcmatch-Muster auf den Blocknamen des Bauteils -> abweichender Katalogblock (erster Treffer gewinnt).",
"rollen": [
{"rolle": "sep_ventil", "praefix": "MB", "block": "ILS_Separator-Ausgang"},
{"rolle": "sep_sensor", "praefix": "BG", "block": "ILS_720002003-Separator"},
{"rolle": "ae_ventil", "praefix": "MB", "block": "ILS_Weiche-Abzweig-Ausgang"},
{"rolle": "kreisel_motor", "praefix": "MA", "block": "ILS_Motor-400102811-Kreisel"},
{"rolle": "vario_motor", "praefix": "MA", "block": "ILS_Motor-200000020-VARIO-links",
"block_nach_name": [{"muster": "*RECHTS*", "block": "ILS_Motor-200000021-VARIO-rechts"}]},
{"rolle": "motorschutz", "praefix": "FC", "block": "ILS_Motorschutz"},
{"rolle": "rad_dreht", "praefix": "BG", "block": "ILS_720002011-Rad dreht"},
{"rolle": "encoder", "praefix": "GF", "block": "ILS_829624040-Encoder-800"},
{"rolle": "finger", "praefix": "BG", "block": "ILS_200000375-Finger in Position"},
{"rolle": "pc_in_pos", "praefix": "BG", "block": "ILS_200000248-PC in Position"},
{"rolle": "scanner", "praefix": "BX", "block": "ILS_Scanner-829534306"}
],
"_bauteile": "Mechanik-Bloecke (wcmatch, Gross/Klein egal; auch verschachtelt in VF_n/GF_n/KREISEL_n) und die Rollen, die an jedem davon sitzen. Jeder Eintrag in benoetigt ist eine Liste von Alternativen - erzeugt wird die ERSTE. Die Reihenfolge ist zugleich die Stapelfolge der Symbole am Bauteil (erstes oben).",
"bauteile": [
{"bauteil": "separator", "bloecke": ["Separator_SP*", "Separator_LP*", "S-SP", "S-LP"],
"benoetigt": [["sep_ventil"], ["sep_sensor"]]},
{"bauteil": "ausschleuser", "bloecke": ["AS_Element*", "AE-D", "AE DS"],
"benoetigt": [["ae_ventil"]]},
{"bauteil": "entladung", "bloecke": ["SC_Entladung*"],
"benoetigt": [["ae_ventil"]]},
{"bauteil": "kreisel_antrieb", "bloecke": ["AN8", "AN8_*"],
"benoetigt": [["kreisel_motor"], ["motorschutz"]]},
{"bauteil": "kreisel_spannstation", "bloecke": ["SP8", "SP8_*"],
"benoetigt": [["rad_dreht", "encoder"]]},
{"bauteil": "vario_motorstation", "bloecke": ["Vario_Motorstation*", "Motorstation*"],
"benoetigt": [["vario_motor"], ["motorschutz"]]},
{"bauteil": "vario_umlenkstation", "bloecke": ["Vario_Umlenkstation*", "Umlenkstation*"],
"benoetigt": [["finger"], ["pc_in_pos"]]},
{"bauteil": "scanner", "bloecke": ["Scanner*", "SCAN"],
"benoetigt": [["scanner"]]}
]
}
+52
View File
@@ -0,0 +1,52 @@
// ============================================================
// hw_ausgabe.dcl - Ziel der HW-Planung waehlen
// Aufruf ueber hw-ausgabe-waehlen (Lisp/HwPlanung.lsp) vor
// HW_MATERIALFLUSS ("Materialfluss erzeugen") und HW_SYMBOLE
// ("Symbole erzeugen") samt Einzelbefehlen.
//
// Der Knopf "Auswaehlen..." schliesst den Dialog per done_dialog 2
// (getfiled funktioniert nicht aus einem action_tile heraus), danach
// wird der Dialog mit dem gewaehlten Pfad erneut gezeigt.
// ============================================================
hw_ausgabe : dialog {
label = "HW Planung - Ausgabe";
: text { key = "aktion"; width = 60; }
: boxed_radio_column {
label = "Erzeugen in";
key = "modus";
: radio_button {
key = "aktuell";
label = "Aktuelle Zeichnung (eigene Layer, fruehere Ausgabe wird ersetzt)";
}
: radio_button {
key = "ergaenzen";
label = "Bestehende Datei ergaenzen";
}
: radio_button {
key = "neu";
label = "Neue Datei";
}
}
: boxed_column {
label = "Bestehende Datei";
key = "box_datei";
: row {
: edit_box { key = "datei"; edit_width = 48; }
: button { key = "waehlen"; label = "Auswaehlen..."; fixed_width = true; }
}
}
: boxed_column {
label = "Neue Datei";
key = "box_neu";
: edit_box { key = "name"; label = "Name:"; edit_width = 40; }
: text { key = "ordner"; width = 60; }
}
errtile;
ok_cancel;
}
+198
View File
@@ -0,0 +1,198 @@
# HW-Planung: Materialfluss, Symbole, Nummerierung
Menue **SSG_LIB -> HW Planung**. Aus dem mechanischen Layout (Ursprungszeichnung)
entstehen Materialfluss und E-Geraete-Symbole - je nach Wahl in der aktuellen
Zeichnung oder in einer eigenen Planungsdatei (Vorgabe `<Zeichnung>_HW.dxf` in
`DXFM_RESULTS`). Die Ursprungsdatei auf der Platte wird nie veraendert.
## Ausgabeziel (Dialog vor dem Erzeugen)
Vor "Materialfluss erzeugen" und "Symbole erzeugen" (auch vor den
Einzelbefehlen `HW_SYM_*`) fragt der Dialog `dcl/hw_ausgabe.dcl`, wohin:
| Wahl | Ergebnis |
|---|---|
| **Aktuelle Zeichnung** | Materialfluss bzw. Symbole kommen in die offene Zeichnung, auf eigene Layer. Die fruehere Ausgabe desselben Befehls (erkannt an XDATA `SSG_HW_MF` bzw. `SSG_HW_SYM` je Geraeteart) wird ersetzt. Technisch: Python schreibt eine Zwischendatei `<Zeichnung>_HW_tmp.dxf` ohne Einheit, LISP fuegt sie bei 0,0 ein und loest den Block auf. Symbolbloecke, die es in der Zeichnung schon gibt, behalten ihre vorhandene Definition. |
| **Bestehende Datei ergaenzen** | Datei ueber "Auswaehlen..." waehlen; der Befehl ersetzt dort nur seinen eigenen Anteil. So landen Materialfluss und Symbole nacheinander in derselben Datei. |
| **Neue Datei** | Name eingeben (ohne Endung, Ordner `DXFM_RESULTS`). Gibt es die Datei schon, bleibt der Dialog mit einem Hinweis offen. |
Die letzte Wahl wird fuer die Sitzung gemerkt. Die Datei muss beim Schreiben in
BricsCAD geschlossen sein. "Symbole nummerieren" arbeitet in einer geoeffneten
**DXF**-Zeichnung - bei Ausgabe in die aktuelle Zeichnung muss diese also eine
DXF sein.
## Ablauf
| Schritt | Menue / Befehl | Wo ausfuehren | Ergebnis |
|---|---|---|---|
| 1 | Materialfluss erzeugen (`HW_MATERIALFLUSS`) | Ursprungszeichnung | Polylinien des Materialflusses im gewaehlten Ziel |
| 2 | Symbole erzeugen (`HW_SYMBOLE`) | Ursprungszeichnung | E-Geraete-Symbole mit ID-Vorlage (z.B. `MB1@@@`) im gewaehlten Ziel |
| 3 | - | Zieldatei oeffnen | Renamer-Bereiche ziehen (Rahmen oder Polylinie, siehe unten) |
| 4 | Symbole nummerieren (`HW_NUMMERIEREN`) | Zieldatei (DXF) | `@` in den Symbolen durch laufende Nummern ersetzt |
Schritt 1 und 2 duerfen in beliebiger Reihenfolge und beliebig oft laufen: jeder
ersetzt in der `_HW.dxf` nur seinen eigenen Anteil (Materialfluss = alles auf den
`HW_*`-Layern, Symbole = Bloecke mit XDATA `SSG_HW_SYM` der jeweiligen Geraeteart).
Was der Anwender selbst gezeichnet hat - Renamer-Bereiche, Polylinien, Notizen -
bleibt stehen. Dafuer muss die `_HW.dxf` waehrend Schritt 1/2 in BricsCAD
**geschlossen** sein (sonst ist sie gesperrt; das Skript meldet das).
## 1. Materialfluss (`lib/hw_materialfluss.py`)
`HW_MATERIALFLUSS` ruft `EXPORTCSV` auf und zeichnet aus der CSV flach (Z = 0):
| Element | Darstellung | Layer |
|---|---|---|
| Kreisel/Eckrad | geschlossenes Rechteck um die Laufbahn, Punktfolge in Drehrichtung, Pfeile | `HW_Kreisel`, `HW_Richtung` |
| Strecke / Gefaellestrecke | Polylinie AS -> Separatoren -> ES (Spalte `TrackIds`), Endpfeil | `HW_Materialfluss`, `HW_Richtung` |
| Weiche | kleiner Kreis am Beruehrpunkt zweier Kreisel | `HW_Weiche` |
| BTMT Beladung | grosser Kreis auf K1, kleiner Kreis auf K2, Linie | `HW_Beladung` |
| BTMT Entladung | kleiner Kreis auf K1, grosser Kreis auf K2, Linie | `HW_Entladung` |
Layernamen/-farben: `cfg/layer.cfg`, Abschnitt `[hw_planung]`.
## 2. Symbole (`Lisp/HwPlanung.lsp` + `lib/hw_symbole.py`)
Regelwerk `cfg/io_regeln.json`:
- **bauteile**: Mechanik-Bloecke (`Separator_SP*`, `AS_Element*`, `AN8_*`, `SP8_*`,
`Vario_Motorstation*`, ...) und die Rollen, die an jedem davon sitzen. Gefunden
werden sie auch, wenn sie in `VF_n`/`GF_n`/`KREISEL_n` verschachtelt sind.
- **rollen**: je Rolle Geraete-Praefix (MB, BG, MA, FC, GF, BX) und Symbol
(Blockname); `block_nach_name` waehlt z.B. fuer eine rechte Vario-Motorstation
den rechten Motor.
- **io_vorlage**: die ID beim Erzeugen ist `<Praefix><io_vorlage>`, Vorgabe `1@@@`
-> `MB1@@@`. Die Platzhalter-ID des Katalogblocks in `VERW`/`BEZEICHNUNG`
(z.B. `Motor MA0`) wird ebenfalls durch die Vorlage ersetzt.
Symboldefinitionen `cfg/hw_symbole.json` - es gibt **keine Symbol-Bibliothek**
als Referenzdokument, `hw_symbole.py` baut die Bloecke bei jedem Lauf neu daraus:
- **vorlagen**: gemeinsames Aussehen, derzeit `io_750` (750 x 270, Sensoren,
Ventile, Motoren, Motorschutz) und `geraet_800` (800 x 350, Encoder, Scanner,
mit Schraffur): Rahmen und je Attribut Lage, Texthoehe, Sichtbarkeit, Farbe,
Layer, Ausrichtung, Textstil, Breitenfaktor, Vorgabewert.
- **symbole**: je Blockname Vorlage, Layer, Farbe, `versatz` (Lage des Rahmens zum
Einfuegepunkt - Eingaenge und Motoren haengen im Katalog nach unten,
`[0, -270]`), `attribute` (Abweichungen von der Vorlage je Tag), `ohne`
(Attribute, die das Symbol nicht hat) und die Attributwerte (`IO` mit
Platzhalter-ID wie `MB0`, `VERW`, `BEZEICHNUNG`, `KENNZEICHNUNG`, `ARTINR`,
`BESCHR`, `SPS`).
- Die Symbole tragen **dieselben Blocknamen wie die ILS-Katalogsymbole** in
`kabellaengen/doc/Symbole/ILS-Symbole.dxf` und sind ihnen exakt nachgebaut
(Einfuegepunkt, Rahmen, Schraffur, Lage/Hoehe/Stil/Farbe aller Attribute,
Vorgabewerte), damit beim Mischen beider Fassungen in einer Zeichnung keine
still abweichende Definition gilt. `tests/test_hw_symbole.py`
(`test_symbole_gleichen_dem_katalog`) prueft das gegen den Katalog, sofern
kabellaengen daneben liegt (oder `ILS_SYMBOLE_KATALOG` gesetzt ist). Aendert
sich ein Katalogsymbol, schlaegt der Test fehl - dann hier nachziehen.
- Jeder Lauf baut die Blockdefinitionen in der `_HW.dxf` neu auf, Katalogfassungen
gleichen Namens werden dort also ueberschrieben.
LISP sucht die Bauteile in der Ursprungszeichnung und schreibt je Rolle den
**Zielpunkt** des Bauteils nach `DXFM_RESULTS/<Zeichnung>_hw_symbole.json`:
Separator-Sensor, AS/ES-Element, Entladung, AN8/SP8-Zentrum (Kreisel-Motor bzw.
Spannstation), Vario-Motor-/Umlenkstation, Scanner - jeweils deren
Einfuegepunkt, flach auf Z = 0. `hw_symbole.py` rechnet daraus die Lage und setzt
die Symbole in die `_HW.dxf`.
### Lage der Symbole
- Die Marke **`REALE_POSITION`** (das kleine `x` am rechten Rahmenrand) liegt
mit ihrem **x genau auf dem Zielpunkt**, der Kasten daneben (links davon).
- Mehrere Symbole eines Bauteils stehen **luekenlos uebereinander**, in der
Reihenfolge von `benoetigt` (erstes oben, z.B. Ventil ueber Sensor am
Separator, Motor ueber Motorschutz), als Gruppe in y um das Ziel zentriert.
Ein einzelnes Symbol hat seine Marke damit genau auf dem Ziel.
- **Ohne Ueberlappung**: jede Gruppe wird gegen die 2D-Bounding-Boxen der schon
gesetzten Rahmen geprueft (Mindestabstand `platzierung.abstand`). Beruehrt sie
eine, wird sie nur in **y** verschoben (+-`schritt`, +-2*`schritt`, ... bis
`max_verschiebung`) - x bleibt auf dem Ziel. Findet sich keine freie Lage,
bleibt sie unverschoben und das Skript meldet die Anzahl.
- Die Lage wird immer aus dem **ganzen** Plan berechnet; ein Einzelbefehl wie
`HW_SYM_VENTILE` setzt die Ventile also genau dorthin, wo sie auch mit
`HW_SYMBOLE` laegen.
- Gegen die Materialfluss-Polylinien und andere Geometrie wird nicht geprueft,
nur Symbol gegen Symbol.
Mubea: 70 von 83 Bauteilgruppen liegen genau auf ihrem Ziel, 13 weichen in y aus
(meist 50-600 mm, Scanner bis 1,7 m).
Einzelbefehle (nicht im Menue, zum schrittweisen Erzeugen und Pruefen; jeder
ersetzt nur seine Geraeteart): `HW_SYM_VENTILE` (MB), `HW_SYM_SENSOREN` (BG),
`HW_SYM_MOTOREN` (MA), `HW_SYM_MOTORSCHUTZ` (FC), `HW_SYM_ENCODER` (GF),
`HW_SYM_SCANNER` (BX); `HW_SYM_LOESCHEN` entfernt alle erzeugten Symbole aus der
`_HW.dxf`.
## 3. Renamer-Bereiche
Ein Renamer-Bereich ist ein Block auf einem der Layer aus `cfg/enumerate.cfg`
(`ILS_RENAMER`, `Omniflo_RENAMER`), der eine **Polylinie** enthaelt und Attribute
traegt - direkt oder zweistufig (aeusserer Block mit Polylinie + innerer
Attribut-Block, wie die `ILS_Renamer-*`-Bloecke in
`kabellaengen/doc/Symbole/ILS-Symbole.dxf`):
| Attribut | Bedeutung |
|---|---|
| `NAME` bzw. `NAME1`..`NAME3` | Nummernmuster, z.B. `BG-1@@@`; die Anzahl `@` bestimmt die Stellen und muss zur `io_vorlage` passen |
| `LAYER_NAME` bzw. `LAYER_NAME1`..`3` | Layer der Symbole, die mitgezaehlt werden (z.B. `ILS_Eingang`, `ILS_Ausgang`) |
| `DIRECTION` | Reihenfolge, siehe unten |
| `KENNZEICHNUNG` | Zuordnung zum Unterverteiler, z.B. `=A01+UC0100` |
`DIRECTION`:
- **Rechteck** (geschlossene Polylinie mit 4 Ecken): `LEFT_RIGHT`, `RIGHT_LEFT`,
`TOP_BOTTOM`, `BOTTOM_TOP` und die Kombinationen `TOP_BOTTOM/LEFT_RIGHT`,
`LEFT_RIGHT/TOP_BOTTOM` usw. (erste Angabe = Hauptsortierung). Mitgezaehlt
werden die Symbole innerhalb des Rechtecks.
- **`POLYLINE_PATH`**: Nummerierung **entlang der Polylinie** in Punktfolge.
Mitgezaehlt werden die Symbole hoechstens `polyline_max_distance` (Vorgabe 500 mm,
`cfg/enumerate.cfg`) von der Linie entfernt. Die Polylinie darf **kein Rechteck**
sein: offen braucht sie mindestens 5 Punkte (genau 4 offene Punkte gelten als
Rechteck), eine 2-Punkt-Linie wird nicht erkannt.
Bereiche mit identischen Attributen (`NAME*`, `LAYER_NAME*`, `DIRECTION`,
`KENNZEICHNUNG`) bilden eine Gruppe und werden fortlaufend gemeinsam nummeriert.
Alle Symbole einer Gruppe teilen sich einen Zaehler, egal auf welchem Layer
(z.B. BG 1001, BG 1002, MB 1003 in Reihenfolge entlang der Linie).
## 4. Nummerieren (`lib/create_numbers.py`)
`HW_NUMMERIEREN` in der geoeffneten `_HW.dxf`:
1. speichert die Zeichnung (`QSAVE`; sie muss eine DXF sein),
2. ruft `create_numbers.py --dryrun --info ... --errorfile ...` auf,
3. traegt die Nummern in die offene Zeichnung ein (Zuordnung ueber Wert und
Einfuegepunkt jedes Attributs mit `@`).
Ergebnisdateien neben der Zeichnung: `<Name>_nummern.json` (alle Umbenennungen) und
- nur bei Problemen - `<Name>_nummern_fehler.json`. Gemeldet werden u.a.: Symbol mit
`@` ausserhalb jedes Bereichs, Symbol in mehreren Bereichen einer Gruppe,
ueberlappende Rechteck-Bereiche, Bereich ohne passende Symbole, `DIRECTION` passt
nicht zur Geometrie, Symbol im Bereich, aber auf einem nicht genannten Layer
(Warnung).
Direktaufruf (z.B. fuer eine DXF ausserhalb von BricsCAD):
```bat
python lib\create_numbers.py --filename Anlage_HW.dxf [--config cfg\enumerate.cfg]
[--info info.json] [--errorfile fehler.json] [--dryrun | --overwrite] [--show_symbol_frames]
```
Ohne `--dryrun`/`--overwrite` entsteht `<Name>_numbered.dxf`.
Die Nummer wird **oktal** gebildet (`oct(counter)`, so gewollt): das 8. Symbol
einer Gruppe bekommt `...010`.
## Herkunft
`create_numbers.py`, `cfg/enumerate.cfg` und der Test
`tests/test_create_numbers.py` sind am 2026-11-03 aus dem Kabeltool-Repo
`kabellaengen` hierher umgezogen (dort entfernt). Es gibt nur diesen einen
Codestand: die benoetigten Helfer aus `kabellaengen/lib/utils.py` stehen direkt
in `create_numbers.py`, die Fehlersammlung `error_collector.py` ist durch zwei
einfache Listen ersetzt (gleiches Format der Fehlerdatei; gegen das Original an
sechs Testzeichnungen verglichen: identische Nummern und Fehlerdateien).
Bewusst doppelt bleibt nur die Konfiguration `cfg/io_regeln.json` (gekuerzt) -
beide Repos werden von verschiedenen Abteilungen gepflegt. Die Testszenen
`Nummerierung1/2`, `BGMG`, `POT` (Erzeugung per `kabellaengen/lib/create_example.py`)
bleiben in kabellaengen; der Modus `--all-examples` ist darum hier entfallen.
+17 -1
View File
@@ -1139,7 +1139,23 @@
"tt-stil-titel": "Textstil aendern",
"hw-csv-fehlt": "\n[HW_MATERIALFLUSS] FEHLER: CSV-Export fehlt: %1 - Materialfluss wird nicht erzeugt.",
"hw-fertig": "\n[HW_MATERIALFLUSS] Materialfluss geschrieben: %1",
"hw-symbole-offen": "HW Planung - Symbole erzeugen ist noch nicht umgesetzt.",
"hw-sym-regeln-fehlt": "\n[HW_SYMBOLE] FEHLER: Regelwerk nicht gefunden oder leer: %1",
"hw-sym-keine-bauteile": "\n[HW_SYMBOLE] Keine Bauteile aus dem Regelwerk in der Zeichnung gefunden.",
"hw-sym-bauteile": "\n[HW_SYMBOLE] Bauteil %1: %2",
"hw-fertig-symbole": "\n[HW_SYMBOLE] Symbole geschrieben: %1",
"hw-num-kein-dxf": "\n[HW_NUMMERIEREN] Nur in einer gespeicherten DXF-Zeichnung moeglich (z.B. <Anlage>_HW.dxf), nicht in %1.",
"hw-num-ergebnis": "\n[HW_NUMMERIEREN] %1 Attribute nummeriert.",
"hw-num-fehler": "\n[HW_NUMMERIEREN] Fehler/Warnungen der Nummerierung: %1",
"hw-aus-materialfluss": "Materialfluss erzeugen - wohin soll die Ausgabe?",
"hw-aus-symbole": "Symbole erzeugen - wohin soll die Ausgabe?",
"hw-aus-ordner": "Ordner: %1",
"hw-aus-waehlen": "Bestehende Datei waehlen",
"hw-aus-datei-fehlt": "Bitte eine bestehende DXF-Datei waehlen.",
"hw-aus-name-ungueltig": "Bitte einen gueltigen Dateinamen eingeben (ohne Pfad und ohne Sonderzeichen wie : * ? < > |).",
"hw-aus-name-existiert": "Die Datei gibt es schon - anderen Namen eingeben oder 'Bestehende Datei ergaenzen' waehlen.",
"hw-aus-dcl-fehlt": "\n[HW Planung] FEHLER: Dialog nicht gefunden: %1",
"hw-fertig-aktuell": "\n[HW Planung] In die aktuelle Zeichnung uebernommen, %1 Objekte der frueheren Ausgabe ersetzt.",
"hw-aktuell-fehler": "\n[HW Planung] FEHLER: Zwischendatei konnte nicht in die aktuelle Zeichnung eingefuegt werden: %1",
"dinformate-dialog-fehlt": "DIN-Formate-Dialog nicht gefunden: %1",
"dinformate-datei-fehlt": "DIN-Formate: Datei nicht gefunden: %1",
"dinformate-prompt-rahmen": "\nEinfuegepunkt Rahmen (untere linke Ecke): ",
+17 -1
View File
@@ -1139,7 +1139,23 @@
"tt-stil-titel": "Change text style",
"hw-csv-fehlt": "\n[HW_MATERIALFLUSS] ERROR: CSV export missing: %1 - material flow not created.",
"hw-fertig": "\n[HW_MATERIALFLUSS] Material flow written: %1",
"hw-symbole-offen": "HW planning - create symbols is not implemented yet.",
"hw-sym-regeln-fehlt": "\n[HW_SYMBOLE] ERROR: rule file not found or empty: %1",
"hw-sym-keine-bauteile": "\n[HW_SYMBOLE] No components from the rule file found in the drawing.",
"hw-sym-bauteile": "\n[HW_SYMBOLE] Component %1: %2",
"hw-fertig-symbole": "\n[HW_SYMBOLE] Symbols written: %1",
"hw-num-kein-dxf": "\n[HW_NUMMERIEREN] Only possible in a saved DXF drawing (e.g. <plant>_HW.dxf), not in %1.",
"hw-num-ergebnis": "\n[HW_NUMMERIEREN] %1 attributes numbered.",
"hw-num-fehler": "\n[HW_NUMMERIEREN] Numbering errors/warnings: %1",
"hw-aus-materialfluss": "Create material flow - where should the output go?",
"hw-aus-symbole": "Create symbols - where should the output go?",
"hw-aus-ordner": "Folder: %1",
"hw-aus-waehlen": "Choose existing file",
"hw-aus-datei-fehlt": "Please choose an existing DXF file.",
"hw-aus-name-ungueltig": "Please enter a valid file name (no path and no special characters like : * ? < > |).",
"hw-aus-name-existiert": "The file already exists - enter another name or choose 'Add to existing file'.",
"hw-aus-dcl-fehlt": "\n[HW Planung] ERROR: dialog not found: %1",
"hw-fertig-aktuell": "\n[HW Planung] Added to the current drawing, %1 objects of the previous output replaced.",
"hw-aktuell-fehler": "\n[HW Planung] ERROR: intermediate file could not be inserted into the current drawing: %1",
"dinformate-dialog-fehlt": "DIN formats dialog not found: %1",
"dinformate-datei-fehlt": "DIN formats: file not found: %1",
"dinformate-prompt-rahmen": "\nBorder insertion point (lower left corner): ",
File diff suppressed because it is too large. Load diff
+55 -5
View File
@@ -9,7 +9,9 @@ Aufruf:
Gestartet vom Befehl HW_MATERIALFLUSS (Lisp/HwPlanung.lsp, Menue HW Planung ->
Materialfluss erzeugen) direkt nach EXPORTCSV; die Ausgabe heisst wie die
Zeichnung mit Anhang "_HW.dxf". Alles wird flach in die XY-Ebene (Z = 0)
gezeichnet - es geht um den Grundriss des Materialflusses.
gezeichnet - es geht um den Grundriss des Materialflusses. Gibt es die Datei
schon (z.B. mit den Symbolen aus hw_symbole.py), wird nur der Inhalt der
HW_*-Layer ersetzt, alles andere bleibt.
Was gezeichnet wird:
* Kreisel/Eckrad: geschlossene Polylinie als Rechteck um die Laufbahn
@@ -200,10 +202,45 @@ def _xy(punkt):
return (punkt[0], punkt[1]) if punkt else None
def zeichnen(elemente, ausgabe, layer):
doc = ezdxf.new("R2018", setup=True)
doc.header["$INSUNITS"] = 4 # mm
# Markierung aller erzeugten Materialfluss-Objekte - daran erkennt LISP sie,
# wenn die Ausgabe in die AKTUELLE Zeichnung geht (HwPlanung.lsp,
# hw-alte-ausgabe), und ein Folgelauf, was er ersetzen darf.
XDATA_APP = "SSG_HW_MF"
def hw_dokument_oeffnen(pfad, einheitenlos=False):
"""Vorhandene Ausgabe-DXF oeffnen oder neu anlegen (R2018, mm).
Materialfluss (dieses Skript) und Symbole (hw_symbole.py) schreiben in
DIESELBE Datei und ersetzen dort nur ihren eigenen Anteil - was der
Anwender ergaenzt hat (Renamer-Rahmen, Polylinien), bleibt erhalten.
einheitenlos: $INSUNITS = 0 - fuer die Zwischendatei, die LISP als Block
in die aktuelle Zeichnung einfuegt; mit Einheit wuerde BricsCAD sie beim
Einfuegen in eine Zeichnung mit anderer Einheit skalieren.
"""
if os.path.isfile(pfad):
doc = ezdxf.readfile(pfad)
else:
doc = ezdxf.new("R2018", setup=True)
doc.header["$INSUNITS"] = 4 # mm
if einheitenlos:
doc.header["$INSUNITS"] = 0
return doc
def _ist_materialfluss(e, eigene_layer):
return e.has_xdata(XDATA_APP) or e.dxf.layer in eigene_layer
def zeichnen(elemente, ausgabe, layer, einheitenlos=False):
doc = hw_dokument_oeffnen(ausgabe, einheitenlos)
msp = doc.modelspace()
# Materialfluss des vorigen Laufs entfernen (markiert oder - aeltere
# Dateien ohne Markierung - auf den HW-Layern)
eigene = {name for name, _farbe in layer.values()}
for e in [e for e in msp if _ist_materialfluss(e, eigene)]:
msp.delete_entity(e)
vorher = {e.dxf.handle for e in msp}
for name, farbe in layer.values():
if name not in doc.layers:
doc.layers.add(name, color=farbe)
@@ -267,6 +304,12 @@ def zeichnen(elemente, ausgabe, layer):
msp.add_line(k1, k2, dxfattribs=ly("hw_entladung"))
zaehler["entladung"] += 1
if XDATA_APP not in doc.appids:
doc.appids.new(XDATA_APP)
for e in msp:
if e.dxf.handle not in vorher:
e.set_xdata(XDATA_APP, [(1000, "materialfluss")])
doc.saveas(ausgabe)
return zaehler, warnungen
@@ -277,6 +320,8 @@ def main(argv=None):
ap.add_argument("ausgabe")
ap.add_argument("--layer-cfg", default=None,
help="Pfad zu layer.cfg (Vorgabe: $DXFM_CFG/layer.cfg)")
ap.add_argument("--einheitenlos", action="store_true",
help="$INSUNITS = 0 (Zwischendatei fuer die aktuelle Zeichnung)")
args = ap.parse_args(argv)
cfg = args.layer_cfg
@@ -284,7 +329,12 @@ def main(argv=None):
cfg = os.path.join(os.environ["DXFM_CFG"], "layer.cfg")
elemente = load_elements(args.csv)
zaehler, warnungen = zeichnen(elemente, args.ausgabe, layer_cfg_lesen(cfg))
try:
zaehler, warnungen = zeichnen(elemente, args.ausgabe, layer_cfg_lesen(cfg),
args.einheitenlos)
except PermissionError:
print(f"FEHLER: {args.ausgabe} ist gesperrt (in BricsCAD geoeffnet?) - bitte schliessen.")
return 1
print(f"HW-Materialfluss geschrieben: {args.ausgabe}")
print(" " + ", ".join(f"{k}: {v}" for k, v in zaehler.items()))
for w in warnungen:
+356
View File
@@ -0,0 +1,356 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
hw_symbole.py - E-Geraete-Symbole der HW-Planung in die _HW.dxf setzen.
Aufruf:
python hw_symbole.py <plan.json> <ausgabe_HW.dxf> --symbole <hw_symbole.json>
Gestartet von HW_SYMBOLE / HW_SYM_* (Lisp/HwPlanung.lsp). Den Plan schreibt
LISP aus der Ursprungszeichnung (Bauteile nach cfg/io_regeln.json):
{"praefixe": ["MB", "BG"],
"symbole": [{"praefix": "MB", "io": "MB1@@@", "block": "ILS_Separator-Ausgang",
"x": 400.0, "y": 13920.0, "rolle": "sep_ventil",
"key": "1A2/3B4", "bauteil": "separator"}, ...]}
x/y ist der ZIELPUNKT des Bauteils (Sensor, AS/ES-Element, Motor- bzw.
Kreiselzentrum), nicht der Einfuegepunkt. Der Plan enthaelt immer ALLE
Symbole; "praefixe" sagt, welche dieser Lauf einfuegt bzw. ersetzt - die
Lage wird trotzdem aus dem ganzen Plan berechnet, damit ein Einzelbefehl
(HW_SYM_VENTILE) dieselbe Lage liefert wie HW_SYMBOLE.
Die Symbole selbst (Rahmen, Layer, Attribute mit Lage/Hoehe/Vorgabewerten)
stehen in cfg/hw_symbole.json - es gibt keine Symbol-Bibliothek als
Referenzdokument. Ablauf:
* die _HW.dxf oeffnen oder neu anlegen (hw_materialfluss.hw_dokument_oeffnen)
* alle frueher erzeugten Symbole der Praefixe in "praefixe" entfernen
(erkannt an der XDATA SSG_HW_SYM) - andere Symbole und alles, was der
Anwender gezeichnet hat, bleiben stehen
* die benoetigten Blockdefinitionen aus der Konfiguration (neu) aufbauen -
so wirkt jede Aenderung an hw_symbole.json beim naechsten Lauf
* Lage berechnen (platzieren): die Marke REALE_POSITION jedes Symbols
liegt mit ihrem x genau auf dem Zielpunkt, der Kasten daneben; die
Symbole eines Bauteils (z.B. Ventil + Sensor am Separator) stehen
luekenlos uebereinander, als Gruppe um den Zielpunkt zentriert.
Ueberlappt eine Gruppe (2D-Bounding-Boxen der Rahmen) mit einer schon
gesetzten, wird sie nur in y verschoben - x bleibt auf dem Ziel.
* je Plan-Eintrag den Block einfuegen; IO = Vorlage (z.B. MB1@@@), die
Platzhalter-ID des Symbols (Wert von IO, z.B. "MA0") wird in VERW/
BEZEICHNUNG ebenfalls durch die Vorlage ersetzt - create_numbers.py
ersetzt spaeter alle '@' in einem Zug.
"""
import argparse
import json
import os
import sys
from collections import Counter
from ezdxf.enums import TextEntityAlignment
from hw_materialfluss import hw_dokument_oeffnen
XDATA_APP = "SSG_HW_SYM"
ERSETZEN_IN = ("VERW", "BEZEICHNUNG")
# Kurzzeichen in hw_symbole.json -> ezdxf (fehlt = links auf der Grundlinie)
AUSRICHTUNG = {
"C": TextEntityAlignment.CENTER,
"R": TextEntityAlignment.RIGHT,
"ML": TextEntityAlignment.MIDDLE_LEFT,
"MC": TextEntityAlignment.MIDDLE_CENTER,
"MR": TextEntityAlignment.MIDDLE_RIGHT,
}
# ------------------------------------------------------------------
# Reine Kerne
# ------------------------------------------------------------------
def symbol_attribute(sdef, vorlage):
"""Attribut-Beschreibungen eines Symbols: die der Vorlage ohne "ohne",
je Tag mit den Abweichungen aus sdef["attribute"] (Wert null = Vorlagen-
Eintrag entfernen, z.B. "stil": null -> Standard-Textstil)."""
ohne = set(sdef.get("ohne", []))
abw = sdef.get("attribute", {})
erg = []
for a in vorlage["attribute"]:
if a["tag"] in ohne:
continue
a = dict(a)
for k, v in abw.get(a["tag"], {}).items():
if v is None:
a.pop(k, None)
else:
a[k] = v
erg.append(a)
return erg
def symbol_vorgaben(sdef, vorlage):
"""{TAG: Vorgabewert} eines Symbols: Vorlagen-"wert", vom Symbol ueberschrieben."""
werte = {a["tag"]: a.get("wert", "") for a in symbol_attribute(sdef, vorlage)}
werte.update({k: v for k, v in sdef.get("werte", {}).items() if k in werte})
return werte
def attribut_werte(vorgaben, io):
"""Werte fuer add_auto_attribs: IO = Vorlage, Platzhalter-ID in VERW/
BEZEICHNUNG ersetzt (z.B. "Motor MA0" -> "Motor MA1@@@")."""
alt = vorgaben.get("IO", "")
werte = {"IO": io}
for tag in ERSETZEN_IN:
if tag in vorgaben and alt:
werte[tag] = vorgaben[tag].replace(alt, io)
return werte
def symbol_geometrie(konfig, name):
"""Rahmen-Box (x0, y0, x1, y1) und Marke (x, y) von REALE_POSITION eines
Symbols, beides relativ zum Einfuegepunkt. Ohne REALE_POSITION gilt die
Mitte der rechten Rahmenkante als Marke."""
sdef = konfig["symbole"][name]
vorlage = konfig["vorlagen"][sdef["vorlage"]]
dx, dy = sdef.get("versatz", [0.0, 0.0])
breite, hoehe = vorlage["rahmen"]
box = (dx, dy, dx + breite, dy + hoehe)
marke = (dx + breite, dy + hoehe / 2.0)
for a in symbol_attribute(sdef, vorlage):
if a["tag"] == "REALE_POSITION":
marke = (a["x"] + dx, a["y"] + dy)
return {"box": box, "marke": marke}
def _gruppe_auslegen(ziel, geometrien):
"""Symbole einer Gruppe uebereinander, Gruppe in y um das Ziel zentriert,
Marke jedes Symbols mit x auf dem Ziel. Rueckgabe: [(einfuegepunkt, box_welt)]."""
oben = ziel[1] + sum(g["box"][3] - g["box"][1] for g in geometrien) / 2.0
erg = []
for g in geometrien:
x0, y0, x1, y1 = g["box"]
unten = oben - (y1 - y0)
ex, ey = ziel[0] - g["marke"][0], unten - y0
erg.append(((ex, ey), (ex + x0, unten, ex + x1, oben)))
oben = unten
return erg
def _ueberlappt(a, b, abstand):
return (a[0] < b[2] + abstand and b[0] < a[2] + abstand
and a[1] < b[3] + abstand and b[1] < a[3] + abstand)
def _verschiebungen(schritt, maximum):
yield 0.0
k = 1
while k * schritt <= maximum:
yield k * schritt
yield -k * schritt
k += 1
def platzieren(gruppen, abstand=20.0, schritt=50.0, max_verschiebung=3000.0):
"""Einfuegepunkte fuer Symbolgruppen ohne Ueberlappung der Rahmen.
gruppen: Liste (ziel (x, y), [geometrie, ...]) in Bearbeitungsfolge,
geometrie wie symbol_geometrie. Jede Gruppe wird ausgelegt
(_gruppe_auslegen) und, falls ihre Rahmen eine schon gesetzte Box
beruehren (Mindestabstand abstand), in y um +-schritt, +-2*schritt, ...
bis max_verschiebung verschoben; x bleibt auf dem Ziel. Findet sich keine
freie Lage, bleibt sie unverschoben und zaehlt als Ueberlappung.
Rueckgabe: (Liste der Einfuegepunkte je Gruppe, Anzahl Ueberlappungen)."""
gesetzt = []
punkte = []
ueberlappungen = 0
for ziel, geometrien in gruppen:
basis = _gruppe_auslegen(ziel, geometrien)
wahl = None
for dy in _verschiebungen(schritt, max_verschiebung):
boxen = [(b[0], b[1] + dy, b[2], b[3] + dy) for _p, b in basis]
if not any(_ueberlappt(b, g, abstand) for b in boxen for g in gesetzt):
wahl = dy
break
if wahl is None:
wahl = 0.0
ueberlappungen += 1
gesetzt.extend((b[0], b[1] + wahl, b[2], b[3] + wahl) for _p, b in basis)
punkte.append([(p[0], p[1] + wahl) for p, _b in basis])
return punkte, ueberlappungen
def plan_gruppen(symbole):
"""Plan-Eintraege nach Bauteil (key) gruppieren, Reihenfolge des Plans.
Rueckgabe: Liste (ziel, [eintrag, ...])."""
gruppen = {}
for s in symbole:
gruppen.setdefault(s.get("key", ""), ((s["x"], s["y"]), []))[1].append(s)
return list(gruppen.values())
# ------------------------------------------------------------------
# Zeichnung
# ------------------------------------------------------------------
def symbol_xdata(insert):
"""(praefix, rolle, key) eines erzeugten Symbols oder None."""
if not insert.has_xdata(XDATA_APP):
return None
werte = [v for code, v in insert.get_xdata(XDATA_APP) if code == 1000]
return tuple((werte + ["", "", ""])[:3])
def alte_symbole_entfernen(msp, praefixe):
"""Erzeugte Symbole der Praefixe loeschen. Rueckgabe: Anzahl."""
weg = [e for e in msp.query("INSERT")
if (x := symbol_xdata(e)) is not None and x[0] in praefixe]
for e in weg:
msp.delete_entity(e)
return len(weg)
def _layer(doc, name):
if name not in doc.layers:
doc.layers.add(name)
def _textstile(doc, konfig):
"""Textstile aus der Konfiguration anlegen. Rueckgabe: Name des Standardstils."""
ts = konfig.get("textstile", {})
for name, schrift in ts.get("schriften", {}).items():
if name not in doc.styles:
doc.styles.add(name, font=schrift)
return ts.get("standard", "Standard")
def block_aufbauen(doc, name, konfig):
"""Blockdefinition name aus der Symbol-Konfiguration (neu) aufbauen.
Der Einfuegepunkt liegt bei (0, 0); Rahmen und Attribute sitzen um
sdef["versatz"] verschoben (Katalog: Eingaenge/Motoren haengen nach unten)."""
sdef = konfig["symbole"][name]
vorlage = konfig["vorlagen"][sdef["vorlage"]]
standard_stil = _textstile(doc, konfig)
dx, dy = sdef.get("versatz", [0.0, 0.0])
layer = sdef["layer"]
_layer(doc, layer)
if name in doc.blocks:
blk = doc.blocks.get(name)
blk.delete_all_entities()
else:
blk = doc.blocks.new(name)
breite, hoehe = vorlage["rahmen"]
ecken = [(dx, dy), (dx + breite, dy), (dx + breite, dy + hoehe), (dx, dy + hoehe)]
blk.add_lwpolyline(ecken, close=True, dxfattribs={"layer": layer, "color": 7})
schraffur = vorlage.get("schraffur")
if schraffur and not sdef.get("ohne_schraffur"):
h_layer = sdef.get("schraffur_layer", layer)
_layer(doc, h_layer)
farbe = sdef.get("schraffur_farbe", 256) # fehlt = VONLAYER
hatch = blk.add_hatch(color=farbe, dxfattribs={"layer": h_layer})
hatch.set_pattern_fill(schraffur["muster"], color=farbe,
scale=schraffur.get("massstab", 1.0))
hatch.paths.add_polyline_path(ecken, is_closed=True)
vorgaben = symbol_vorgaben(sdef, vorlage)
for a in symbol_attribute(sdef, vorlage):
attr_layer = a.get("layer", layer)
_layer(doc, attr_layer)
dxf = {"layer": attr_layer, "height": a["hoehe"], "style": a.get("stil", standard_stil)}
if "breite" in a:
dxf["width"] = a["breite"]
farbe = a.get("farbe")
if farbe == "symbol":
farbe = sdef.get("farbe")
if isinstance(farbe, int):
dxf["color"] = farbe
punkt = (a["x"] + dx, a["y"] + dy)
attdef = blk.add_attdef(a["tag"], punkt, text=vorgaben.get(a["tag"], ""), dxfattribs=dxf)
if a.get("unsichtbar"):
attdef.is_invisible = True
if a.get("ausrichtung") in AUSRICHTUNG:
attdef.set_placement(punkt, align=AUSRICHTUNG[a["ausrichtung"]])
return vorgaben
def symbole_setzen(doc, plan, konfig):
"""Symbole aus dem Plan in doc setzen (nur die Praefixe aus plan
"praefixe", Lage aus dem ganzen Plan). Rueckgabe: (gesetzt je Praefix,
entfernt, Bloecke ohne Definition in der Konfiguration, Anzahl Gruppen
mit Ueberlappung)."""
msp = doc.modelspace()
praefixe = set(plan.get("praefixe") or [])
symbole = plan.get("symbole") or []
entfernt = alte_symbole_entfernen(msp, praefixe)
bekannt = konfig.get("symbole", {})
namen = sorted({s["block"] for s in symbole})
fehlend = [n for n in namen if n not in bekannt]
vorgaben_je_block = {n: block_aufbauen(doc, n, konfig) for n in namen if n in bekannt}
if XDATA_APP not in doc.appids:
doc.appids.new(XDATA_APP)
p = konfig.get("platzierung", {})
geometrie = {n: symbol_geometrie(konfig, n) for n in vorgaben_je_block}
gruppen = [(ziel, [s for s in eintraege if s["block"] in geometrie])
for ziel, eintraege in plan_gruppen(symbole)]
gruppen = [g for g in gruppen if g[1]]
punkte, ueberlappungen = platzieren(
[(ziel, [geometrie[s["block"]] for s in eintraege]) for ziel, eintraege in gruppen],
abstand=p.get("abstand", 20.0), schritt=p.get("schritt", 50.0),
max_verschiebung=p.get("max_verschiebung", 3000.0))
gesetzt = Counter()
for (_ziel, eintraege), gruppen_punkte in zip(gruppen, punkte):
for s, (x, y) in zip(eintraege, gruppen_punkte):
if s["praefix"] not in praefixe:
continue
ref = msp.add_blockref(s["block"], (x, y, 0.0),
dxfattribs={"layer": bekannt[s["block"]]["layer"]})
ref.add_auto_attribs(attribut_werte(vorgaben_je_block[s["block"]], s["io"]))
ref.set_xdata(XDATA_APP, [(1000, s["praefix"]), (1000, s["rolle"]),
(1000, s.get("key", ""))])
gesetzt[s["praefix"]] += 1
return gesetzt, entfernt, fehlend, ueberlappungen
def main(argv=None):
ap = argparse.ArgumentParser(description=__doc__.split("\n")[1])
ap.add_argument("plan")
ap.add_argument("ausgabe")
ap.add_argument("--symbole", required=True, help="Symboldefinitionen (cfg/hw_symbole.json)")
ap.add_argument("--einheitenlos", action="store_true",
help="$INSUNITS = 0 (Zwischendatei fuer die aktuelle Zeichnung)")
args = ap.parse_args(argv)
if not os.path.isfile(args.symbole):
print(f"FEHLER: Symboldefinitionen nicht gefunden: {args.symbole}")
return 1
with open(args.symbole, encoding="utf-8") as fh:
konfig = json.load(fh)
with open(args.plan, encoding="utf-8") as fh:
plan = json.load(fh)
doc = hw_dokument_oeffnen(args.ausgabe, args.einheitenlos)
gesetzt, entfernt, fehlend, ueberlappungen = symbole_setzen(doc, plan, konfig)
try:
doc.saveas(args.ausgabe)
except PermissionError:
print(f"FEHLER: {args.ausgabe} ist gesperrt (in BricsCAD geoeffnet?) - bitte schliessen.")
return 1
print(f"HW-Symbole geschrieben: {args.ausgabe}")
if entfernt:
print(f" {entfernt} vorhandene Symbole ersetzt")
for p in plan.get("praefixe") or []:
print(f" {p}: {gesetzt.get(p, 0)} Symbole")
for b in fehlend:
print(f" WARNUNG: Symbol nicht in {os.path.basename(args.symbole)} definiert: {b}")
if ueberlappungen:
print(f" WARNUNG: {ueberlappungen} Bauteil(e) ohne freie Lage - Symbole ueberlappen dort")
return 0
if __name__ == "__main__":
sys.exit(main())
+138 -125
View File
@@ -698,295 +698,301 @@
<Command>^C^C(ssg-ensure "HwPlanung") HW_SYMBOLE</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00159">
<MenuMacro UID="MM_00158">
<Macro>
<Name>Symbole nummerieren</Name>
<Command>^C^C(ssg-ensure "HwPlanung") HW_NUMMERIEREN</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00161">
<Macro>
<Name>Marker einfuegen</Name>
<Command>^C^C(ssg-ensure "Tro_Edit") TRO_INSERT</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00160">
<MenuMacro UID="MM_00162">
<Macro>
<Name>Marker bearbeiten</Name>
<Command>^C^C(ssg-ensure "Tro_Edit") TRO_EDIT</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00161">
<MenuMacro UID="MM_00163">
<Macro>
<Name>Bearbeiten (Auto)</Name>
<Command>^C^C(ssg-ensure "SSG_LIB_Commands") SSG_BLOCKEDIT</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00163">
<MenuMacro UID="MM_00165">
<Macro>
<Name>Verbindung einfuegen</Name>
<Command>^C^C(ssg-ensure "Connection_Edit") CONNECTION_INSERT</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00164">
<MenuMacro UID="MM_00166">
<Macro>
<Name>Verbindung bearbeiten</Name>
<Command>^C^C(ssg-ensure "Connection_Edit") CONNECTION_EDIT</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00166">
<MenuMacro UID="MM_00168">
<Macro>
<Name>Eingang</Name>
<Command>^C^C(ssg-ensure "SSG_LIB_Commands") ILS_EINGANG_INSERT</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00167">
<MenuMacro UID="MM_00169">
<Macro>
<Name>Ausgang</Name>
<Command>^C^C(ssg-ensure "SSG_LIB_Commands") ILS_AUSGANG_INSERT</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00168">
<MenuMacro UID="MM_00170">
<Macro>
<Name>SPS Schliessen</Name>
<Command>^C^C</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00170">
<MenuMacro UID="MM_00172">
<Macro>
<Name>Hoehen-Check (Montagehoehe/Buehne)</Name>
<Command>^C^C(ssg-ensure "check_hoehen") HOEHEN_CHECK</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00171">
<MenuMacro UID="MM_00173">
<Macro>
<Name>Export Sivas</Name>
<Command>^C^C(ssg-ensure "export") EXPORTSIVAS</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00172">
<MenuMacro UID="MM_00174">
<Macro>
<Name>Export CSV</Name>
<Command>^C^C(ssg-ensure "export") EXPORTCSV</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00174">
<MenuMacro UID="MM_00176">
<Macro>
<Name>Umschalten 2D und 3D</Name>
<Command>^C^C(ssg-ensure "SSG_LIB_Commands") SSG_DIM_SWITCH</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00175">
<MenuMacro UID="MM_00177">
<Macro>
<Name>Alle Bloecke auf 2D</Name>
<Command>^C^C(ssg-ensure "SSG_LIB_Commands") SSG_DIM_ALL_2D</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00176">
<MenuMacro UID="MM_00178">
<Macro>
<Name>Alle Bloecke auf 3D</Name>
<Command>^C^C(ssg-ensure "SSG_LIB_Commands") SSG_DIM_ALL_3D</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00177">
<MenuMacro UID="MM_00179">
<Macro>
<Name>Sprache</Name>
<Command>^C^C(ssg-ensure "SSG_LIB_Commands") SSG_SPRACHE</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00178">
<MenuMacro UID="MM_00180">
<Macro>
<Name>Planquadrate an/aus</Name>
<Command>^C^C(ssg-ensure "DrawPlanquadrate") PLANQUADRATE_TOGGLE</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00179">
<MenuMacro UID="MM_00181">
<Macro>
<Name>CSV-Export</Name>
<Command>^C^C(ssg-ensure "export") CSV_EXPORT_KONFIG</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00181">
<MenuMacro UID="MM_00183">
<Macro>
<Name>Bloecke zaehlen</Name>
<Command>^C^C(ssg-ensure "ssg_blocktools") SSG_BLOCK_COUNT</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00182">
<MenuMacro UID="MM_00184">
<Macro>
<Name>Bloecke loeschen</Name>
<Command>^C^C(ssg-ensure "ssg_blocktools") SSG_BLOCK_DELETE</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00183">
<MenuMacro UID="MM_00185">
<Macro>
<Name>Attribute ersetzen</Name>
<Command>^C^C(ssg-ensure "ssg_blocktools") SSG_ATTR_ERSETZEN</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00185">
<MenuMacro UID="MM_00187">
<Macro>
<Name>Text ersetzen</Name>
<Command>^C^C(ssg-ensure "ssg_texttools") SSG_TEXT_ERSETZEN</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00186">
<MenuMacro UID="MM_00188">
<Macro>
<Name>Text parallel beschriften</Name>
<Command>^C^C(ssg-ensure "ssg_texttools") SSG_TEXT_PARALLEL</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00187">
<MenuMacro UID="MM_00189">
<Macro>
<Name>Texthoehe aendern</Name>
<Command>^C^C(ssg-ensure "ssg_texttools") SSG_TEXT_HOEHE</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00188">
<MenuMacro UID="MM_00190">
<Macro>
<Name>Textstil aendern</Name>
<Command>^C^C(ssg-ensure "ssg_texttools") SSG_TEXT_STIL</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00190">
<MenuMacro UID="MM_00192">
<Macro>
<Name>Faehnchen + Stueckliste aus Zeichnung</Name>
<Command>^C^C(ssg-ensure "ssg_stueckliste") SSG_FAEHNCHEN_STUECKLISTE</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00191">
<MenuMacro UID="MM_00193">
<Macro>
<Name>Din Formate</Name>
<Command>^C^C(ssg-ensure "DinFormate") DIN_FORMATE</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00193">
<MenuMacro UID="MM_00195">
<Macro>
<Name>alle Tests ausfuehren und exportieren</Name>
<Command>^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_run_all.lsp")) (c:SSG_RUN_ALL_TESTS_EXPORT)</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00194">
<MenuMacro UID="MM_00196">
<Macro>
<Name>Tests ausfuehren und stehen lassen</Name>
<Command>^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_run_all.lsp")) (c:SSG_RUN_ALL_TESTS_OFFEN)</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00195">
<MenuMacro UID="MM_00197">
<Macro>
<Name>Export CSV/Sivas ALL</Name>
<Command>^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_export_all.lsp")) (c:TEST_EXPORT_ALL)</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00197">
<MenuMacro UID="MM_00199">
<Macro>
<Name>Kreisel</Name>
<Command>^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_kreisel.lsp")) (c:TEST_KREISEL)</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00198">
<MenuMacro UID="MM_00200">
<Macro>
<Name>Foerderer</Name>
<Command>^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_foerderer.lsp")) (c:TEST_FOERDERER)</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00199">
<MenuMacro UID="MM_00201">
<Macro>
<Name>Foerderer-Linienzug</Name>
<Command>^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_linienzug.lsp")) (c:TEST_LINIENZUG) (c:TEST_LINIENZUG_STUFE2) (c:TEST_LINIENZUG_MODUS2)</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00200">
<MenuMacro UID="MM_00202">
<Macro>
<Name>Omniflo</Name>
<Command>^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_omniflo.lsp")) (c:TEST_OMNIFLO)</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00201">
<MenuMacro UID="MM_00203">
<Macro>
<Name>Omniflo Strecke</Name>
<Command>^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_omniflo_strecke.lsp")) (c:TEST_OMNIFLO_STRECKE)</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00202">
<MenuMacro UID="MM_00204">
<Macro>
<Name>Gefaellestrecke</Name>
<Command>^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_gefaellestrecke.lsp")) (c:TEST_GEFAELLESTRECKE)</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00203">
<MenuMacro UID="MM_00205">
<Macro>
<Name>Mubea Gesamtmodell</Name>
<Command>^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_mubea.lsp")) (c:TEST_MUBEA)</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00204">
<MenuMacro UID="MM_00206">
<Macro>
<Name>HundM05 Kreisel-Abschnitt</Name>
<Command>^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_hundm05.lsp")) (c:TEST_HUNDM05)</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00205">
<MenuMacro UID="MM_00207">
<Macro>
<Name>Dematic/Arvato Flowpuffer (Vorlage)</Name>
<Command>^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_dematic_arvato.lsp")) (c:TEST_DEMATIC_ARVATO)</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00206">
<MenuMacro UID="MM_00208">
<Macro>
<Name>ILS 3.0 Showroom (Vorlage)</Name>
<Command>^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_showroom_ils.lsp")) (c:TEST_SHOWROOM_ILS)</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00207">
<MenuMacro UID="MM_00209">
<Macro>
<Name>Testanlage test_system</Name>
<Command>^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_test_system.lsp")) (c:TEST_TEST_SYSTEM)</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00208">
<MenuMacro UID="MM_00210">
<Macro>
<Name>KS 2D/3D pruefen (CSV)</Name>
<Command>^C^C(load (strcat (getenv "DXFMAKRO") "/Lisp/ks_pruefung.lsp")) KS_PRUEFEN</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00220">
<MenuMacro UID="MM_00222">
<Macro>
<Name>Kreisel bearbeiten</Name>
<Command>(ssg-ensure "KreiselInsert") KreiselEdit</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00221">
<MenuMacro UID="MM_00223">
<Macro>
<Name>Eckrad bearbeiten</Name>
<Command>(ssg-ensure "KreiselInsert") EckradEdit</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00222">
<MenuMacro UID="MM_00224">
<Macro>
<Name>Omniflo Element bearbeiten</Name>
<Command>(ssg-ensure "OmniModulInsert") OMNI_EDIT</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00223">
<MenuMacro UID="MM_00225">
<Macro>
<Name>Gefaellestrecke bearbeiten</Name>
<Command>(ssg-ensure "Gefaellestrecke") GEFAELLESTRECKE_EDIT</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00224">
<MenuMacro UID="MM_00226">
<Macro>
<Name>VarioFoerderer bearbeiten</Name>
<Command>(ssg-ensure "VarioFoerderer") VARIOFOERDERER_EDIT</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00225">
<MenuMacro UID="MM_00227">
<Macro>
<Name>TRO-Marker bearbeiten</Name>
<Command>(ssg-ensure "Tro_Edit") TRO_EDIT</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00226">
<MenuMacro UID="MM_00228">
<Macro>
<Name>Verbindung bearbeiten</Name>
<Command>(ssg-ensure "Connection_Edit") CONNECTION_EDIT</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00228">
<MenuMacro UID="MM_00230">
<Macro>
<Name>SSG Bearbeiten (Auto)</Name>
<Command>(ssg-ensure "SSG_LIB_Commands") SSG_BLOCKEDIT</Command>
@@ -1006,11 +1012,11 @@
<PopMenuRef pUID="PM_00132"/>
<PopMenuRef pUID="PM_00140"/>
<PopMenuRef pUID="PM_00154"/>
<PopMenuRef pUID="PM_00157"/>
<PopMenuRef pUID="PM_00169"/>
<PopMenuRef pUID="PM_00173"/>
<PopMenuRef pUID="PM_00180"/>
<PopMenuRef pUID="PM_00192"/>
<PopMenuRef pUID="PM_00159"/>
<PopMenuRef pUID="PM_00171"/>
<PopMenuRef pUID="PM_00175"/>
<PopMenuRef pUID="PM_00182"/>
<PopMenuRef pUID="PM_00194"/>
</PopMenu>
<PopMenu UID="PM_00005">
<Name>ILS</Name>
@@ -1787,335 +1793,342 @@
<MacroRef MenuMacroID="MM_00156"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem IsSeparator="true"/>
<PopMenuItem>
<NameRef>Symbole nummerieren</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00158"/>
</MenuItem>
</PopMenuItem>
</PopMenu>
<PopMenu UID="PM_00157">
<PopMenu UID="PM_00159">
<Name>SPS</Name>
<PopMenuRef pUID="PM_00158"/>
<PopMenuRef pUID="PM_00162"/>
<PopMenuRef pUID="PM_00160"/>
<PopMenuRef pUID="PM_00164"/>
<PopMenuItem>
<NameRef>Eingang</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00166"/>
<MacroRef MenuMacroID="MM_00168"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem>
<NameRef>Ausgang</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00167"/>
<MacroRef MenuMacroID="MM_00169"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem>
<NameRef>SPS Schliessen</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00168"/>
<MacroRef MenuMacroID="MM_00170"/>
</MenuItem>
</PopMenuItem>
</PopMenu>
<PopMenu UID="PM_00158">
<PopMenu UID="PM_00160">
<Name>TRO</Name>
<PopMenuItem>
<NameRef>Marker einfuegen</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00159"/>
<MacroRef MenuMacroID="MM_00161"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem>
<NameRef>Marker bearbeiten</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00160"/>
<MacroRef MenuMacroID="MM_00162"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem>
<NameRef>Bearbeiten (Auto)</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00161"/>
<MacroRef MenuMacroID="MM_00163"/>
</MenuItem>
</PopMenuItem>
</PopMenu>
<PopMenu UID="PM_00162">
<PopMenu UID="PM_00164">
<Name>Connections</Name>
<PopMenuItem>
<NameRef>Verbindung einfuegen</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00163"/>
<MacroRef MenuMacroID="MM_00165"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem>
<NameRef>Verbindung bearbeiten</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00164"/>
<MacroRef MenuMacroID="MM_00166"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem>
<NameRef>Bearbeiten (Auto)</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00161"/>
<MacroRef MenuMacroID="MM_00163"/>
</MenuItem>
</PopMenuItem>
</PopMenu>
<PopMenu UID="PM_00169">
<PopMenu UID="PM_00171">
<Name>Export</Name>
<PopMenuItem>
<NameRef>Hoehen-Check (Montagehoehe/Buehne)</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00170"/>
<MacroRef MenuMacroID="MM_00172"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem>
<NameRef>Export Sivas</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00171"/>
<MacroRef MenuMacroID="MM_00173"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem>
<NameRef>Export CSV</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00172"/>
</MenuItem>
</PopMenuItem>
</PopMenu>
<PopMenu UID="PM_00173">
<Name>Konfiguration</Name>
<PopMenuItem>
<NameRef>Umschalten 2D und 3D</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00174"/>
</MenuItem>
</PopMenuItem>
</PopMenu>
<PopMenu UID="PM_00175">
<Name>Konfiguration</Name>
<PopMenuItem>
<NameRef>Alle Bloecke auf 2D</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00175"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem>
<NameRef>Alle Bloecke auf 3D</NameRef>
<NameRef>Umschalten 2D und 3D</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00176"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem>
<NameRef>Sprache</NameRef>
<NameRef>Alle Bloecke auf 2D</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00177"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem>
<NameRef>Planquadrate an/aus</NameRef>
<NameRef>Alle Bloecke auf 3D</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00178"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem>
<NameRef>CSV-Export</NameRef>
<NameRef>Sprache</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00179"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem>
<NameRef>Planquadrate an/aus</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00180"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem>
<NameRef>CSV-Export</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00181"/>
</MenuItem>
</PopMenuItem>
</PopMenu>
<PopMenu UID="PM_00180">
<PopMenu UID="PM_00182">
<Name>Werkzeuge</Name>
<PopMenuItem>
<NameRef>Bloecke zaehlen</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00181"/>
<MacroRef MenuMacroID="MM_00183"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem>
<NameRef>Bloecke loeschen</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00182"/>
<MacroRef MenuMacroID="MM_00184"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem>
<NameRef>Attribute ersetzen</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00183"/>
<MacroRef MenuMacroID="MM_00185"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem IsSeparator="true"/>
<PopMenuItem>
<NameRef>Text ersetzen</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00185"/>
<MacroRef MenuMacroID="MM_00187"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem>
<NameRef>Text parallel beschriften</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00186"/>
<MacroRef MenuMacroID="MM_00188"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem>
<NameRef>Texthoehe aendern</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00187"/>
<MacroRef MenuMacroID="MM_00189"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem>
<NameRef>Textstil aendern</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00188"/>
<MacroRef MenuMacroID="MM_00190"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem IsSeparator="true"/>
<PopMenuItem>
<NameRef>Faehnchen + Stueckliste aus Zeichnung</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00190"/>
<MacroRef MenuMacroID="MM_00192"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem>
<NameRef>Din Formate</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00191"/>
<MacroRef MenuMacroID="MM_00193"/>
</MenuItem>
</PopMenuItem>
</PopMenu>
<PopMenu UID="PM_00192">
<PopMenu UID="PM_00194">
<Name>Tests</Name>
<PopMenuItem>
<NameRef>alle Tests ausfuehren und exportieren</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00193"/>
<MacroRef MenuMacroID="MM_00195"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem>
<NameRef>Tests ausfuehren und stehen lassen</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00194"/>
<MacroRef MenuMacroID="MM_00196"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem>
<NameRef>Export CSV/Sivas ALL</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00195"/>
<MacroRef MenuMacroID="MM_00197"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem IsSeparator="true"/>
<PopMenuItem>
<NameRef>Kreisel</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00197"/>
<MacroRef MenuMacroID="MM_00199"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem>
<NameRef>Foerderer</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00198"/>
<MacroRef MenuMacroID="MM_00200"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem>
<NameRef>Foerderer-Linienzug</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00199"/>
<MacroRef MenuMacroID="MM_00201"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem>
<NameRef>Omniflo</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00200"/>
<MacroRef MenuMacroID="MM_00202"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem>
<NameRef>Omniflo Strecke</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00201"/>
<MacroRef MenuMacroID="MM_00203"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem>
<NameRef>Gefaellestrecke</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00202"/>
<MacroRef MenuMacroID="MM_00204"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem>
<NameRef>Mubea Gesamtmodell</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00203"/>
<MacroRef MenuMacroID="MM_00205"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem>
<NameRef>HundM05 Kreisel-Abschnitt</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00204"/>
<MacroRef MenuMacroID="MM_00206"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem>
<NameRef>Dematic/Arvato Flowpuffer (Vorlage)</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00205"/>
<MacroRef MenuMacroID="MM_00207"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem>
<NameRef>ILS 3.0 Showroom (Vorlage)</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00206"/>
<MacroRef MenuMacroID="MM_00208"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem>
<NameRef>Testanlage test_system</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00207"/>
<MacroRef MenuMacroID="MM_00209"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem>
<NameRef>KS 2D/3D pruefen (CSV)</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00208"/>
<MacroRef MenuMacroID="MM_00210"/>
</MenuItem>
</PopMenuItem>
</PopMenu>
<PopMenu UID="PM_00219">
<PopMenu UID="PM_00221">
<Alias>POP501</Alias>
<Name>SSG_LIB Edit Context</Name>
<PopMenuItem>
<NameRef>Kreisel bearbeiten</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00220"/>
<MacroRef MenuMacroID="MM_00222"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem>
<NameRef>Eckrad bearbeiten</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00221"/>
<MacroRef MenuMacroID="MM_00223"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem>
<NameRef>Omniflo Element bearbeiten</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00222"/>
<MacroRef MenuMacroID="MM_00224"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem>
<NameRef>Gefaellestrecke bearbeiten</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00223"/>
<MacroRef MenuMacroID="MM_00225"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem>
<NameRef>VarioFoerderer bearbeiten</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00224"/>
<MacroRef MenuMacroID="MM_00226"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem>
<NameRef>TRO-Marker bearbeiten</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00225"/>
<MacroRef MenuMacroID="MM_00227"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem>
<NameRef>Verbindung bearbeiten</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00226"/>
<MacroRef MenuMacroID="MM_00228"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem IsSeparator="true"/>
<PopMenuItem>
<NameRef>SSG Bearbeiten (Auto)</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00228"/>
<MacroRef MenuMacroID="MM_00230"/>
</MenuItem>
</PopMenuItem>
</PopMenu>
+3 -1
View File
@@ -148,7 +148,9 @@
[<-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
[Symbole erzeugen]^C^C(ssg-ensure "HwPlanung") HW_SYMBOLE
[--]
[<-Symbole nummerieren]^C^C(ssg-ensure "HwPlanung") HW_NUMMERIEREN
[->SPS]
[->TRO]
[Marker einfuegen]^C^C(ssg-ensure "Tro_Edit") TRO_INSERT
+371
View File
@@ -0,0 +1,371 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Tests fuer lib/create_numbers.py (Nummerierung der Symbole entlang Renamer-Bereichen).
Uebernommen aus kabellaengen/lib/create_numbers_test.py.
"""
import os
import sys
import unittest
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "lib"))
from create_numbers import sort_symbols_by_direction # noqa: E402
def create_test_symbols():
"""
Erstellt 16 Test-Symbole auf einem 4x4 Raster.
Raster-Layout (Y-Achse nach oben, wie in DXF üblich):
Y=300: (0,300) (100,300) (200,300) (300,300) <- Oben
Y=200: (0,200) (100,200) (200,200) (300,200)
Y=100: (0,100) (100,100) (200,100) (300,100)
Y=0: (0,0) (100,0) (200,0) (300,0) <- Unten
X=0 X=100 X=200 X=300
^ ^
Links Rechts
"""
symbols = []
symbol_id = 1
# Erstelle 4x4 Raster
for y in [0, 100, 200, 300]:
for x in [0, 100, 200, 300]:
symbols.append({
'id': symbol_id,
'position': (x, y),
'entity': None, # Für den Test nicht benötigt
'attributes': {'IO': f'SYM-{symbol_id}@@'},
'layer': 'TEST_LAYER'
})
symbol_id += 1
return symbols
class TestEntitySorting(unittest.TestCase):
"""Tests for running all combinations of sort_symbols_by_direction."""
@classmethod
def setUpClass(cls):
"""Wird einmal vor allen Tests ausgeführt."""
print("="*70)
print("Test Suite: sort_symbols_by_direction")
print("="*70)
print()
sys.stdout.flush()
def test_top_bottom_left_right(self):
"""
Test TOP_BOTTOM/LEFT_RIGHT: Von oben nach unten, in jeder Zeile von links nach rechts
"""
symbols = create_test_symbols()
sorted_symbols = sort_symbols_by_direction(symbols, "TOP_BOTTOM/LEFT_RIGHT")
# Erwartete Reihenfolge: Zeile Y=300, dann Y=200, dann Y=100, dann Y=0
# In jeder Zeile: X=0, X=100, X=200, X=300
expected_positions = [
(0, 300), (100, 300), (200, 300), (300, 300), # Zeile Y=300 (oben)
(0, 200), (100, 200), (200, 200), (300, 200), # Zeile Y=200
(0, 100), (100, 100), (200, 100), (300, 100), # Zeile Y=100
(0, 0), (100, 0), (200, 0), (300, 0) # Zeile Y=0 (unten)
]
actual_positions = [s['position'] for s in sorted_symbols]
assert actual_positions == expected_positions, f"Fehler! Erwartet: {expected_positions}, Erhalten: {actual_positions}"
def test_top_bottom_right_left(self):
"""
Test TOP_BOTTOM/RIGHT_LEFT: Von oben nach unten, in jeder Zeile von rechts nach links
"""
symbols = create_test_symbols()
sorted_symbols = sort_symbols_by_direction(symbols, "TOP_BOTTOM/RIGHT_LEFT")
# Erwartete Reihenfolge: Zeile Y=300, dann Y=200, dann Y=100, dann Y=0
# In jeder Zeile: X=300, X=200, X=100, X=0
expected_positions = [
(300, 300), (200, 300), (100, 300), (0, 300), # Zeile Y=300 (oben)
(300, 200), (200, 200), (100, 200), (0, 200), # Zeile Y=200
(300, 100), (200, 100), (100, 100), (0, 100), # Zeile Y=100
(300, 0), (200, 0), (100, 0), (0, 0) # Zeile Y=0 (unten)
]
actual_positions = [s['position'] for s in sorted_symbols]
assert actual_positions == expected_positions, f"Fehler! Erwartet: {expected_positions}, Erhalten: {actual_positions}"
def test_bottom_top_left_right(self):
"""
Test BOTTOM_TOP/LEFT_RIGHT: Von unten nach oben, in jeder Zeile von links nach rechts
"""
symbols = create_test_symbols()
sorted_symbols = sort_symbols_by_direction(symbols, "BOTTOM_TOP/LEFT_RIGHT")
# Erwartete Reihenfolge: Zeile Y=0, dann Y=100, dann Y=200, dann Y=300
# In jeder Zeile: X=0, X=100, X=200, X=300
expected_positions = [
(0, 0), (100, 0), (200, 0), (300, 0), # Zeile Y=0 (unten)
(0, 100), (100, 100), (200, 100), (300, 100), # Zeile Y=100
(0, 200), (100, 200), (200, 200), (300, 200), # Zeile Y=200
(0, 300), (100, 300), (200, 300), (300, 300) # Zeile Y=300 (oben)
]
actual_positions = [s['position'] for s in sorted_symbols]
assert actual_positions == expected_positions, f"Fehler! Erwartet: {expected_positions}, Erhalten: {actual_positions}"
def test_bottom_top_right_left(self):
"""
Test BOTTOM_TOP/RIGHT_LEFT: Von unten nach oben, in jeder Zeile von rechts nach links
"""
symbols = create_test_symbols()
sorted_symbols = sort_symbols_by_direction(symbols, "BOTTOM_TOP/RIGHT_LEFT")
# Erwartete Reihenfolge: Zeile Y=0, dann Y=100, dann Y=200, dann Y=300
# In jeder Zeile: X=300, X=200, X=100, X=0
expected_positions = [
(300, 0), (200, 0), (100, 0), (0, 0), # Zeile Y=0 (unten)
(300, 100), (200, 100), (100, 100), (0, 100), # Zeile Y=100
(300, 200), (200, 200), (100, 200), (0, 200), # Zeile Y=200
(300, 300), (200, 300), (100, 300), (0, 300) # Zeile Y=300 (oben)
]
actual_positions = [s['position'] for s in sorted_symbols]
assert actual_positions == expected_positions, f"Fehler! Erwartet: {expected_positions}, Erhalten: {actual_positions}"
def test_left_right(self):
"""
Test LEFT_RIGHT: Spaltenweise von links nach rechts
"""
symbols = create_test_symbols()
sorted_symbols = sort_symbols_by_direction(symbols, "LEFT_RIGHT")
# Spalte für Spalte: X=0, dann X=100, dann X=200, dann X=300
# Innerhalb jeder Spalte: Y aufsteigend (0, 100, 200, 300)
expected_positions = [
(0, 0), (0, 100), (0, 200), (0, 300), # Spalte X=0
(100, 0), (100, 100), (100, 200), (100, 300), # Spalte X=100
(200, 0), (200, 100), (200, 200), (200, 300), # Spalte X=200
(300, 0), (300, 100), (300, 200), (300, 300) # Spalte X=300
]
actual_positions = [s['position'] for s in sorted_symbols]
assert actual_positions == expected_positions, f"Fehler! Erwartet: {expected_positions}, Erhalten: {actual_positions}"
def test_right_left(self):
"""
Test RIGHT_LEFT: Spaltenweise von rechts nach links
"""
symbols = create_test_symbols()
sorted_symbols = sort_symbols_by_direction(symbols, "RIGHT_LEFT")
# Spalte für Spalte: X=300, dann X=200, dann X=100, dann X=0
# Innerhalb jeder Spalte: Y aufsteigend (0, 100, 200, 300)
expected_positions = [
(300, 0), (300, 100), (300, 200), (300, 300), # Spalte X=300
(200, 0), (200, 100), (200, 200), (200, 300), # Spalte X=200
(100, 0), (100, 100), (100, 200), (100, 300), # Spalte X=100
(0, 0), (0, 100), (0, 200), (0, 300) # Spalte X=0
]
actual_positions = [s['position'] for s in sorted_symbols]
assert actual_positions == expected_positions, f"Fehler! Erwartet: {expected_positions}, Erhalten: {actual_positions}"
def test_top_bottom(self):
"""
Test TOP_BOTTOM: Default ist TOP_BOTTOM/LEFT_RIGHT
"""
symbols = create_test_symbols()
sorted_symbols = sort_symbols_by_direction(symbols, "TOP_BOTTOM")
# Sollte identisch mit TOP_BOTTOM/LEFT_RIGHT sein
expected_positions = [
(0, 300), (100, 300), (200, 300), (300, 300),
(0, 200), (100, 200), (200, 200), (300, 200),
(0, 100), (100, 100), (200, 100), (300, 100),
(0, 0), (100, 0), (200, 0), (300, 0)
]
actual_positions = [s['position'] for s in sorted_symbols]
assert actual_positions == expected_positions, f"Fehler! Erwartet: {expected_positions}, Erhalten: {actual_positions}"
def test_bottom_top(self):
"""
Test BOTTOM_TOP: Default ist BOTTOM_TOP/LEFT_RIGHT
"""
symbols = create_test_symbols()
sorted_symbols = sort_symbols_by_direction(symbols, "BOTTOM_TOP")
# Sollte identisch mit BOTTOM_TOP/LEFT_RIGHT sein
expected_positions = [
(0, 0), (100, 0), (200, 0), (300, 0),
(0, 100), (100, 100), (200, 100), (300, 100),
(0, 200), (100, 200), (200, 200), (300, 200),
(0, 300), (100, 300), (200, 300), (300, 300)
]
actual_positions = [s['position'] for s in sorted_symbols]
assert actual_positions == expected_positions, f"Fehler! Erwartet: {expected_positions}, Erhalten: {actual_positions}"
def test_left_right_top_bottom(self):
"""
Test LEFT_RIGHT/TOP_BOTTOM: Umgekehrte Reihenfolge, identisch mit TOP_BOTTOM/LEFT_RIGHT
"""
symbols = create_test_symbols()
sorted_symbols = sort_symbols_by_direction(symbols, "LEFT_RIGHT/TOP_BOTTOM")
expected_positions = [
(0, 300), (0, 200), (0, 100), (0, 0),
(100, 300), (100, 200), (100, 100), (100, 0),
(200, 300), (200, 200), (200, 100), (200, 0),
(300, 300), (300, 200), (300, 100), (300, 0)
]
actual_positions = [s['position'] for s in sorted_symbols]
assert actual_positions == expected_positions, f"Fehler! Erwartet: {expected_positions}, Erhalten: {actual_positions}"
def test_right_left_top_bottom(self):
"""
Test RIGHT_LEFT/TOP_BOTTOM: Umgekehrte Reihenfolge, identisch mit TOP_BOTTOM/RIGHT_LEFT
"""
symbols = create_test_symbols()
sorted_symbols = sort_symbols_by_direction(symbols, "RIGHT_LEFT/TOP_BOTTOM")
expected_positions = [
(300, 300), (300, 200), (300, 100), (300, 0),
(200, 300), (200, 200), (200, 100), (200, 0),
(100, 300), (100, 200), (100, 100), (100, 0),
(0, 300), (0, 200), (0, 100), (0, 0)
]
actual_positions = [s['position'] for s in sorted_symbols]
assert actual_positions == expected_positions, f"Fehler! Erwartet: {expected_positions}, Erhalten: {actual_positions}"
def test_left_right_bottom_top(self):
"""
Test LEFT_RIGHT/BOTTOM_TOP: Umgekehrte Reihenfolge, identisch mit BOTTOM_TOP/LEFT_RIGHT
"""
symbols = create_test_symbols()
sorted_symbols = sort_symbols_by_direction(symbols, "LEFT_RIGHT/BOTTOM_TOP")
expected_positions = [
(0, 0), (0, 100), (0, 200), (0, 300),
(100, 0), (100, 100), (100, 200), (100, 300),
(200, 0), (200, 100), (200, 200), (200, 300),
(300, 0), (300, 100), (300, 200), (300, 300),
]
actual_positions = [s['position'] for s in sorted_symbols]
assert actual_positions == expected_positions, f"Fehler! Erwartet: {expected_positions}, Erhalten: {actual_positions}"
def test_right_left_bottom_top(self):
"""
Test RIGHT_LEFT/BOTTOM_TOP: Umgekehrte Reihenfolge, identisch mit BOTTOM_TOP/RIGHT_LEFT
"""
symbols = create_test_symbols()
sorted_symbols = sort_symbols_by_direction(symbols, "RIGHT_LEFT/BOTTOM_TOP")
# Sollte identisch mit BOTTOM_TOP/RIGHT_LEFT sein
expected_positions = [
(300, 0), (300, 100), (300, 200), (300, 300),
(200, 0), (200, 100), (200, 200), (200, 300),
(100, 0), (100, 100), (100, 200), (100, 300),
(0, 0), (0, 100), (0, 200), (0, 300),
]
actual_positions = [s['position'] for s in sorted_symbols]
assert actual_positions == expected_positions, f"Fehler! Erwartet: {expected_positions}, Erhalten: {actual_positions}"
def test_fallback(self):
"""
Test Fallback (unbekannte DIRECTION): Default ist TOP_BOTTOM/LEFT_RIGHT
"""
symbols = create_test_symbols()
sorted_symbols = sort_symbols_by_direction(symbols, "UNKNOWN_DIRECTION")
# Sollte identisch mit TOP_BOTTOM/LEFT_RIGHT sein
expected_positions = [
(0, 300), (100, 300), (200, 300), (300, 300),
(0, 200), (100, 200), (200, 200), (300, 200),
(0, 100), (100, 100), (200, 100), (300, 100),
(0, 0), (100, 0), (200, 0), (300, 0)
]
actual_positions = [s['position'] for s in sorted_symbols]
assert actual_positions == expected_positions, f"Fehler! Erwartet: {expected_positions}, Erhalten: {actual_positions}"
# print("\n16 Symbole auf 4x4 Raster:")
# print(" X-Koordinaten: 0, 100, 200, 300")
# print(" Y-Koordinaten: 0, 100, 200, 300")
# print("\nUnterstützte Richtungen (beide Reihenfolgen):")
# print(" - TOP_BOTTOM/LEFT_RIGHT = LEFT_RIGHT/TOP_BOTTOM")
# print(" - TOP_BOTTOM/RIGHT_LEFT = RIGHT_LEFT/TOP_BOTTOM")
# print(" - BOTTOM_TOP/LEFT_RIGHT = LEFT_RIGHT/BOTTOM_TOP")
# print(" - BOTTOM_TOP/RIGHT_LEFT = RIGHT_LEFT/BOTTOM_TOP")
# print("\nEinfache Richtungen:")
# print(" - LEFT_RIGHT: Spaltenweise links→rechts (sortiert nach X, dann Y)")
# print(" - RIGHT_LEFT: Spaltenweise rechts→links (sortiert nach -X, dann Y)")
# print(" - TOP_BOTTOM → TOP_BOTTOM/LEFT_RIGHT (zeilenweise)")
# print(" - BOTTOM_TOP → BOTTOM_TOP/LEFT_RIGHT (zeilenweise)")
# # Test kombinierte Richtungen (Y/X Reihenfolge)
# test_top_bottom_left_right()
# test_top_bottom_right_left()
# test_bottom_top_left_right()
# test_bottom_top_right_left()
# # Test einfache Richtungen
# test_left_right()
# test_right_left()
# test_top_bottom()
# test_bottom_top()
# # Test umgekehrte Reihenfolge (X/Y Reihenfolge)
# test_left_right_top_bottom()
# test_right_left_top_bottom()
# test_left_right_bottom_top()
# test_right_left_bottom_top()
# # Test Fallback
# test_fallback()
# print("\n" + "="*70)
# print("✓✓✓ ALLE 13 TESTS BESTANDEN ✓✓✓")
# print("="*70)
# print("\nZusammenfassung:")
# print(" - Alle 4 kombinierten Richtungen (Y/X) funktionieren")
# print(" - Alle 4 kombinierten Richtungen (X/Y) funktionieren")
# print(" - Alle 4 einfachen Richtungen mappen korrekt")
# print(" - Fallback funktioniert")
if __name__ == '__main__':
unittest.main()
+37
View File
@@ -92,3 +92,40 @@ def test_csv_nach_dxf(tmp_path):
msp = ezdxf.readfile(str(dxf_pfad)).modelspace()
assert len(msp.query('LWPOLYLINE[layer=="HW_Kreisel"]')) == 1
assert len(msp.query('CIRCLE[layer=="HW_Weiche"]')) == 1
def _csv_mit_kreisel(tmp_path):
kopf = ("Elementnummer;TeileArt;TeileId;Bezeichnung;Planquadrat;Anzahl;Position;"
"Boundingbox;Insertpoint;K1;K2;K3;K4;Nachbarn;TrackIds;Fehler;Warnungen;Merkmale")
zeile = ('1;"ILS 2.0 Kreisel";"0001";"K1";"";1;"0.00, 0.00, 0.00";"5800.00, 800.00, 1.00";'
'"";"";"";"";"";"";"";"";"";{"Drehrichtung": "UZS", "Drehung": 0}')
csv_pfad = tmp_path / "export.csv"
csv_pfad.write_text(kopf + "\n" + zeile + "\n", encoding="utf-8")
return csv_pfad
def test_materialfluss_markiert_und_ersetzt_nur_eigenes(tmp_path):
"""Erzeugte Objekte tragen XDATA SSG_HW_MF; ein zweiter Lauf ersetzt sie,
laesst aber fremde Geometrie (z.B. Renamer-Rahmen) stehen."""
csv_pfad = _csv_mit_kreisel(tmp_path)
dxf_pfad = tmp_path / "Test_HW.dxf"
assert hw.main([str(csv_pfad), str(dxf_pfad), "--layer-cfg", ""]) == 0
doc = ezdxf.readfile(str(dxf_pfad))
msp = doc.modelspace()
assert msp and all(e.has_xdata(hw.XDATA_APP) for e in msp)
msp.add_line((0, 0), (1, 1), dxfattribs={"layer": "ILS_RENAMER"})
doc.saveas(str(dxf_pfad))
assert hw.main([str(csv_pfad), str(dxf_pfad), "--layer-cfg", ""]) == 0
msp = ezdxf.readfile(str(dxf_pfad)).modelspace()
assert len(msp.query('LWPOLYLINE[layer=="HW_Kreisel"]')) == 1
assert len(msp.query('LINE[layer=="ILS_RENAMER"]')) == 1
def test_materialfluss_einheitenlos(tmp_path):
"""Zwischendatei fuer die aktuelle Zeichnung: $INSUNITS = 0, sonst
skaliert BricsCAD sie beim Einfuegen."""
csv_pfad = _csv_mit_kreisel(tmp_path)
dxf_pfad = tmp_path / "Test_HW_tmp.dxf"
assert hw.main([str(csv_pfad), str(dxf_pfad), "--layer-cfg", "", "--einheitenlos"]) == 0
assert ezdxf.readfile(str(dxf_pfad)).header["$INSUNITS"] == 0
+278
View File
@@ -0,0 +1,278 @@
"""Tests fuer lib/hw_symbole.py, cfg/hw_symbole.json und die Kette
Symbole -> Renamer -> create_numbers."""
import json
import os
import sys
import ezdxf
import pytest
ROOT = os.path.join(os.path.dirname(__file__), "..")
sys.path.insert(0, os.path.join(ROOT, "lib"))
import create_numbers # noqa: E402
import hw_symbole as hs # noqa: E402
KONFIG = {
"textstile": {"standard": "ARIAL", "schriften": {"ARIAL": "arial.ttf"}},
"vorlagen": {"klein": {"rahmen": [750.0, 270.0], "attribute": [
{"tag": "IO", "x": 100.0, "y": 130.0, "hoehe": 125.0, "farbe": "symbol"},
{"tag": "VERW", "x": 20.0, "y": 20.0, "hoehe": 70.0},
{"tag": "BEZEICHNUNG", "x": 0.0, "y": -80.0, "hoehe": 60.0, "unsichtbar": True},
{"tag": "REALE_POSITION", "x": 715.0, "y": 110.0, "hoehe": 50.0,
"ausrichtung": "MC", "wert": "x"}]}},
"symbole": {
"SYM_VENTIL": {"vorlage": "klein", "layer": "ILS_Ausgang", "farbe": 3,
"werte": {"IO": "MB0", "VERW": "Sep", "BEZEICHNUNG": "Separator (MB0)"}},
"SYM_SENSOR": {"vorlage": "klein", "layer": "ILS_Eingang",
"werte": {"IO": "BG0", "VERW": "In Sep"}}},
}
def _plan(praefixe, symbole):
return {"praefixe": praefixe, "symbole": symbole}
def _sym(praefix, block, x, y, rolle="r"):
return {"praefix": praefix, "io": f"{praefix}1@@@", "block": block,
"x": x, "y": y, "rolle": rolle, "key": f"{x}", "bauteil": "separator"}
def test_attribut_werte_ersetzt_platzhalter():
vorgaben = {"IO": "MA0", "VERW": "ILS-KR-MA0", "BEZEICHNUNG": "Motor MA0", "ARTINR": "1"}
assert hs.attribut_werte(vorgaben, "MA1@@@") == {
"IO": "MA1@@@", "VERW": "ILS-KR-MA1@@@", "BEZEICHNUNG": "Motor MA1@@@"}
assert hs.attribut_werte({"IO": ""}, "BX1@@@") == {"IO": "BX1@@@"}
def test_symbol_vorgaben_vorlage_und_symbol():
werte = hs.symbol_vorgaben(KONFIG["symbole"]["SYM_SENSOR"], KONFIG["vorlagen"]["klein"])
assert werte == {"IO": "BG0", "VERW": "In Sep", "BEZEICHNUNG": "", "REALE_POSITION": "x"}
def test_block_aufbauen_aus_konfiguration():
doc = ezdxf.new("R2018")
hs.block_aufbauen(doc, "SYM_VENTIL", KONFIG)
blk = doc.blocks.get("SYM_VENTIL")
rahmen = blk.query("LWPOLYLINE")[0]
assert rahmen.dxf.layer == "ILS_Ausgang" and rahmen.closed
attdefs = {a.dxf.tag: a for a in blk.query("ATTDEF")}
assert list(attdefs) == ["IO", "VERW", "BEZEICHNUNG", "REALE_POSITION"]
assert attdefs["IO"].dxf.text == "MB0" and attdefs["IO"].dxf.color == 3
assert attdefs["BEZEICHNUNG"].is_invisible
assert attdefs["REALE_POSITION"].dxf.text == "x"
assert "ARIAL" in doc.styles
# Erneuter Aufbau ersetzt den Inhalt statt ihn zu verdoppeln
hs.block_aufbauen(doc, "SYM_VENTIL", KONFIG)
assert len(doc.blocks.get("SYM_VENTIL").query("ATTDEF")) == 4
def test_symbole_setzen_und_nur_eigene_praefixe_ersetzen():
doc = ezdxf.new("R2018")
doc.modelspace().add_line((0, 0), (1, 1), dxfattribs={"layer": "HW_Materialfluss"})
plan = _plan(["MB", "BG"], [_sym("MB", "SYM_VENTIL", 0, 0),
_sym("BG", "SYM_SENSOR", 0, 350),
_sym("MB", "FEHLT", 9, 9)])
gesetzt, entfernt, fehlend, ueberlappt = hs.symbole_setzen(doc, plan, KONFIG)
assert gesetzt == {"MB": 1, "BG": 1} and entfernt == 0 and fehlend == ["FEHLT"]
assert ueberlappt == 0
ventil = doc.modelspace().query('INSERT[name=="SYM_VENTIL"]')[0]
attr = {a.dxf.tag: a.dxf.text for a in ventil.attribs}
assert ventil.dxf.layer == "ILS_Ausgang"
assert attr["IO"] == "MB1@@@" and attr["BEZEICHNUNG"] == "Separator (MB1@@@)"
assert attr["REALE_POSITION"] == "x"
assert hs.symbol_xdata(ventil)[:2] == ("MB", "r")
# Nur Ventile neu: Sensor und fremde Geometrie bleiben stehen
gesetzt, entfernt, _, _ = hs.symbole_setzen(
doc, _plan(["MB"], [_sym("MB", "SYM_VENTIL", 100, 0),
_sym("BG", "SYM_SENSOR", 100, 0)]), KONFIG)
msp = doc.modelspace()
assert entfernt == 1 and gesetzt == {"MB": 1}
assert len(msp.query('INSERT[name=="SYM_VENTIL"]')) == 1
assert len(msp.query('INSERT[name=="SYM_SENSOR"]')) == 1
assert len(msp.query("LINE")) == 1
def test_echte_konfiguration_deckt_regelwerk_ab():
"""Jedes Symbol aus cfg/io_regeln.json ist in cfg/hw_symbole.json definiert,
jede Vorlage existiert, und jedes Symbol baut sich fehlerfrei auf."""
with open(os.path.join(ROOT, "cfg", "io_regeln.json"), encoding="utf-8") as fh:
regeln = json.load(fh)
with open(os.path.join(ROOT, "cfg", "hw_symbole.json"), encoding="utf-8") as fh:
konfig = json.load(fh)
bloecke = set()
for r in regeln["rollen"]:
bloecke.add(r["block"])
bloecke.update(v["block"] for v in r.get("block_nach_name", []))
assert bloecke - set(konfig["symbole"]) == set()
doc = ezdxf.new("R2018")
for name, sdef in konfig["symbole"].items():
assert sdef["vorlage"] in konfig["vorlagen"]
assert "IO" in sdef["werte"]
hs.block_aufbauen(doc, name, konfig)
def _renamer_pfad(doc, punkte):
"""Renamer-Block mit Polylinie (POLYLINE_PATH) fuer BG und MB."""
blk = doc.blocks.new("RENAMER_TEST")
blk.add_lwpolyline(punkte)
werte = {"NAME1": "BG-1@@@", "LAYER_NAME1": "ILS_Eingang",
"NAME2": "MB-1@@@", "LAYER_NAME2": "ILS_Ausgang",
"KENNZEICHNUNG": "=A01+UC0100", "DIRECTION": "POLYLINE_PATH"}
for i, tag in enumerate(werte):
blk.add_attdef(tag, (0, -100 * i))
if "ILS_RENAMER" not in doc.layers:
doc.layers.add("ILS_RENAMER")
ref = doc.modelspace().add_blockref("RENAMER_TEST", (0, 0), dxfattribs={"layer": "ILS_RENAMER"})
ref.add_auto_attribs(werte)
def test_kette_symbole_renamer_nummerierung(tmp_path):
symbole = tmp_path / "hw_symbole.json"
symbole.write_text(json.dumps(KONFIG), encoding="utf-8")
hw = str(tmp_path / "Anlage_HW.dxf")
plan_pfad = tmp_path / "plan.json"
# Zwei Separatoren (Ziel auf y=0), der rechte zuerst im Plan - nummeriert
# wird entlang der Polylinie
plan_pfad.write_text(json.dumps(_plan(["MB", "BG"], [
_sym("MB", "SYM_VENTIL", 5000, 0),
_sym("BG", "SYM_SENSOR", 5000, 0),
_sym("MB", "SYM_VENTIL", 1000, 0),
_sym("BG", "SYM_SENSOR", 1000, 0)])), encoding="utf-8")
assert hs.main([str(plan_pfad), hw, "--symbole", str(symbole)]) == 0
doc = ezdxf.readfile(hw)
# create_numbers verlangt fuer POLYLINE_PATH mind. 4 Punkte und kein
# Rechteck (offen mit genau 4 Punkten gilt als Rechteck) - also 5 Punkte
_renamer_pfad(doc, [(-1000, 0), (1000, 0), (3000, 0), (5000, 0), (7000, 0)])
doc.saveas(hw)
cfg = tmp_path / "enumerate.cfg"
cfg.write_text("[Layers]\nILS_RENAMER\n[Parameters]\npolyline_max_distance = 500.0\n",
encoding="utf-8")
info = tmp_path / "info.json"
rc = create_numbers.main(["--filename", hw, "--config", str(cfg), "--dryrun",
"--info", str(info), "--errorfile", str(tmp_path / "err.json")])
assert rc == 0
umbenannt = json.loads(info.read_text(encoding="utf-8"))["renamed_symbols"]
# Einfuegepunkt = Ziel - Marke (715): links das Paar am Ziel x=1000
links = {r["new_value"] for r in umbenannt if round(r["position"]["x"]) == 1000 - 715}
rechts = {r["new_value"] for r in umbenannt if round(r["position"]["x"]) == 5000 - 715}
assert {"MB1001", "BG1002"} <= links or {"BG1001", "MB1002"} <= links
assert {"MB1003", "BG1004"} <= rechts or {"BG1003", "MB1004"} <= rechts
assert any(r["new_value"] in ("Separator (MB1001)", "Separator (MB1002)") for r in umbenannt)
# Katalog der ILS-Symbole im Nachbar-Repo kabellaengen (Quelle von hw_symbole.json)
KATALOG = os.environ.get("ILS_SYMBOLE_KATALOG", os.path.join(
ROOT, "..", "kabellaengen", "doc", "Symbole", "ILS-Symbole.dxf"))
def _signatur(blk):
"""Vergleichbare Beschreibung einer Blockdefinition: Rahmen, Schraffur, ATTDEFs."""
attdefs = {}
for a in blk.query("ATTDEF"):
h, v = a.dxf.get("halign", 0), a.dxf.get("valign", 0)
p = a.dxf.align_point if (h or v) else a.dxf.insert
attdefs[a.dxf.tag] = {
"x": round(p.x, 1), "y": round(p.y, 1), "hoehe": a.dxf.height,
"unsichtbar": a.is_invisible, "layer": a.dxf.layer, "ausrichtung": (h, v),
"stil": a.dxf.get("style", "ARIAL"), "breite": round(a.dxf.get("width", 1.0), 2),
"farbe": a.dxf.get("color", 256) or 256, "text": a.dxf.text}
rahmen = sorted({(round(p[0], 1), round(p[1], 1))
for e in blk.query("LWPOLYLINE") for p in e.get_points()})
schraffur = [(h.dxf.pattern_name, h.dxf.pattern_scale, h.dxf.layer, h.dxf.get("color", 256))
for h in blk.query("HATCH")]
return rahmen, schraffur, attdefs
@pytest.mark.skipif(not os.path.isfile(KATALOG), reason="kabellaengen-Katalog nicht gefunden")
def test_symbole_gleichen_dem_katalog():
"""Die aus cfg/hw_symbole.json gebauten Symbole tragen dieselben Blocknamen
wie die Katalogsymbole in kabellaengen - sie muessen darum auch gleich
aussehen (Einfuegepunkt, Rahmen, Lage/Hoehe/Stil aller Attribute, Werte),
sonst gilt beim Mischen beider Fassungen still die falsche."""
with open(os.path.join(ROOT, "cfg", "hw_symbole.json"), encoding="utf-8") as fh:
konfig = json.load(fh)
katalog = ezdxf.readfile(KATALOG)
doc = ezdxf.new("R2018")
abweichungen = []
for name in konfig["symbole"]:
hs.block_aufbauen(doc, name, konfig)
soll_r, soll_s, soll_a = _signatur(katalog.blocks.get(name))
ist_r, ist_s, ist_a = _signatur(doc.blocks.get(name))
if soll_r != ist_r:
abweichungen.append(f"{name}: Rahmen {soll_r} != {ist_r}")
if soll_s != ist_s:
abweichungen.append(f"{name}: Schraffur {soll_s} != {ist_s}")
for tag in sorted(set(soll_a) | set(ist_a)):
s, i = soll_a.get(tag), ist_a.get(tag)
if s is None or i is None:
abweichungen.append(f"{name}.{tag}: nur im {'Katalog' if i is None else 'eigenen Symbol'}")
continue
for k in s:
gleich = abs(s[k] - i[k]) <= 0.11 if k in ("x", "y") else s[k] == i[k]
if not gleich:
abweichungen.append(f"{name}.{tag}.{k}: Katalog {s[k]!r}, eigen {i[k]!r}")
assert abweichungen == []
# ------------------------------------------------------------------
# Platzierung
# ------------------------------------------------------------------
def _geo(x0, y0, x1, y1, mx, my):
return {"box": (x0, y0, x1, y1), "marke": (mx, my)}
OBEN = _geo(0, 0, 750, 270, 750, 135) # wie Katalog-Ausgang
UNTEN = _geo(0, -270, 750, 0, 750, -135) # wie Katalog-Eingang (haengt nach unten)
def test_symbol_geometrie_aus_konfiguration():
with open(os.path.join(ROOT, "cfg", "hw_symbole.json"), encoding="utf-8") as fh:
konfig = json.load(fh)
assert hs.symbol_geometrie(konfig, "ILS_Separator-Ausgang") == {
"box": (0.0, 0.0, 750.0, 270.0), "marke": (750.0, 135.0)}
assert hs.symbol_geometrie(konfig, "ILS_720002003-Separator") == {
"box": (0.0, -270.0, 750.0, 0.0), "marke": (750.0, -135.0)}
def test_platzieren_einzeln_marke_auf_ziel():
(punkte,), ueber = hs.platzieren([((1000.0, 500.0), [OBEN])])
assert ueber == 0
ex, ey = punkte[0]
assert (ex + 750, ey + 135) == (1000.0, 500.0) # Marke genau auf dem Ziel
(punkte,), _ = hs.platzieren([((1000.0, 500.0), [UNTEN])])
assert (punkte[0][0] + 750, punkte[0][1] - 135) == (1000.0, 500.0)
def test_platzieren_paar_uebereinander():
(punkte,), ueber = hs.platzieren([((1000.0, 0.0), [OBEN, UNTEN])])
assert ueber == 0
(x1, y1), (x2, y2) = punkte
# Marken beide mit x auf dem Ziel, Kaesten links daneben, luekenlos
# uebereinander und zusammen um das Ziel zentriert
assert x1 + 750 == 1000.0 and x2 + 750 == 1000.0
assert (y1, y1 + 270) == (0.0, 270.0) # oberer Kasten 0..270
assert (y2 - 270, y2) == (-270.0, 0.0) # unterer Kasten -270..0
def test_platzieren_weicht_in_y_aus():
gruppen = [((1000.0, 0.0), [OBEN, UNTEN]), ((1200.0, 100.0), [OBEN])]
(paar, (einzel,)), ueber = hs.platzieren(gruppen, abstand=20.0, schritt=50.0)
assert ueber == 0
assert einzel[0] + 750 == 1200.0 # x bleibt auf dem Ziel
unten, oben = einzel[1], einzel[1] + 270
assert oben <= -270 - 20 or unten >= 270 + 20 # frei von Paar-Kasten -270..270
def test_platzieren_meldet_ueberlappung_ohne_platz():
gruppen = [((0.0, 0.0), [OBEN]), ((0.0, 0.0), [OBEN])]
_punkte, ueber = hs.platzieren(gruppen, schritt=50.0, max_verschiebung=100.0)
assert ueber == 1
+103 -3
View File
@@ -3663,15 +3663,115 @@
)
)
;; HW-Planung: Ausgabename der Materialfluss-Skizze
(defun tu-tests-hw ()
;; HW-Planung: Ausgabename, JSON-Zahlen, Symbol-Regelwerk (Zuordnung Block ->
;; Bauteil -> Katalogsymbol, IO-Vorlage, Plan + JSON) und das Zuordnen der
;; create_numbers-Ergebnisse
(defun tu-tests-hw ( / _regeln _plan _json _echt _fehlt _eintrag)
(princ "\n[HwPlanung]")
(if (not (car (atoms-family 1 '("HW-DXF-NAME"))))
(tu-record nil "HwPlanung-Modul geladen"
"HwPlanung.lsp nicht geladen - siehe tests/test_unit.scr")
(progn
(tu-eq "hw-dxf-name/dwg" "Mubea_HW.dxf" (hw-dxf-name "Mubea.dwg"))
(tu-eq "hw-dxf-name/punkt" "A.B_HW.dxf" (hw-dxf-name "A.B.dwg")))
(tu-eq "hw-dxf-name/punkt" "A.B_HW.dxf" (hw-dxf-name "A.B.dwg"))
;; Ausgabe-Dialog: Dateiname und Eingabepruefung
(tu-eq "hw-name-ohne-dxf" "Anlage_HW" (hw-name-ohne-dxf " Anlage_HW.DXF "))
(tu-eq "hw-name-ohne-dxf/ohne" "Plan 2" (hw-name-ohne-dxf "Plan 2"))
(tu-true "hw-name-gueltig-p/ok" (hw-name-gueltig-p "Mubea_HW"))
(tu-nil "hw-name-gueltig-p/leer" (hw-name-gueltig-p " "))
(tu-nil "hw-name-gueltig-p/pfad" (hw-name-gueltig-p "C:/x/Mubea"))
(tu-nil "hw-name-gueltig-p/stern" (hw-name-gueltig-p "a*b"))
(tu-eq "hw-neu-pfad" "C:/erg/Mubea_HW.dxf" (hw-neu-pfad "C:/erg" "Mubea_HW.dxf"))
(tu-nil "hw-ausgabe-pruefen/aktuell" (hw-ausgabe-pruefen "aktuell" nil "" nil))
(tu-nil "hw-ausgabe-pruefen/ergaenzen" (hw-ausgabe-pruefen "ergaenzen" T "" nil))
(tu-eq "hw-ausgabe-pruefen/ergaenzen-fehlt" "hw-aus-datei-fehlt"
(hw-ausgabe-pruefen "ergaenzen" nil "" nil))
(tu-nil "hw-ausgabe-pruefen/neu" (hw-ausgabe-pruefen "neu" nil "Neu_HW" nil))
(tu-eq "hw-ausgabe-pruefen/neu-ungueltig" "hw-aus-name-ungueltig"
(hw-ausgabe-pruefen "neu" nil "a?b" nil))
(tu-eq "hw-ausgabe-pruefen/neu-existiert" "hw-aus-name-existiert"
(hw-ausgabe-pruefen "neu" nil "Mubea_HW" T))
(tu-eq "hw-json-zahl/neg" "-12.500" (hw-json-zahl -12.5))
(tu-eq "hw-json-zahl/klein" "0.050" (hw-json-zahl 0.05))
(tu-eq "hw-json-zahl/rund" "1001.000" (hw-json-zahl 1000.9999))
(tu-eq "hw-json-zahl/null" "0.000" (hw-json-zahl -0.0001))
;; Struktur wie ssg-json-parse sie aus cfg/io_regeln.json liefert
(setq _regeln
(list '("io_vorlage" . "1@@@")
(list "rollen"
'(("rolle" . "sep_ventil") ("praefix" . "MB") ("block" . "SYM_MB"))
'(("rolle" . "sep_sensor") ("praefix" . "BG") ("block" . "SYM_BG"))
'(("rolle" . "vario_motor") ("praefix" . "MA") ("block" . "SYM_MA_LI")
("block_nach_name" (("muster" . "*RECHTS*") ("block" . "SYM_MA_RE")))))
(list "bauteile"
'(("bauteil" . "separator") ("bloecke" "Separator_SP*" "S-SP")
("benoetigt" ("sep_ventil") ("sep_sensor")))
'(("bauteil" . "vario") ("bloecke" "Vario_Motorstation*")
("benoetigt" ("vario_motor" "andere_alternative"))))))
(tu-eq "hw-sym-io-vorlage" "MB1@@@" (hw-sym-io-vorlage _regeln "MB"))
(tu-eq "hw-sym-io-vorlage/vorgabe" "BG1@@@" (hw-sym-io-vorlage nil "BG"))
(tu-eq "hw-sym-bauteil-fuer-block/sep" "separator"
(ssg-json-get (hw-sym-bauteil-fuer-block _regeln "separator_sp_3D") "bauteil"))
(tu-nil "hw-sym-bauteil-fuer-block/fremd" (hw-sym-bauteil-fuer-block _regeln "AN8_3D"))
(tu-eq "hw-sym-muster" "SEPARATOR_SP*,S-SP,VARIO_MOTORSTATION*" (hw-sym-muster _regeln))
(tu-eq "hw-sym-block-fuer/rechts" "SYM_MA_RE"
(hw-sym-block-fuer (hw-sym-rolle _regeln "vario_motor") "Vario_Motorstation_500mm_rechts_3D"))
(tu-eq "hw-sym-block-fuer/links" "SYM_MA_LI"
(hw-sym-block-fuer (hw-sym-rolle _regeln "vario_motor") "Vario_Motorstation_500mm_links_3D"))
(tu-eq "hw-sym-ziel/flach" '(1000.0 2000.0 0.0) (hw-sym-ziel '(1000.0 2000.0 5.0)))
;; Plan: zwei Separatoren (Sortierung nach x) + eine Motorstation
(setq _plan
(hw-sym-plan _regeln
(hw-sym-instanzen-sortieren _regeln
(list (list (hw-sym-bauteil-fuer-block _regeln "Vario_Motorstation_rechts")
"Vario_Motorstation_rechts" '(0.0 0.0 0.0) "V1")
(list (hw-sym-bauteil-fuer-block _regeln "Separator_SP_3D")
"Separator_SP_3D" '(500.0 0.0 0.0) "S2")
(list (hw-sym-bauteil-fuer-block _regeln "Separator_SP_3D")
"Separator_SP_3D" '(100.0 0.0 2000.0) "S1")))))
(tu-eq "hw-sym-plan/keys" '("S1" "S1" "S2" "S2" "V1") (mapcar '(lambda (r) (nth 5 r)) _plan))
;; Zielpunkt = Bauteilpunkt (flach), beide Symbole eines Bauteils gleich -
;; die Lage daneben/uebereinander rechnet hw_symbole.py
(tu-eq "hw-sym-plan/erster" '("MB" "MB1@@@" "SYM_MB" (100.0 0.0 0.0) "sep_ventil" "S1" "separator")
(car _plan))
(tu-eq "hw-sym-plan/sensor-gleiches-ziel" '(100.0 0.0 0.0) (nth 3 (cadr _plan)))
(tu-eq "hw-sym-plan/stapelfolge" '("sep_ventil" "sep_sensor")
(list (nth 4 (car _plan)) (nth 4 (cadr _plan))))
(tu-eq "hw-sym-plan/motor-rechts" "SYM_MA_RE" (nth 2 (last _plan)))
;; JSON fuer hw_symbole.py: wieder einlesbar, Komma-Setzung korrekt
(setq _json (ssg-json-parse (ssg-join (hw-sym-plan->json '("MB" "MA") _plan) "\n")))
(tu-eq "hw-sym-plan->json/praefixe" '("MB" "MA") (ssg-json-get _json "praefixe"))
(tu-eq "hw-sym-plan->json/anzahl" 5 (length (ssg-json-get _json "symbole")))
(tu-eq "hw-sym-plan->json/x" 100.0 (ssg-json-get (car (ssg-json-get _json "symbole")) "x"))
(tu-eq "hw-sym-plan->json/leer" nil
(ssg-json-get (ssg-json-parse (ssg-join (hw-sym-plan->json '("BX") nil) "\n")) "symbole"))
;; Ergebnis von create_numbers zuordnen (Wert + Einfuegepunkt)
(setq _eintrag '(("old_value" . "MB1@@@") ("new_value" . "MB1001")
("position" ("x" . 400.0) ("y" . 0))))
(tu-true "hw-num-eintrag-p/passt" (hw-num-eintrag-p _eintrag "MB1@@@" '(400.004 0.0 0.0)))
(tu-nil "hw-num-eintrag-p/wert" (hw-num-eintrag-p _eintrag "BG1@@@" '(400.0 0.0 0.0)))
(tu-nil "hw-num-eintrag-p/lage" (hw-num-eintrag-p _eintrag "MB1@@@" '(400.0 350.0 0.0)))
;; Echtes Regelwerk: jede benoetigte Rolle hat eine Rollen-Definition
(setq _echt (if (getenv "DXFM_CFG")
(ssg-json-laden (strcat (getenv "DXFM_CFG") "/io_regeln.json"))))
(if _echt
(progn
(foreach b (ssg-json-get _echt "bauteile")
(foreach alt (ssg-json-get b "benoetigt")
(foreach r alt (if (null (hw-sym-rolle _echt r)) (setq _fehlt (cons r _fehlt))))))
(tu-nil "hw-sym-regelwerk/rollen-vollstaendig" _fehlt)
(tu-eq "hw-sym-regelwerk/io-vorlage" "MB1@@@" (hw-sym-io-vorlage _echt "MB"))
(tu-eq "hw-sym-regelwerk/as-element" "ausschleuser"
(ssg-json-get (hw-sym-bauteil-fuer-block _echt "AS_Element_90_links_3D") "bauteil")))
(tu-record nil "hw-sym-regelwerk" "cfg/io_regeln.json nicht gefunden (DXFM_CFG?)"))
)
)
)