From 22a358554dc40edfa14bb0ba65a4dfe8aa52e188 Mon Sep 17 00:00:00 2001 From: Michael Stangl Date: Wed, 10 Jun 2026 16:58:12 +0200 Subject: [PATCH] =?UTF-8?q?Pyrx=20Startup=20skripte=20und=20python=20Eleme?= =?UTF-8?q?nt=20entfernt,=20da=20Schwierigkeiten=20mit=20Kompabilit=C3=A4t?= =?UTF-8?q?en?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bin/on_start.lsp | 146 +------ lib/elemente/__init__.py | 0 lib/elemente/dblclick.py | 50 --- lib/elemente/eckrad.py | 305 -------------- lib/elemente/kreisel.py | 683 -------------------------------- lib/elemente/omniflo.py | 399 ------------------- lib/elemente/omniflo_boegen.py | 159 -------- lib/elemente/omniflo_weichen.py | 265 ------------- lib/elemente/utils.py | 245 ------------ 9 files changed, 16 insertions(+), 2236 deletions(-) delete mode 100644 lib/elemente/__init__.py delete mode 100644 lib/elemente/dblclick.py delete mode 100644 lib/elemente/eckrad.py delete mode 100644 lib/elemente/kreisel.py delete mode 100644 lib/elemente/omniflo.py delete mode 100644 lib/elemente/omniflo_boegen.py delete mode 100644 lib/elemente/omniflo_weichen.py delete mode 100644 lib/elemente/utils.py diff --git a/bin/on_start.lsp b/bin/on_start.lsp index 6609b34..8724a73 100644 --- a/bin/on_start.lsp +++ b/bin/on_start.lsp @@ -23,106 +23,6 @@ (if (car (atoms-family 1 (list fname))) "JA" "NEIN") ) -;; ------------------------------------------------------------ -;; SSG-DBG-PYRX - PyRx-Ladeprozess ins Debug-Log schreiben -;; Wird nach arxload aufgerufen, damit alle Ergebniswerte vorliegen. -;; ------------------------------------------------------------ -(defun ssg-dbg-pyrx (pyrx-pfad ver-major brx-datei brx-result / arx-name) - (dbgmsg "[PyRx] --- Umgebung ---") - (dbgmsg (strcat "PYTHONHOME: " (if (getenv "PYTHONHOME") (getenv "PYTHONHOME") "nicht gesetzt"))) - (dbgmsg (strcat "PYTHONPATH: " (if (getenv "PYTHONPATH") (getenv "PYTHONPATH") "nicht gesetzt"))) - (dbgmsg (strcat "PATH (Anfang): " (substr (getenv "PATH") 1 120))) - (dbgmsg "[PyRx] --- Eingabewerte ---") - (dbgmsg (strcat "pyrx-pfad: " pyrx-pfad)) - (dbgmsg (strcat "ver-major: " ver-major)) - (dbgmsg (strcat "brx-datei: " brx-datei)) - (dbgmsg "[PyRx] --- arxload Ergebnis ---") - (dbgmsg (strcat "arxload Rueckgabe: " (vl-princ-to-string brx-result))) - (dbgmsg (strcat "arxload Fehler? " (if (vl-catch-all-error-p brx-result) (vl-catch-all-error-message brx-result) "nein"))) - (setq arx-name (strcat "rxloaderv" ver-major ".0.brx")) - (dbgmsg (strcat "(arx) rxloader? " (if (member arx-name (arx)) "JA" "NEIN"))) - (dbgmsg (strcat "(arx) Liste: " (vl-princ-to-string (arx)))) - (dbgflush) -) - -;; ------------------------------------------------------------ -;; SSG-LIB-LOAD-PYRX - PyRx BRX laden und Python-Module via PYLOAD starten -;; Aufruf: (ssg-lib-load-pyrx menu-pfad) -;; Deaktiviert wegen PyRx/BricsCAD V25.3 Inkompatibilitaet (cad-pyrx 2.2.58.5504) -;; Reaktivieren sobald kompatible Version verfuegbar ist. -;; ------------------------------------------------------------ -(defun ssg-lib-load-pyrx (menu-pfad / acadver-str ver-punkt ver-major pyrx-pfad brx-datei brx-result) - ;; BricsCAD-Version ermitteln - (setq acadver-str (getvar "ACADVER")) - (setq ver-punkt (vl-string-search "." acadver-str)) - (setq ver-major (if ver-punkt (substr acadver-str 1 ver-punkt) "25")) - ;; Pyrx-Pfad: .venv (primaer), dann System/User Python - (setq pyrx-pfad - (cond - ((findfile (strcat menu-pfad "/.venv/Lib/site-packages/pyrx/RxLoaderV" ver-major ".0.brx")) - (strcat menu-pfad "/.venv/Lib/site-packages/pyrx")) - ((and (getenv "PYTHONHOME") - (findfile (strcat (getenv "PYTHONHOME") "/Lib/site-packages/pyrx/RxLoaderV" ver-major ".0.brx"))) - (strcat (getenv "PYTHONHOME") "/Lib/site-packages/pyrx")) - ((and (getenv "APPDATA") - (findfile (strcat (getenv "APPDATA") "/Python/Python312/site-packages/pyrx/RxLoaderV" ver-major ".0.brx"))) - (strcat (getenv "APPDATA") "/Python/Python312/site-packages/pyrx")) - ((and (getenv "LOCALAPPDATA") - (findfile (strcat (getenv "LOCALAPPDATA") "/Programs/Python/Python312/Lib/site-packages/pyrx/RxLoaderV" ver-major ".0.brx"))) - (strcat (getenv "LOCALAPPDATA") "/Programs/Python/Python312/Lib/site-packages/pyrx")) - (t nil) - ) - ) - ;; BRX laden wenn PyRx noch nicht via Startup-Suite aktiv - (if (not (member (strcat "rxloaderv" ver-major ".0.brx") (arx))) - (if pyrx-pfad - (progn - (setq brx-datei (strcat pyrx-pfad "/RxLoaderV" ver-major ".0.brx")) - (setq brx-result (vl-catch-all-apply 'arxload (list brx-datei))) - (ssg-dbg-pyrx pyrx-pfad ver-major brx-datei brx-result) - (if (vl-catch-all-error-p brx-result) - (progn - (princ (strcat "\n[SSG_LIB] FEHLER: RxLoaderV" ver-major ".0.brx nicht geladen: " - (vl-catch-all-error-message brx-result))) - (princ "\n[SSG_LIB] HINWEIS: PyRx einmalig via APPLOAD -> Startup Suite einrichten.") - ) - (princ (strcat "\n[SSG_LIB] PyRx RxLoader geladen (V" ver-major "): " brx-datei)) - ) - ) - (princ (strcat "\n[SSG_LIB] WARNUNG: RxLoaderV" ver-major ".0.brx nicht gefunden." - "\n PYTHONHOME=" (if (getenv "PYTHONHOME") (getenv "PYTHONHOME") "nicht gesetzt"))) - ) - (progn - (dbgmsg "[PyRx] PyRx bereits via Startup-Suite aktiv") - (princ (strcat "\n[SSG_LIB] PyRx (V" ver-major ") via Startup-Suite aktiv.")) - ) - ) - ;; Python-Module laden - nur wenn PYLOAD verfuegbar - (dbgmsg "[PYLOAD] --- Python-Module ---") - (if (not (member "PYLOAD" (atoms-family 0))) - (progn - (dbgmsg "[PYLOAD] PYLOAD nicht verfuegbar - ueberspringe alle Module") - (princ "\n[SSG_LIB] WARNUNG: PYLOAD nicht verfuegbar (PyRx nicht geladen).") - (princ "\n[SSG_LIB] Python-Befehle (KreiselInsert etc.) nicht aktiv.") - ) - (foreach pymod '("eckrad" "kreisel" "dblclick" "omniflo" "omniflo_boegen" "omniflo_weichen") - (if (findfile (strcat menu-pfad "/lib/elemente/" pymod ".py")) - (progn - (dbgmsg (strcat "PYLOAD start: " pymod ".py")) - (command "PYLOAD" (strcat menu-pfad "/lib/elemente/" pymod ".py")) - (dbgmsg (strcat "PYLOAD done: " pymod ".py")) - (princ (strcat "\n[SSG_LIB] Python-Modul " pymod ".py geladen.")) - ) - (progn - (dbgmsg (strcat "PYLOAD skip: " pymod ".py -- nicht gefunden")) - (princ (strcat "\n[SSG_LIB] HINWEIS: " pymod ".py nicht gefunden.")) - ) - ) - ) - ) - (dbgflush) -) - ;; ------------------------------------------------------------ ;; SSG-LIB-REGISTER-CHECK - Verfuegbarkeit aller Befehle ins Debug-Log schreiben ;; ------------------------------------------------------------ @@ -137,19 +37,16 @@ (dbgmsg (strcat "c:ILS_BTMT_Beladung: " (ssg-dbg-fn "C:ILS_BTMT_BELADUNG"))) (dbgmsg (strcat "c:ILS_K90LA: " (ssg-dbg-fn "C:ILS_K90LA"))) (dbgmsg (strcat "c:ILS_TEF_Strecke: " (ssg-dbg-fn "C:ILS_TEF_STRECKE"))) - (dbgmsg "[CHECK] --- PyRx Befehle (BRX-Ebene, nicht via atoms-family pruefbar) ---") - (dbgmsg (strcat "KreiselInsert: " (ssg-dbg-fn "C:KREISELINSERT"))) - (dbgmsg (strcat "KreiselEdit: " (ssg-dbg-fn "C:KREISELEDIT"))) - (dbgmsg (strcat "EckradEdit: " (ssg-dbg-fn "C:ECKRADEDIT"))) - (dbgmsg (strcat "SSG_BLOCKEDIT: " (ssg-dbg-fn "C:SSG_BLOCKEDIT"))) - (dbgmsg (strcat "OMNI_AP60: " (ssg-dbg-fn "C:OMNI_AP60"))) - (dbgmsg (strcat "OMNI_APB_630_90: " (ssg-dbg-fn "C:OMNI_APB_630_90"))) - (dbgmsg (strcat "OMNI_W90_Einfach: " (ssg-dbg-fn "C:OMNI_W90_EINFACH"))) + (dbgmsg (strcat "c:KreiselInsert: " (ssg-dbg-fn "C:KREISELINSERT"))) + (dbgmsg (strcat "c:KreiselEdit: " (ssg-dbg-fn "C:KREISELEDIT"))) + (dbgmsg (strcat "c:ILS_Eckrad: " (ssg-dbg-fn "C:ILS_ECKRAD"))) + (dbgmsg (strcat "c:OMNI_AP60: " (ssg-dbg-fn "C:OMNI_AP60"))) + (dbgmsg (strcat "c:OMNI_APB_630_90: " (ssg-dbg-fn "C:OMNI_APB_630_90"))) + (dbgmsg (strcat "c:OMNI_W90_Einfach: " (ssg-dbg-fn "C:OMNI_W90_EINFACH"))) (dbgflush) ) -(defun ssg-lib-startup ( / lisp-pfad menu-pfad curr-srch menu-datei - acadver-str ver-punkt ver-major lsp-fb-pfad) +(defun ssg-lib-startup ( / lisp-pfad menu-pfad curr-srch menu-datei lsp-fb-pfad) ;; Pfade aus Umgebungsvariablen lesen (setq lisp-pfad (getenv "DXFM_LISP")) @@ -213,28 +110,17 @@ ;; DBLCLKEDIT deaktivieren (Block-Editor nicht bei Doppelklick oeffnen) (setvar "DBLCLKEDIT" 0) - ;; (ssg-lib-load-pyrx menu-pfad) ;; deaktiviert - PyRx/BricsCAD V25.3 Inkompatibilitaet - - ;; Fallback: LSP-Routinen laden wenn PyRx nicht aktiv - (setq acadver-str (getvar "ACADVER")) - (setq ver-punkt (vl-string-search "." acadver-str)) - (setq ver-major (if ver-punkt (substr acadver-str 1 ver-punkt) "25")) - (if (not (member (strcat "rxloaderv" ver-major ".0.brx") (arx))) - (progn - (dbgmsg "[FALLBACK] PyRx nicht aktiv - lade LSP-Fallbacks") - (foreach lsp-fb (list "KreiselInsert" "OmniModulInsert") - (setq lsp-fb-pfad (strcat lisp-pfad "/" lsp-fb ".lsp")) - (if (findfile lsp-fb-pfad) - (progn - (ssg-lib-safe-load lsp-fb-pfad) - (dbgmsg (strcat "[FALLBACK] geladen: " lsp-fb ".lsp")) - (princ (strcat "\n[SSG_LIB] Fallback geladen: " lsp-fb ".lsp")) - ) - (dbgmsg (strcat "[FALLBACK] nicht gefunden: " lsp-fb ".lsp")) - ) + ;; LSP-Erweiterungen laden (KreiselInsert, OmniModulInsert) + (foreach lsp-fb (list "KreiselInsert" "OmniModulInsert") + (setq lsp-fb-pfad (strcat lisp-pfad "/" lsp-fb ".lsp")) + (if (findfile lsp-fb-pfad) + (progn + (ssg-lib-safe-load lsp-fb-pfad) + (dbgmsg (strcat "[LOAD] geladen: " lsp-fb ".lsp")) + (princ (strcat "\n[SSG_LIB] Geladen: " lsp-fb ".lsp")) ) + (dbgmsg (strcat "[LOAD] nicht gefunden: " lsp-fb ".lsp")) ) - (dbgmsg "[FALLBACK] PyRx aktiv - kein LSP-Fallback noetig") ) (dbgflush) diff --git a/lib/elemente/__init__.py b/lib/elemente/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/lib/elemente/dblclick.py b/lib/elemente/dblclick.py deleted file mode 100644 index 2abb1d0..0000000 --- a/lib/elemente/dblclick.py +++ /dev/null @@ -1,50 +0,0 @@ -# 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 deleted file mode 100644 index 40bae78..0000000 --- a/lib/elemente/eckrad.py +++ /dev/null @@ -1,305 +0,0 @@ -# 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 deleted file mode 100644 index de124a5..0000000 --- a/lib/elemente/kreisel.py +++ /dev/null @@ -1,683 +0,0 @@ -# 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 deleted file mode 100644 index 95be3c0..0000000 --- a/lib/elemente/omniflo.py +++ /dev/null @@ -1,399 +0,0 @@ -# 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, - 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 deleted file mode 100644 index 1b1e415..0000000 --- a/lib/elemente/omniflo_boegen.py +++ /dev/null @@ -1,159 +0,0 @@ -# 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 - -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 deleted file mode 100644 index 59ff1f6..0000000 --- a/lib/elemente/omniflo_weichen.py +++ /dev/null @@ -1,265 +0,0 @@ -# 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 - -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 deleted file mode 100644 index 027f743..0000000 --- a/lib/elemente/utils.py +++ /dev/null @@ -1,245 +0,0 @@ -# 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/data/ils/" - 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()