diff --git a/CLAUDE.md b/CLAUDE.md index 96cccfa..64a06fd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -59,6 +59,40 @@ Werden via `bin/setenv.bat` gesetzt. Wichtigste: - Komponentendaten (Masse, Sivas-IDs) liegen als JSON in `data/json/` - Sprache im Code und Kommentaren: Deutsch +## Python-Bibliotheken + +### PyRx-Module (`lib/elemente/`) - laufen innerhalb BricsCAD via PyRx + +Abhaengigkeiten: PyRx (`PyRx`, `PyDb`, `PyGe`, `PyEd`), `wx` (wxPython fuer Dialoge). + +| Modul | Beschreibung | +|---|---| +| `elemente/utils.py` | Gemeinsame Hilfsfunktionen: Block-Pfad, Nummernvergabe (`component_next_number`), Layer/Textstil anlegen, DWG-Bloecke laden, Block mit Attributen einfuegen | +| `elemente/kreisel.py` | ILS Kreisel komplett in Python. Befehle: `KreiselInsert`, `KreiselConnect`, `KreiselRedraw`, `KreiselQuick`, `KreiselEdit`, `KreiselParams`, `KreiselLabelSetup`, `KreiselLabelPos`, `KreiselLabelHoehe`. Enthaelt `KreiselEditDialog` (wxPython, ersetzt `kreisel_edit.dcl`) | +| `elemente/eckrad.py` | ILS Eckrad in Python. Befehle: `ILS_Eckrad`, `EckradEdit`. Enthaelt `EckradEditDialog` (wxPython) | +| `elemente/dblclick.py` | Dispatcher `SSG_BLOCKEDIT` fuer Rechtsklick-Bearbeitung. Erkennt KREISEL_*/ECKRAD_* Bloecke und oeffnet passenden wx-Dialog, sonst BEDIT | + +### Standalone-Skripte (`lib/`) - laufen extern mit ezdxf + +Abhaengigkeiten: `ezdxf`, Standard-Library. Benoetigen Umgebungsvariablen (`DXFM_DATA`, `DXFM_RESULTS`, `DXFM_CFG`). + +| Skript | Beschreibung | +|---|---| +| `omniflo_utils.py` | Gemeinsame Hilfsfunktionen fuer Omniflo-Skripte: `ROW_GROUPS` (Reihen-Gruppierung), `SWITCH_FILTERS` (Schalter-Filter), `build_row_layout`, `import_element_as_block`, `draw_cross`, `load_omniflo_data` | +| `set_einfuegepkt.py` | Setzt `$INSBASE` (Einfuegepunkt) in Omniflo-DXF-Dateien. Schalter je Typ: `--boegen`, `--weichen45`, `--weichen90`, `--weichenkoerper`, `--weichen-parallel`, `--delta`, `--dreifachweiche`, `--sternweiche`, `--show-omniflo` | +| `set_koords.py` | Setzt Koordinatensystem-Bloecke (K1-K4) in Omniflo-DXF-Dateien. Schalter: `--k1set` bis `--k4set`, `--set-all`, `--force`, `--show-omniflo`, `--test`, `--number` | +| `attradd.py` | Fuegt ATTDEF-Attribute zu Omniflo-DXF-Dateien hinzu. Liest Konfiguration aus `cfg/attradd.cfg`. Schalter: `--number`, `--dry-run` | +| `export_sivas.py` | Erzeugt Sivas-Export-CSV mit Summierungszeilen (ILS Automation, Omniflo Sum). Aufruf: `python export_sivas.py ` | +| `export_csv.py` | Erzeugt einfache Item-Liste als CSV (ohne Summierung). Aufruf: `python export_csv.py ` | +| `testpycall.py` | Testskript fuer LISP-Python-Aufruf via CALLPYTHON | + +### Typische Verarbeitungskette (Omniflo-DXF) + +1. `attradd.py` - Attribute zu Quell-DXFs hinzufuegen +2. `set_einfuegepkt.py` - Einfuegepunkte berechnen und setzen +3. `set_koords.py` - Koordinatensysteme (K1-K4) setzen +4. `export_sivas.py` / `export_csv.py` - Export aus Zeichnung + ## Entwicklungsstand Siehe `doc/Entwicklungsplan.md` fuer Meilensteinplanung und Aufwandsschaetzung. diff --git a/Lisp/KreiselInsert.lsp b/Lisp/KreiselInsert.lsp deleted file mode 100644 index ab57c29..0000000 --- a/Lisp/KreiselInsert.lsp +++ /dev/null @@ -1,1197 +0,0 @@ -;; KreiselInsert.lsp -;; Kreisel Modul-Einfuegesystem mit Compound-Block + Attribute -;; Version: 6.0 (Attribute, Redraw, 2-Linien-Connect) -;; Setzt ssg_core.lsp voraus. -;; ============================================ - -;; ============================================================ -;; TEIL 1: GLOBALE EINSTELLUNGEN -;; ============================================================ - -;; Zähler für Nummerierung (werden in Zeichnung gespeichert) -(if (null #Kreisel_LetzteNr) - (setq #Kreisel_LetzteNr 0) -) -(if (null #Eckrad_LetzteNr) - (setq #Eckrad_LetzteNr 0) -) - -;; Block-Pfad -(setq block-pfad (getenv "DXFM_BLOCKS")) - -(if (or (null block-pfad) (= block-pfad "")) - (progn - (setq block-pfad "C:/Users/y.wang/Documents/dxfmakros/Blocks/") - (alert (strcat "Umgebungsvariable DXFM_BLOCKS nicht gefunden.\nNutze Standardpfad: " block-pfad)) - ) - (setq block-pfad (strcat (vl-string-right-trim "/" (vl-string-translate "\\" "/" block-pfad)) "/")) -) - -(setq *block-path* block-pfad) -(princ (strcat "\nBlock-Pfad aktiv: " block-pfad)) - -;; Kreisel-Geometrie-Konstanten -(setq *kreisel-durchmesser* 800.0) ;; Kreisdurchmesser AN8/SP8 -(setq *kreisel-pin-abstand* 100.0) ;; Einrueckung PIN-Linie vom Kreisrand -(setq *kreisel-default-laenge* 2300.0) ;; Standard-Tangentenlaenge -(setq *kreisel-default-hoehe* 2000.0) ;; Standard-Höhe (Z-Koordinate) - -;; Attribut-Definitionen: ((TAG DEFAULT) ...) -(setq *kreisel-attrib-defs* - '(("NAME" "") - ("DREHRICHTUNG" "UZS") - ("N_SEPARATOREN" "2") - ("KREISELART" "STANDARD") - ("N_SCANNER" "0") - ("N_RAMPEN" "0") - ("DREHUNG" "0") - ("ABSTAND" "2300") - ("NUMMER" "0") - ("HOEHE" "0")) -) - -;; Eckrad-Einstellungen -(setq *eckrad-default-hoehe* 2000.0) ;; Standard-Höhe (Z-Koordinate) - -;; Eckrad Attribut-Definitionen: ((TAG DEFAULT) ...) -(setq *eckrad-attrib-defs* - '(("NAME" "") - ("DREHRICHTUNG" "UZS") - ("DREHUNG" "0") - ("NUMMER" "0") - ("HOEHE" "0")) -) - -;; Letzter gewaehlter Typ (gespeichert zwischen Aufrufen) -(if (null #Kreisel_Typ) (setq #Kreisel_Typ "STANDARD")) - -;; Beschriftungs-Einstellungen (NUR EINMAL definieren!) -(setq *kreisel-beschriftung-layer* "S_KRS_BESCHR") -(setq *kreisel-beschriftung-hoehe* 100.0) ;; Texthöhe in mm -(setq *kreisel-beschriftung-farbe* 7) ;; 7 = weiß -(setq *kreisel-beschriftung-abstand-oben* 0.0) ;; Abstand über Kreisel-Mitte -(setq *kreisel-beschriftung-schriftart* "Arial") ;; Schriftart - - -;; ============================================================ -;; TEIL 2: HILFSFUNKTIONEN -;; ============================================================ - -;; Funktion zum Erstellen/Setzen der Schriftart Arial -(defun kreisel-setup-textstyle ( / style-name) - (setq style-name "Kreisel_Arial") - - ;; Prüfen ob der Textstil bereits existiert - (if (not (tblsearch "STYLE" style-name)) - (progn - ;; Neuen Textstil mit Arial erstellen - (command "_.STYLE" style-name "Arial" 0 1 0 "N" "N" "N") - (princ (strcat "\n-> Textstil '" style-name "' mit Arial erstellt")) - ) - ) - - ;; Aktuellen Textstil setzen - (setvar "TEXTSTYLE" style-name) - style-name -) - -;; Allgemeine Nummerierung: Naechste freie Nummer fuer einen Komponententyp ermitteln -;; blockprefix = Blockname-Praefix fuer ssget (z.B. "KREISEL_*" oder "ECKRAD_*") -;; lastNrVar = Symbol der globalen Zaehler-Variable (z.B. '#Kreisel_LetzteNr) -;; Rueckgabe: naechste freie Nummer (integer) -(defun component-next-number (blockprefix lastNrVar / maxNr tag val ss i ent attribs lastNr result) - (dbgf "component-next-number") - (dbg 'blockprefix) - (dbg 'lastNrVar) - (setq maxNr 0) - (setq lastNr (eval lastNrVar)) - (dbgp (strcat "lastNr=" (itoa lastNr))) - (if (setq ss (ssget "_X" (list '(0 . "INSERT") (cons 2 blockprefix)))) - (progn - (dbgp (strcat "Gefundene Bloecke: " (itoa (sslength ss)))) - (setq i 0) - (repeat (sslength ss) - (setq ent (ssname ss i)) - (setq attribs (ssg-attrib-read ent)) - (setq tag (cdr (assoc "NUMMER" attribs))) - (if (and tag (setq val (atoi tag)) (> val maxNr)) - (setq maxNr val) - ) - (setq i (1+ i)) - ) - ) - (dbgp "Keine bestehenden Bloecke gefunden") - ) - (if (= maxNr 0) - (setq maxNr lastNr) - ) - (set lastNrVar (1+ maxNr)) - (setq result (eval lastNrVar)) - (dbgp (strcat "Naechste Nummer: " (itoa result))) - (dbgreturn result) -) - -;; Wrapper: Naechste Kreisel-Nummer -(defun kreisel-next-number ( / result) - (dbgf "kreisel-next-number") - (setq result (component-next-number "KREISEL_*" '#Kreisel_LetzteNr)) - (dbgreturn result) -) - -;; Wrapper: Naechste Eckrad-Nummer -(defun eckrad-next-number ( / result) - (dbgf "eckrad-next-number") - (setq result (component-next-number "ECKRAD_*" '#Eckrad_LetzteNr)) - (dbgreturn result) -) - -;; Beschriftungstext erzeugen -(defun kreisel-make-label (nummer abstand kreiselart / textStr insertPoint radius mitteX mitteY textHoehe) - ;; Text: "KREISEL 1: PIN_4000" - (setq textStr (strcat "KREISEL " (itoa nummer) ": " kreiselart "_" (rtos abstand 2 0))) - - ;; Geometrie berechnen - (setq radius (/ *kreisel-durchmesser* 2.0)) - - ;; Mitte zwischen AN8 und SP8 (X-Mitte des gesamten Kreisels) - (setq mitteX (+ radius (/ abstand 2.0))) - - ;; Y-Position: Leicht über der mitteX - (setq mitteY *kreisel-beschriftung-abstand-oben*) - - ;; Texthöhe - (setq textHoehe *kreisel-beschriftung-hoehe*) ;; 100 mm - - ;; Position - (setq insertPoint (list mitteX mitteY 0)) - - ;; Schriftart Arial einstellen - (kreisel-setup-textstyle) - - ;; Layer erstellen - (ssg-make-layer *kreisel-beschriftung-layer* (itoa *kreisel-beschriftung-farbe*) T) - - ;; Text mit entmake erstellen (zentriert) - (entmake (list - '(0 . "TEXT") - (cons 8 *kreisel-beschriftung-layer*) - (cons 10 insertPoint) - (cons 40 textHoehe) - (cons 1 textStr) - (cons 50 0.0) - (cons 7 "Kreisel_Arial") ;; Arial Textstil - (cons 62 *kreisel-beschriftung-farbe*) - '(72 . 1) ;; Horizontale Ausrichtung: Mitte - '(73 . 2) ;; Vertikale Ausrichtung: Mitte - (cons 11 insertPoint) ;; Ausrichtungspunkt - )) - - (princ (strcat "\n-> Beschriftung: " textStr " (Höhe=" (rtos textHoehe 2 0) "mm, Arial, zentriert)")) - textStr -) - -;; ... Rest Ihrer Funktionen (kreisel-ask-typ, kreisel-project-point, etc.) - -;; Kreiselart abfragen. Rueckgabe: "PIN" oder "STANDARD" -(defun kreisel-ask-typ ( / typNum typ) - (princ "\n[1] STANDARD (ohne PIN)") - (princ "\n[2] PIN") - (princ (strcat "\nBitte waehlen <" (if (= #Kreisel_Typ "PIN") "2" "1") ">: ")) - (setq typNum (getstring "")) - (cond - ((= typNum "2") (setq typ "PIN")) - (t (setq typ "STANDARD")) - ) - (setq #Kreisel_Typ typ) - typ -) - -;; Punkt auf Gerade projizieren (Fusspunkt der Senkrechten) -(defun kreisel-project-point (pt lstart lend / dx dy tval) - (setq dx (- (car lend) (car lstart)) - dy (- (cadr lend) (cadr lstart))) - (setq tval (/ (+ (* (- (car pt) (car lstart)) dx) - (* (- (cadr pt) (cadr lstart)) dy)) - (+ (* dx dx) (* dy dy)))) - (list (+ (car lstart) (* tval dx)) - (+ (cadr lstart) (* tval dy))) -) - -;; Linien-Endpunkte aus LINE-Entity lesen. Rueckgabe: ((sx sy sz) (ex ey ez)) -(defun kreisel-get-line-pts (ent / ed p10 p11) - (setq ed (entget ent)) - (setq p10 (cdr (assoc 10 ed)) - p11 (cdr (assoc 11 ed))) - (list p10 p11) -) - -;; Pruefen ob zwei Richtungsvektoren parallel sind -(defun kreisel-parallel-p (d1 d2 / cross len1 len2) - (setq cross (abs (- (* (car d1) (cadr d2)) (* (cadr d1) (car d2)))) - len1 (sqrt (+ (* (car d1) (car d1)) (* (cadr d1) (cadr d1)))) - len2 (sqrt (+ (* (car d2) (car d2)) (* (cadr d2) (cadr d2))))) - (< (/ cross (* len1 len2)) 0.01) -) - - -;; Attribut-Funktionen: ssg-attrib-read, ssg-attrib-merge, -;; ssg-attrib-defaults, ssg-attrib-make-defs aus ssg_core.lsp - - -;; ============================================================ -;; TEIL 3: COMPOUND BLOCK ERZEUGEN UND EINFUEGEN -;; ============================================================ - -;; Zeichnet Kreisel-Geometrie bei (0,0), erzeugt Block mit Attributen, -;; fuegt ihn ein und setzt Attribut-Werte. -;; -;; basePoint = Einfuegepunkt (2D-Liste), AN-Seite -;; abstand = Tangentenlaenge zwischen Kreismitten -;; rotation = Einfuegewinkel in Grad (0=rechts, 90=oben, 180=links, 270=unten) -;; attribs = alist mit Attribut-Ueberschreibungen (nil = alles Defaults) -;; ABSTAND und DREHUNG werden automatisch aus den Parametern gesetzt. -;; KREISELART bestimmt ob PIN-Linien gezeichnet werden. -;; -;; Blockgeometrie (bei Rotation 0, horizontal): -;; Basispunkt (0,0) = linke Kreistangente (AN-Seite) -;; AN8 Zentrum bei (radius, 0) -;; SP8 Zentrum bei (radius+abstand, 0) -;; Gesamtbreite = durchmesser + abstand -;; -;; Rueckgabe: Entity-Name des eingefuegten Blocks - -(defun draw-module (basePoint abstand rotation attribs / lastEnt ss e bname - radius pin-y is-pin - oldAttreq oldAttdia - kreisel-nummer block-exists - z-hoehe block-hoehe - insertPoint block-ent - att-entity att-data att-tag new-value - beforeAtt) - - ;; Attribute vorbereiten - (setq attribs (ssg-attrib-merge attribs *kreisel-attrib-defs*)) - (setq attribs (subst (cons "ABSTAND" (rtos abstand 2 0)) - (assoc "ABSTAND" attribs) attribs)) - (setq attribs (subst (cons "DREHUNG" (rtos rotation 2 1)) - (assoc "DREHUNG" attribs) attribs)) - (setq attribs (subst (cons "HOEHE" (rtos (if (caddr basePoint) (caddr basePoint) 0.0) 2 1)) - (assoc "HOEHE" attribs) attribs)) - (setq is-pin (equal (cdr (assoc "KREISELART" attribs)) "PIN")) - - ;; NUMMER: Bestehende beibehalten (Redraw/Edit), sonst neue vergeben - (setq kreisel-nummer (atoi (cdr (assoc "NUMMER" attribs)))) - (if (<= kreisel-nummer 0) - (progn - (setq kreisel-nummer (kreisel-next-number)) - (setq attribs (subst (cons "NUMMER" (itoa kreisel-nummer)) - (assoc "NUMMER" attribs) attribs)) - ) - ) - - ;; NAME automatisch setzen: "KreiselN" - (setq attribs (subst (cons "NAME" (strcat "Kreisel" (itoa kreisel-nummer))) - (assoc "NAME" attribs) attribs)) - - ;; --- HÖHE BESTIMMEN --- - ;; Priorität 1: Aus basePoint (wenn 3D Punkt mit Z > 0) - ;; Priorität 2: Aus Attribut HOEHE (wenn vorhanden) - ;; Priorität 3: Default 0 - (if (and basePoint (caddr basePoint) (> (caddr basePoint) 0)) - (progn - (setq z-hoehe (caddr basePoint)) - (setq block-hoehe (rtos z-hoehe 2 0)) - (princ (strcat "\n-> Höhe aus Basispunkt übernommen: " block-hoehe)) - ) - (progn - ;; Höhe aus Attributen lesen - (setq block-hoehe (cdr (assoc "HOEHE" attribs))) - (if (or (null block-hoehe) (= block-hoehe "")) - (setq block-hoehe "0") - ) - (setq z-hoehe (atof block-hoehe)) - ) - ) - - ;; Attribut HOEHE aktualisieren - (setq attribs (subst (cons "HOEHE" block-hoehe) - (assoc "HOEHE" attribs) attribs)) - - ;; Abgeleitete Masse - (setq radius (/ *kreisel-durchmesser* 2.0)) - (setq pin-y (- radius *kreisel-pin-abstand*)) - - ;; Blockname Format: KREISEL_1_PIN_2300 oder KREISEL_1_STANDARD_2300 - (setq bname (strcat "KREISEL_" - (itoa kreisel-nummer) "_" - (if is-pin "PIN" "STANDARD") "_" - (rtos abstand 2 0))) - - ;; Blocknamen bereinigen (Punkt durch nichts ersetzen, da wir keine Dezimalstellen wollen) - (setq bname (vl-string-subst "" "." bname)) - - ;; Pruefen ob Block bereits existiert - (setq block-exists (tblsearch "BLOCK" bname)) - - ;; ATTREQ/ATTDIA und OSMODE VOR Sub-Block-Insertions setzen, damit INSERT - ;; nicht nach Attributwerten fragt und OSNAP die Koordinaten nicht verschiebt - (setvar "ATTREQ" 0) - (setvar "ATTDIA" 0) - (setvar "OSMODE" 0) - - ;; --- Geometrie NUR zeichnen, wenn Block noch nicht existiert --- - (if (not block-exists) - (progn - ;; WICHTIG: Vor dem Zeichnen den aktuellen letzten Entity merken - ;; Aber wenn die Zeichnung leer ist, setzen wir lastEnt auf nil - (if (setq lastEnt (entlast)) - (progn - ;; Falls lastEnt ein INSERT mit Attributen ist, muessen wir - ;; bis zum SEQEND navigieren, da entnext sonst in die - ;; ATTRIB-Sub-Entities hineinlaeuft - (if (and (= (cdr (assoc 0 (entget lastEnt))) "INSERT") - (= (cdr (assoc 66 (entget lastEnt))) 1)) - (progn - (setq lastEnt (entnext lastEnt)) - (while (and lastEnt - (/= (cdr (assoc 0 (entget lastEnt))) "SEQEND")) - (setq lastEnt (entnext lastEnt)) - ) - ;; lastEnt ist jetzt SEQEND - entnext darauf gibt das naechste Top-Level-Entity - ) - ) - ) - (setq lastEnt nil) ;; Zeichnung ist leer - ) - - ;; AN8 Kreis (Antriebsstation) bei (radius, 0) - (command "_.INSERT" (strcat block-pfad "AN8.dwg") (list radius 0.0 0.0) 1 1 0) - - ;; SP8 Kreis (Spannstation) bei (radius+abstand, 0) - (command "_.INSERT" (strcat block-pfad "SP8.dwg") (list (+ radius abstand) 0.0 0.0) 1 1 0) - - ;; Tangenten auf Layer S_LP - (ssg-make-layer "S_LP" "7" T) - (if is-pin - (setvar "CECOLOR" "5") - (setvar "CECOLOR" "1") - ) - - ;; Obere Tangente - (command "_.LINE" (list radius radius 0) (list (+ radius abstand) radius 0) "") - ;; Untere Tangente - (command "_.LINE" (list radius (- radius) 0) (list (+ radius abstand) (- radius) 0) "") - - ;; PIN-Linien - (if is-pin - (progn - (ssg-make-layer "pinbereich" "3" T) - (setvar "CECOLOR" "3") - - ;; Obere PIN-Linie - (command "_.LINE" (list *kreisel-durchmesser* pin-y 0) (list (float abstand) pin-y 0) "") - ;; Untere PIN-Linie - (command "_.LINE" (list *kreisel-durchmesser* (- pin-y) 0) (list (float abstand) (- pin-y) 0) "") - ) - ) - - ;; BESCHRIFTUNG einfügen (vor Block-Erzeugung, damit sie im Block ist) - (kreisel-make-label kreisel-nummer abstand (if is-pin "PIN" "STANDARD") ) - - ;; Unsichtbare Attribut-Definitionen erzeugen - (ssg-attrib-make-defs *kreisel-attrib-defs* 50.0) - - ;; --- Neue Entities einsammeln --- - (setq ss (ssadd)) - - ;; Korrekte Behandlung wenn lastEnt nil ist (Zeichnung war leer) - (if (null lastEnt) - (setq e (entnext)) - (setq e (entnext lastEnt)) - ) - - (while e - (ssadd e ss) - (setq e (entnext e)) - ) - - ;; --- Block erzeugen (Entities werden aus Zeichnung entfernt) --- - (if (> (sslength ss) 0) - (command "_.-BLOCK" bname (list 0.0 0.0 0.0) ss "") - (princ "\nFehler: Keine Entities zum Block erstellen!") - ) - - (princ (strcat "\n-> Neue Blockdefinition: " bname)) - ) - (princ (strcat "\n-> Verwende bestehende Blockdefinition: " bname)) - ) - - ;; --- Block am Zielpunkt einfuegen (ohne Attribut-Dialog) --- - ;; ATTREQ/ATTDIA wurden bereits oben gesetzt - - ;; Einfügepunkt: X,Y aus basePoint, Z aus ermittelter Höhe - - (setq insertPoint (list (car basePoint) (cadr basePoint) z-hoehe)) - (command "_.INSERT" bname insertPoint 1 1 rotation) - - ;; --- Attribut-Werte durch direkte Datenbankänderung setzen --- - (setq block-ent (entlast)) - (setq att-entity (entnext block-ent)) - - (while att-entity - (setq att-data (entget att-entity)) - (if (= (cdr (assoc 0 att-data)) "ATTRIB") - (progn - (setq att-tag (cdr (assoc 2 att-data))) - (setq new-value (cdr (assoc att-tag attribs))) - (if new-value - (progn - (setq att-data (subst (cons 1 new-value) (assoc 1 att-data) att-data)) - (entmod att-data) - ) - ) - ) - ) - (setq att-entity (entnext att-entity)) - ) - (entupd block-ent) - (princ (strcat "\n-> Kreisel #" (itoa kreisel-nummer) " eingefügt: " bname - " (Höhe=" block-hoehe ")")) - (entlast) -) - - -;; ============================================================ -;; TEIL 4: BEFEHLE -;; ============================================================ - -;; -------------------------------------------- -;; KreiselInsert: Manuell Position + Parameter -;; -------------------------------------------- - -;; -------------------------------------------- -;; KreiselInsert: Manuell Position + Parameter -;; -------------------------------------------- -(defun c:KreiselInsert ( / pt abstand typ rotation rotStr hoehe - selectedEnt blockAttribs oldOsmode) - (ssg-start "Kreisel Modul einfuegen" '(("OSMODE") ("CECOLOR"))) - - ;; Temporär Osnap für Blockerkennung setzen - (setq oldOsmode (getvar "OSMODE")) - (setvar "OSMODE" (logior oldOsmode 512)) ;; 512 = INS (Block-Einfügepunkt) - - ;; 1. Einfuegepunkt - (setq pt (getpoint "\nBasispunkt (AN-Seite): ")) - (if (null pt) - (progn - (setvar "OSMODE" oldOsmode) - (ssg-end) - (princ "\nBefehl abgebrochen.") - (princ) - (return) - ) - ) - - ;; Osnap zurücksetzen auf den ursprünglichen Wert - (setvar "OSMODE" oldOsmode) - - ;; 2. Prüfen ob ein Block getroffen wurde - (setq selectedEnt (nentselp pt)) - - ;; --- HÖHE BESTIMMEN --- - (cond - ;; Fall 1: Auf einem Kreisel-Block geklickt - ((and selectedEnt - (setq selectedEnt (car selectedEnt)) - (setq blockAttribs (ssg-attrib-read selectedEnt)) - (assoc "HOEHE" blockAttribs)) - (setq hoehe (atof (cdr (assoc "HOEHE" blockAttribs)))) - (princ (strcat "\n-> Höhe von bestehendem Kreisel übernommen: " (rtos hoehe 2 0))) - ) - ;; Fall 2: Punkt hat Z-Koordinate > 0 - ((and (> (length pt) 2) (> (caddr pt) 0)) - (setq hoehe (caddr pt)) - (princ (strcat "\n-> Höhe aus 3D-Punkt übernommen: " (rtos hoehe 2 0))) - ) - ;; Fall 3: Sonst separat abfragen - (t - (setq hoehe (getreal (strcat "\nHöhe (Z-Koordinate) <" (rtos *kreisel-default-hoehe* 2 0) ">: "))) - (if (null hoehe) (setq hoehe *kreisel-default-hoehe*)) - ) - ) - - ;; 3. Abstand - (initget 6) - (setq abstand (getdist (list (car pt) (cadr pt)) - (strcat "\nAbstand (Tangentenlaenge) <" - (rtos *kreisel-default-laenge* 2 0) ">: "))) - (if (null abstand) (setq abstand *kreisel-default-laenge*)) - (princ (strcat "\n-> Abstand: " (rtos abstand 2 0) " mm")) - - ;; 4. Rotation - (setq rotStr (getstring "\nRotation in Grad <0>: ")) - (if (= rotStr "") - (setq rotation 0.0) - (setq rotation (atof rotStr)) - ) - - ;; 5. Kreiselart - (setq typ (kreisel-ask-typ)) - - (princ (strcat "\n-> Rotation: " (rtos rotation 2 1) - " Höhe: " (rtos hoehe 2 0) - " Kreiselart: " typ)) - - ;; 6. Modul einfuegen - (draw-module (list (car pt) (cadr pt) hoehe) abstand rotation - (list (cons "KREISELART" typ) - (cons "HOEHE" (rtos hoehe 2 0)))) - - ;; KEIN weiteres (setvar "OSMODE" ...) mehr nötig! - ;; ssg-end stellt nur CLAYER und CMDECHO wieder her, nicht OSMODE - - (princ "\nKreisel Modul eingefuegt.") - (ssg-end) -) - - -;; ----------------------------------------------- -;; KreiselConnect: Linie zeichnen fuer Kreisel-Platzierung -;; -;; Ablauf: -;; 1. Startpunkt anklicken -> aeusserster AN-Punkt (Antriebsstation) -;; 2. Endpunkt anklicken -> aeusserster SP-Punkt (Spannstation) -;; 3. Abstand = Distanz - Durchmesser, Rotation aus Winkel -;; 4. Hoehe aus Z-Koordinate des Startpunkts -;; 5. Nur Kreiselart (PIN/STANDARD) wird abgefragt -;; ----------------------------------------------- -(defun c:KreiselConnect ( / ptStart ptEnd dx dy dist abstand rotation typ hoehe) - - (ssg-start "Kreisel Smart Connect" '(("OSMODE") ("CECOLOR"))) - - (princ "\n--- Kreisel durch Linie definieren (AN -> SP) ---") - - ;; 1. Startpunkt (aeusserster Punkt AN-Seite) - (setq ptStart (getpoint "\nStartpunkt (AN-Seite aussen): ")) - (if (null ptStart) - (progn (ssg-end) (princ "\nAbgebrochen.") (princ) (exit)) - ) - - ;; 2. Endpunkt (aeusserster Punkt SP-Seite) mit Gummiband - (setq ptEnd (getpoint ptStart "\nEndpunkt (SP-Seite aussen): ")) - (if (null ptEnd) - (progn (ssg-end) (princ "\nAbgebrochen.") (princ) (exit)) - ) - - ;; 3. Geometrie berechnen - (setq dx (- (car ptEnd) (car ptStart)) - dy (- (cadr ptEnd) (cadr ptStart))) - (setq dist (sqrt (+ (* dx dx) (* dy dy)))) - (setq abstand (- dist *kreisel-durchmesser*)) - - (if (<= abstand 0.0) - (progn - (ssg-emsg (strcat "Distanz zu klein! (" - (rtos dist 2 0) " < " (rtos *kreisel-durchmesser* 2 0) ")")) - (ssg-end) - (exit) - ) - ) - - ;; 4. Rotation aus Winkel Start->End - (setq rotation (* (/ 180.0 pi) (atan dy dx))) - - ;; 5. Hoehe aus Z-Koordinate des Startpunkts - (if (and (> (length ptStart) 2) (caddr ptStart)) - (setq hoehe (caddr ptStart)) - (setq hoehe 0.0) - ) - (if (<= hoehe 0.0) - (progn - (setq hoehe (getreal (strcat "\nHoehe (Z-Koordinate) <" (rtos *kreisel-default-hoehe* 2 0) ">: "))) - (if (null hoehe) (setq hoehe *kreisel-default-hoehe*)) - ) - (princ (strcat "\n-> Hoehe aus Startpunkt uebernommen: " (rtos hoehe 2 0))) - ) - - ;; 6. Kreiselart abfragen - (setq typ (kreisel-ask-typ)) - - (princ (strcat "\n-> Abstand: " (rtos abstand 2 0) " mm" - " Rotation: " (rtos rotation 2 1) " Grad" - " Hoehe: " (rtos hoehe 2 0) - " Kreiselart: " typ)) - - ;; 7. Modul einfuegen - (draw-module (list (car ptStart) (cadr ptStart) hoehe) abstand rotation - (list (cons "KREISELART" typ) - (cons "HOEHE" (rtos hoehe 2 0)))) - - (princ "\nKreisel Connect erfolgreich!") - (ssg-end) -) - - -;; Neue Befehle für Beschriftungs-Einstellungen -(defun c:KreiselLabelPos ( / dx dy) - (setq dx (getreal (strcat "\nX-Abstand von AN8-Mitte <" (rtos *kreisel-beschriftung-abstand-x* 2 0) ">: "))) - (if dx (setq *kreisel-beschriftung-abstand-x* dx)) - (setq dy (getreal (strcat "\nY-Abstand von AN8-Mitte <" (rtos *kreisel-beschriftung-abstand-y* 2 0) ">: "))) - (if dy (setq *kreisel-beschriftung-abstand-y* dy)) - (princ (strcat "\nNeue Beschriftungsposition: X=" (rtos *kreisel-beschriftung-abstand-x* 2 0) - " Y=" (rtos *kreisel-beschriftung-abstand-y* 2 0))) - (princ) -) - -(defun c:KreiselLabelHoehe ( / h) - (setq h (getreal (strcat "\nTexthöhe <" (rtos *kreisel-beschriftung-hoehe* 2 0) ">: "))) - (if h (setq *kreisel-beschriftung-hoehe* h)) - (princ (strcat "\nNeue Texthöhe: " (rtos *kreisel-beschriftung-hoehe* 2 0))) - (princ) -) - -;; ----------------------------------------------- -;; KreiselRedraw: Bestehenden Kreisel neu zeichnen -;; -;; Liest Attribute aus dem bestehenden Block, -;; loescht ihn, und zeichnet ihn mit (optional -;; geaendertem) Abstand neu. Alle Attribute bleiben -;; erhalten. -;; ----------------------------------------------- -(defun c:KreiselRedraw ( / sel ent ed attribs basePoint rotation newAbstand) - - (ssg-start "Kreisel Neu Zeichnen" '(("CECOLOR"))) - - ;; 1. Bestehenden Kreisel-Block auswaehlen - (setq sel (entsel "\nKreisel-Block auswaehlen: ")) - (if (null sel) (exit)) - (setq ent (car sel)) - (setq ed (entget ent)) - - ;; Pruefen ob INSERT - (if (/= (cdr (assoc 0 ed)) "INSERT") - (progn (ssg-emsg "Kein Block ausgewaehlt!") (exit)) - ) - - ;; 2. Bestehende Werte auslesen - (setq attribs (ssg-attrib-read ent)) - (setq basePoint (cdr (assoc 10 ed))) - (setq rotation (* (/ 180.0 pi) (cdr (assoc 50 ed)))) - - ;; NEU - Höhe aus Attributen lesen und anzeigen: - (princ (strcat "\n-> Bestehend: Abstand=" (cdr (assoc "ABSTAND" attribs)) - " Drehung=" (cdr (assoc "DREHUNG" attribs)) - " Höhe=" (cdr (assoc "HOEHE" attribs)) ;; <-- NEU - " Art=" (cdr (assoc "KREISELART" attribs)))) - - ;; 3. Neuen Abstand abfragen (Enter = beibehalten) - (initget 6) - (setq newAbstand (getdist (strcat "\nNeuer Abstand <" (cdr (assoc "ABSTAND" attribs)) ">: "))) - (if (null newAbstand) - (setq newAbstand (atof (cdr (assoc "ABSTAND" attribs)))) - ) - ;; NEU - Neue Höhe abfragen: - (setq newHoehe (getreal (strcat "\nNeue Höhe <" (cdr (assoc "HOEHE" attribs)) ">: "))) - (if (null newHoehe) - (setq newHoehe (atof (cdr (assoc "HOEHE" attribs)))) - ) - ;; 4. Alten Block loeschen - (command "_.ERASE" ent "") - - ;; 5. Neu zeichnen mit bestehenden Attributen + neuem Abstand - (draw-module (list (car basePoint) (cadr basePoint) newHoehe) - newAbstand rotation attribs) - - (princ (strcat "\nKreisel neu gezeichnet. Abstand=" (rtos newAbstand 2 0))) - (ssg-end) -) - - -;; -------------------------------------------- -;; KreiselQuick: Schnell mit Defaults (horizontal) -;; -------------------------------------------- -(defun c:KreiselQuick ( / pt) - (ssg-start "Kreisel Schnell Einfuegen" '(("CECOLOR"))) - (setq pt (getpoint "\nBasispunkt (AN-Seite): ")) - ;; NEU - Höhe abfragen: - (setq hoehe (getreal (strcat "\nHöhe (Z-Koordinate) <0>: "))) - (if (null hoehe) (setq hoehe 0.0)) - (if pt - ;; NEU - Höhe im Aufruf übergeben: - (draw-module (list (car pt) (cadr pt) hoehe) *kreisel-default-laenge* 0.0 - (list (cons "HOEHE" (rtos hoehe 2 0)))) - ) - (princ "\nSchnell Einfuegen fertig.") - (ssg-end) -) - - -;; ============================================================ -;; TEIL 5: KREISEL BEARBEITEN (DCL-Dialog) -;; ============================================================ - -;; Ausrichtungs-Definitionen: ((Label Rotation) ...) -;; Rotation in Grad: 0=rechts, 90=oben, 180=links, 270=unten -(setq *kreisel-ausrichtungen* - '(("Horizontal MS" 0.0) - ("Horizontal SM" 180.0) - ("Vertikal MS" 90.0) - ("Vertikal SM" 270.0)) -) - -;; Rotation (Grad) -> Ausrichtungs-Index (fuer popup_list) -(defun kreisel-rotation-to-idx (rot / idx i entry diff) - (setq idx 0 i 0) - (foreach entry *kreisel-ausrichtungen* - (setq diff (abs (- (rem (+ rot 360.0) 360.0) - (rem (+ (cadr entry) 360.0) 360.0)))) - (if (< diff 1.0) (setq idx i)) - (setq i (1+ i)) - ) - idx -) - -;; Ausrichtungs-Index -> Rotation (Grad) -(defun kreisel-idx-to-rotation (idx) - (cadr (nth idx *kreisel-ausrichtungen*)) -) - -;; ----------------------------------------------- -;; KreiselEdit: Bestehenden Kreisel per Dialog bearbeiten -;; -;; Ablauf: -;; 1. Kreisel-Block auswaehlen (oder vorselektiert) -;; 2. DCL-Dialog mit aktuellen Werten oeffnen -;; 3. Bei OK: Block loeschen und mit neuen Werten neu zeichnen -;; ----------------------------------------------- -(defun c:KreiselEdit ( / sel ss ent ed attribs basePoint rotation - dcl-pfad dat ergebnis - dlg-name dlg-abstand dlg-hoehe dlg-ausrichtung - dlg-drehrichtung dlg-kreiselart - newAbstand newRotation ausrichtLabels) - - ;; Implied Selection VOR ssg-start pruefen (ssg-start hebt Selektion auf) - (setq ss (ssget "I")) - (if (and ss (= (sslength ss) 1) - (= (cdr (assoc 0 (entget (ssname ss 0)))) "INSERT")) - (setq ent (ssname ss 0)) - ) - - (ssg-start "Kreisel bearbeiten" '(("OSMODE") ("CECOLOR") ("ATTREQ") ("ATTDIA"))) - (setvar "OSMODE" 0) - - ;; 1. Block auswaehlen (falls nicht vorselektiert) - (if (null ent) - (progn - (setq sel (entsel "\nKreisel-Block auswaehlen: ")) - (if sel (setq ent (car sel))) - ) - ) - (if (null ent) (progn (ssg-end) (exit))) - - (setq ed (entget ent)) - (if (/= (cdr (assoc 0 ed)) "INSERT") - (progn (ssg-emsg "Kein Block ausgewaehlt!") (ssg-end) (exit)) - ) - - ;; 2. Bestehende Werte auslesen - (setq attribs (ssg-attrib-read ent)) - (setq basePoint (cdr (assoc 10 ed))) - (setq rotation (* (/ 180.0 pi) (cdr (assoc 50 ed)))) - - ;; Pruefen ob Block Kreisel-Attribute hat (mindestens ABSTAND) - (if (null (assoc "ABSTAND" attribs)) - (progn - (ssg-emsg "Kein Kreisel-Block! (Attribut ABSTAND fehlt)") - (ssg-end) - (exit) - ) - ) - - ;; Fehlende Attribute mit Defaults auffuellen - (foreach def *kreisel-attrib-defs* - (if (null (assoc (car def) attribs)) - (setq attribs (append attribs (list (cons (car def) (cadr def))))) - ) - ) - - ;; HOEHE immer aus Z-Koordinate des Einfuegepunkts befuellen - (setq attribs (subst (cons "HOEHE" (rtos (if (caddr basePoint) (caddr basePoint) 0.0) 2 1)) - (assoc "HOEHE" attribs) attribs)) - - (princ (strcat "\n-> Bestehend: Abstand=" (cdr (assoc "ABSTAND" attribs)) - " Drehung=" (rtos rotation 2 1) - " Art=" (cdr (assoc "KREISELART" attribs)))) - - ;; 3. DCL-Dialog laden - (setq dcl-pfad (strcat (getenv "DXFM_DCL") "/kreisel_edit.dcl")) - (setq dat (load_dialog dcl-pfad)) - (if (not (new_dialog "kreisel_edit" dat)) - (progn (alert (strcat "Dialog nicht verfuegbar: " dcl-pfad)) (ssg-end) (exit)) - ) - - ;; Ausrichtungs-Labels fuer popup_list aufbauen - (setq ausrichtLabels "") - (foreach entry *kreisel-ausrichtungen* - (if (= ausrichtLabels "") - (setq ausrichtLabels (car entry)) - (setq ausrichtLabels (strcat ausrichtLabels "\n" (car entry))) - ) - ) - - ;; Tiles initialisieren - (set_tile "name" (cdr (assoc "NAME" attribs))) - (set_tile "abstand" (cdr (assoc "ABSTAND" attribs))) - (set_tile "hoehe" (cdr (assoc "HOEHE" attribs))) - - (start_list "ausrichtung") - (foreach entry *kreisel-ausrichtungen* - (add_list (car entry)) - ) - (end_list) - (set_tile "ausrichtung" (itoa (kreisel-rotation-to-idx rotation))) - - (start_list "drehrichtung") - (add_list "UZS") - (add_list "GUZ") - (end_list) - (set_tile "drehrichtung" (if (equal (cdr (assoc "DREHRICHTUNG" attribs)) "GUZ") "1" "0")) - - (start_list "kreiselart") - (add_list "STANDARD") - (add_list "PIN") - (end_list) - (set_tile "kreiselart" (if (equal (cdr (assoc "KREISELART" attribs)) "PIN") "1" "0")) - - ;; Actions - (action_tile "accept" - (strcat - "(setq dlg-name (get_tile \"name\"))" - "(setq dlg-abstand (get_tile \"abstand\"))" - "(setq dlg-hoehe (get_tile \"hoehe\"))" - "(setq dlg-ausrichtung (get_tile \"ausrichtung\"))" - "(setq dlg-drehrichtung (get_tile \"drehrichtung\"))" - "(setq dlg-kreiselart (get_tile \"kreiselart\"))" - "(done_dialog 1)" - ) - ) - (action_tile "cancel" "(done_dialog 0)") - - (setq ergebnis (start_dialog)) - (unload_dialog dat) - - ;; 4. Bei OK: Block loeschen und neu zeichnen - (if (= ergebnis 1) - (progn - ;; Werte aufbereiten - (setq newAbstand (atof dlg-abstand)) - (setq newRotation (kreisel-idx-to-rotation (atoi dlg-ausrichtung))) - - ;; Attribute aktualisieren - (setq attribs (subst (cons "NAME" dlg-name) - (assoc "NAME" attribs) attribs)) - (setq attribs (subst (cons "HOEHE" dlg-hoehe) - (assoc "HOEHE" attribs) attribs)) - (setq attribs (subst (cons "KREISELART" - (if (= dlg-kreiselart "1") "PIN" "STANDARD")) - (assoc "KREISELART" attribs) attribs)) - (setq attribs (subst (cons "DREHRICHTUNG" - (if (= dlg-drehrichtung "1") "GUZ" "UZS")) - (assoc "DREHRICHTUNG" attribs) attribs)) - - ;; Alten Block loeschen - (command "_.ERASE" ent "") - - ;; Neu zeichnen mit Z-Koordinate aus Hoehe-Dialogwert - (draw-module (list (car basePoint) (cadr basePoint) (atof dlg-hoehe)) - newAbstand newRotation attribs) - - (princ (strcat "\nKreisel aktualisiert: " - "Name=" dlg-name - " Abstand=" (rtos newAbstand 2 0) - " Hoehe=" dlg-hoehe - " Rotation=" (rtos newRotation 2 1))) - ) - (princ "\nAbgebrochen.") - ) - - (ssg-end) -) - - -;; -------------------------------------------- -;; KreiselParams: Aktuelle Parameter anzeigen -;; -------------------------------------------- -(defun c:KreiselParams () - (princ "\n=== Kreisel Parameter ===") - (princ (strcat "\nKreiselart: " #Kreisel_Typ)) - (princ (strcat "\nDurchmesser: " (rtos *kreisel-durchmesser* 2 0))) - (princ (strcat "\nDefault-Laenge: " (rtos *kreisel-default-laenge* 2 0))) - ;; NEU - Default-Höhe anzeigen: - (princ (strcat "\nDefault-Höhe: " (rtos *kreisel-default-hoehe* 2 0))) - (princ "\nAttribute im Block:") - (foreach def *kreisel-attrib-defs* - (princ (strcat "\n " (car def) " = " (cadr def))) - ) - (princ "\n=========================\n") - (princ) -) - - -;; -------------------------------------------- -;; ILS_Eckrad: Befehl - fragt Eingaben ab und fuegt Eckrad ein -;; -;; Ablauf: -;; 1. Beruehrpunkt anklicken (wo der Kreis tangential anliegen soll) -;; 2. Richtungspunkt anklicken (bestimmt Versatzrichtung zum Kreismittelpunkt) -;; 3. Kreismittelpunkt = Beruehrpunkt + Radius in Richtung des 2. Klicks -;; 4. Hoehe aus Z-Koordinate oder manuell -;; -------------------------------------------- -(defun c:ILS_Eckrad ( / ptTangent ptDir dx dy dist ux uy radius - centerPt rotation hoehe) - (dbgf "c:ILS_Eckrad") - - ;; 1. Beruehrpunkt (Tangentenpunkt) - (setq ptTangent (getpoint "\nBeruehrpunkt Eckrad (Tangente): ")) - (if (null ptTangent) (progn (dbgreturn nil) (exit))) - (dbg 'ptTangent) - - ;; 2. Richtungspunkt (Gummiband vom Beruehrpunkt) - (setq ptDir (getpoint ptTangent "\nRichtung zum Kreismittelpunkt: ")) - (if (null ptDir) (progn (dbgreturn nil) (exit))) - (dbg 'ptDir) - - ;; 3. Winkel und Versatz berechnen - (setq dx (- (car ptDir) (car ptTangent)) - dy (- (cadr ptDir) (cadr ptTangent))) - (setq dist (sqrt (+ (* dx dx) (* dy dy)))) - - (if (<= dist 0.001) - (progn - (princ "\nFehler: Beruehr- und Richtungspunkt sind identisch!") - (dbgreturn nil) - (exit) - ) - ) - - ;; Einheitsvektor in Richtung Beruehrpunkt -> Richtungspunkt - (setq ux (/ dx dist) - uy (/ dy dist)) - - ;; Radius = halber Kreisdurchmesser - (setq radius (/ *kreisel-durchmesser* 2.0)) - - ;; Kreismittelpunkt = Beruehrpunkt + Radius * Richtung - (setq centerPt (list (+ (car ptTangent) (* radius ux)) - (+ (cadr ptTangent) (* radius uy)))) - (dbg 'centerPt) - - ;; Rotation = Winkel der Versatzrichtung - (setq rotation (* (/ 180.0 pi) (atan dy dx))) - (dbg 'rotation) - - ;; 4. Hoehe bestimmen - (if (and (> (length ptTangent) 2) (caddr ptTangent) (> (caddr ptTangent) 0)) - (progn - (setq hoehe (caddr ptTangent)) - (princ (strcat "\n-> Hoehe aus 3D-Punkt uebernommen: " (rtos hoehe 2 0))) - ) - (progn - (setq hoehe (getreal (strcat "\nHoehe (Z-Koordinate) <" (rtos *eckrad-default-hoehe* 2 0) ">: "))) - (if (null hoehe) (setq hoehe *eckrad-default-hoehe*)) - ) - ) - (dbg 'hoehe) - - ;; 5. Einfuegen (centerPt ist der Kreismittelpunkt) - (ils-eckrad-insert (list (car centerPt) (cadr centerPt) hoehe) rotation nil) - (dbgreturn nil) -) - -;; -------------------------------------------- -;; ils-eckrad-insert: Erzeugt Eckrad als Compound-Block mit Attributen -;; Parameter: -;; pt - Einfuegepunkt (Liste x y z) -;; rotation - Drehwinkel in Grad -;; attribs - alist mit Attribut-Ueberschreibungen (nil = Defaults) -;; Rueckgabe: Entity-Name des eingefuegten Blocks -;; -------------------------------------------- -(defun ils-eckrad-insert (pt rotation attribs / lastEnt ss e bname - eckrad-nummer block-exists - z-hoehe block-hoehe - insertPoint block-ent - att-entity att-data att-tag new-value) - (dbgf "ils-eckrad-insert") - (dbg 'pt) - (dbg 'rotation) - - (ssg-start "Eckrad einfuegen" '(("ATTREQ") ("ATTDIA") ("OSMODE") ("CECOLOR"))) - - ;; Attribute vorbereiten - (setq attribs (ssg-attrib-merge attribs *eckrad-attrib-defs*)) - (setq attribs (subst (cons "DREHUNG" (rtos rotation 2 1)) - (assoc "DREHUNG" attribs) attribs)) - (dbgp "Attribute nach merge vorbereitet") - - ;; Hoehe bestimmen - (if (and pt (caddr pt) (> (caddr pt) 0)) - (progn - (setq z-hoehe (caddr pt)) - (setq block-hoehe (rtos z-hoehe 2 0)) - ) - (progn - (setq block-hoehe (cdr (assoc "HOEHE" attribs))) - (if (or (null block-hoehe) (= block-hoehe "")) - (setq block-hoehe "0") - ) - (setq z-hoehe (atof block-hoehe)) - ) - ) - (setq attribs (subst (cons "HOEHE" block-hoehe) - (assoc "HOEHE" attribs) attribs)) - (dbg 'z-hoehe) - (dbg 'block-hoehe) - - ;; Automatische Nummer vergeben - (setq eckrad-nummer (eckrad-next-number)) - (setq attribs (subst (cons "NUMMER" (itoa eckrad-nummer)) - (assoc "NUMMER" attribs) attribs)) - (dbg 'eckrad-nummer) - - ;; Blockname: ECKRAD_1, ECKRAD_2, ... - (setq bname (strcat "ECKRAD_" (itoa eckrad-nummer))) - (dbg 'bname) - - ;; Pruefen ob Block bereits existiert - (setq block-exists (tblsearch "BLOCK" bname)) - (dbgp (strcat "block-exists=" (if block-exists "JA" "NEIN"))) - - ;; ATTREQ/ATTDIA und OSMODE setzen - (setvar "ATTREQ" 0) - (setvar "ATTDIA" 0) - (setvar "OSMODE" 0) - - ;; --- Geometrie NUR zeichnen, wenn Block noch nicht existiert --- - (if (not block-exists) - (progn - (dbgp "Erzeuge neue Blockdefinition...") - ;; Letzten Entity merken (mit SEQEND-Schutz wie bei draw-module) - (if (setq lastEnt (entlast)) - (progn - (dbgp (strcat "lastEnt Typ=" (cdr (assoc 0 (entget lastEnt))))) - (if (and (= (cdr (assoc 0 (entget lastEnt))) "INSERT") - (= (cdr (assoc 66 (entget lastEnt))) 1)) - (progn - (dbgp "lastEnt ist INSERT mit Attributen -> navigiere zu SEQEND") - (setq lastEnt (entnext lastEnt)) - (while (and lastEnt - (/= (cdr (assoc 0 (entget lastEnt))) "SEQEND")) - (setq lastEnt (entnext lastEnt)) - ) - (dbgp "SEQEND erreicht") - ) - ) - ) - (setq lastEnt nil) - ) - - ;; AN8 Block einfuegen bei (0,0) - Eckrad ist ein einzelner Kreis - (dbgp (strcat "INSERT Block: " *block-path* "AN8.dwg")) - (command "_.INSERT" (strcat *block-path* "AN8.dwg") (list 0.0 0.0 0.0) 1 1 0) - - ;; Unsichtbare Attribut-Definitionen erzeugen - (dbgp (strcat "Erzeuge " (itoa (length *eckrad-attrib-defs*)) " Attribut-Definitionen")) - (ssg-attrib-make-defs *eckrad-attrib-defs* 50.0) - - ;; Neue Entities einsammeln - (setq ss (ssadd)) - (if (null lastEnt) - (setq e (entnext)) - (setq e (entnext lastEnt)) - ) - (while e - (ssadd e ss) - (setq e (entnext e)) - ) - (dbgp (strcat "Entities eingesammelt: " (itoa (sslength ss)))) - - ;; Block erzeugen - (if (> (sslength ss) 0) - (progn - (command "_.-BLOCK" bname (list 0.0 0.0 0.0) ss "") - (dbgp (strcat "Blockdefinition erzeugt: " bname)) - ) - (progn - (princ "\nFehler: Keine Entities zum Block erstellen!") - (dbgp "FEHLER: Keine Entities fuer Block!") - ) - ) - (princ (strcat "\n-> Neue Blockdefinition: " bname)) - ) - (progn - (princ (strcat "\n-> Verwende bestehende Blockdefinition: " bname)) - (dbgp (strcat "Verwende bestehende Blockdefinition: " bname)) - ) - ) - - ;; Block am Zielpunkt einfuegen - (setq insertPoint (list (car pt) (cadr pt) z-hoehe)) - (dbg 'insertPoint) - (dbgp (strcat "INSERT " bname " bei " (rtos (car insertPoint) 2 1) - "," (rtos (cadr insertPoint) 2 1) - "," (rtos (caddr insertPoint) 2 1) - " Rotation=" (rtos rotation 2 1))) - (command "_.INSERT" bname insertPoint 1 1 rotation) - - ;; Attribut-Werte durch direkte Datenbankaenderung setzen - (setq block-ent (entlast)) - (setq att-entity (entnext block-ent)) - (dbgp "Setze Attribut-Werte:") - (while att-entity - (setq att-data (entget att-entity)) - (if (= (cdr (assoc 0 att-data)) "ATTRIB") - (progn - (setq att-tag (cdr (assoc 2 att-data))) - (setq new-value (cdr (assoc att-tag attribs))) - (if new-value - (progn - (dbgp (strcat " " att-tag "=" new-value)) - (setq att-data (subst (cons 1 new-value) (assoc 1 att-data) att-data)) - (entmod att-data) - ) - ) - ) - ) - (setq att-entity (entnext att-entity)) - ) - (entupd block-ent) - - (princ (strcat "\n-> Eckrad #" (itoa eckrad-nummer) " eingefuegt: " bname - " bei " (rtos (car pt) 2 1) "," (rtos (cadr pt) 2 1) - " Rotation=" (rtos rotation 2 1) - " Hoehe=" block-hoehe)) - (ssg-end) - (dbgreturn (entlast)) -) - -;; NEU: KreiselLabelSetup - Hier einfügen! -(defun c:KreiselLabelSetup ( / hoehe farbe abstand schriftart) - (setq hoehe (getreal (strcat "\nSchrifthöhe <" (rtos *kreisel-beschriftung-hoehe* 2 0) ">: "))) - (if hoehe (setq *kreisel-beschriftung-hoehe* hoehe)) - - (setq farbe (getint (strcat "\nSchriftfarbe (1=Rot,2=Gelb,3=Grün,4=Cyan,5=Blau,6=Magenta,7=Weiß) <" (itoa *kreisel-beschriftung-farbe*) ">: "))) - (if farbe (setq *kreisel-beschriftung-farbe* farbe)) - - (setq abstand (getreal (strcat "\nAbstand über Kreisel-Mitte <" (rtos *kreisel-beschriftung-abstand-oben* 2 0) ">: "))) - (if abstand (setq *kreisel-beschriftung-abstand-oben* abstand)) - - (princ (strcat "\nNeue Einstellungen: Höhe=" (rtos *kreisel-beschriftung-hoehe* 2 0) - ", Farbe=" (itoa *kreisel-beschriftung-farbe*) - ", Y-Versatz=" (rtos *kreisel-beschriftung-abstand-oben* 2 0) - ", Schriftart=Arial")) - (princ) -) - -(princ "\nKreiselInsert.lsp geladen (Version 7.0 - Auto-Nummerierung + Beschriftung + Attribute + Redraw + Eckrad)") -(princ "\nBefehle: KreiselInsert, KreiselConnect, KreiselRedraw, KreiselEdit, KreiselQuick, KreiselParams, ILS_Eckrad") -(princ) diff --git a/Lisp/OmniModulInsert.lsp b/Lisp/OmniModulInsert.lsp deleted file mode 100644 index 6c7be8d..0000000 --- a/Lisp/OmniModulInsert.lsp +++ /dev/null @@ -1,1137 +0,0 @@ -;; ============================================================ -;; OMNIFLO JSON-DATEN LADEN UND ABFRAGEN -;; Liest omniflo_boegen.json und omniflo_weichen.json ein. -;; Daten werden als Listen von Assoziationslisten gespeichert. -;; -;; Globale Variablen: -;; *OMNI-BOEGEN* - Liste aller Boegen-Eintraege -;; *OMNI-WEICHEN* - Liste aller Weichen-Eintraege -;; -;; Funktionen: -;; (omni:load-data) - Beide JSON-Dateien laden -;; (omni:get-bogen sivasid) - Bogen-Info zu einer SivasId -;; (omni:get-weiche sivasid) - Weichen-Info zu einer SivasId -;; (omni:filter lst key wert) - Eintraege nach Key/Wert filtern -;; ============================================================ - - -;; --- Datei zeilenweise einlesen --- -(defun omni:read-file-lines (datei / f zeile ergebnis) - (setq f (open datei "r")) - (if (null f) - (progn - (princ (strcat "\n[OMNI] FEHLER: Datei nicht gefunden: " datei)) - nil - ) - (progn - (setq ergebnis nil) - (while (setq zeile (read-line f)) - (setq ergebnis (cons zeile ergebnis)) - ) - (close f) - (reverse ergebnis) - ) - ) -) - - -;; --- Whitespace und Komma am Ende einer Zeile entfernen --- -(defun omni:trim (s / len ch) - (setq s (vl-string-trim " \t\r\n" s)) - (setq len (strlen s)) - (if (and (> len 0) (= (substr s len 1) ",")) - (setq s (substr s 1 (1- len))) - ) - (vl-string-trim " \t\r\n" s) -) - - -;; --- JSON-Wert parsen (String, Zahl, null) --- -(defun omni:parse-value (val-str / len) - (setq val-str (omni:trim val-str)) - (cond - ((= val-str "null") nil) - ((= (substr val-str 1 1) "\"") - ;; String: Anfuehrungszeichen entfernen - (setq len (strlen val-str)) - (if (and (> len 1) (= (substr val-str len 1) "\"")) - (substr val-str 2 (- len 2)) - val-str - ) - ) - (T - ;; Zahl - (if (vl-string-search "." val-str) - (atof val-str) - (atoi val-str) - ) - ) - ) -) - - -;; --- Eine Key-Value-Zeile parsen -> dotted pair oder nil --- -(defun omni:parse-kv-line (zeile / pos key rest val) - (setq zeile (omni:trim zeile)) - ;; Muss mit " beginnen (Key) - (if (and (> (strlen zeile) 0) (= (substr zeile 1 1) "\"")) - (progn - ;; Key extrahieren: zweites Anfuehrungszeichen finden - ;; vl-string-search ist 0-basiert, substr ist 1-basiert - (setq pos (vl-string-search "\"" zeile 1)) - (if pos - (progn - (setq key (substr zeile 2 (1- pos))) - ;; Rest nach dem schliessenden " extrahieren - (setq rest (substr zeile (+ pos 2))) - ;; ":" im Rest finden - (setq pos (vl-string-search ":" rest)) - (if pos - (progn - (setq val (substr rest (+ pos 2))) - (cons key (omni:parse-value val)) - ) - ) - ) - ) - ) - ) -) - - -;; --- JSON-Array von flachen Objekten parsen --- -(defun omni:parse-json-array (zeilen / in-obj obj ergebnis zeile kv trimmed) - (setq in-obj nil - obj nil - ergebnis nil - ) - (foreach zeile zeilen - (setq trimmed (omni:trim zeile)) - (cond - ;; Objekt-Start - ((= trimmed "{") - (setq in-obj T - obj nil - ) - ) - ;; Objekt-Ende - ((or (= trimmed "}") (= trimmed "},")) - (if (and in-obj obj) - (setq ergebnis (cons (reverse obj) ergebnis)) - ) - (setq in-obj nil) - ) - ;; Key-Value-Zeile innerhalb eines Objekts - (in-obj - (setq kv (omni:parse-kv-line zeile)) - (if kv (setq obj (cons kv obj))) - ) - ) - ) - (reverse ergebnis) -) - - -;; --- JSON-Datei laden und als Liste von Alists zurueckgeben --- -(defun omni:load-json (datei / zeilen) - (setq zeilen (omni:read-file-lines datei)) - (if zeilen - (omni:parse-json-array zeilen) - ) -) - - -;; --- Beide Datensaetze laden --- -(defun omni:load-data ( / data-pfad f-boegen f-weichen) - (setq data-pfad (getenv "DXFM_DATA")) - (if (null data-pfad) - (progn - (princ "\n[OMNI] FEHLER: DXFM_DATA nicht gesetzt!") - nil - ) - (progn - ;; Boegen laden - (setq f-boegen (strcat data-pfad "/json/omniflo_boegen.json")) - (setq *OMNI-BOEGEN* (omni:load-json f-boegen)) - (if *OMNI-BOEGEN* - (princ (strcat "\n[OMNI] " (itoa (length *OMNI-BOEGEN*)) " Boegen geladen.")) - (princ (strcat "\n[OMNI] WARNUNG: Keine Boegen geladen aus " f-boegen)) - ) - ;; Weichen laden - (setq f-weichen (strcat data-pfad "/json/omniflo_weichen.json")) - (setq *OMNI-WEICHEN* (omni:load-json f-weichen)) - (if *OMNI-WEICHEN* - (princ (strcat "\n[OMNI] " (itoa (length *OMNI-WEICHEN*)) " Weichen geladen.")) - (princ (strcat "\n[OMNI] WARNUNG: Keine Weichen geladen aus " f-weichen)) - ) - T - ) - ) -) - - -;; --- SivasId zu String konvertieren (fuer Vergleich) --- -(defun omni:sivasid-to-str (id) - (cond - ((= (type id) 'STR) id) - ((= (type id) 'INT) (itoa id)) - ((= (type id) 'REAL) (itoa (fix id))) - (T "") - ) -) - - -;; --- Eintrag anhand SivasId in einer Liste suchen --- -(defun omni:get-by-sivasid (lst sivasid / such-str eintrag sivasnr gefunden) - (setq such-str (omni:sivasid-to-str sivasid)) - (setq gefunden nil) - (foreach eintrag lst - (if (null gefunden) - (progn - (setq sivasnr (cdr (assoc "Sivasnr" eintrag))) - (if (= (omni:sivasid-to-str sivasnr) such-str) - (setq gefunden eintrag) - ) - ) - ) - ) - gefunden -) - - -;; --- Bogen anhand SivasId abfragen --- -;; Rueckgabe: Assoziationsliste mit allen Eigenschaften oder nil -;; Beispiel: (omni:get-bogen 821104025) -;; -> (("Sivasnr" . 821104025) ("ProfilTyp" . "APB 110 R 550/22,5...") ...) -(defun omni:get-bogen (sivasid) - (if (null *OMNI-BOEGEN*) (omni:load-data)) - (omni:get-by-sivasid *OMNI-BOEGEN* sivasid) -) - - -;; --- Weiche anhand SivasId abfragen --- -;; Rueckgabe: Assoziationsliste mit allen Eigenschaften oder nil -;; Beispiel: (omni:get-weiche 834372001) -;; -> (("Sivasnr" . 834372001) ("ProfilTyp" . "WEICHE S 45...") ...) -(defun omni:get-weiche (sivasid) - (if (null *OMNI-WEICHEN*) (omni:load-data)) - (omni:get-by-sivasid *OMNI-WEICHEN* sivasid) -) - - -;; --- Eintraege nach beliebigem Key/Wert filtern --- -;; lst = *OMNI-BOEGEN* oder *OMNI-WEICHEN* -;; key = Schluesselname z.B. "KurvenWinkel" -;; wert = Gesuchter Wert z.B. 45 -;; Rueckgabe: Liste aller passenden Eintraege -;; -;; Beispiel: (omni:filter *OMNI-BOEGEN* "KurvenWinkel" 45) -;; -> Liste aller 45-Grad-Boegen -;; Beispiel: (omni:filter *OMNI-WEICHEN* "WeichenTyp" "Doppelweiche") -;; -> Liste aller Doppelweichen -(defun omni:filter (lst key wert / eintrag val ergebnis) - (setq ergebnis nil) - (foreach eintrag lst - (setq val (cdr (assoc key eintrag))) - (if (equal val wert) - (setq ergebnis (cons eintrag ergebnis)) - ) - ) - (reverse ergebnis) -) - - -;; --- Einzelnen Wert aus einem Eintrag lesen --- -;; eintrag = Assoziationsliste (Rueckgabe von omni:get-bogen etc.) -;; key = Schluesselname z.B. "Radius" -;; Beispiel: (omni:val (omni:get-bogen 821104025) "Radius") -> 550 -(defun omni:val (eintrag key) - (cdr (assoc key eintrag)) -) - - -;; --- SivasNummern aller Boegen fuer einen bestimmten Winkel --- -;; winkel = KurvenWinkel z.B. 90, 45, 22.5 -;; Rueckgabe: Liste von SivasIds (gemischt INT/STR) -;; Beispiel: (omni:boegen-ids 45) -> (821104021 821104026 "821104026+0_B10071" ...) -(defun omni:boegen-ids (winkel / lst eintrag ergebnis) - (if (null *OMNI-BOEGEN*) (omni:load-data)) - (setq ergebnis nil) - (foreach eintrag *OMNI-BOEGEN* - (if (equal (cdr (assoc "KurvenWinkel" eintrag)) winkel) - (setq ergebnis (cons (cdr (assoc "Sivasnr" eintrag)) ergebnis)) - ) - ) - (reverse ergebnis) -) - - -;; --- SivasNummern aller Boegen, gruppiert nach Winkel --- -;; Rueckgabe: Assoziationsliste ((180 . (id ...)) (90 . (id ...)) ...) -;; Beispiel: (omni:boegen-ids-alle) -;; -> ((180 821104043) (90 821104022 821104030 ...) (67.5 ...) (45 ...) (22.5 ...)) -(defun omni:boegen-ids-alle () - (list - (cons 180 (omni:boegen-ids 180)) - (cons 90 (omni:boegen-ids 90)) - (cons 67.5 (omni:boegen-ids 67.5)) - (cons 45 (omni:boegen-ids 45)) - (cons 22.5 (omni:boegen-ids 22.5)) - ) -) - - -;; --- SivasNummern von Weichen nach Winkel und WeichenTyp --- -;; winkel = KurvenWinkel z.B. 90, 45 -;; weichentyp = "Einzelweiche", "Doppelweiche", "Dreiwegeweiche" -;; Beispiel: (omni:weichen-ids 90 "Einzelweiche") -(defun omni:weichen-ids (winkel weichentyp / eintrag ergebnis) - (if (null *OMNI-WEICHEN*) (omni:load-data)) - (setq ergebnis nil) - (foreach eintrag *OMNI-WEICHEN* - (if (and (equal (cdr (assoc "KurvenWinkel" eintrag)) winkel) - (= (cdr (assoc "WeichenTyp" eintrag)) weichentyp) - ) - (setq ergebnis (cons (cdr (assoc "Sivasnr" eintrag)) ergebnis)) - ) - ) - (reverse ergebnis) -) - - -;; --- SivasNummern von Weichenkombinationen (Delta/Star) --- -;; typ = "DELTA" oder "STAR" -;; Filtert anhand ProfilTyp-Inhalt -(defun omni:weichen-kombi-ids (typ / eintrag ergebnis profil) - (if (null *OMNI-WEICHEN*) (omni:load-data)) - (setq ergebnis nil) - (foreach eintrag *OMNI-WEICHEN* - (setq profil (cdr (assoc "ProfilTyp" eintrag))) - (if (and profil (vl-string-search typ profil)) - (setq ergebnis (cons (cdr (assoc "Sivasnr" eintrag)) ergebnis)) - ) - ) - (reverse ergebnis) -) - - -;; --- Alle Weichen-SivasNummern gruppiert --- -;; Rueckgabe: Assoziationsliste nach Kategorie -;; Beispiel: (omni:weichen-ids-alle) -;; -> (("W90-Einfach" id1 id2 ...) -;; ("W90-Doppel" ...) -;; ... -;; ("WKomb-Delta" ...) -;; ("WKomb-Star" ...)) -(defun omni:weichen-ids-alle () - (list - ;; 90 Grad - (cons "W90-Einfach" (omni:weichen-ids 90 "Einzelweiche")) - (cons "W90-Doppel" (omni:weichen-ids 90 "Doppelweiche")) - (cons "W90-Dreiwege" (omni:weichen-ids 90 "Dreiwegeweiche")) - ;; 45 Grad - (cons "W45-Einfach" (omni:weichen-ids 45 "Einzelweiche")) - (cons "W45-Doppel" (omni:weichen-ids 45 "Doppelweiche")) - (cons "W45-Dreiwege" (omni:weichen-ids 45 "Dreiwegeweiche")) - ;; Parallel (KurvenWinkel = 0) - (cons "WP-Einfach" (omni:weichen-ids 0 "Einzelweiche")) - (cons "WP-Doppel" (omni:weichen-ids 0 "Doppelweiche")) - (cons "WP-Dreiwege" (omni:weichen-ids 0 "Dreiwegeweiche")) - ;; Weichenkoerper (KurvenWinkel = 22.5) - (cons "WK-Einfach" (omni:weichen-ids 22.5 "Einzelweiche")) - (cons "WK-Doppel" (omni:weichen-ids 22.5 "Doppelweiche")) - (cons "WK-Dreiwege" (omni:weichen-ids 22.5 "Dreiwegeweiche")) - ;; Weichenkombinationen - (cons "WKomb-Delta" (omni:weichen-kombi-ids "DELTA")) - (cons "WKomb-Star" (omni:weichen-kombi-ids "STAR")) - ) -) - - -;; --- BricsCAD-Befehl: Daten laden --- -(defun c:OMNI_LOAD () - (omni:load-data) - (princ) -) - - -;; --- BricsCAD-Befehl: Bogen-Info anzeigen --- -(defun c:OMNI_INFO_BOGEN ( / id eintrag) - (if (null *OMNI-BOEGEN*) (omni:load-data)) - (setq id (getstring T "\nSivasId des Bogens eingeben: ")) - (if (= id "") (princ "\nAbgebrochen.") - (progn - ;; Versuch als Zahl, dann als String - (setq eintrag (omni:get-bogen (if (/= (atoi id) 0) (atoi id) id))) - (if eintrag - (progn - (princ (strcat "\n ProfilTyp: " (omni:sivasid-to-str (omni:val eintrag "ProfilTyp")))) - (princ (strcat "\n Radius: " (itoa (fix (omni:val eintrag "Radius"))))) - (princ (strcat "\n KurvenWinkel: " (rtos (omni:val eintrag "KurvenWinkel") 2 1))) - (princ (strcat "\n Breite: " (itoa (fix (omni:val eintrag "Breite"))))) - (princ (strcat "\n Laenge: " (itoa (fix (omni:val eintrag "Länge"))))) - (princ (strcat "\n Antriebsart: " (itoa (fix (omni:val eintrag "Antriebsart"))))) - ) - (princ (strcat "\nKein Bogen mit SivasId '" id "' gefunden.")) - ) - ) - ) - (princ) -) - - -;; --- BricsCAD-Befehl: Weichen-Info anzeigen --- -(defun c:OMNI_INFO_WEICHE ( / id eintrag wkl) - (if (null *OMNI-WEICHEN*) (omni:load-data)) - (setq id (getstring T "\nSivasId der Weiche eingeben: ")) - (if (= id "") (princ "\nAbgebrochen.") - (progn - (setq eintrag (omni:get-weiche (if (/= (atoi id) 0) (atoi id) id))) - (if eintrag - (progn - (princ (strcat "\n ProfilTyp: " (omni:sivasid-to-str (omni:val eintrag "ProfilTyp")))) - (princ (strcat "\n WeichenTyp: " (omni:sivasid-to-str (omni:val eintrag "WeichenTyp")))) - (setq wkl (omni:val eintrag "KurvenWinkel")) - (princ (strcat "\n KurvenWinkel: " (if (= (type wkl) 'INT) (itoa wkl) (rtos wkl 2 1)))) - (princ (strcat "\n Schaltungstyp: " (omni:sivasid-to-str (omni:val eintrag "Schaltungstyp")))) - (princ (strcat "\n Breite: " (itoa (fix (omni:val eintrag "Breite"))))) - (princ (strcat "\n Laenge: " (itoa (fix (omni:val eintrag "Länge"))))) - (princ (strcat "\n KurvenRichtung: " (itoa (fix (omni:val eintrag "KurvenRichtung"))))) - (if (omni:val eintrag "WeichenkörperLänge") - (princ (strcat "\n WK-Laenge: " (rtos (omni:val eintrag "WeichenkörperLänge") 2 1))) - ) - ) - (princ (strcat "\nKeine Weiche mit SivasId '" id "' gefunden.")) - ) - ) - ) - (princ) -) - - -;; ============================================================ -;; OMNIFLO BOGEN-DIALOG -;; Generischer Dialog fuer Bogen-Auswahl nach Winkel -;; ============================================================ - - -;; --- Detail-Felder im Dialog aktualisieren --- -;; Wird bei Dropdown-Aenderung aufgerufen (action_tile callback) -(defun omni:bogen-dlg-update (idx-str / idx eintrag wkl) - (setq idx (atoi idx-str)) - (setq eintrag (nth idx *OMNI-DLG-BOEGEN*)) - (if eintrag - (progn - (set_tile "val_sivasnr" - (omni:sivasid-to-str (omni:val eintrag "Sivasnr"))) - (setq wkl (omni:val eintrag "KurvenWinkel")) - (set_tile "val_winkel" - (if (= (type wkl) 'INT) (strcat (itoa wkl) " Grad") - (strcat (rtos wkl 2 1) " Grad") - ) - ) - (set_tile "val_radius" - (itoa (fix (omni:val eintrag "Radius")))) - (set_tile "val_breite" - (itoa (fix (omni:val eintrag "Breite")))) - (set_tile "val_laenge" - (itoa (fix (omni:val eintrag "Länge")))) - ) - ) -) - - -;; --- Bogen-Auswahl-Dialog oeffnen --- -;; winkel = KurvenWinkel (90, 67.5, 45, 22.5, 180) -;; Rueckgabe: gewaehlter Bogen-Eintrag (alist) oder nil bei Abbruch -(defun omni:bogen-dialog (winkel / dcl-pfad dat boegen-liste - profil-liste ergebnis) - (if (null *OMNI-BOEGEN*) (omni:load-data)) - - ;; Boegen nach Winkel filtern - (setq boegen-liste (omni:filter *OMNI-BOEGEN* "KurvenWinkel" winkel)) - (if (null boegen-liste) - (progn - (alert (strcat "Keine Boegen fuer " - (if (= (type winkel) 'INT) (itoa winkel) (rtos winkel 2 1)) - " Grad gefunden.")) - nil - ) - (progn - ;; In globaler Variable fuer Dialog-Callbacks speichern - (setq *OMNI-DLG-BOEGEN* boegen-liste) - - ;; ProfilTyp-Liste fuer Dropdown aufbauen - (setq profil-liste - (mapcar '(lambda (e) (cdr (assoc "ProfilTyp" e))) boegen-liste)) - - ;; DCL laden - (setq dcl-pfad (strcat (getenv "DXFM_DCL") "/omniflo_boegen.dcl")) - (setq dat (load_dialog dcl-pfad)) - (if (not (new_dialog "omniflo_boegen" dat)) - (progn (alert "Bogen-Dialog konnte nicht geladen werden.") nil) - (progn - ;; Dropdown befuellen - (start_list "bogenart") - (mapcar 'add_list profil-liste) - (end_list) - (set_tile "bogenart" "0") - - ;; Startwerte anzeigen - (omni:bogen-dlg-update "0") - - ;; Callback bei Dropdown-Aenderung - (action_tile "bogenart" "(omni:bogen-dlg-update $value)") - - ;; OK/Abbrechen - (action_tile "accept" - "(setq ergebnis (atoi (get_tile \"bogenart\"))) (done_dialog 1)") - (action_tile "cancel" "(done_dialog 0)") - - (start_dialog) - (unload_dialog dat) - - ;; Gewaehlten Eintrag zurueckgeben - (if ergebnis - (nth ergebnis *OMNI-DLG-BOEGEN*) - nil - ) - ) - ) - ) - ) -) - - -;; ============================================================ -;; OMNIFLO MODUL-BEFEHLE -;; ============================================================ - -;; --- Omniflo / Boegen --- -(defun c:OMNI_APB_630_90 ( / eintrag) - (setq eintrag (omni:bogen-dialog 90)) - (if eintrag - (omni:insert-from-entry eintrag) - (princ "\n[BOGEN] Abgebrochen.") - ) - (princ) -) - -(defun c:OMNI_APB_550_675 ( / eintrag) - (setq eintrag (omni:bogen-dialog 67.5)) - (if eintrag - (omni:insert-from-entry eintrag) - (princ "\n[BOGEN] Abgebrochen.") - ) - (princ) -) - -(defun c:OMNI_APB_630_45 ( / eintrag) - (setq eintrag (omni:bogen-dialog 45)) - (if eintrag - (omni:insert-from-entry eintrag) - (princ "\n[BOGEN] Abgebrochen.") - ) - (princ) -) - -(defun c:OMNI_APB_550_225 ( / eintrag) - (setq eintrag (omni:bogen-dialog 22.5)) - (if eintrag - (omni:insert-from-entry eintrag) - (princ "\n[BOGEN] Abgebrochen.") - ) - (princ) -) - -(defun c:OMNI_APB_650_180 ( / eintrag) - (setq eintrag (omni:bogen-dialog 180)) - (if eintrag - (omni:insert-from-entry eintrag) - (princ "\n[BOGEN] Abgebrochen.") - ) - (princ) -) - -;; --- Omniflo / Aluprofil Gerade --- - -;; Aluprofil-Fahrstrecke einfuegen. -;; -;; Ablauf: -;; 1. Quell-Block (.dwg) temporaer einfuegen, Attribute lesen, loeschen -;; 2. Nutzer waehlt Einfuegepunkt -> Text "blockname" wird horizontal platziert -;; 3. Nutzer klickt Endpunkt -> Linie definiert Fahrstreckenlaenge und Richtung -;; 4. Laenge wird gegen laengemax geprueft (Fehlermeldung bei Ueberschreitung) -;; 5. Compound-Block aus Linie + ATTDEFs (mit Quell-Werten) wird erzeugt -;; Blockname: blockname_YYYYMMDDHHMMSS -;; 6. Text wird gedreht, oberhalb der Linie positioniert, Inhalt aktualisiert -;; z.B. "APG110 L=2879" -;; -;; blockname = Profiltyp (z.B. "AP60", "AP110", "APG110") -;; laengemax = Maximale Fahrstreckenlaenge in mm -(defun omni:insert-block (blockname laengemax / - pt pt2 laenge laengeStr - dx dy angle angleDeg - textEnt textHeight textGap - perpX perpY textPt ed - lastEnt ss e bname blockEnt - srcAttribs attribDefs attribs - ok) - - (setq textHeight 100.0) - (setq textGap 20.0) - - ;; 0. Attribute aus der Quell-.dwg lesen (vor ssg-start, da eigene INSERT/ERASE) - (setq srcAttribs (ssg-attrib-read-dwg (strcat *block-path* blockname ".dwg"))) - (if (null srcAttribs) - (progn (princ "\n[OMNI] Keine Attribute im Quell-Block gefunden.") nil) - (progn - ;; LAENGEMAX aus Quell-Attribut lesen (falls vorhanden), sonst Parameter - (if (and (assoc "LAENGEMAX" srcAttribs) - (> (strlen (cdr (assoc "LAENGEMAX" srcAttribs))) 0)) - (setq laengemax (atof (cdr (assoc "LAENGEMAX" srcAttribs)))) - ) - - (setq pt (getpoint "\nEinfuegepunkt waehlen: ")) - (if (null pt) - (progn (princ "\n[OMNI] Abgebrochen.") nil) - (progn - (ssg-start "OMNI Aluprofil" '(("OSMODE") ("ATTREQ") ("ATTDIA"))) - ;; ATTREQ/ATTDIA auf 0 setzen (werden durch ssg-end wiederhergestellt) - (setvar "ATTREQ" 0) - (setvar "ATTDIA" 0) - - ;; 1. Text horizontal am Einfuegepunkt erzeugen - (entmake (list '(0 . "TEXT") - (cons 8 (getvar "CLAYER")) - (cons 10 (list (car pt) (cadr pt) - (if (caddr pt) (caddr pt) 0.0))) - (cons 40 textHeight) - (cons 1 blockname) - '(50 . 0.0))) - (setq textEnt (entlast)) - - ;; 2. Endpunkt fuer Fahrstrecke abfragen (Schleife bei Ueberschreitung) - (setq ok nil) - (while (not ok) - (setq pt2 (getpoint pt "\nEndpunkt der Fahrstrecke waehlen: ")) - (if (null pt2) - (progn - ;; Abbruch: Text loeschen - (command "_.ERASE" textEnt "") - (princ "\n[OMNI] Abgebrochen.") - (setq ok T pt nil) - ) - (progn - (setq laenge (distance (list (car pt) (cadr pt)) - (list (car pt2) (cadr pt2)))) - (if (> laenge laengemax) - (alert (strcat "Die gewuenschte Fahrstreckenlaenge von " - (rtos laenge 2 0) " mm ueberschreitet die Maximallaenge von " - (rtos laengemax 2 0) " mm des Fahrstreckenprofils.\n" - "Bitte neu definieren!")) - (progn - (setq laengeStr (rtos laenge 2 0)) - (setq dx (- (car pt2) (car pt)) - dy (- (cadr pt2) (cadr pt))) - (setq angle (atan dy dx)) - (setq angleDeg (* (/ 180.0 pi) angle)) - - ;; 3. Compound-Block: Linie bei (0,0) + ATTDEFs aus Quell-Attributen - (setq lastEnt textEnt) - - ;; Linie von Ursprung bis (laenge, 0) - (command "_.LINE" (list 0.0 0.0) (list laenge 0.0) "") - - ;; ATTDEFs aus Quell-Attributen erzeugen - (setq attribDefs (ssg-attrib-alist-to-defs srcAttribs)) - (ssg-attrib-make-defs attribDefs 50.0) - - ;; Neue Entities einsammeln (alles nach textEnt) - (setq ss (ssadd)) - (setq e (entnext lastEnt)) - (while e (ssadd e ss) (setq e (entnext e))) - - ;; Block mit eindeutigem Zeitstempel-Namen erzeugen - (setq bname (strcat blockname "_" (ssg-timestamp))) - ;; Falls Name schon existiert, Suffix anhaengen - (if (tblsearch "BLOCK" bname) - (setq bname (strcat bname "_" (itoa (fix (* (getvar "CDATE") 1000000))))) - ) - (command "_.-BLOCK" bname (list 0.0 0.0 0.0) ss "") - - ;; Block einfuegen (ATTREQ/ATTDIA bereits auf 0) - (command "_.INSERT" bname pt 1 1 angleDeg) - - (setq blockEnt (entlast)) - - ;; 4. Attribute setzen: Quell-Werte + LAENGE/A aktualisieren - (setq attribs srcAttribs) - (if (assoc "LAENGE" attribs) - (setq attribs (subst (cons "LAENGE" laengeStr) - (assoc "LAENGE" attribs) attribs)) - (setq attribs (cons (cons "LAENGE" laengeStr) attribs)) - ) - (if (assoc "A" attribs) - (setq attribs (subst (cons "A" laengeStr) - (assoc "A" attribs) attribs)) - (setq attribs (cons (cons "A" laengeStr) attribs)) - ) - (ssg-attrib-set-on blockEnt attribs) - - ;; 5. Text aktualisieren: oberhalb der Linie positionieren, - ;; um Linienwinkel drehen, Inhalt mit Laenge ergaenzen - (setq perpX (* (- (sin angle)) textGap) - perpY (* (cos angle) textGap)) - (setq textPt (list (+ (car pt) perpX) - (+ (cadr pt) perpY) - (if (caddr pt) (caddr pt) 0.0))) - - (setq ed (entget textEnt)) - (setq ed (subst (cons 10 textPt) (assoc 10 ed) ed)) - (if (assoc 50 ed) - (setq ed (subst (cons 50 angle) (assoc 50 ed) ed)) - (setq ed (append ed (list (cons 50 angle)))) - ) - (setq ed (subst (cons 1 (strcat blockname " L=" laengeStr)) - (assoc 1 ed) ed)) - (entmod ed) - (entupd textEnt) - - (princ (strcat "\n[OMNI] " bname " eingefuegt. Laenge=" laengeStr " mm")) - (setq ok T) - ) - ) - ) - ) - ) - - (ssg-end) - pt - ) - ) - ) - ) -) - -(defun c:OMNI_AP60 () - (omni:insert-block "AP60" 7000.0) - (princ) -) -(defun c:OMNI_AP110 () - (omni:insert-block "AP110" 6000.0) - (princ) -) -(defun c:OMNI_APG110 () - (omni:insert-block "APG110" 6000.0) - (princ) -) - -;; ============================================================ -;; OMNIFLO WEICHEN-DIALOG -;; Generischer Dialog fuer Weichen-Auswahl nach Winkel/Typ -;; mit Filter fuer Schaltungstyp und Richtung -;; ============================================================ - - -;; --- Aktuelle Filterwerte aus Radio-Buttons lesen --- -(defun omni:weichen-get-filter ( / sch ri) - (setq sch "alle") - (if (= (get_tile "sch_m") "1") (setq sch "M")) - (if (= (get_tile "sch_p") "1") (setq sch "P")) - (setq ri "alle") - (if (= (get_tile "ri_links") "1") (setq ri "1")) - (if (= (get_tile "ri_rechts") "1") (setq ri "2")) - (list sch ri) -) - - -;; --- Gefilterte Weichen-Liste aufbauen --- -;; basis-liste = alle Weichen fuer den gewaehlten Winkel/Typ -;; sch-filter = "M", "P" oder "alle" -;; ri-filter = "1", "2" oder "alle" -(defun omni:weichen-filter-liste (basis-liste sch-filter ri-filter / eintrag sch kr ergebnis) - (setq ergebnis nil) - (foreach eintrag basis-liste - (setq sch (cdr (assoc "Schaltungstyp" eintrag))) - (setq kr (cdr (assoc "KurvenRichtung" eintrag))) - (if (and - (or (= sch-filter "alle") (= sch sch-filter)) - (or (= ri-filter "alle") - (= (omni:sivasid-to-str kr) ri-filter)) - ) - (setq ergebnis (cons eintrag ergebnis)) - ) - ) - (reverse ergebnis) -) - - -;; --- Dropdown mit gefilterter Liste neu befuellen --- -(defun omni:weichen-dlg-refresh ( / filter sch-f ri-f gefiltert profil-liste) - (setq filter (omni:weichen-get-filter)) - (setq sch-f (car filter)) - (setq ri-f (cadr filter)) - (setq gefiltert (omni:weichen-filter-liste *OMNI-DLG-WEICHEN-BASIS* sch-f ri-f)) - (setq *OMNI-DLG-WEICHEN* gefiltert) - - ;; Dropdown neu befuellen - (setq profil-liste - (mapcar '(lambda (e) (cdr (assoc "ProfilTyp" e))) gefiltert)) - (start_list "profiltyp") - (mapcar 'add_list profil-liste) - (end_list) - - ;; Ersten Eintrag anzeigen (falls vorhanden) - (if gefiltert - (progn - (set_tile "profiltyp" "0") - (omni:weichen-dlg-update "0") - ) - (progn - (set_tile "val_sivasnr" "---") - (set_tile "val_winkel" "---") - (set_tile "val_breite" "---") - (set_tile "val_laenge" "---") - ) - ) -) - - -;; --- Detail-Felder im Weichen-Dialog aktualisieren --- -(defun omni:weichen-dlg-update (idx-str / idx eintrag wkl) - (setq idx (atoi idx-str)) - (setq eintrag (nth idx *OMNI-DLG-WEICHEN*)) - (if eintrag - (progn - (set_tile "val_sivasnr" - (omni:sivasid-to-str (omni:val eintrag "Sivasnr"))) - (setq wkl (omni:val eintrag "KurvenWinkel")) - (set_tile "val_winkel" - (if (= (type wkl) 'INT) (strcat (itoa wkl) " Grad") - (strcat (rtos wkl 2 1) " Grad") - ) - ) - (set_tile "val_breite" - (itoa (fix (omni:val eintrag "Breite")))) - (set_tile "val_laenge" - (itoa (fix (omni:val eintrag "Länge")))) - ) - ) -) - - -;; --- Weichen-Auswahl-Dialog oeffnen --- -;; winkel = KurvenWinkel (90, 45, 0, 22.5) -;; weichentyp = "Einzelweiche", "Doppelweiche", "Dreiwegeweiche" oder nil fuer alle -;; Rueckgabe: gewaehlter Weichen-Eintrag (alist) oder nil bei Abbruch -(defun omni:weichen-dialog (winkel weichentyp / dcl-pfad dat basis-liste - profil-liste ergebnis) - (if (null *OMNI-WEICHEN*) (omni:load-data)) - - ;; Weichen nach Winkel filtern (nil = alle Winkel) - (if winkel - (setq basis-liste (omni:filter *OMNI-WEICHEN* "KurvenWinkel" winkel)) - (setq basis-liste *OMNI-WEICHEN*) - ) - - ;; Optional nach WeichenTyp filtern - (if weichentyp - (setq basis-liste - (vl-remove-if-not - '(lambda (e) (= (cdr (assoc "WeichenTyp" e)) weichentyp)) - basis-liste)) - ) - - (if (null basis-liste) - (progn - (alert (strcat "Keine Weichen fuer " - (cond - ((null winkel) "alle Winkel") - ((= (type winkel) 'INT) (strcat (itoa winkel) " Grad")) - (T (strcat (rtos winkel 2 1) " Grad")) - ) - (if weichentyp (strcat " / " weichentyp) "") - " gefunden.")) - nil - ) - (progn - ;; Basis-Liste und gefilterte Liste in globalen Variablen speichern - (setq *OMNI-DLG-WEICHEN-BASIS* basis-liste) - (setq *OMNI-DLG-WEICHEN* basis-liste) - - ;; ProfilTyp-Liste fuer Dropdown aufbauen - (setq profil-liste - (mapcar '(lambda (e) (cdr (assoc "ProfilTyp" e))) basis-liste)) - - ;; DCL laden - (setq dcl-pfad (strcat (getenv "DXFM_DCL") "/omniflo_weichen.dcl")) - (setq dat (load_dialog dcl-pfad)) - (if (not (new_dialog "omniflo_weichen" dat)) - (progn (alert "Weichen-Dialog konnte nicht geladen werden.") nil) - (progn - ;; Dropdown befuellen - (start_list "profiltyp") - (mapcar 'add_list profil-liste) - (end_list) - (set_tile "profiltyp" "0") - - ;; Startwerte anzeigen - (omni:weichen-dlg-update "0") - - ;; Callback bei Dropdown-Aenderung - (action_tile "profiltyp" "(omni:weichen-dlg-update $value)") - - ;; Callbacks fuer Filter-Radio-Buttons (alle loesen Refresh aus) - (action_tile "sch_alle" "(omni:weichen-dlg-refresh)") - (action_tile "sch_m" "(omni:weichen-dlg-refresh)") - (action_tile "sch_p" "(omni:weichen-dlg-refresh)") - (action_tile "ri_alle" "(omni:weichen-dlg-refresh)") - (action_tile "ri_links" "(omni:weichen-dlg-refresh)") - (action_tile "ri_rechts" "(omni:weichen-dlg-refresh)") - - ;; OK/Abbrechen - (action_tile "accept" - "(setq ergebnis (atoi (get_tile \"profiltyp\"))) (done_dialog 1)") - (action_tile "cancel" "(done_dialog 0)") - - (start_dialog) - (unload_dialog dat) - - ;; Gewaehlten Eintrag zurueckgeben - (if ergebnis - (nth ergebnis *OMNI-DLG-WEICHEN*) - nil - ) - ) - ) - ) - ) -) - - -;; ============================================================ -;; OMNIFLO DXF-BLOCK EINFUEGEN -;; Fuegt eine DXF-Datei aus data/omniflo/ als Block ein. -;; Der Benutzer waehlt den Einfuegepunkt und Drehwinkel. -;; ============================================================ - -;; --- DXF-Datei als Block einfuegen --- -;; sivasnr-str = SivasNr als String (Dateiname ohne .dxf) -;; Rueckgabe: Einfuegepunkt oder nil bei Abbruch -(defun omni:insert-dxf (sivasnr-str / dxf-pfad data-pfad pt) - (setq data-pfad (getenv "DXFM_DATA")) - (if (null data-pfad) - (progn - (princ "\n[OMNI] FEHLER: DXFM_DATA nicht gesetzt!") - nil - ) - (progn - (setq dxf-pfad (strcat data-pfad "/omniflo/" sivasnr-str ".dxf")) - (if (not (findfile dxf-pfad)) - (progn - (princ (strcat "\n[OMNI] FEHLER: DXF nicht gefunden: " dxf-pfad)) - nil - ) - (progn - (setq pt (getpoint "\nEinfuegepunkt waehlen: ")) - (if (null pt) - (progn (princ "\n[OMNI] Abgebrochen.") nil) - (progn - (ssg-start "OMNI DXF Insert" '(("OSMODE"))) - - ;; Block einfuegen mit Winkelabfrage (pause) - (command "_.INSERT" dxf-pfad pt "" "" pause) - - (princ (strcat "\n[OMNI] " sivasnr-str " eingefuegt.")) - (ssg-end) - pt - ) - ) - ) - ) - ) - ) -) - - -;; --- Eintrag aus Dialog-Ergebnis einfuegen --- -;; Nimmt einen Eintrag (alist) und fuegt die zugehoerige DXF ein. -(defun omni:insert-from-entry (eintrag / sivasnr-str) - (if eintrag - (progn - (setq sivasnr-str (omni:sivasid-to-str (omni:val eintrag "Sivasnr"))) - (princ (strcat "\n[OMNI] Einfuegen: " - (omni:val eintrag "ProfilTyp") " (" sivasnr-str ")")) - (omni:insert-dxf sivasnr-str) - ) - ) -) - - -;; ============================================================ -;; OMNIFLO WEICHEN-BEFEHLE -;; ============================================================ - -;; --- Omniflo / Weiche 90 Grad --- -(defun c:OMNI_W90_Einfach ( / eintrag) - (setq eintrag (omni:weichen-dialog 90 "Einzelweiche")) - (if eintrag - (omni:insert-from-entry eintrag) - (princ "\n[WEICHE] Abgebrochen.") - ) - (princ) -) - -(defun c:OMNI_W90_Doppel ( / eintrag) - (setq eintrag (omni:weichen-dialog 90 "Doppelweiche")) - (if eintrag - (omni:insert-from-entry eintrag) - (princ "\n[WEICHE] Abgebrochen.") - ) - (princ) -) - -(defun c:OMNI_W90_Dreiwege ( / eintrag) - (setq eintrag (omni:weichen-dialog 90 "Dreiwegeweiche")) - (if eintrag - (omni:insert-from-entry eintrag) - (princ "\n[WEICHE] Abgebrochen.") - ) - (princ) -) - -;; --- Omniflo / Weiche 45 Grad --- -(defun c:OMNI_W45_Einfach ( / eintrag) - (setq eintrag (omni:weichen-dialog 45 "Einzelweiche")) - (if eintrag - (omni:insert-from-entry eintrag) - (princ "\n[WEICHE] Abgebrochen.") - ) - (princ) -) - -(defun c:OMNI_W45_Doppel ( / eintrag) - (setq eintrag (omni:weichen-dialog 45 "Doppelweiche")) - (if eintrag - (omni:insert-from-entry eintrag) - (princ "\n[WEICHE] Abgebrochen.") - ) - (princ) -) - -(defun c:OMNI_W45_Dreiwege ( / eintrag) - (setq eintrag (omni:weichen-dialog 45 "Dreiwegeweiche")) - (if eintrag - (omni:insert-from-entry eintrag) - (princ "\n[WEICHE] Abgebrochen.") - ) - (princ) -) - -;; --- Omniflo / Weichen Parallel --- -(defun c:OMNI_WP_Einfach ( / eintrag) - (setq eintrag (omni:weichen-dialog 0 "Einzelweiche")) - (if eintrag - (omni:insert-from-entry eintrag) - (princ "\n[WEICHE] Abgebrochen.") - ) - (princ) -) - -(defun c:OMNI_WP_Doppel ( / eintrag) - (setq eintrag (omni:weichen-dialog 0 "Doppelweiche")) - (if eintrag - (omni:insert-from-entry eintrag) - (princ "\n[WEICHE] Abgebrochen.") - ) - (princ) -) - -(defun c:OMNI_WP_Dreiwege ( / eintrag) - (setq eintrag (omni:weichen-dialog 0 "Dreiwegeweiche")) - (if eintrag - (omni:insert-from-entry eintrag) - (princ "\n[WEICHE] Abgebrochen.") - ) - (princ) -) - -;; --- Omniflo / Weichenkoerper --- -(defun c:OMNI_WK_Einfach ( / eintrag) - (setq eintrag (omni:weichen-dialog 22.5 "Einzelweiche")) - (if eintrag - (omni:insert-from-entry eintrag) - (princ "\n[WEICHE] Abgebrochen.") - ) - (princ) -) - -(defun c:OMNI_WK_Doppel ( / eintrag) - (setq eintrag (omni:weichen-dialog 22.5 "Doppelweiche")) - (if eintrag - (omni:insert-from-entry eintrag) - (princ "\n[WEICHE] Abgebrochen.") - ) - (princ) -) - -(defun c:OMNI_WK_Dreiwege ( / eintrag) - (setq eintrag (omni:weichen-dialog 22.5 "Dreiwegeweiche")) - (if eintrag - (omni:insert-from-entry eintrag) - (princ "\n[WEICHE] Abgebrochen.") - ) - (princ) -) - -;; --- Omniflo / Weichenkombinationen --- -(defun c:OMNI_WKomb_Delta ( / eintrag) - (setq eintrag (omni:weichen-dialog nil "Deltaweiche")) - (if eintrag - (omni:insert-from-entry eintrag) - (princ "\n[WEICHE] Abgebrochen.") - ) - (princ) -) - -(defun c:OMNI_WKomb_Star ( / eintrag) - (setq eintrag (omni:weichen-dialog nil "Sternweiche")) - (if eintrag - (omni:insert-from-entry eintrag) - (princ "\n[WEICHE] Abgebrochen.") - ) - (princ) -) - -;; --- Omniflo / TEF Elemente --- -(defun c:OMNI_TEF_UmlenkR () - (princ "\n[DUMMY] Umlenkspannst. rechts fuer TEF links") - (princ) -) - -(defun c:OMNI_TEF_UmlenkL () - (princ "\n[DUMMY] Umlenkspannst. links fuer TEF rechts") - (princ) -) - -(defun c:OMNI_TEF_AntriebL () - (princ "\n[DUMMY] Antriebst. links fuer TEF links") - (princ) -) - -(defun c:OMNI_TEF_AntriebR () - (princ "\n[DUMMY] Antriebst. rechts fuer TEF rechts") - (princ) -) - -(defun c:OMNI_TEF_Gerade () - (princ "\n[DUMMY] TEF Gerade") - (princ) -) - -;; --- Omniflo / KettenFoerderer --- -(defun c:OMNI_KettenFoerderer () - (princ "\n[DUMMY] KettenFoerderer") - (princ) -) diff --git a/Lisp/VarioFoerderer.lsp b/Lisp/VarioFoerderer.lsp index 2c88685..88af6ae 100644 --- a/Lisp/VarioFoerderer.lsp +++ b/Lisp/VarioFoerderer.lsp @@ -46,6 +46,7 @@ (setq *lib-initialized* nil) (setq *bogen-cache-loaded* nil) (setq *modul-cache* nil) +(setq *ks-cache* nil) ;; KS-Daten Cache: (("blockname" ("KS_EIN" ...) ("KS_AUS" ...)) ...) ;; ============================================================ ;; TEIL 3: HILFSFUNKTIONEN @@ -72,6 +73,30 @@ (list (- (car p2) (car p1)) (- (cadr p2) (cadr p1)) (- (caddr p2) (caddr p1))) ) +;; KS-Daten relativ zu einem Basispunkt machen (fuer Cache) +(defun ks-relativize (ks-data base-pt) + (mapcar '(lambda (item) + (list (car item) + (mapcar '(lambda (pt) + (list (- (car pt) (car base-pt)) + (- (cadr pt) (cadr base-pt)) + (- (caddr pt) (caddr base-pt)))) + (cadr item)))) + ks-data) +) + +;; Relative KS-Daten auf einen Basispunkt anwenden +(defun ks-absolutize (ks-rel base-pt) + (mapcar '(lambda (item) + (list (car item) + (mapcar '(lambda (pt) + (list (+ (car pt) (car base-pt)) + (+ (cadr pt) (cadr base-pt)) + (+ (caddr pt) (caddr base-pt)))) + (cadr item)))) + ks-rel) +) + ;; ============================================================ ;; TEIL 4: KS_EIN/KS_AUS EXTRAKTION (KERN-FUNKTION) ;; ============================================================ @@ -79,13 +104,14 @@ ;; Grad-Zeichen für Blocknamen (setq grad-zeichen (chr 176)) -;; Extrahiert KS_EIN und KS_AUS aus einem Block +;; Extrahiert KS_EIN und KS_AUS aus einem Block (intern, ohne Cache) ;; Rückgabe: (("KS_EIN" (origin x-end y-end z-end)) ;; ("KS_AUS" (origin x-end y-end z-end))) -(defun extract-ks-from-block (block-obj / sub-entities sub-obj ks-results - ks-ref inner-entities inner-obj origin x-end y-end z-end - line-start line-end axis line-len line-vec) - (dbgf "extract-ks-from-block") +(defun extract-ks-from-block-raw (block-obj / sub-entities sub-obj ks-results + ks-ref inner-entities inner-obj origin x-end y-end z-end + line-start line-end axis line-len line-vec) + (dbgf "extract-ks-from-block-raw") + (dbg block-obj) (setq ks-results '()) ;; Block explodieren @@ -144,6 +170,35 @@ (dbgreturn ks-results) ) +;; Gecachte Version: prueft *ks-cache* anhand Blockname +;; Beim ersten Aufruf wird explodiert und das Ergebnis relativ +;; zum Einfuegepunkt im Cache gespeichert. Weitere Aufrufe +;; desselben Blocktyps liefern die Daten ohne Explode. +(defun extract-ks-from-block (block-obj / blockname insert-pt cached result ks-rel) + (dbgf "extract-ks-from-block") + (setq blockname (vla-get-Name block-obj)) + (setq insert-pt (vlax-safearray->list + (vlax-variant-value (vla-get-InsertionPoint block-obj)))) + (setq cached (assoc blockname *ks-cache*)) + (if cached + (progn + (dbg (strcat " KS-Cache HIT: " blockname)) + (dbgreturn (ks-absolutize (cdr cached) insert-pt)) + ) + (progn + (dbg (strcat " KS-Cache MISS: " blockname)) + (setq result (extract-ks-from-block-raw block-obj)) + (if result + (progn + (setq ks-rel (ks-relativize result insert-pt)) + (setq *ks-cache* (cons (cons blockname ks-rel) *ks-cache*)) + ) + ) + (dbgreturn result) + ) + ) +) + ;; ============================================================ ;; TEIL 5: PUNKTE-AUSWAHL MIT Z-HÖHE (HIER EINFÜGEN!) ;; ============================================================ @@ -258,6 +313,7 @@ (dbgreturn t) ) (progn + (setq *ks-cache* nil) (princ "\n Lade Blockdefinitionen aus Bibliothek...") (if (findfile koordinaten-lib) diff --git a/Lisp/ssg_load.lsp b/Lisp/ssg_load.lsp index 90bcd15..bb6d7a9 100644 --- a/Lisp/ssg_load.lsp +++ b/Lisp/ssg_load.lsp @@ -25,10 +25,10 @@ (dbgopen "debugfile.dbg" "DXFM_LOG") ; Debug-Datei im Log-Verzeichnis oeffnen (load (strcat base-path "ssg_dialog.lsp") "ssg_dialog.lsp nicht gefunden") (load (strcat base-path "ssg_layer.lsp") "ssg_layer.lsp nicht gefunden") - (load (strcat base-path "KreiselInsert.lsp") "KreiselInsert.lsp nicht gefunden") + ;; KreiselInsert.lsp entfernt - ersetzt durch Python/PyRx (lib/elemente/kreisel.py, eckrad.py) (load (strcat base-path "VarioFoerderer.lsp") "VarioFoerderer.lsp nicht gefunden") (load (strcat base-path "tests.lsp") "tests.lsp nicht gefunden") - (load (strcat base-path "OmniModulInsert.lsp") "OmniModulInsert.lsp nicht gefunden") + ;; OmniModulInsert.lsp entfernt - ersetzt durch Python/PyRx (lib/elemente/omniflo*.py) (load (strcat base-path "SSG_LIB_Commands.lsp") "SSG_LIB_Commands.lsp nicht gefunden") (prompt "\nSSG-Bibliotheken geladen: ssg_core, ssg_dbg, ssg_dialog, ssg_layer") (princ) diff --git a/Lisp/tests.lsp b/Lisp/tests.lsp index 999a433..f477324 100644 --- a/Lisp/tests.lsp +++ b/Lisp/tests.lsp @@ -202,3 +202,441 @@ (defun c:EXPORTCSV () (csv:run-export "EXPORTCSV" "export_csv.py" "export.csv") ) + +;; ============================================================ +;; TEST_FOERDERER - Automatischer Test ohne Benutzereingabe +;; Erzeugt 12 Variofoerderer (6x Auf, 6x Ab) mit deltaL=7000 +;; und verschiedenen Hoehenstufen. Tabellarische Zusammenfassung. +;; ============================================================ +(defun c:TEST_FOERDERER ( / deltaL deltaH richtung ergebnis + best-winkel L_GF L_VF dateiname + y-offset hoehen-liste idx + anz-gebaut anz-nicht-gebaut + result-pfad erfolgsquote + zusammenfassung-liste + nr deltaH_val status winkel L_GF_val L_VF_val) + + ;; Bibliothek initialisieren (laedt Block-Library und extrahiert Masse) + (if (not *lib-initialized*) + (init-bibliothek) + ) + + (ssg-start "TEST_FOERDERER" '(("OSMODE") ("CECOLOR") ("ATTREQ") ("ATTDIA"))) + (setvar "OSMODE" 0) + (setvar "ATTREQ" 0) + (setvar "ATTDIA" 0) + + (setq deltaL 7000) + (setq hoehen-liste '(0 1000 2000 3000 4000 5000)) + (setq y-offset 0) + (setq idx 0) + (setq zusammenfassung-liste '()) + (setq anz-gebaut 0) + (setq anz-nicht-gebaut 0) + + (princ "\n") + (princ "\n================================================================") + (princ "\n FOERDERTEST - PRODUKTIONSPROTOKOLL") + (princ "\n================================================================") + (princ (strcat "\n Distanz dL = " (rtos deltaL 2 0) " mm")) + (princ "\n Hoehenstufen: 0, 1000, 2000, 3000, 4000, 5000 mm") + (princ "\n Richtungen: Auf + Ab") + (princ "\n================================================================") + (princ "\n Nr. Richtung dH (mm) Status Winkel L_GF (mm) L_VF (mm)") + (princ "\n================================================================") + + ;; ========================================================== + ;; TEIL 1: Aufwaerts-Tests (6 Foerderer) + ;; ========================================================== + (foreach deltaH hoehen-liste + (setq idx (1+ idx)) + (setq richtung "Auf") + + (setq ergebnis (berechne-alle-winkel deltaL deltaH richtung)) + (setq best-winkel (car ergebnis)) + (setq L_GF (cadr ergebnis)) + (setq L_VF (caddr ergebnis)) + + (if (and best-winkel L_GF L_VF (> L_GF 0) (> L_VF 0)) + (progn + (setq anz-gebaut (1+ anz-gebaut)) + (princ (strcat "\n " + (itoa idx) " " + (if (< idx 10) " " "") + "Auf " + (rtos deltaH 4 0) " " + "GEBAUT " + (itoa best-winkel) " Grad " + (rtos L_GF 2 1) " " + (rtos L_VF 2 1))) + + (foerderanlage-einfuegen deltaL deltaH richtung best-winkel + (/ L_GF 2.0) (/ L_GF 2.0) L_VF + (list 0 y-offset 0)) + + (setq zusammenfassung-liste (cons + (list idx richtung deltaH "GEBAUT" best-winkel L_GF L_VF) + zusammenfassung-liste)) + ) + (progn + (setq anz-nicht-gebaut (1+ anz-nicht-gebaut)) + (princ (strcat "\n " + (itoa idx) " " + (if (< idx 10) " " "") + "Auf " + (rtos deltaH 4 0) " " + "NICHT " + "--- " + "--- " + "---")) + + (cond + ((and (<= L_GF 0) (<= L_VF 0)) + (princ "\n -> Grund: L_GF und L_VF sind negativ (Strecke zu kurz)")) + ((<= L_GF 0) + (princ "\n -> Grund: L_GF ist negativ (Gefaellestrecke zu kurz)")) + ((<= L_VF 0) + (princ "\n -> Grund: L_VF ist negativ (keine passende Steigung moeglich)")) + ((null best-winkel) + (princ "\n -> Grund: Kein passender Winkel (Hoehendifferenz zu gross/klein)")) + (t + (princ "\n -> Grund: Unbekannter Fehler in der Berechnung")) + ) + + (setq zusammenfassung-liste (cons + (list idx richtung deltaH "NICHT_GEBAUT" nil nil nil) + zusammenfassung-liste)) + ) + ) + (setq y-offset (- y-offset 200)) + ) + + ;; ========================================================== + ;; TEIL 2: Abwaerts-Tests (6 Foerderer) + ;; ========================================================== + (foreach deltaH hoehen-liste + (setq idx (1+ idx)) + (setq richtung "Ab") + + (if (< deltaH 1) + (progn + ;; deltaH = 0 bei Abwaerts ist prinzipiell unmoeglich + (setq anz-nicht-gebaut (1+ anz-nicht-gebaut)) + (princ (strcat "\n " + (itoa idx) " " + (if (< idx 10) " " "") + "Ab " + (rtos deltaH 4 0) " " + "NICHT " + "--- " + "--- " + "---")) + (princ "\n -> Grund: Bei Richtung 'Ab' mit dH=0 ist aus geometrischen Gruenden") + (princ "\n keine Foerderanlage moeglich (immer negative Netto-Hoehe)") + + (setq zusammenfassung-liste (cons + (list idx richtung deltaH "GEOMETRISCH_UNMOEGLICH" nil nil nil) + zusammenfassung-liste)) + ) + (progn + (setq ergebnis (berechne-alle-winkel deltaL deltaH richtung)) + (setq best-winkel (car ergebnis)) + (setq L_GF (cadr ergebnis)) + (setq L_VF (caddr ergebnis)) + + (if (and best-winkel L_GF L_VF (> L_GF 0) (> L_VF 0)) + (progn + (setq anz-gebaut (1+ anz-gebaut)) + (princ (strcat "\n " + (itoa idx) " " + (if (< idx 10) " " "") + "Ab " + (rtos deltaH 4 0) " " + "GEBAUT " + (itoa best-winkel) " Grad " + (rtos L_GF 2 1) " " + (rtos L_VF 2 1))) + + (foerderanlage-einfuegen deltaL deltaH richtung best-winkel + (/ L_GF 2.0) (/ L_GF 2.0) L_VF + (list 0 y-offset 0)) + + (setq zusammenfassung-liste (cons + (list idx richtung deltaH "GEBAUT" best-winkel L_GF L_VF) + zusammenfassung-liste)) + ) + (progn + (setq anz-nicht-gebaut (1+ anz-nicht-gebaut)) + (princ (strcat "\n " + (itoa idx) " " + (if (< idx 10) " " "") + "Ab " + (rtos deltaH 4 0) " " + "NICHT " + "--- " + "--- " + "---")) + + (cond + ((and (<= L_GF 0) (<= L_VF 0)) + (princ "\n -> Grund: L_GF und L_VF sind negativ (Strecke zu kurz)")) + ((<= L_GF 0) + (princ "\n -> Grund: L_GF ist negativ (Gefaellestrecke zu kurz)")) + ((<= L_VF 0) + (princ "\n -> Grund: L_VF ist negativ (Hoehendifferenz zu klein fuer diesen Weg)")) + ((null best-winkel) + (princ "\n -> Grund: Kein passender Winkel (Hoehendifferenz zu gross)")) + (t + (princ "\n -> Grund: Unbekannter Fehler in der Berechnung")) + ) + + (setq zusammenfassung-liste (cons + (list idx richtung deltaH "NICHT_GEBAUT" nil nil nil) + zusammenfassung-liste)) + ) + ) + ) + ) + (setq y-offset (- y-offset 200)) + ) + + ;; ========================================================== + ;; TEIL 3: ZUSAMMENFASSUNG + ;; ========================================================== + (princ "\n\n================================================================================") + (princ "\n ZUSAMMENFASSUNG") + (princ (strcat "\n dL = " (rtos deltaL 2 0) " mm")) + (princ "\n================================================================================") + + (foreach item (reverse zusammenfassung-liste) + (setq nr (car item)) + (setq richtung (cadr item)) + (setq deltaH_val (caddr item)) + (setq status (cadddr item)) + (setq winkel (car (cddddr item))) + (setq L_GF_val (cadr (cddddr item))) + (setq L_VF_val (caddr (cddddr item))) + + (cond + ((= status "GEBAUT") + (princ (strcat "\n " + (itoa nr) ". " richtung + (if (= richtung "Auf") " " " ") + (rtos deltaH_val 2 0) " mm " + (itoa winkel) " Grad " + (rtos L_GF_val 2 2) " mm " + (rtos L_VF_val 2 2) " mm GEBAUT"))) + ((= status "GEOMETRISCH_UNMOEGLICH") + (princ (strcat "\n " + (itoa nr) ". " richtung + (if (= richtung "Auf") " " " ") + (rtos deltaH_val 2 0) " mm " + "--- " + "--- mm " + "--- mm GEOMETRISCH UNMOEGLICH"))) + (t + (princ (strcat "\n " + (itoa nr) ". " richtung + (if (= richtung "Auf") " " " ") + (rtos deltaH_val 2 0) " mm " + "--- " + "--- mm " + "--- mm NICHT GEBAUT"))) + ) + ) + + (princ "\n\n================================================================================") + (princ (strcat "\n Erfolgreich gebaut: " (itoa anz-gebaut) " von " (itoa idx))) + (princ (strcat "\n Erfolgsquote: " (rtos (/ (* anz-gebaut 100.0) idx) 2 1) "%")) + (princ "\n================================================================================") + + (princ "\n\n Erklaerung:") + (princ "\n ----------") + (princ "\n GEBAUT:") + (princ "\n - Auf, dH=0 -> L_VF > 0 kompensiert 3 Grad-Gefaellestrecken") + (princ "\n - Auf, dH>0 -> Normalfall") + (princ "\n - Ab, dH>=1000 -> Normalfall") + (princ "\n") + (princ "\n NICHT GEBAUT:") + (princ "\n - Ab, dH=0 -> Geometrisch unmoeglich (Netto-Hoehe immer negativ)") + (princ "\n - Auf, dH=5000 -> 48 Grad Steigung reicht nicht aus (L_VF negativ)") + (princ "\n - Ab, dH=5000 -> 48 Grad Gefaelle reicht nicht aus") + (princ "\n================================================================================") + + (ssg-end) + + ;; Datei speichern + (if (getenv "DXFM_RESULTS") + (setq result-pfad (getenv "DXFM_RESULTS")) + (setq result-pfad (strcat (getenv "DXFM_BLOCKS") "/../results")) + ) + + (setq dateiname (strcat result-pfad + "/" + (menucmd "M=$(edtime,$(getvar,date),YYYYMODD-HHmm)") + ".dxf")) + (princ (strcat "\n\nSpeichere als: " dateiname)) + (command "_.DXFOUT" dateiname "V" "2018" "16") + (princ "\n\n>>> FOERDERTEST abgeschlossen (12 Foerderer)! <<<") + (princ) +) + +;; ============================================================ +;; TEST_KSEINAUS - KS_EIN/KS_AUS Ausrichtungstest +;; +;; Fuegt alle Vario-Bloecke in 3 Gruppen ein, jeweils 2 Reihen: +;; Gruppe 1: Stationaere Elemente (AUS, EIN, Separator, Umlenk, Motor) +;; Gruppe 2: Alle Boegen aufwaerts (in Z verschoben) +;; Gruppe 3: Alle Boegen abwaerts (in Z verschoben) +;; +;; Pro Gruppe: +;; Reihe A: nach KS_EIN ausgerichtet (KS_EIN X/Y = Referenz-X/Y) +;; Reihe B: nach KS_AUS ausgerichtet (KS_AUS X/Y = Referenz-X/Y) +;; Luecke von 400mm zwischen den Reihen +;; ============================================================ + +;; Hilfsfunktion: Eine Liste von Bloecken in 2 Reihen aufreihen +;; x-start: X-Position der Gruppe +;; y-start: Y-Startposition (wird zurueckgegeben als naechster freier Y) +;; z-offset: Z-Verschiebung (fuer Boegen) +;; Rueckgabe: naechste freie Y-Position +(defun ks-test-reihe (namen x-start y-start z-offset y-abstand y-luecke / + bname block-obj ks-data ks-ein ks-aus + insert-pt offset-vec y-pos idx) + + ;; --- Reihe A: nach KS_EIN --- + (princ "\n Reihe KS_EIN:") + (setq y-pos y-start) + (setq idx 0) + + (foreach bname namen + (setq idx (1+ idx)) + (setq insert-pt (list x-start y-pos z-offset)) + (setq block-obj (vla-InsertBlock modelspace + (vlax-3D-point insert-pt) + bname 1.0 1.0 1.0 0)) + (setq ks-data (extract-ks-from-block block-obj)) + (setq ks-ein (cadr (assoc "KS_EIN" ks-data))) + + (if ks-ein + (progn + (setq offset-vec (list (- x-start (car (car ks-ein))) + (- y-pos (cadr (car ks-ein))) + (- z-offset (caddr (car ks-ein))))) + (vla-Move block-obj + (vlax-3D-point '(0 0 0)) + (vlax-3D-point offset-vec)) + (princ (strcat "\n " (itoa idx) ". " bname " OK")) + ) + (princ (strcat "\n " (itoa idx) ". " bname " WARNUNG: KS_EIN fehlt!")) + ) + (setq y-pos (- y-pos y-abstand)) + ) + + ;; --- Luecke --- + (setq y-pos (- y-pos y-luecke)) + + ;; --- Reihe B: nach KS_AUS --- + (princ "\n Reihe KS_AUS:") + (setq idx 0) + + (foreach bname namen + (setq idx (1+ idx)) + (setq insert-pt (list x-start y-pos z-offset)) + (setq block-obj (vla-InsertBlock modelspace + (vlax-3D-point insert-pt) + bname 1.0 1.0 1.0 0)) + (setq ks-data (extract-ks-from-block block-obj)) + (setq ks-aus (cadr (assoc "KS_AUS" ks-data))) + + (if ks-aus + (progn + (setq offset-vec (list (- x-start (car (car ks-aus))) + (- y-pos (cadr (car ks-aus))) + (- z-offset (caddr (car ks-aus))))) + (vla-Move block-obj + (vlax-3D-point '(0 0 0)) + (vlax-3D-point offset-vec)) + (princ (strcat "\n " (itoa idx) ". " bname " OK")) + ) + (princ (strcat "\n " (itoa idx) ". " bname " WARNUNG: KS_AUS fehlt!")) + ) + (setq y-pos (- y-pos y-abstand)) + ) + + ;; Naechste freie Y-Position zurueckgeben + y-pos +) + +(defun c:TEST_KSEINAUS ( / stationen boegen-auf-liste boegen-ab-liste + bname y-pos y-abstand y-luecke z-bogen) + + ;; Bibliothek initialisieren + (if (not *lib-initialized*) + (init-bibliothek) + ) + + (ssg-start "TEST_KSEINAUS" '(("OSMODE") ("CECOLOR") ("ATTREQ") ("ATTDIA"))) + (setvar "OSMODE" 0) + (setvar "ATTREQ" 0) + (setvar "ATTDIA" 0) + + (setq y-abstand 200) + (setq y-luecke 400) + (setq z-bogen 2500) + + ;; Gruppe 1: Stationaere Elemente + (setq stationen (list + "_3D_AS_90_links" + "Staustrecke_Separator_SP_300_mm" + "Vario_Umlenkstation_500mm" + "Vario_Motorstation_500mm" + "_3D_ES_90_links" + )) + + ;; Gruppe 2+3: Boegen sammeln + (setq boegen-auf-liste '()) + (setq boegen-ab-liste '()) + (foreach w '(3 6 9 12 15 18 21 27 33 39 45 51) + (setq bname (strcat "Vario_Bogen_auf_" (itoa w) grad-zeichen)) + (if (tblsearch "BLOCK" bname) + (setq boegen-auf-liste (append boegen-auf-liste (list bname))) + ) + (setq bname (strcat "Vario_Bogen_ab_" (itoa w) grad-zeichen)) + (if (tblsearch "BLOCK" bname) + (setq boegen-ab-liste (append boegen-ab-liste (list bname))) + ) + ) + + (princ "\n================================================================") + (princ "\n TEST_KSEINAUS - KS-Ausrichtungstest") + (princ (strcat "\n Stationen: " (itoa (length stationen)))) + (princ (strcat "\n Boegen auf: " (itoa (length boegen-auf-liste)))) + (princ (strcat "\n Boegen ab: " (itoa (length boegen-ab-liste)))) + (princ (strcat "\n Z-Offset Boegen: " (rtos z-bogen 2 0) " mm")) + (princ "\n================================================================") + + ;; Gruppe 1: Stationaere Elemente bei X=0, Z=0 + (princ "\n\n GRUPPE 1: Stationaere Elemente (Z=0)") + (setq y-pos (ks-test-reihe stationen 0 0 0 y-abstand y-luecke)) + + ;; Gruppe 2: Boegen aufwaerts bei X=3000, Z=z-bogen + (princ "\n\n GRUPPE 2: Boegen aufwaerts (Z=" ) + (princ (strcat (rtos z-bogen 2 0) ")")) + (setq y-pos (ks-test-reihe boegen-auf-liste 3000 0 z-bogen y-abstand y-luecke)) + + ;; Gruppe 3: Boegen abwaerts bei X=6000, Z=z-bogen + (princ "\n\n GRUPPE 3: Boegen abwaerts (Z=") + (princ (strcat (rtos z-bogen 2 0) ")")) + (setq y-pos (ks-test-reihe boegen-ab-liste 6000 0 z-bogen y-abstand y-luecke)) + + (ssg-end) + + (princ "\n\n================================================================") + (princ "\n TEST_KSEINAUS abgeschlossen") + (princ "\n X=0: Stationen (KS_EIN + KS_AUS)") + (princ "\n X=3000: Boegen auf (KS_EIN + KS_AUS)") + (princ "\n X=6000: Boegen ab (KS_EIN + KS_AUS)") + (princ "\n================================================================") + (princ) +) diff --git a/bin/on_start.lsp b/bin/on_start.lsp index 2336836..18480d9 100644 --- a/bin/on_start.lsp +++ b/bin/on_start.lsp @@ -60,6 +60,22 @@ (princ "\n[SSG_LIB] WARNUNG: DXFMAKRO nicht gesetzt, Menue nicht geladen!") ) + ;; DBLCLKEDIT deaktivieren (Block-Editor nicht bei Doppelklick oeffnen) + (setvar "DBLCLKEDIT" 0) + + ;; Python-Module laden (PyRx) + (if menu-pfad + (foreach pymod '("eckrad" "kreisel" "dblclick" "omniflo" "omniflo_boegen" "omniflo_weichen") + (if (findfile (strcat menu-pfad "/lib/elemente/" pymod ".py")) + (progn + (command "PYLOAD" (strcat menu-pfad "/lib/elemente/" pymod ".py")) + (princ (strcat "\n[SSG_LIB] Python-Modul " pymod ".py geladen.")) + ) + (princ (strcat "\n[SSG_LIB] HINWEIS: " pymod ".py nicht gefunden.")) + ) + ) + ) + (princ "\n[SSG_LIB] Startup abgeschlossen.") ) ) diff --git a/dcl/kreisel_edit.dcl b/dcl/kreisel_edit.dcl deleted file mode 100644 index b715316..0000000 --- a/dcl/kreisel_edit.dcl +++ /dev/null @@ -1,53 +0,0 @@ -// ============================================================ -// kreisel_edit.dcl - Kreisel Parameter bearbeiten -// Erlaubt das Aendern von Abstand, Hoehe, Ausrichtung, -// Drehrichtung, Typ und Name eines bestehenden Kreisels. -// ============================================================ - -kreisel_edit : dialog { - label = "Kreisel bearbeiten"; - - : column { - - : edit_box { - key = "name"; - label = "Kreiselname:"; - edit_width = 30; - } - - : row { - : edit_box { - key = "abstand"; - label = "Abstand (mm):"; - edit_width = 10; - } - : edit_box { - key = "hoehe"; - label = "Hoehe (mm):"; - edit_width = 10; - } - } - - : popup_list { - key = "ausrichtung"; - label = "Ausrichtung:"; - width = 30; - } - - : row { - : popup_list { - key = "drehrichtung"; - label = "Motordrehrichtung:"; - width = 16; - } - : popup_list { - key = "kreiselart"; - label = "Typ:"; - width = 16; - } - } - - } - - ok_cancel; -} diff --git a/dcl/omniflo_boegen.dcl b/dcl/omniflo_boegen.dcl deleted file mode 100644 index c4830b1..0000000 --- a/dcl/omniflo_boegen.dcl +++ /dev/null @@ -1,54 +0,0 @@ -// ============================================================ -// omniflo_boegen.dcl - Bogen-Auswahl Dialog fuer Omniflo -// Zeigt gefilterte Boegen nach Winkel mit Details an. -// ============================================================ - -omniflo_boegen : dialog { - label = "Omniflo Bogen Auswahl"; - - : row { - : popup_list { - key = "bogenart"; - label = "Bogenart:"; - width = 42; - } - : edit_box { - key = "val_sivasnr"; - label = "SivasNr:"; - width = 22; - is_enabled = false; - } - } - - : row { - : edit_box { - key = "val_winkel"; - label = "Winkel:"; - width = 16; - is_enabled = false; - } - : edit_box { - key = "val_radius"; - label = "Radius:"; - width = 16; - is_enabled = false; - } - } - - : row { - : edit_box { - key = "val_breite"; - label = "Breite:"; - width = 16; - is_enabled = false; - } - : edit_box { - key = "val_laenge"; - label = "Laenge:"; - width = 16; - is_enabled = false; - } - } - - ok_cancel; -} diff --git a/dcl/omniflo_weichen.dcl b/dcl/omniflo_weichen.dcl deleted file mode 100644 index e629c70..0000000 --- a/dcl/omniflo_weichen.dcl +++ /dev/null @@ -1,65 +0,0 @@ -// ============================================================ -// omniflo_weichen.dcl - Weichen-Auswahl Dialog fuer Omniflo -// Zeigt gefilterte Weichen mit Details und Filteroptionen an. -// ============================================================ - -omniflo_weichen : dialog { - label = "Omniflo Weichen Auswahl"; - - : row { - : popup_list { - key = "profiltyp"; - label = "Profiltyp-Auswahl:"; - width = 50; - } - } - - : row { - : boxed_radio_column { - key = "schaltung"; - label = "Schaltungstyp"; - : radio_button { key = "sch_alle"; label = "Alle"; value = "1"; } - : radio_button { key = "sch_m"; label = "Mechanisch (M)"; } - : radio_button { key = "sch_p"; label = "Pneumatisch (P)"; } - } - : boxed_radio_column { - key = "richtung"; - label = "Richtung"; - : radio_button { key = "ri_alle"; label = "Alle"; value = "1"; } - : radio_button { key = "ri_links"; label = "Links"; } - : radio_button { key = "ri_rechts"; label = "Rechts"; } - } - } - - : row { - : edit_box { - key = "val_sivasnr"; - label = "SivasNr:"; - width = 22; - is_enabled = false; - } - : edit_box { - key = "val_winkel"; - label = "Winkel:"; - width = 12; - is_enabled = false; - } - } - - : row { - : edit_box { - key = "val_breite"; - label = "Breite:"; - width = 12; - is_enabled = false; - } - : edit_box { - key = "val_laenge"; - label = "Laenge:"; - width = 12; - is_enabled = false; - } - } - - ok_cancel; -} diff --git a/lib/elemente/__init__.py b/lib/elemente/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/elemente/dblclick.py b/lib/elemente/dblclick.py new file mode 100644 index 0000000..2abb1d0 --- /dev/null +++ b/lib/elemente/dblclick.py @@ -0,0 +1,50 @@ +# dblclick.py +# SSG_BLOCKEDIT - Dispatcher fuer Rechtsklick-Bearbeitung von SSG-Bloecken +# Prueft den selektierten Block und oeffnet den passenden Dialog. +# ============================================ + +import PyRx as Rx +import PyDb as Db +import PyEd as Ed + +from elemente.kreisel import kreisel_edit_entity +from elemente.eckrad import eckrad_edit_entity + + +@Rx.command("SSG_BLOCKEDIT") +def ssg_blockedit(): + """Dispatcher: prueft selektierten Block und oeffnet passenden Dialog. + + Wird aus dem Rechtsklick-Kontextmenue aufgerufen. + Erkennt KREISEL_* und ECKRAD_* Bloecke und oeffnet den jeweiligen + wxPython-Bearbeitungsdialog. Bei anderen Bloecken wird BEDIT gestartet. + """ + ed = Ed.Editor() + db = Db.HostApplicationServices().workingDatabase() + + # Implied Selection nutzen (bereits selektiertes Objekt) + res = ed.selectImplied() + if res[0] != Ed.PromptStatus.eOk: + return + + ss = res[1] + if ss.length() == 0: + return + + ent_id = ss.getObjectIds()[0] + ent = Db.Entity(ent_id, Db.OpenMode.kForRead) + + if not ent.isKindOf(Db.BlockReference.desc()): + return + + bref = Db.BlockReference(ent_id, Db.OpenMode.kForRead) + btr = Db.BlockTableRecord(bref.blockTableRecord(), Db.OpenMode.kForRead) + bname = btr.getName().upper() + + if bname.startswith("KREISEL_"): + kreisel_edit_entity(db, ent_id) + elif bname.startswith("ECKRAD_"): + eckrad_edit_entity(db, ent_id) + else: + # Anderer Block -> normaler Block-Editor + ed.runCommand("_.BEDIT\n") diff --git a/lib/elemente/eckrad.py b/lib/elemente/eckrad.py new file mode 100644 index 0000000..40bae78 --- /dev/null +++ b/lib/elemente/eckrad.py @@ -0,0 +1,305 @@ +# eckrad.py +# ILS Eckrad - PyRx-Implementierung +# Ersetzt c:ILS_Eckrad und ils-eckrad-insert aus KreiselInsert.lsp +# Setzt voraus: PyRx (cad-pyrx) in BricsCAD/AutoCAD geladen +# ============================================ + +import math + +import PyRx as Rx +import PyGe as Ge +import PyDb as Db +import PyEd as Ed +import wx + +from elemente.utils import ( + get_block_path, + component_next_number, + merge_attribs, + read_block_attribs, + ensure_block_from_dwg, + create_attrib_defs, + insert_block_with_attribs, +) + +# ============================================================ +# Konstanten (aus KreiselInsert.lsp) +# ============================================================ + +KREISEL_DURCHMESSER = 800.0 +ECKRAD_DEFAULT_HOEHE = 2000.0 + +ECKRAD_ATTRIB_DEFS = [ + ("NAME", ""), + ("DREHRICHTUNG", "UZS"), + ("DREHUNG", "0"), + ("NUMMER", "0"), + ("HOEHE", "0"), +] + + +# ============================================================ +# Eckrad-spezifische Funktionen +# ============================================================ + +def ensure_eckrad_block(db, bname, block_path): + """AN8.dwg als Basis-Block laden und als ECKRAD_n Block definieren, + falls noch nicht vorhanden.""" + bt = Db.BlockTable(db.blockTableId(), Db.OpenMode.kForRead) + if bt.has(bname): + print(f"\n-> Verwende bestehende Blockdefinition: {bname}") + return True + + if not ensure_block_from_dwg(db, "AN8.dwg", block_path): + return False + + # Neue Blockdefinition ECKRAD_n erzeugen + bt.upgradeOpen() + new_btr = Db.BlockTableRecord() + new_btr.setName(bname) + bt.add(new_btr) + + # AN8-Blockdefinition als BlockReference einfuegen + an8_id = bt.getAt("AN8") + bref = Db.BlockReference(Ge.Point3d(0, 0, 0), an8_id) + new_btr.appendAcDbEntity(bref) + + # Unsichtbare Attribut-Definitionen erzeugen + create_attrib_defs(new_btr, ECKRAD_ATTRIB_DEFS) + + print(f"\n-> Neue Blockdefinition: {bname}") + return True + + +def insert_eckrad(db, insert_point, rotation_deg, attribs=None): + """Eckrad als Block-Referenz mit Attributen einfuegen. + + Parameter: + db - aktuelle Datenbank + insert_point - Ge.Point3d (x, y, z) + rotation_deg - Drehwinkel in Grad + attribs - dict mit Attribut-Ueberschreibungen (optional) + Rueckgabe: ObjectId der eingefuegten BlockReference + """ + block_path = get_block_path() + + # Attribute vorbereiten + merged = merge_attribs(attribs, ECKRAD_ATTRIB_DEFS) + merged["DREHUNG"] = f"{rotation_deg:.1f}" + + # Hoehe aus Z-Koordinate oder Attribut + z_hoehe = insert_point.z + if z_hoehe > 0: + merged["HOEHE"] = f"{z_hoehe:.0f}" + else: + try: + z_hoehe = float(merged.get("HOEHE", "0")) + except ValueError: + z_hoehe = 0.0 + + # Nummer vergeben + nummer = component_next_number(db, "ECKRAD_") + merged["NUMMER"] = str(nummer) + merged["NAME"] = f"ECKRAD_{nummer}" + + # Blockname + bname = f"ECKRAD_{nummer}" + + # Block-Definition sicherstellen + if not ensure_eckrad_block(db, bname, block_path): + return None + + # Block einfuegen mit Attributen + pt = Ge.Point3d(insert_point.x, insert_point.y, z_hoehe) + obj_id = insert_block_with_attribs(db, bname, pt, rotation_deg, merged) + + print(f"\n-> Eckrad #{nummer} eingefuegt: {bname}" + f" bei {insert_point.x:.1f},{insert_point.y:.1f}" + f" Rotation={rotation_deg:.1f} Hoehe={z_hoehe:.0f}") + + return obj_id + + +# ============================================================ +# CAD-Befehl: ILS_Eckrad +# ============================================================ + +@Rx.command("ILS_Eckrad") +def ils_eckrad(): + """Befehl ILS_Eckrad - fragt Eingaben ab und fuegt Eckrad ein. + + Ablauf: + 1. Beruehrpunkt anklicken (wo der Kreis tangential anliegen soll) + 2. Richtungspunkt anklicken (bestimmt Versatzrichtung zum Kreismittelpunkt) + 3. Kreismittelpunkt = Beruehrpunkt + Radius in Richtung des 2. Klicks + 4. Hoehe aus Z-Koordinate oder manuell + """ + ed = Ed.Editor() + db = Db.HostApplicationServices().workingDatabase() + + # 1. Beruehrpunkt (Tangentenpunkt) + res_tangent = ed.getPoint("\nBeruehrpunkt Eckrad (Tangente): ") + if res_tangent[0] != Ed.PromptStatus.eOk: + return + pt_tangent = res_tangent[1] + + # 2. Richtungspunkt (Gummiband vom Beruehrpunkt) + res_dir = ed.getPoint("\nRichtung zum Kreismittelpunkt: ", pt_tangent) + if res_dir[0] != Ed.PromptStatus.eOk: + return + pt_dir = res_dir[1] + + # 3. Winkel und Versatz berechnen + dx = pt_dir.x - pt_tangent.x + dy = pt_dir.y - pt_tangent.y + dist = math.sqrt(dx * dx + dy * dy) + + if dist <= 0.001: + print("\nFehler: Beruehr- und Richtungspunkt sind identisch!") + return + + ux = dx / dist + uy = dy / dist + radius = KREISEL_DURCHMESSER / 2.0 + + center_x = pt_tangent.x + radius * ux + center_y = pt_tangent.y + radius * uy + rotation = math.degrees(math.atan2(dy, dx)) + + # 4. Hoehe bestimmen + if pt_tangent.z > 0: + hoehe = pt_tangent.z + print(f"\n-> Hoehe aus 3D-Punkt uebernommen: {hoehe:.0f}") + else: + res_hoehe = ed.getDouble(f"\nHoehe (Z-Koordinate) <{ECKRAD_DEFAULT_HOEHE:.0f}>: ") + if res_hoehe[0] == Ed.PromptStatus.eOk: + hoehe = res_hoehe[1] + else: + hoehe = ECKRAD_DEFAULT_HOEHE + + # 5. Einfuegen + insert_pt = Ge.Point3d(center_x, center_y, hoehe) + insert_eckrad(db, insert_pt, rotation) + + +# ============================================================ +# Eckrad bearbeiten (wx-Dialog) +# ============================================================ + +class EckradEditDialog(wx.Dialog): + """Dialog zum Bearbeiten eines bestehenden Eckrads.""" + + def __init__(self, parent, attribs): + super().__init__(parent, title="Eckrad bearbeiten", + size=(320, 220), + style=wx.DEFAULT_DIALOG_STYLE) + + panel = wx.Panel(self) + sizer = wx.BoxSizer(wx.VERTICAL) + + # Name (readonly) + row_name = wx.BoxSizer(wx.HORIZONTAL) + row_name.Add(wx.StaticText(panel, label="Name:"), 0, + wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 5) + self.txt_name = wx.TextCtrl(panel, value=attribs.get("NAME", ""), + size=(180, -1), style=wx.TE_READONLY) + row_name.Add(self.txt_name, 1) + sizer.Add(row_name, 0, wx.EXPAND | wx.ALL, 5) + + # Drehrichtung + row_dreh = wx.BoxSizer(wx.HORIZONTAL) + row_dreh.Add(wx.StaticText(panel, label="Drehrichtung:"), 0, + wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 5) + self.cb_drehrichtung = wx.Choice(panel, choices=["UZS", "GUZ"]) + self.cb_drehrichtung.SetSelection( + 1 if attribs.get("DREHRICHTUNG") == "GUZ" else 0) + row_dreh.Add(self.cb_drehrichtung, 0) + sizer.Add(row_dreh, 0, wx.EXPAND | wx.ALL, 5) + + # Hoehe + row_hoehe = wx.BoxSizer(wx.HORIZONTAL) + row_hoehe.Add(wx.StaticText(panel, label="Hoehe (mm):"), 0, + wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 5) + self.txt_hoehe = wx.TextCtrl(panel, value=attribs.get("HOEHE", "0"), + size=(80, -1)) + row_hoehe.Add(self.txt_hoehe, 0) + sizer.Add(row_hoehe, 0, wx.EXPAND | wx.ALL, 5) + + # OK / Cancel + btn_sizer = self.CreateStdDialogButtonSizer(wx.OK | wx.CANCEL) + sizer.Add(btn_sizer, 0, wx.EXPAND | wx.ALL, 10) + + panel.SetSizer(sizer) + self.Fit() + self.CenterOnScreen() + + def get_values(self): + """Dialog-Werte als dict zurueckgeben.""" + return { + "drehrichtung": "GUZ" if self.cb_drehrichtung.GetSelection() == 1 else "UZS", + "hoehe": self.txt_hoehe.GetValue(), + } + + +def eckrad_edit_entity(db, ent_id): + """Eckrad per wxPython-Dialog bearbeiten (fuer eine bestimmte Entity-ID). + + Wird von EckradEdit und SSG_BLOCKEDIT aufgerufen. + """ + bref = Db.BlockReference(ent_id, Db.OpenMode.kForRead) + attribs = read_block_attribs(bref) + base_point = bref.position() + rotation_deg = math.degrees(bref.rotation()) + + # HOEHE aus Z-Koordinate + if base_point.z > 0: + attribs["HOEHE"] = f"{base_point.z:.0f}" + + print(f"\n-> Bestehend: Name={attribs.get('NAME', '?')}" + f" Drehrichtung={attribs.get('DREHRICHTUNG', '?')}" + f" Hoehe={attribs.get('HOEHE', '?')}") + + dlg = EckradEditDialog(None, attribs) + result = dlg.ShowModal() + + if result == wx.ID_OK: + new_values = dlg.get_values() + dlg.Destroy() + + attribs["DREHRICHTUNG"] = new_values["drehrichtung"] + attribs["HOEHE"] = new_values["hoehe"] + + new_hoehe = float(new_values["hoehe"]) + + # Alten Block loeschen + bref.upgradeOpen() + bref.erase() + + # Neu einfuegen + insert_pt = Ge.Point3d(base_point.x, base_point.y, new_hoehe) + insert_eckrad(db, insert_pt, rotation_deg, attribs) + + print(f"\nEckrad aktualisiert: Drehrichtung={new_values['drehrichtung']}" + f" Hoehe={new_values['hoehe']}") + else: + dlg.Destroy() + print("\nAbgebrochen.") + + +@Rx.command("EckradEdit") +def cmd_eckrad_edit(): + """EckradEdit: Bestehenden Eckrad per wxPython-Dialog bearbeiten.""" + ed = Ed.Editor() + db = Db.HostApplicationServices().workingDatabase() + + res = ed.entSel("\nEckrad-Block auswaehlen: ") + if res[0] != Ed.PromptStatus.eOk: + return + ent_id = res[1] + + ent = Db.Entity(ent_id, Db.OpenMode.kForRead) + if not ent.isKindOf(Db.BlockReference.desc()): + print("\nKein Block ausgewaehlt!") + return + + eckrad_edit_entity(db, ent_id) diff --git a/lib/elemente/kreisel.py b/lib/elemente/kreisel.py new file mode 100644 index 0000000..de124a5 --- /dev/null +++ b/lib/elemente/kreisel.py @@ -0,0 +1,683 @@ +# kreisel.py +# ILS Kreisel - PyRx-Implementierung +# Ersetzt alle Kreisel-Routinen aus KreiselInsert.lsp +# Befehle: KreiselInsert, KreiselConnect, KreiselRedraw, +# KreiselQuick, KreiselEdit, KreiselParams, +# KreiselLabelSetup, KreiselLabelPos, KreiselLabelHoehe +# ============================================ + +import math + +import PyRx as Rx +import PyGe as Ge +import PyDb as Db +import PyEd as Ed +import wx + +from elemente.utils import ( + get_block_path, + component_next_number, + merge_attribs, + read_block_attribs, + ensure_layer, + ensure_textstyle, + ensure_block_from_dwg, + create_attrib_defs, + insert_block_with_attribs, +) + +# ============================================================ +# Konstanten (aus KreiselInsert.lsp) +# ============================================================ + +KREISEL_DURCHMESSER = 800.0 +KREISEL_PIN_ABSTAND = 100.0 +KREISEL_DEFAULT_LAENGE = 2300.0 +KREISEL_DEFAULT_HOEHE = 2000.0 + +KREISEL_ATTRIB_DEFS = [ + ("NAME", ""), + ("DREHRICHTUNG", "UZS"), + ("N_SEPARATOREN", "2"), + ("KREISELART", "STANDARD"), + ("N_SCANNER", "0"), + ("N_RAMPEN", "0"), + ("DREHUNG", "0"), + ("ABSTAND", "2300"), + ("NUMMER", "0"), + ("HOEHE", "0"), +] + +# Ausrichtungs-Definitionen: (Label, Rotation in Grad) +KREISEL_AUSRICHTUNGEN = [ + ("Horizontal MS", 0.0), + ("Horizontal SM", 180.0), + ("Vertikal MS", 90.0), + ("Vertikal SM", 270.0), +] + +# Globaler Zustand (pro Sitzung) +_kreisel_typ = "STANDARD" + +# Beschriftungs-Einstellungen +_beschriftung_layer = "S_KRS_BESCHR" +_beschriftung_hoehe = 100.0 +_beschriftung_farbe = 7 +_beschriftung_abstand_oben = 0.0 +_beschriftung_abstand_x = 0.0 +_beschriftung_abstand_y = 0.0 + + +# ============================================================ +# Kreisel-spezifische Hilfsfunktionen +# ============================================================ + +def rotation_to_idx(rot): + """Rotation (Grad) -> Index in KREISEL_AUSRICHTUNGEN.""" + best_idx = 0 + for i, (_, r) in enumerate(KREISEL_AUSRICHTUNGEN): + diff = abs((rot % 360) - (r % 360)) + if diff < 1.0: + best_idx = i + return best_idx + + +def idx_to_rotation(idx): + """Index in KREISEL_AUSRICHTUNGEN -> Rotation (Grad).""" + return KREISEL_AUSRICHTUNGEN[idx][1] + + +# ============================================================ +# Kern-Funktion: draw_module (entspricht draw-module in LISP) +# ============================================================ + +def draw_module(db, base_point, abstand, rotation_deg, attribs=None): + """Kreisel-Geometrie zeichnen, Block definieren, einfuegen, Attribute setzen. + + Parameter: + db - aktuelle Datenbank + base_point - Ge.Point3d (x, y, z) Basispunkt AN-Seite + abstand - Tangentenlaenge zwischen Kreismitten (mm) + rotation_deg - Einfuegewinkel in Grad + attribs - dict mit Attribut-Ueberschreibungen (optional) + Rueckgabe: ObjectId der eingefuegten BlockReference + """ + block_path = get_block_path() + radius = KREISEL_DURCHMESSER / 2.0 + pin_y = radius - KREISEL_PIN_ABSTAND + + # Attribute vorbereiten + merged = merge_attribs(attribs, KREISEL_ATTRIB_DEFS) + merged["ABSTAND"] = f"{abstand:.0f}" + merged["DREHUNG"] = f"{rotation_deg:.1f}" + + # Hoehe bestimmen + z_hoehe = base_point.z + if z_hoehe > 0: + merged["HOEHE"] = f"{z_hoehe:.0f}" + else: + try: + z_hoehe = float(merged.get("HOEHE", "0")) + except ValueError: + z_hoehe = 0.0 + merged["HOEHE"] = f"{z_hoehe:.0f}" if z_hoehe > 0 else merged.get("HOEHE", "0") + + is_pin = merged.get("KREISELART", "STANDARD") == "PIN" + + # Nummer: bestehende beibehalten (Redraw/Edit), sonst neue vergeben + try: + kreisel_nummer = int(merged.get("NUMMER", "0")) + except ValueError: + kreisel_nummer = 0 + if kreisel_nummer <= 0: + kreisel_nummer = component_next_number(db, "KREISEL_") + merged["NUMMER"] = str(kreisel_nummer) + + merged["NAME"] = f"Kreisel{kreisel_nummer}" + + # Blockname: KREISEL_1_PIN_2300 oder KREISEL_1_STANDARD_2300 + art_str = "PIN" if is_pin else "STANDARD" + bname = f"KREISEL_{kreisel_nummer}_{art_str}_{abstand:.0f}" + bname = bname.replace(".", "") + + bt = Db.BlockTable(db.blockTableId(), Db.OpenMode.kForRead) + block_exists = bt.has(bname) + + if not block_exists: + # AN8.dwg und SP8.dwg laden + if not ensure_block_from_dwg(db, "AN8.dwg", block_path): + return None + if not ensure_block_from_dwg(db, "SP8.dwg", block_path): + return None + + bt.upgradeOpen() + new_btr = Db.BlockTableRecord() + new_btr.setName(bname) + bt.add(new_btr) + + # AN8 bei (radius, 0) + an8_id = bt.getAt("AN8") + bref_an8 = Db.BlockReference(Ge.Point3d(radius, 0, 0), an8_id) + new_btr.appendAcDbEntity(bref_an8) + + # SP8 bei (radius+abstand, 0) + sp8_id = bt.getAt("SP8") + bref_sp8 = Db.BlockReference(Ge.Point3d(radius + abstand, 0, 0), sp8_id) + new_btr.appendAcDbEntity(bref_sp8) + + # Tangenten-Linien auf Layer S_LP + ensure_layer(db, "S_LP", 7) + lp_lt = Db.LayerTable(db.layerTableId(), Db.OpenMode.kForRead) + lp_layer_id = lp_lt.getAt("S_LP") + + line_color = Db.Color() + line_color.setColorIndex(5 if is_pin else 1) + + # Obere Tangente + line_top = Db.Line(Ge.Point3d(radius, radius, 0), + Ge.Point3d(radius + abstand, radius, 0)) + line_top.setLayer(lp_layer_id) + line_top.setColor(line_color) + new_btr.appendAcDbEntity(line_top) + + # Untere Tangente + line_bot = Db.Line(Ge.Point3d(radius, -radius, 0), + Ge.Point3d(radius + abstand, -radius, 0)) + line_bot.setLayer(lp_layer_id) + line_bot.setColor(line_color) + new_btr.appendAcDbEntity(line_bot) + + # PIN-Linien + if is_pin: + ensure_layer(db, "pinbereich", 3) + pin_lt = Db.LayerTable(db.layerTableId(), Db.OpenMode.kForRead) + pin_layer_id = pin_lt.getAt("pinbereich") + pin_color = Db.Color() + pin_color.setColorIndex(3) + + pin_top = Db.Line(Ge.Point3d(KREISEL_DURCHMESSER, pin_y, 0), + Ge.Point3d(abstand, pin_y, 0)) + pin_top.setLayer(pin_layer_id) + pin_top.setColor(pin_color) + new_btr.appendAcDbEntity(pin_top) + + pin_bot = Db.Line(Ge.Point3d(KREISEL_DURCHMESSER, -pin_y, 0), + Ge.Point3d(abstand, -pin_y, 0)) + pin_bot.setLayer(pin_layer_id) + pin_bot.setColor(pin_color) + new_btr.appendAcDbEntity(pin_bot) + + # Beschriftung + ensure_layer(db, _beschriftung_layer, _beschriftung_farbe) + style_name = ensure_textstyle(db) + tst = Db.TextStyleTable(db.textStyleTableId(), Db.OpenMode.kForRead) + style_id = tst.getAt(style_name) + descr_lt = Db.LayerTable(db.layerTableId(), Db.OpenMode.kForRead) + descr_layer_id = descr_lt.getAt(_beschriftung_layer) + + label_text = f"KREISEL {kreisel_nummer}: {art_str}_{abstand:.0f}" + mitte_x = radius + abstand / 2.0 + mitte_y = _beschriftung_abstand_oben + label_color = Db.Color() + label_color.setColorIndex(_beschriftung_farbe) + + txt = Db.Text() + txt.setPosition(Ge.Point3d(mitte_x, mitte_y, 0)) + txt.setAlignmentPoint(Ge.Point3d(mitte_x, mitte_y, 0)) + txt.setHeight(_beschriftung_hoehe) + txt.setTextString(label_text) + txt.setTextStyle(style_id) + txt.setLayer(descr_layer_id) + txt.setColor(label_color) + txt.setHorizontalMode(Db.TextHorzMode.kTextCenter) + txt.setVerticalMode(Db.TextVertMode.kTextVertMid) + new_btr.appendAcDbEntity(txt) + + # Unsichtbare Attribut-Definitionen + create_attrib_defs(new_btr, KREISEL_ATTRIB_DEFS) + + print(f"\n-> Neue Blockdefinition: {bname}") + else: + print(f"\n-> Verwende bestehende Blockdefinition: {bname}") + + # Block am Zielpunkt einfuegen + pt = Ge.Point3d(base_point.x, base_point.y, z_hoehe) + obj_id = insert_block_with_attribs(db, bname, pt, rotation_deg, merged) + + print(f"\n-> Kreisel #{kreisel_nummer} eingefuegt: {bname} (Hoehe={z_hoehe:.0f})") + return obj_id + + +# ============================================================ +# Befehle +# ============================================================ + +@Rx.command("KreiselInsert") +def cmd_kreisel_insert(): + """KreiselInsert: Manuell Position + Parameter.""" + ed = Ed.Editor() + db = Db.HostApplicationServices().workingDatabase() + + # 1. Einfuegepunkt + res = ed.getPoint("\nBasispunkt (AN-Seite): ") + if res[0] != Ed.PromptStatus.eOk: + print("\nBefehl abgebrochen.") + return + pt = res[1] + + # 2. Hoehe bestimmen + hoehe = _get_hoehe(ed, pt) + + # 3. Abstand + pdo = Ed.PromptDistanceOptions("\nAbstand (Tangentenlaenge): ") + pdo.setDefaultValue(KREISEL_DEFAULT_LAENGE) + pdo.setAllowNone(True) + res_dist = ed.getDist(pdo) + if res_dist[0] == Ed.PromptStatus.eOk: + abstand = res_dist[1] + else: + abstand = KREISEL_DEFAULT_LAENGE + print(f"\n-> Abstand: {abstand:.0f} mm") + + # 4. Rotation + res_rot = ed.getString("\nRotation in Grad <0>: ") + if res_rot[0] == Ed.PromptStatus.eOk and res_rot[1]: + try: + rotation = float(res_rot[1]) + except ValueError: + rotation = 0.0 + else: + rotation = 0.0 + + # 5. Kreiselart + typ = _ask_kreiselart(ed) + + print(f"\n-> Rotation: {rotation:.1f} Hoehe: {hoehe:.0f} Kreiselart: {typ}") + + # 6. Modul einfuegen + draw_module(db, Ge.Point3d(pt.x, pt.y, hoehe), abstand, rotation, + {"KREISELART": typ, "HOEHE": f"{hoehe:.0f}"}) + print("\nKreisel Modul eingefuegt.") + + +@Rx.command("KreiselConnect") +def cmd_kreisel_connect(): + """KreiselConnect: Linie zeichnen fuer Kreisel-Platzierung (AN -> SP).""" + ed = Ed.Editor() + db = Db.HostApplicationServices().workingDatabase() + + print("\n--- Kreisel durch Linie definieren (AN -> SP) ---") + + # 1. Startpunkt (AN-Seite) + res_start = ed.getPoint("\nStartpunkt (AN-Seite aussen): ") + if res_start[0] != Ed.PromptStatus.eOk: + print("\nAbgebrochen.") + return + pt_start = res_start[1] + + # 2. Endpunkt (SP-Seite) mit Gummiband + res_end = ed.getPoint("\nEndpunkt (SP-Seite aussen): ", pt_start) + if res_end[0] != Ed.PromptStatus.eOk: + print("\nAbgebrochen.") + return + pt_end = res_end[1] + + # 3. Geometrie berechnen + dx = pt_end.x - pt_start.x + dy = pt_end.y - pt_start.y + dist = math.sqrt(dx * dx + dy * dy) + abstand = dist - KREISEL_DURCHMESSER + + if abstand <= 0.0: + print(f"\nDistanz zu klein! ({dist:.0f} < {KREISEL_DURCHMESSER:.0f})") + return + + # 4. Rotation + rotation = math.degrees(math.atan2(dy, dx)) + + # 5. Hoehe + hoehe = pt_start.z if pt_start.z > 0 else 0.0 + if hoehe <= 0.0: + res_h = ed.getDouble(f"\nHoehe (Z-Koordinate) <{KREISEL_DEFAULT_HOEHE:.0f}>: ") + hoehe = res_h[1] if res_h[0] == Ed.PromptStatus.eOk else KREISEL_DEFAULT_HOEHE + else: + print(f"\n-> Hoehe aus Startpunkt uebernommen: {hoehe:.0f}") + + # 6. Kreiselart + typ = _ask_kreiselart(ed) + + print(f"\n-> Abstand: {abstand:.0f} mm Rotation: {rotation:.1f} Grad" + f" Hoehe: {hoehe:.0f} Kreiselart: {typ}") + + # 7. Modul einfuegen + draw_module(db, Ge.Point3d(pt_start.x, pt_start.y, hoehe), abstand, rotation, + {"KREISELART": typ, "HOEHE": f"{hoehe:.0f}"}) + print("\nKreisel Connect erfolgreich!") + + +@Rx.command("KreiselRedraw") +def cmd_kreisel_redraw(): + """KreiselRedraw: Bestehenden Kreisel neu zeichnen.""" + ed = Ed.Editor() + db = Db.HostApplicationServices().workingDatabase() + + # 1. Bestehenden Kreisel-Block auswaehlen + res = ed.entSel("\nKreisel-Block auswaehlen: ") + if res[0] != Ed.PromptStatus.eOk: + return + ent_id = res[1] + + ent = Db.Entity(ent_id, Db.OpenMode.kForRead) + if not ent.isKindOf(Db.BlockReference.desc()): + print("\nKein Block ausgewaehlt!") + return + + bref = Db.BlockReference(ent_id, Db.OpenMode.kForRead) + attribs = read_block_attribs(bref) + base_point = bref.position() + rotation = math.degrees(bref.rotation()) + + if "ABSTAND" not in attribs: + print("\nKein Kreisel-Block! (Attribut ABSTAND fehlt)") + return + + print(f"\n-> Bestehend: Abstand={attribs.get('ABSTAND', '?')}" + f" Drehung={attribs.get('DREHUNG', '?')}" + f" Hoehe={attribs.get('HOEHE', '?')}" + f" Art={attribs.get('KREISELART', '?')}") + + # 2. Neuen Abstand abfragen + old_abstand = float(attribs.get("ABSTAND", "2300")) + res_a = ed.getDouble(f"\nNeuer Abstand <{old_abstand:.0f}>: ") + new_abstand = res_a[1] if res_a[0] == Ed.PromptStatus.eOk else old_abstand + + # 3. Neue Hoehe abfragen + old_hoehe = float(attribs.get("HOEHE", "0")) + res_h = ed.getDouble(f"\nNeue Hoehe <{old_hoehe:.0f}>: ") + new_hoehe = res_h[1] if res_h[0] == Ed.PromptStatus.eOk else old_hoehe + + # 4. Alten Block loeschen + bref.upgradeOpen() + bref.erase() + + # 5. Neu zeichnen mit bestehenden Attributen + neuem Abstand + draw_module(db, Ge.Point3d(base_point.x, base_point.y, new_hoehe), + new_abstand, rotation, attribs) + print(f"\nKreisel neu gezeichnet. Abstand={new_abstand:.0f}") + + +@Rx.command("KreiselQuick") +def cmd_kreisel_quick(): + """KreiselQuick: Schnell mit Defaults (horizontal).""" + ed = Ed.Editor() + db = Db.HostApplicationServices().workingDatabase() + + res = ed.getPoint("\nBasispunkt (AN-Seite): ") + if res[0] != Ed.PromptStatus.eOk: + return + pt = res[1] + + res_h = ed.getDouble("\nHoehe (Z-Koordinate) <0>: ") + hoehe = res_h[1] if res_h[0] == Ed.PromptStatus.eOk else 0.0 + + draw_module(db, Ge.Point3d(pt.x, pt.y, hoehe), + KREISEL_DEFAULT_LAENGE, 0.0, + {"HOEHE": f"{hoehe:.0f}"}) + print("\nSchnell Einfuegen fertig.") + + +def kreisel_edit_entity(db, ent_id): + """Kreisel per wxPython-Dialog bearbeiten (fuer eine bestimmte Entity-ID). + + Wird von KreiselEdit und SSG_BLOCKEDIT aufgerufen. + """ + bref = Db.BlockReference(ent_id, Db.OpenMode.kForRead) + attribs = read_block_attribs(bref) + base_point = bref.position() + rotation = math.degrees(bref.rotation()) + + if "ABSTAND" not in attribs: + print("\nKein Kreisel-Block! (Attribut ABSTAND fehlt)") + return + + # Fehlende Attribute mit Defaults auffuellen + for tag, default in KREISEL_ATTRIB_DEFS: + if tag not in attribs: + attribs[tag] = default + + # HOEHE aus Z-Koordinate + attribs["HOEHE"] = f"{base_point.z:.1f}" if base_point.z > 0 else attribs.get("HOEHE", "0") + + print(f"\n-> Bestehend: Abstand={attribs.get('ABSTAND')}" + f" Drehung={rotation:.1f}" + f" Art={attribs.get('KREISELART')}") + + # wxPython-Dialog + dlg = KreiselEditDialog(None, attribs, rotation) + result = dlg.ShowModal() + + if result == wx.ID_OK: + new_values = dlg.get_values() + dlg.Destroy() + + attribs["NAME"] = new_values["name"] + attribs["HOEHE"] = new_values["hoehe"] + attribs["KREISELART"] = new_values["kreiselart"] + attribs["DREHRICHTUNG"] = new_values["drehrichtung"] + + new_abstand = float(new_values["abstand"]) + new_rotation = new_values["rotation"] + + # Alten Block loeschen + bref.upgradeOpen() + bref.erase() + + # Neu zeichnen + draw_module(db, + Ge.Point3d(base_point.x, base_point.y, float(new_values["hoehe"])), + new_abstand, new_rotation, attribs) + + print(f"\nKreisel aktualisiert: Name={new_values['name']}" + f" Abstand={new_abstand:.0f} Hoehe={new_values['hoehe']}" + f" Rotation={new_rotation:.1f}") + else: + dlg.Destroy() + print("\nAbgebrochen.") + + +@Rx.command("KreiselEdit") +def cmd_kreisel_edit(): + """KreiselEdit: Bestehenden Kreisel per wxPython-Dialog bearbeiten.""" + ed = Ed.Editor() + db = Db.HostApplicationServices().workingDatabase() + + res = ed.entSel("\nKreisel-Block auswaehlen: ") + if res[0] != Ed.PromptStatus.eOk: + return + ent_id = res[1] + + ent = Db.Entity(ent_id, Db.OpenMode.kForRead) + if not ent.isKindOf(Db.BlockReference.desc()): + print("\nKein Block ausgewaehlt!") + return + + kreisel_edit_entity(db, ent_id) + + +@Rx.command("KreiselParams") +def cmd_kreisel_params(): + """KreiselParams: Aktuelle Parameter anzeigen.""" + print("\n=== Kreisel Parameter ===") + print(f"\nKreiselart: {_kreisel_typ}") + print(f"\nDurchmesser: {KREISEL_DURCHMESSER:.0f}") + print(f"\nDefault-Laenge: {KREISEL_DEFAULT_LAENGE:.0f}") + print(f"\nDefault-Hoehe: {KREISEL_DEFAULT_HOEHE:.0f}") + print("\nAttribute im Block:") + for tag, default in KREISEL_ATTRIB_DEFS: + print(f"\n {tag} = {default}") + print("\n=========================\n") + + +@Rx.command("KreiselLabelSetup") +def cmd_kreisel_label_setup(): + """KreiselLabelSetup: Beschriftungs-Einstellungen aendern.""" + global _beschriftung_hoehe, _beschriftung_farbe, _beschriftung_abstand_oben + ed = Ed.Editor() + + res = ed.getDouble(f"\nSchrifthoehe <{_beschriftung_hoehe:.0f}>: ") + if res[0] == Ed.PromptStatus.eOk: + _beschriftung_hoehe = res[1] + + res = ed.getInteger(f"\nSchriftfarbe (1-7) <{_beschriftung_farbe}>: ") + if res[0] == Ed.PromptStatus.eOk: + _beschriftung_farbe = res[1] + + res = ed.getDouble(f"\nAbstand ueber Kreisel-Mitte <{_beschriftung_abstand_oben:.0f}>: ") + if res[0] == Ed.PromptStatus.eOk: + _beschriftung_abstand_oben = res[1] + + print(f"\nNeue Einstellungen: Hoehe={_beschriftung_hoehe:.0f}" + f", Farbe={_beschriftung_farbe}" + f", Y-Versatz={_beschriftung_abstand_oben:.0f}" + f", Schriftart=Arial") + + +@Rx.command("KreiselLabelPos") +def cmd_kreisel_label_pos(): + """KreiselLabelPos: Beschriftungsposition aendern.""" + global _beschriftung_abstand_x, _beschriftung_abstand_y + ed = Ed.Editor() + + res = ed.getDouble(f"\nX-Abstand von AN8-Mitte <{_beschriftung_abstand_x:.0f}>: ") + if res[0] == Ed.PromptStatus.eOk: + _beschriftung_abstand_x = res[1] + + res = ed.getDouble(f"\nY-Abstand von AN8-Mitte <{_beschriftung_abstand_y:.0f}>: ") + if res[0] == Ed.PromptStatus.eOk: + _beschriftung_abstand_y = res[1] + + print(f"\nNeue Beschriftungsposition: X={_beschriftung_abstand_x:.0f}" + f" Y={_beschriftung_abstand_y:.0f}") + + +@Rx.command("KreiselLabelHoehe") +def cmd_kreisel_label_hoehe(): + """KreiselLabelHoehe: Texthoehe aendern.""" + global _beschriftung_hoehe + ed = Ed.Editor() + + res = ed.getDouble(f"\nTexthoehe <{_beschriftung_hoehe:.0f}>: ") + if res[0] == Ed.PromptStatus.eOk: + _beschriftung_hoehe = res[1] + + print(f"\nNeue Texthoehe: {_beschriftung_hoehe:.0f}") + + +# ============================================================ +# Interne Helfer fuer Befehle +# ============================================================ + +def _get_hoehe(ed, pt): + """Hoehe aus 3D-Punkt oder manuell abfragen.""" + if pt.z > 0: + print(f"\n-> Hoehe aus 3D-Punkt uebernommen: {pt.z:.0f}") + return pt.z + res = ed.getDouble(f"\nHoehe (Z-Koordinate) <{KREISEL_DEFAULT_HOEHE:.0f}>: ") + if res[0] == Ed.PromptStatus.eOk: + return res[1] + return KREISEL_DEFAULT_HOEHE + + +def _ask_kreiselart(ed): + """Kreiselart (PIN/STANDARD) abfragen.""" + global _kreisel_typ + default_str = "2" if _kreisel_typ == "PIN" else "1" + print(f"\n[1] STANDARD (ohne PIN)") + print(f"\n[2] PIN") + res = ed.getString(f"\nBitte waehlen <{default_str}>: ") + if res[0] == Ed.PromptStatus.eOk and res[1] == "2": + _kreisel_typ = "PIN" + else: + _kreisel_typ = "STANDARD" + return _kreisel_typ + + +# ============================================================ +# wxPython-Dialog: Ersetzt kreisel_edit.dcl +# ============================================================ + +class KreiselEditDialog(wx.Dialog): + """Dialog zum Bearbeiten eines bestehenden Kreisels. + Ersetzt den DCL-Dialog kreisel_edit.dcl.""" + + def __init__(self, parent, attribs, rotation): + super().__init__(parent, title="Kreisel bearbeiten", + size=(380, 300), + style=wx.DEFAULT_DIALOG_STYLE) + + panel = wx.Panel(self) + sizer = wx.BoxSizer(wx.VERTICAL) + + # Name + row_name = wx.BoxSizer(wx.HORIZONTAL) + row_name.Add(wx.StaticText(panel, label="Kreiselname:"), 0, + wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 5) + self.txt_name = wx.TextCtrl(panel, value=attribs.get("NAME", ""), size=(200, -1)) + row_name.Add(self.txt_name, 1) + sizer.Add(row_name, 0, wx.EXPAND | wx.ALL, 5) + + # Abstand + Hoehe + row_ah = wx.BoxSizer(wx.HORIZONTAL) + row_ah.Add(wx.StaticText(panel, label="Abstand (mm):"), 0, + wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 5) + self.txt_abstand = wx.TextCtrl(panel, value=attribs.get("ABSTAND", "2300"), size=(80, -1)) + row_ah.Add(self.txt_abstand, 0, wx.RIGHT, 15) + row_ah.Add(wx.StaticText(panel, label="Hoehe (mm):"), 0, + wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 5) + self.txt_hoehe = wx.TextCtrl(panel, value=attribs.get("HOEHE", "0"), size=(80, -1)) + row_ah.Add(self.txt_hoehe, 0) + sizer.Add(row_ah, 0, wx.EXPAND | wx.ALL, 5) + + # Ausrichtung + row_aus = wx.BoxSizer(wx.HORIZONTAL) + row_aus.Add(wx.StaticText(panel, label="Ausrichtung:"), 0, + wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 5) + choices = [label for label, _ in KREISEL_AUSRICHTUNGEN] + self.cb_ausrichtung = wx.Choice(panel, choices=choices) + self.cb_ausrichtung.SetSelection(rotation_to_idx(rotation)) + row_aus.Add(self.cb_ausrichtung, 1) + sizer.Add(row_aus, 0, wx.EXPAND | wx.ALL, 5) + + # Drehrichtung + Kreiselart + row_dk = wx.BoxSizer(wx.HORIZONTAL) + row_dk.Add(wx.StaticText(panel, label="Drehrichtung:"), 0, + wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 5) + self.cb_drehrichtung = wx.Choice(panel, choices=["UZS", "GUZ"]) + self.cb_drehrichtung.SetSelection(1 if attribs.get("DREHRICHTUNG") == "GUZ" else 0) + row_dk.Add(self.cb_drehrichtung, 0, wx.RIGHT, 15) + row_dk.Add(wx.StaticText(panel, label="Typ:"), 0, + wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 5) + self.cb_kreiselart = wx.Choice(panel, choices=["STANDARD", "PIN"]) + self.cb_kreiselart.SetSelection(1 if attribs.get("KREISELART") == "PIN" else 0) + row_dk.Add(self.cb_kreiselart, 0) + sizer.Add(row_dk, 0, wx.EXPAND | wx.ALL, 5) + + # OK / Cancel + btn_sizer = self.CreateStdDialogButtonSizer(wx.OK | wx.CANCEL) + sizer.Add(btn_sizer, 0, wx.EXPAND | wx.ALL, 10) + + panel.SetSizer(sizer) + self.Fit() + self.CenterOnScreen() + + def get_values(self): + """Dialog-Werte als dict zurueckgeben.""" + idx = self.cb_ausrichtung.GetSelection() + return { + "name": self.txt_name.GetValue(), + "abstand": self.txt_abstand.GetValue(), + "hoehe": self.txt_hoehe.GetValue(), + "rotation": idx_to_rotation(idx), + "drehrichtung": "GUZ" if self.cb_drehrichtung.GetSelection() == 1 else "UZS", + "kreiselart": "PIN" if self.cb_kreiselart.GetSelection() == 1 else "STANDARD", + } diff --git a/lib/elemente/omniflo.py b/lib/elemente/omniflo.py new file mode 100644 index 0000000..ddb0cee --- /dev/null +++ b/lib/elemente/omniflo.py @@ -0,0 +1,400 @@ +# omniflo.py +# Omniflo Kern-Modul: JSON-Daten, DXF-Insert, Aluprofil, TEF-Dummies +# Ersetzt den Kern von OmniModulInsert.lsp +# ============================================ + +import os +import math +from datetime import datetime + +import PyRx as Rx +import PyGe as Ge +import PyDb as Db +import PyEd as Ed + +from elemente.utils import ( + get_data_path, + load_json_data, + get_block_path, + read_block_attribs, + ensure_block_from_dxf, +) + +# ============================================================ +# JSON-Daten Cache +# ============================================================ + +_boegen_data = None +_weichen_data = None + + +def load_boegen(): + """omniflo_boegen.json laden und cachen.""" + global _boegen_data + if _boegen_data is not None: + return _boegen_data + _boegen_data = load_json_data("json/omniflo_boegen.json") + print(f"\n[OMNI] {len(_boegen_data)} Boegen geladen.") + return _boegen_data + + +def load_weichen(): + """omniflo_weichen.json laden und cachen.""" + global _weichen_data + if _weichen_data is not None: + return _weichen_data + _weichen_data = load_json_data("json/omniflo_weichen.json") + print(f"\n[OMNI] {len(_weichen_data)} Weichen geladen.") + return _weichen_data + + +def filter_by(data, key, value): + """Eintraege nach key/value filtern.""" + return [e for e in data if e.get(key) == value] + + +def get_by_sivasnr(data, sivasnr): + """Eintrag anhand Sivasnr suchen.""" + s = str(sivasnr) + for e in data: + if str(e.get("Sivasnr", "")) == s: + return e + return None + + +def sivasnr_to_str(val): + """SivasNr-Wert als String zurueckgeben.""" + if val is None: + return "" + if isinstance(val, float): + return str(int(val)) + return str(val) + + +# ============================================================ +# DXF-Block einfuegen +# ============================================================ + +def insert_dxf_block(sivasnr_str): + """DXF aus data/omniflo/ als Block einfuegen. + + User waehlt Einfuegepunkt und Drehwinkel. + Rueckgabe: Einfuegepunkt oder None bei Abbruch. + """ + dp = get_data_path() + if not dp: + return None + + dxf_path = f"{dp}/omniflo/{sivasnr_str}.dxf" + ed = Ed.Editor() + + res_pt = ed.getPoint("\nEinfuegepunkt waehlen: ") + if res_pt[0] != Ed.PromptStatus.eOk: + print("\n[OMNI] Abgebrochen.") + return None + pt = res_pt[1] + + res_ang = ed.getAngle("\nDrehwinkel <0>: ", pt) + angle_rad = res_ang[1] if res_ang[0] == Ed.PromptStatus.eOk else 0.0 + + db = Db.HostApplicationServices().workingDatabase() + + # DXF als Block-Definition laden + if not ensure_block_from_dxf(db, dxf_path, sivasnr_str): + return None + + # Block einfuegen + bt = Db.BlockTable(db.blockTableId(), Db.OpenMode.kForRead) + block_id = bt.getAt(sivasnr_str) + model = Db.BlockTableRecord(db.modelSpaceId(), Db.OpenMode.kForWrite) + + bref = Db.BlockReference(pt, block_id) + bref.setRotation(angle_rad) + model.appendAcDbEntity(bref) + + print(f"\n[OMNI] {sivasnr_str} eingefuegt.") + return pt + + +def insert_from_entry(eintrag): + """Eintrag aus Dialog-Ergebnis einfuegen (DXF anhand Sivasnr).""" + if not eintrag: + return None + sivasnr_str = sivasnr_to_str(eintrag.get("Sivasnr")) + profil = eintrag.get("ProfilTyp", "?") + print(f"\n[OMNI] Einfuegen: {profil} ({sivasnr_str})") + return insert_dxf_block(sivasnr_str) + + +# ============================================================ +# Aluprofil-Fahrstrecke einfuegen +# ============================================================ + +def insert_aluprofil(blockname, laengemax): + """Aluprofil-Fahrstrecke einfuegen (AP60/AP110/APG110). + + Ablauf: + 1. Quell-Block (.dwg) laden und Attribute lesen + 2. Nutzer waehlt Einfuegepunkt -> Text wird platziert + 3. Nutzer waehlt Endpunkt -> Laengenvalidierung + 4. Compound-Block aus Linie + ATTDEFs wird erzeugt + 5. Text wird gedreht und oberhalb der Linie positioniert + """ + ed = Ed.Editor() + db = Db.HostApplicationServices().workingDatabase() + block_path = get_block_path() + + text_height = 100.0 + text_gap = 20.0 + + # Quell-Block laden und Attribute lesen + dwg_path = block_path + blockname + ".dwg" + if not os.path.isfile(dwg_path): + print(f"\n[OMNI] FEHLER: {dwg_path} nicht gefunden!") + return + + src_block_name = f"_SRC_{blockname}" + bt = Db.BlockTable(db.blockTableId(), Db.OpenMode.kForRead) + if not bt.has(src_block_name): + aux_db = Db.Database(False, True) + aux_db.readDwg(dwg_path) + db.insert(aux_db, src_block_name) + + # Attribute aus Quell-Block lesen + src_attribs = {} + bt = Db.BlockTable(db.blockTableId(), Db.OpenMode.kForRead) + if bt.has(src_block_name): + btr = Db.BlockTableRecord(bt.getAt(src_block_name), Db.OpenMode.kForRead) + for ent_id in btr: + ent = Db.Entity(ent_id, Db.OpenMode.kForRead) + if ent.isKindOf(Db.AttributeDefinition.desc()): + attdef = Db.AttributeDefinition(ent_id, Db.OpenMode.kForRead) + src_attribs[attdef.tag()] = attdef.textString() + + # LAENGEMAX aus Quell-Attribut ueberschreiben falls vorhanden + if "LAENGEMAX" in src_attribs and src_attribs["LAENGEMAX"]: + try: + laengemax = float(src_attribs["LAENGEMAX"]) + except ValueError: + pass + + # 1. Einfuegepunkt + res_pt = ed.getPoint("\nEinfuegepunkt waehlen: ") + if res_pt[0] != Ed.PromptStatus.eOk: + print("\n[OMNI] Abgebrochen.") + return + pt = res_pt[1] + z_val = pt.z if pt.z != 0 else 0.0 + + # Text-Entity am Einfuegepunkt erzeugen + model = Db.BlockTableRecord(db.modelSpaceId(), Db.OpenMode.kForWrite) + text_ent = Db.Text() + text_ent.setPosition(Ge.Point3d(pt.x, pt.y, z_val)) + text_ent.setHeight(text_height) + text_ent.setTextString(blockname) + text_ent.setRotation(0.0) + model.appendAcDbEntity(text_ent) + + # 2. Endpunkt-Schleife mit Laengenvalidierung + while True: + res_end = ed.getPoint("\nEndpunkt der Fahrstrecke waehlen: ", pt) + if res_end[0] != Ed.PromptStatus.eOk: + text_ent.upgradeOpen() + text_ent.erase() + print("\n[OMNI] Abgebrochen.") + return + + pt2 = res_end[1] + dx = pt2.x - pt.x + dy = pt2.y - pt.y + laenge = math.sqrt(dx * dx + dy * dy) + + if laenge > laengemax: + print(f"\n[OMNI] Fahrstreckenlaenge {laenge:.0f} mm ueberschreitet " + f"Maximum von {laengemax:.0f} mm. Bitte neu definieren!") + continue + + angle = math.atan2(dy, dx) + laenge_str = f"{laenge:.0f}" + + # 3. Compound-Block erzeugen: Linie + Attribute + timestamp = datetime.now().strftime("%Y%m%d%H%M%S") + bname = f"{blockname}_{timestamp}" + + bt = Db.BlockTable(db.blockTableId(), Db.OpenMode.kForRead) + if bt.has(bname): + bname = f"{bname}_2" + + bt.upgradeOpen() + new_btr = Db.BlockTableRecord() + new_btr.setName(bname) + bt.add(new_btr) + + # Linie von (0,0) bis (laenge, 0) + line = Db.Line(Ge.Point3d(0, 0, 0), Ge.Point3d(laenge, 0, 0)) + new_btr.appendAcDbEntity(line) + + # Attribute aus Quell-Block als ATTDEFs + y_pos = 0.0 + attdef_height = 50.0 + for tag, default in src_attribs.items(): + attdef = Db.AttributeDefinition() + attdef.setPosition(Ge.Point3d(0, y_pos, 0)) + attdef.setHeight(attdef_height) + attdef.setTextString(default) + attdef.setPrompt(tag) + attdef.setTag(tag) + attdef.setInvisible(True) + new_btr.appendAcDbEntity(attdef) + y_pos -= attdef_height * 2.0 + + # Block einfuegen + block_id = bt.getAt(bname) + bref = Db.BlockReference(pt, block_id) + bref.setRotation(angle) + model.appendAcDbEntity(bref) + + # Attribute setzen: Quell-Werte + LAENGE/A aktualisieren + attrib_values = dict(src_attribs) + attrib_values["LAENGE"] = laenge_str + attrib_values["A"] = laenge_str + + btr_def = Db.BlockTableRecord(block_id, Db.OpenMode.kForRead) + for ent_id in btr_def: + ent = Db.Entity(ent_id, Db.OpenMode.kForRead) + if ent.isKindOf(Db.AttributeDefinition.desc()): + ad = Db.AttributeDefinition(ent_id, Db.OpenMode.kForRead) + attref = Db.AttributeReference() + attref.setPropertiesFrom(ad) + attref.setTag(ad.tag()) + attref.setInvisible(ad.isInvisible()) + attref.setPosition(ad.position()) + attref.setHeight(ad.height()) + val = attrib_values.get(ad.tag(), ad.textString()) + attref.setTextString(val) + bref.appendAttribute(attref) + + # 4. Text aktualisieren: oberhalb der Linie positionieren + perp_x = -math.sin(angle) * text_gap + perp_y = math.cos(angle) * text_gap + text_pt = Ge.Point3d(pt.x + perp_x, pt.y + perp_y, z_val) + + text_ent.upgradeOpen() + text_ent.setPosition(text_pt) + text_ent.setRotation(angle) + text_ent.setTextString(f"{blockname} L={laenge_str}") + + print(f"\n[OMNI] {bname} eingefuegt. Laenge={laenge_str} mm") + break + + +# ============================================================ +# Aluprofil-Commands +# ============================================================ + +@Rx.command("OMNI_AP60") +def cmd_omni_ap60(): + insert_aluprofil("AP60", 7000.0) + +@Rx.command("OMNI_AP110") +def cmd_omni_ap110(): + insert_aluprofil("AP110", 6000.0) + +@Rx.command("OMNI_APG110") +def cmd_omni_apg110(): + insert_aluprofil("APG110", 6000.0) + + +# ============================================================ +# TEF / KettenFoerderer Dummy-Commands +# ============================================================ + +@Rx.command("OMNI_TEF_UmlenkR") +def cmd_tef_umlenkr(): + print("\n[DUMMY] Umlenkspannst. rechts fuer TEF links") + +@Rx.command("OMNI_TEF_UmlenkL") +def cmd_tef_umlenkl(): + print("\n[DUMMY] Umlenkspannst. links fuer TEF rechts") + +@Rx.command("OMNI_TEF_AntriebL") +def cmd_tef_antriebl(): + print("\n[DUMMY] Antriebst. links fuer TEF links") + +@Rx.command("OMNI_TEF_AntriebR") +def cmd_tef_antriebr(): + print("\n[DUMMY] Antriebst. rechts fuer TEF rechts") + +@Rx.command("OMNI_TEF_Gerade") +def cmd_tef_gerade(): + print("\n[DUMMY] TEF Gerade") + +@Rx.command("OMNI_KettenFoerderer") +def cmd_kettenfoerderer(): + print("\n[DUMMY] KettenFoerderer") + + +# ============================================================ +# Info-Commands +# ============================================================ + +@Rx.command("OMNI_LOAD") +def cmd_omni_load(): + """Daten neu laden (Cache leeren).""" + global _boegen_data, _weichen_data + _boegen_data = None + _weichen_data = None + load_boegen() + load_weichen() + +@Rx.command("OMNI_INFO_BOGEN") +def cmd_omni_info_bogen(): + """Bogen-Info anhand SivasId anzeigen.""" + ed = Ed.Editor() + res = ed.getString("\nSivasId des Bogens eingeben: ") + if res[0] != Ed.PromptStatus.eOk or not res[1]: + print("\nAbgebrochen.") + return + sid = res[1] + try: + sid_val = int(sid) + except ValueError: + sid_val = sid + eintrag = get_by_sivasnr(load_boegen(), sid_val) + if eintrag: + print(f"\n ProfilTyp: {eintrag.get('ProfilTyp', '?')}") + print(f" Radius: {eintrag.get('Radius', '?')}") + print(f" KurvenWinkel: {eintrag.get('KurvenWinkel', '?')}") + print(f" Breite: {eintrag.get('Breite', '?')}") + print(f" Laenge: {eintrag.get('Länge', '?')}") + print(f" Antriebsart: {eintrag.get('Antriebsart', '?')}") + else: + print(f"\nKein Bogen mit SivasId '{sid}' gefunden.") + +@Rx.command("OMNI_INFO_WEICHE") +def cmd_omni_info_weiche(): + """Weichen-Info anhand SivasId anzeigen.""" + ed = Ed.Editor() + res = ed.getString("\nSivasId der Weiche eingeben: ") + if res[0] != Ed.PromptStatus.eOk or not res[1]: + print("\nAbgebrochen.") + return + sid = res[1] + try: + sid_val = int(sid) + except ValueError: + sid_val = sid + eintrag = get_by_sivasnr(load_weichen(), sid_val) + if eintrag: + print(f"\n ProfilTyp: {eintrag.get('ProfilTyp', '?')}") + print(f" WeichenTyp: {eintrag.get('WeichenTyp', '?')}") + print(f" KurvenWinkel: {eintrag.get('KurvenWinkel', '?')}") + print(f" Schaltungstyp: {eintrag.get('Schaltungstyp', '?')}") + print(f" Breite: {eintrag.get('Breite', '?')}") + print(f" Laenge: {eintrag.get('Länge', '?')}") + print(f" KurvenRichtung: {eintrag.get('KurvenRichtung', '?')}") + wkl = eintrag.get("WeichenkörperLänge") + if wkl: + print(f" WK-Laenge: {wkl}") + else: + print(f"\nKeine Weiche mit SivasId '{sid}' gefunden.") diff --git a/lib/elemente/omniflo_boegen.py b/lib/elemente/omniflo_boegen.py new file mode 100644 index 0000000..45d1ad9 --- /dev/null +++ b/lib/elemente/omniflo_boegen.py @@ -0,0 +1,161 @@ +# omniflo_boegen.py +# Omniflo Bogen-Auswahl-Dialog (wx) und Bogen-Commands +# Ersetzt die Bogen-Teile von OmniModulInsert.lsp + omniflo_boegen.dcl +# ============================================ + +import wx + +import PyRx as Rx +import PyDb as Db +import PyEd as Ed + +from elemente.omniflo import ( + load_boegen, + filter_by, + insert_from_entry, + sivasnr_to_str, +) + + +# ============================================================ +# Bogen-Auswahl-Dialog (ersetzt omniflo_boegen.dcl) +# ============================================================ + +class BogenSelectDialog(wx.Dialog): + """Bogen-Auswahl-Dialog. + + Layout: + - Dropdown: Bogenart (ProfilTyp-Liste) + - Detail-Felder (readonly): SivasNr, Winkel, Radius, Breite, Laenge + - OK / Abbrechen + """ + + def __init__(self, parent, boegen_liste): + super().__init__(parent, title="Omniflo Bogen auswaehlen", + size=(460, 260), + style=wx.DEFAULT_DIALOG_STYLE) + self._boegen = boegen_liste + self._selected_idx = 0 + + panel = wx.Panel(self) + sizer = wx.BoxSizer(wx.VERTICAL) + + # Dropdown: Bogenart + row_art = wx.BoxSizer(wx.HORIZONTAL) + row_art.Add(wx.StaticText(panel, label="Bogenart:"), 0, + wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 5) + profil_list = [e.get("ProfilTyp", "?") for e in boegen_liste] + self.cb_bogenart = wx.Choice(panel, choices=profil_list) + self.cb_bogenart.SetSelection(0) + self.cb_bogenart.Bind(wx.EVT_CHOICE, self._on_selection) + row_art.Add(self.cb_bogenart, 1) + sizer.Add(row_art, 0, wx.EXPAND | wx.ALL, 5) + + # Detail-Felder (2 Spalten) + grid = wx.FlexGridSizer(cols=4, hgap=10, vgap=5) + grid.AddGrowableCol(1) + grid.AddGrowableCol(3) + + grid.Add(wx.StaticText(panel, label="SivasNr:"), 0, wx.ALIGN_CENTER_VERTICAL) + self.txt_sivasnr = wx.TextCtrl(panel, style=wx.TE_READONLY, size=(140, -1)) + grid.Add(self.txt_sivasnr, 0) + + grid.Add(wx.StaticText(panel, label="Winkel:"), 0, wx.ALIGN_CENTER_VERTICAL) + self.txt_winkel = wx.TextCtrl(panel, style=wx.TE_READONLY, size=(100, -1)) + grid.Add(self.txt_winkel, 0) + + grid.Add(wx.StaticText(panel, label="Radius:"), 0, wx.ALIGN_CENTER_VERTICAL) + self.txt_radius = wx.TextCtrl(panel, style=wx.TE_READONLY, size=(100, -1)) + grid.Add(self.txt_radius, 0) + + grid.Add(wx.StaticText(panel, label="Breite:"), 0, wx.ALIGN_CENTER_VERTICAL) + self.txt_breite = wx.TextCtrl(panel, style=wx.TE_READONLY, size=(100, -1)) + grid.Add(self.txt_breite, 0) + + grid.Add(wx.StaticText(panel, label="Laenge:"), 0, wx.ALIGN_CENTER_VERTICAL) + self.txt_laenge = wx.TextCtrl(panel, style=wx.TE_READONLY, size=(100, -1)) + grid.Add(self.txt_laenge, 0) + + sizer.Add(grid, 0, wx.EXPAND | wx.ALL, 5) + + # OK / Cancel + btn_sizer = self.CreateStdDialogButtonSizer(wx.OK | wx.CANCEL) + sizer.Add(btn_sizer, 0, wx.EXPAND | wx.ALL, 10) + + panel.SetSizer(sizer) + self.Fit() + self.CenterOnScreen() + + # Startwerte + self._update_details(0) + + def _on_selection(self, event): + self._selected_idx = self.cb_bogenart.GetSelection() + self._update_details(self._selected_idx) + + def _update_details(self, idx): + if idx < 0 or idx >= len(self._boegen): + return + e = self._boegen[idx] + self.txt_sivasnr.SetValue(sivasnr_to_str(e.get("Sivasnr"))) + wkl = e.get("KurvenWinkel", 0) + self.txt_winkel.SetValue(f"{wkl} Grad") + self.txt_radius.SetValue(str(e.get("Radius", "?"))) + self.txt_breite.SetValue(str(e.get("Breite", "?"))) + self.txt_laenge.SetValue(str(e.get("Länge", "?"))) + + def get_selected(self): + """Gewaehlten Bogen-Eintrag zurueckgeben.""" + if 0 <= self._selected_idx < len(self._boegen): + return self._boegen[self._selected_idx] + return None + + +# ============================================================ +# Generische Bogen-Einfuege-Logik +# ============================================================ + +def _bogen_command(winkel): + """Bogen-Dialog oeffnen, Auswahl einfuegen.""" + boegen = filter_by(load_boegen(), "KurvenWinkel", winkel) + if not boegen: + wkl_str = f"{winkel}" if isinstance(winkel, int) else f"{winkel:.1f}" + print(f"\nKeine Boegen fuer {wkl_str} Grad gefunden.") + return + + dlg = BogenSelectDialog(None, boegen) + if dlg.ShowModal() == wx.ID_OK: + eintrag = dlg.get_selected() + dlg.Destroy() + if eintrag: + insert_from_entry(eintrag) + else: + print("\n[BOGEN] Kein Eintrag ausgewaehlt.") + else: + dlg.Destroy() + print("\n[BOGEN] Abgebrochen.") + + +# ============================================================ +# Bogen-Commands (gleiche Namen wie LISP) +# ============================================================ + +@Rx.command("OMNI_APB_630_90") +def cmd_bogen_90(): + _bogen_command(90) + +@Rx.command("OMNI_APB_550_675") +def cmd_bogen_675(): + _bogen_command(67.5) + +@Rx.command("OMNI_APB_630_45") +def cmd_bogen_45(): + _bogen_command(45) + +@Rx.command("OMNI_APB_550_225") +def cmd_bogen_225(): + _bogen_command(22.5) + +@Rx.command("OMNI_APB_650_180") +def cmd_bogen_180(): + _bogen_command(180) diff --git a/lib/elemente/omniflo_weichen.py b/lib/elemente/omniflo_weichen.py new file mode 100644 index 0000000..848eab8 --- /dev/null +++ b/lib/elemente/omniflo_weichen.py @@ -0,0 +1,267 @@ +# omniflo_weichen.py +# Omniflo Weichen-Auswahl-Dialog (wx) und Weichen-Commands +# Ersetzt die Weichen-Teile von OmniModulInsert.lsp + omniflo_weichen.dcl +# ============================================ + +import wx + +import PyRx as Rx +import PyDb as Db +import PyEd as Ed + +from elemente.omniflo import ( + load_weichen, + filter_by, + insert_from_entry, + sivasnr_to_str, +) + + +# ============================================================ +# Weichen-Auswahl-Dialog (ersetzt omniflo_weichen.dcl) +# ============================================================ + +class WeichenSelectDialog(wx.Dialog): + """Weichen-Auswahl-Dialog mit Filtern. + + Layout: + - Dropdown: ProfilTyp (gefiltert) + - Filter-Gruppe Schaltungstyp: Alle / M / P (wx.RadioBox) + - Filter-Gruppe Richtung: Alle / Links / Rechts (wx.RadioBox) + - Detail-Felder (readonly): SivasNr, Winkel, Breite, Laenge + - OK / Abbrechen + """ + + def __init__(self, parent, basis_liste): + super().__init__(parent, title="Omniflo Weiche auswaehlen", + size=(500, 320), + style=wx.DEFAULT_DIALOG_STYLE) + self._basis = basis_liste + self._gefiltert = list(basis_liste) + self._selected_idx = 0 + + panel = wx.Panel(self) + sizer = wx.BoxSizer(wx.VERTICAL) + + # Dropdown: ProfilTyp + row_profil = wx.BoxSizer(wx.HORIZONTAL) + row_profil.Add(wx.StaticText(panel, label="Profiltyp:"), 0, + wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 5) + self.cb_profil = wx.Choice(panel, choices=[]) + self.cb_profil.Bind(wx.EVT_CHOICE, self._on_selection) + row_profil.Add(self.cb_profil, 1) + sizer.Add(row_profil, 0, wx.EXPAND | wx.ALL, 5) + + # Filter-Gruppe: Schaltungstyp + Richtung nebeneinander + filter_sizer = wx.BoxSizer(wx.HORIZONTAL) + + self.rb_schaltung = wx.RadioBox(panel, label="Schaltungstyp", + choices=["Alle", "M", "P"], + majorDimension=1, + style=wx.RA_SPECIFY_COLS) + self.rb_schaltung.Bind(wx.EVT_RADIOBOX, self._apply_filter) + filter_sizer.Add(self.rb_schaltung, 1, wx.EXPAND | wx.RIGHT, 5) + + self.rb_richtung = wx.RadioBox(panel, label="Richtung", + choices=["Alle", "Links", "Rechts"], + majorDimension=1, + style=wx.RA_SPECIFY_COLS) + self.rb_richtung.Bind(wx.EVT_RADIOBOX, self._apply_filter) + filter_sizer.Add(self.rb_richtung, 1, wx.EXPAND) + + sizer.Add(filter_sizer, 0, wx.EXPAND | wx.ALL, 5) + + # Detail-Felder (2 Spalten) + grid = wx.FlexGridSizer(cols=4, hgap=10, vgap=5) + grid.AddGrowableCol(1) + grid.AddGrowableCol(3) + + grid.Add(wx.StaticText(panel, label="SivasNr:"), 0, wx.ALIGN_CENTER_VERTICAL) + self.txt_sivasnr = wx.TextCtrl(panel, style=wx.TE_READONLY, size=(140, -1)) + grid.Add(self.txt_sivasnr, 0) + + grid.Add(wx.StaticText(panel, label="Winkel:"), 0, wx.ALIGN_CENTER_VERTICAL) + self.txt_winkel = wx.TextCtrl(panel, style=wx.TE_READONLY, size=(100, -1)) + grid.Add(self.txt_winkel, 0) + + grid.Add(wx.StaticText(panel, label="Breite:"), 0, wx.ALIGN_CENTER_VERTICAL) + self.txt_breite = wx.TextCtrl(panel, style=wx.TE_READONLY, size=(100, -1)) + grid.Add(self.txt_breite, 0) + + grid.Add(wx.StaticText(panel, label="Laenge:"), 0, wx.ALIGN_CENTER_VERTICAL) + self.txt_laenge = wx.TextCtrl(panel, style=wx.TE_READONLY, size=(100, -1)) + grid.Add(self.txt_laenge, 0) + + sizer.Add(grid, 0, wx.EXPAND | wx.ALL, 5) + + # OK / Cancel + btn_sizer = self.CreateStdDialogButtonSizer(wx.OK | wx.CANCEL) + sizer.Add(btn_sizer, 0, wx.EXPAND | wx.ALL, 10) + + panel.SetSizer(sizer) + self.Fit() + self.CenterOnScreen() + + # Initiale Befuellung + self._refresh_dropdown() + + def _apply_filter(self, event=None): + """Filter anwenden und Dropdown neu befuellen.""" + self._refresh_dropdown() + + def _refresh_dropdown(self): + """Dropdown mit gefilterter Liste neu fuellen.""" + sch_idx = self.rb_schaltung.GetSelection() + ri_idx = self.rb_richtung.GetSelection() + + # Schaltungstyp-Filter + sch_filter = {0: None, 1: "M", 2: "P"}.get(sch_idx) + # Richtungs-Filter (1=links, 2=rechts) + ri_filter = {0: None, 1: 1, 2: 2}.get(ri_idx) + + self._gefiltert = [] + for e in self._basis: + if sch_filter and e.get("Schaltungstyp") != sch_filter: + continue + if ri_filter and e.get("KurvenRichtung") != ri_filter: + continue + self._gefiltert.append(e) + + # Dropdown neu befuellen + self.cb_profil.Clear() + for e in self._gefiltert: + self.cb_profil.Append(e.get("ProfilTyp", "?")) + + if self._gefiltert: + self.cb_profil.SetSelection(0) + self._selected_idx = 0 + self._update_details(0) + else: + self._selected_idx = -1 + self.txt_sivasnr.SetValue("---") + self.txt_winkel.SetValue("---") + self.txt_breite.SetValue("---") + self.txt_laenge.SetValue("---") + + def _on_selection(self, event): + self._selected_idx = self.cb_profil.GetSelection() + self._update_details(self._selected_idx) + + def _update_details(self, idx): + if idx < 0 or idx >= len(self._gefiltert): + return + e = self._gefiltert[idx] + self.txt_sivasnr.SetValue(sivasnr_to_str(e.get("Sivasnr"))) + wkl = e.get("KurvenWinkel", 0) + self.txt_winkel.SetValue(f"{wkl} Grad") + self.txt_breite.SetValue(str(e.get("Breite", "?"))) + self.txt_laenge.SetValue(str(e.get("Länge", "?"))) + + def get_selected(self): + """Gewaehlten Weichen-Eintrag zurueckgeben.""" + if 0 <= self._selected_idx < len(self._gefiltert): + return self._gefiltert[self._selected_idx] + return None + + +# ============================================================ +# Generische Weichen-Einfuege-Logik +# ============================================================ + +def _weichen_command(winkel, weichentyp): + """Weichen-Dialog oeffnen, Auswahl einfuegen.""" + alle = load_weichen() + + # Nach Winkel filtern (None = alle Winkel) + if winkel is not None: + basis = filter_by(alle, "KurvenWinkel", winkel) + else: + basis = list(alle) + + # Nach WeichenTyp filtern + if weichentyp: + basis = [e for e in basis if e.get("WeichenTyp") == weichentyp] + + if not basis: + wkl_str = "alle Winkel" if winkel is None else f"{winkel} Grad" + typ_str = f" / {weichentyp}" if weichentyp else "" + print(f"\nKeine Weichen fuer {wkl_str}{typ_str} gefunden.") + return + + dlg = WeichenSelectDialog(None, basis) + if dlg.ShowModal() == wx.ID_OK: + eintrag = dlg.get_selected() + dlg.Destroy() + if eintrag: + insert_from_entry(eintrag) + else: + print("\n[WEICHE] Kein Eintrag ausgewaehlt.") + else: + dlg.Destroy() + print("\n[WEICHE] Abgebrochen.") + + +# ============================================================ +# Weichen-Commands (gleiche Namen wie LISP) +# ============================================================ + +# --- 90 Grad --- +@Rx.command("OMNI_W90_Einfach") +def cmd_w90_einfach(): + _weichen_command(90, "Einzelweiche") + +@Rx.command("OMNI_W90_Doppel") +def cmd_w90_doppel(): + _weichen_command(90, "Doppelweiche") + +@Rx.command("OMNI_W90_Dreiwege") +def cmd_w90_dreiwege(): + _weichen_command(90, "Dreiwegeweiche") + +# --- 45 Grad --- +@Rx.command("OMNI_W45_Einfach") +def cmd_w45_einfach(): + _weichen_command(45, "Einzelweiche") + +@Rx.command("OMNI_W45_Doppel") +def cmd_w45_doppel(): + _weichen_command(45, "Doppelweiche") + +@Rx.command("OMNI_W45_Dreiwege") +def cmd_w45_dreiwege(): + _weichen_command(45, "Dreiwegeweiche") + +# --- Parallel (KurvenWinkel=0) --- +@Rx.command("OMNI_WP_Einfach") +def cmd_wp_einfach(): + _weichen_command(0, "Einzelweiche") + +@Rx.command("OMNI_WP_Doppel") +def cmd_wp_doppel(): + _weichen_command(0, "Doppelweiche") + +@Rx.command("OMNI_WP_Dreiwege") +def cmd_wp_dreiwege(): + _weichen_command(0, "Dreiwegeweiche") + +# --- Weichenkoerper (KurvenWinkel=22.5) --- +@Rx.command("OMNI_WK_Einfach") +def cmd_wk_einfach(): + _weichen_command(22.5, "Einzelweiche") + +@Rx.command("OMNI_WK_Doppel") +def cmd_wk_doppel(): + _weichen_command(22.5, "Doppelweiche") + +@Rx.command("OMNI_WK_Dreiwege") +def cmd_wk_dreiwege(): + _weichen_command(22.5, "Dreiwegeweiche") + +# --- Weichenkombinationen --- +@Rx.command("OMNI_WKomb_Delta") +def cmd_wkomb_delta(): + _weichen_command(None, "Deltaweiche") + +@Rx.command("OMNI_WKomb_Star") +def cmd_wkomb_star(): + _weichen_command(None, "Sternweiche") diff --git a/lib/elemente/utils.py b/lib/elemente/utils.py new file mode 100644 index 0000000..b1e297a --- /dev/null +++ b/lib/elemente/utils.py @@ -0,0 +1,245 @@ +# elemente/utils.py +# Gemeinsame Hilfsfunktionen fuer alle ILS-Elemente (Kreisel, Eckrad, etc.) +# ============================================ + +import os +import json +import math + +import PyDb as Db +import PyGe as Ge + +# Globale Zaehler pro Komponententyp (pro Sitzung) +_letzte_nummern = {} + + +def get_data_path(): + """Daten-Pfad aus Umgebungsvariable DXFM_DATA lesen.""" + dp = os.environ.get("DXFM_DATA", "") + if not dp: + print("\nFEHLER: DXFM_DATA nicht gesetzt!") + return None + return dp.replace("\\", "/").rstrip("/") + + +def load_json_data(rel_path): + """JSON-Datei relativ zum DXFM_DATA-Pfad laden. + + Parameter: + rel_path - relativer Pfad, z.B. "json/omniflo_boegen.json" + Rueckgabe: geparste Daten oder leere Liste bei Fehler + """ + dp = get_data_path() + if not dp: + return [] + path = f"{dp}/{rel_path}" + try: + with open(path, "r", encoding="utf-8") as f: + return json.load(f) + except (FileNotFoundError, json.JSONDecodeError) as e: + print(f"\nFEHLER beim Laden von {path}: {e}") + return [] + + +def get_block_path(): + """Block-Pfad aus Umgebungsvariable DXFM_BLOCKS lesen.""" + bp = os.environ.get("DXFM_BLOCKS", "") + if not bp: + bp = "C:/Users/y.wang/Documents/dxfmakros/Blocks/" + print(f"\nUmgebungsvariable DXFM_BLOCKS nicht gefunden. Nutze Standardpfad: {bp}") + bp = bp.replace("\\", "/").rstrip("/") + "/" + return bp + + +def component_next_number(db, prefix): + """Naechste freie Nummer fuer einen Komponententyp ermitteln. + + Scannt alle INSERT-Entities im Modelspace deren Blockname mit + `prefix` beginnt (z.B. "ECKRAD_", "KREISEL_") und liest das + NUMMER-Attribut aus. Gibt die naechste freie Nummer zurueck. + + Entspricht component-next-number aus KreiselInsert.lsp. + + Parameter: + db - aktuelle Datenbank + prefix - Blockname-Praefix, z.B. "ECKRAD_" oder "KREISEL_" + Rueckgabe: naechste freie Nummer (int) + """ + global _letzte_nummern + prefix_upper = prefix.upper() + max_nr = 0 + + model = Db.BlockTableRecord(db.modelSpaceId(), Db.OpenMode.kForRead) + for obj_id in model: + ent = Db.Entity(obj_id, Db.OpenMode.kForRead) + if not ent.isKindOf(Db.BlockReference.desc()): + continue + bref = Db.BlockReference(obj_id, Db.OpenMode.kForRead) + btr = Db.BlockTableRecord(bref.blockTableRecord(), Db.OpenMode.kForRead) + bname = btr.getName() + if not bname.upper().startswith(prefix_upper): + continue + for att_id in bref.attributeIds(): + att = Db.AttributeReference(att_id, Db.OpenMode.kForRead) + if att.tag().upper() == "NUMMER": + try: + val = int(att.textString()) + if val > max_nr: + max_nr = val + except ValueError: + pass + + if max_nr == 0: + max_nr = _letzte_nummern.get(prefix_upper, 0) + + _letzte_nummern[prefix_upper] = max_nr + 1 + return _letzte_nummern[prefix_upper] + + +def merge_attribs(overrides, attrib_defs): + """Attribut-Defaults mit Ueberschreibungen zusammenfuehren. + + Parameter: + overrides - dict mit Ueberschreibungen (darf None sein) + attrib_defs - Liste von (TAG, DEFAULT) Tupeln + Rueckgabe: dict mit allen Tags + """ + result = {tag: default for tag, default in attrib_defs} + if overrides: + result.update(overrides) + return result + + +def read_block_attribs(bref): + """Alle Attribute einer BlockReference als dict lesen.""" + attribs = {} + for att_id in bref.attributeIds(): + att = Db.AttributeReference(att_id, Db.OpenMode.kForRead) + attribs[att.tag()] = att.textString() + return attribs + + +def ensure_layer(db, layer_name, color_index, set_active=False): + """Layer anlegen falls nicht vorhanden, optional aktiv setzen.""" + lt = Db.LayerTable(db.layerTableId(), Db.OpenMode.kForRead) + if not lt.has(layer_name): + lt.upgradeOpen() + lr = Db.LayerTableRecord() + lr.setName(layer_name) + c = Db.Color() + c.setColorIndex(int(color_index)) + lr.setColor(c) + lt.add(lr) + if set_active: + layer_id = lt.getAt(layer_name) + db.setClayer(layer_id) + + +def ensure_textstyle(db, style_name="Kreisel_Arial", font="Arial"): + """Textstil anlegen falls nicht vorhanden.""" + tst = Db.TextStyleTable(db.textStyleTableId(), Db.OpenMode.kForRead) + if not tst.has(style_name): + tst.upgradeOpen() + ts = Db.TextStyleTableRecord() + ts.setName(style_name) + ts.setFont(font, False, False, 0, 0) + tst.add(ts) + return style_name + + +def ensure_block_from_dwg(db, dwg_name, block_path=None): + """Externen DWG-Block in die Datenbank laden falls nicht vorhanden.""" + if block_path is None: + block_path = get_block_path() + bt = Db.BlockTable(db.blockTableId(), Db.OpenMode.kForRead) + base_name = os.path.splitext(dwg_name)[0] + if not bt.has(base_name): + dwg_path = block_path + dwg_name + if not os.path.isfile(dwg_path): + print(f"\nFehler: {dwg_name} nicht gefunden unter: {dwg_path}") + return False + aux_db = Db.Database(False, True) + aux_db.readDwg(dwg_path) + db.insert(aux_db, base_name) + return True + + +def ensure_block_from_dxf(db, dxf_path, block_name=None): + """DXF-Datei als Block-Definition laden falls nicht vorhanden. + + Parameter: + db - aktuelle Datenbank + dxf_path - absoluter Pfad zur DXF-Datei + block_name - Name der Block-Definition (default: Dateiname ohne .dxf) + Rueckgabe: True wenn erfolgreich, False bei Fehler + """ + if block_name is None: + block_name = os.path.splitext(os.path.basename(dxf_path))[0] + bt = Db.BlockTable(db.blockTableId(), Db.OpenMode.kForRead) + if not bt.has(block_name): + if not os.path.isfile(dxf_path): + print(f"\nFehler: DXF nicht gefunden: {dxf_path}") + return False + aux_db = Db.Database(False, True) + aux_db.dxfIn(dxf_path) + db.insert(aux_db, block_name) + return True + + +def create_attrib_defs(btr, attrib_defs, text_height=50.0): + """Unsichtbare Attribut-Definitionen in einer BlockTableRecord erzeugen. + + Parameter: + btr - Db.BlockTableRecord (kForWrite) + attrib_defs - Liste von (TAG, DEFAULT) Tupeln + text_height - Texthoehe der ATTDEFs + """ + y_pos = 0.0 + for tag, default in attrib_defs: + attdef = Db.AttributeDefinition() + attdef.setPosition(Ge.Point3d(0, y_pos, 0)) + attdef.setHeight(text_height) + attdef.setTextString(default) + attdef.setPrompt(tag) + attdef.setTag(tag) + attdef.setInvisible(True) + btr.appendAcDbEntity(attdef) + y_pos -= text_height * 2.0 + + +def insert_block_with_attribs(db, bname, insert_point, rotation_deg, attrib_values): + """Block einfuegen und Attribute aus dict setzen. + + Parameter: + db - aktuelle Datenbank + bname - Name der Blockdefinition + insert_point - Ge.Point3d + rotation_deg - Drehwinkel in Grad + attrib_values - dict {TAG: Wert} + Rueckgabe: ObjectId der eingefuegten BlockReference + """ + bt = Db.BlockTable(db.blockTableId(), Db.OpenMode.kForRead) + block_id = bt.getAt(bname) + model = Db.BlockTableRecord(db.modelSpaceId(), Db.OpenMode.kForWrite) + + bref = Db.BlockReference(insert_point, block_id) + bref.setRotation(math.radians(rotation_deg)) + model.appendAcDbEntity(bref) + + # Attribute aus Block-Definition erzeugen und Werte setzen + btr = Db.BlockTableRecord(block_id, Db.OpenMode.kForRead) + for ent_id in btr: + ent = Db.Entity(ent_id, Db.OpenMode.kForRead) + if ent.isKindOf(Db.AttributeDefinition.desc()): + attdef = Db.AttributeDefinition(ent_id, Db.OpenMode.kForRead) + attref = Db.AttributeReference() + attref.setPropertiesFrom(attdef) + attref.setTag(attdef.tag()) + attref.setInvisible(attdef.isInvisible()) + attref.setPosition(attdef.position()) + attref.setHeight(attdef.height()) + val = attrib_values.get(attdef.tag(), attdef.textString()) + attref.setTextString(val) + bref.appendAttribute(attref) + + return bref.objectId() diff --git a/lib/utils.py b/lib/omniflo_utils.py similarity index 100% rename from lib/utils.py rename to lib/omniflo_utils.py diff --git a/lib/set_einfuegepkt.py b/lib/set_einfuegepkt.py index 6830692..64fb8ee 100644 --- a/lib/set_einfuegepkt.py +++ b/lib/set_einfuegepkt.py @@ -31,7 +31,7 @@ import sys import ezdxf -from utils import ( +from omniflo_utils import ( ROW_GROUPS, TEXT_HEIGHT, TEXT_MARGIN, CROSS_SIZE, ROW_LABEL_WIDTH, SWITCH_FILTERS, build_row_layout, import_element_as_block, draw_cross, diff --git a/lib/set_koords.py b/lib/set_koords.py index 43c6eef..2102386 100644 --- a/lib/set_koords.py +++ b/lib/set_koords.py @@ -53,7 +53,7 @@ import sys import ezdxf from ezdxf.math import Matrix44 -from utils import ( +from omniflo_utils import ( ROW_GROUPS, TEXT_HEIGHT, TEXT_MARGIN, CROSS_SIZE, ROW_LABEL_WIDTH, SWITCH_FILTERS, load_omniflo_data, build_row_layout, import_element_as_block, diff --git a/menu/SSG_LIB.mnu b/menu/SSG_LIB.mnu index f64b2a8..1a8e159 100644 --- a/menu/SSG_LIB.mnu +++ b/menu/SSG_LIB.mnu @@ -80,3 +80,7 @@ [Call Python]^C^CCALLPYTHON [Export Sivas]^C^CEXPORTSIVAS [<-Export CSV]^C^CEXPORTCSV + +***POP501 +[SSG_LIB Edit Context] +[SSG Bearbeiten]^C^CSSG_BLOCKEDIT diff --git a/requirements.txt b/requirements.txt index bc55c26..969f874 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,9 @@ # Python-Abhaengigkeiten # Installieren mit: pip install -r requirements.txt +# PyRx - Python fuer AutoCAD/BricsCAD (in-process API) +cad-pyrx >= 1.2.0 + # Beispiel-Abhaengigkeiten – anpassen nach Bedarf: # pydantic >= 2.0.0 # pytest >= 9.0.0