From d0373d09f3b7b09fc5009461fc147f2417ddc821 Mon Sep 17 00:00:00 2001 From: Michael Stangl Date: Mon, 1 Jun 2026 11:08:19 +0200 Subject: [PATCH] =?UTF-8?q?Omniflo=20Boegen=20und=20Weichen=20auch=20durch?= =?UTF-8?q?=20.py=20erzeugen=20und=20=C3=A4ndern.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Lisp/OmniModulInsert.lsp | 1137 ------------------------------- Lisp/ssg_load.lsp | 4 +- bin/on_start.lsp | 5 +- dcl/omniflo_boegen.dcl | 54 -- dcl/omniflo_weichen.dcl | 65 -- lib/elemente/dblclick.py | 50 ++ lib/elemente/eckrad.py | 125 ++++ lib/elemente/kreisel.py | 42 +- lib/elemente/omniflo.py | 400 +++++++++++ lib/elemente/omniflo_boegen.py | 161 +++++ lib/elemente/omniflo_weichen.py | 267 ++++++++ lib/elemente/utils.py | 51 ++ menu/SSG_LIB.mnu | 4 + 13 files changed, 1088 insertions(+), 1277 deletions(-) delete mode 100644 Lisp/OmniModulInsert.lsp delete mode 100644 dcl/omniflo_boegen.dcl delete mode 100644 dcl/omniflo_weichen.dcl create mode 100644 lib/elemente/dblclick.py create mode 100644 lib/elemente/omniflo.py create mode 100644 lib/elemente/omniflo_boegen.py create mode 100644 lib/elemente/omniflo_weichen.py 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/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/bin/on_start.lsp b/bin/on_start.lsp index 62081bd..18480d9 100644 --- a/bin/on_start.lsp +++ b/bin/on_start.lsp @@ -60,9 +60,12 @@ (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") + (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")) 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/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 index ba18ee4..40bae78 100644 --- a/lib/elemente/eckrad.py +++ b/lib/elemente/eckrad.py @@ -10,11 +10,13 @@ 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, @@ -178,3 +180,126 @@ def ils_eckrad(): # 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 index 1e1d708..de124a5 100644 --- a/lib/elemente/kreisel.py +++ b/lib/elemente/kreisel.py @@ -426,23 +426,11 @@ def cmd_kreisel_quick(): print("\nSchnell Einfuegen fertig.") -@Rx.command("KreiselEdit") -def cmd_kreisel_edit(): - """KreiselEdit: Bestehenden Kreisel per wxPython-Dialog bearbeiten.""" - ed = Ed.Editor() - db = Db.HostApplicationServices().workingDatabase() - - # 1. 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 +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() @@ -464,7 +452,7 @@ def cmd_kreisel_edit(): f" Drehung={rotation:.1f}" f" Art={attribs.get('KREISELART')}") - # 2. wxPython-Dialog (ersetzt DCL) + # wxPython-Dialog dlg = KreiselEditDialog(None, attribs, rotation) result = dlg.ShowModal() @@ -472,7 +460,6 @@ def cmd_kreisel_edit(): new_values = dlg.get_values() dlg.Destroy() - # Attribute aktualisieren attribs["NAME"] = new_values["name"] attribs["HOEHE"] = new_values["hoehe"] attribs["KREISELART"] = new_values["kreiselart"] @@ -498,6 +485,25 @@ def cmd_kreisel_edit(): 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.""" 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 index f923ef5..b1e297a 100644 --- a/lib/elemente/utils.py +++ b/lib/elemente/utils.py @@ -3,6 +3,7 @@ # ============================================ import os +import json import math import PyDb as Db @@ -12,6 +13,34 @@ import PyGe as Ge _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", "") @@ -135,6 +164,28 @@ def ensure_block_from_dwg(db, dwg_name, block_path=None): 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. 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