Merge branch 'master' of https://gitea.schoenenberger.de/Schoenenberger_Systeme_GmbH/dxfmakros
This commit is contained in:
commit
57b01ad516
19 files changed
+4089
-183
No files matched your search
@@ -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
@@ -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)
|
||||
)
|
||||
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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
|
||||
@@ -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"}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"]]}
|
||||
]
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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:
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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
@@ -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?)"))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
Reference in new issue
Block a user