Pyrx Startup skripte und python Element entfernt, da Schwierigkeiten mit Kompabilitäten
This commit is contained in:
+16
-130
@@ -23,106 +23,6 @@
|
|||||||
(if (car (atoms-family 1 (list fname))) "JA" "NEIN")
|
(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
|
;; 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_BTMT_Beladung: " (ssg-dbg-fn "C:ILS_BTMT_BELADUNG")))
|
||||||
(dbgmsg (strcat "c:ILS_K90LA: " (ssg-dbg-fn "C:ILS_K90LA")))
|
(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 (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 "c:KreiselInsert: " (ssg-dbg-fn "C:KREISELINSERT")))
|
||||||
(dbgmsg (strcat "KreiselInsert: " (ssg-dbg-fn "C:KREISELINSERT")))
|
(dbgmsg (strcat "c:KreiselEdit: " (ssg-dbg-fn "C:KREISELEDIT")))
|
||||||
(dbgmsg (strcat "KreiselEdit: " (ssg-dbg-fn "C:KREISELEDIT")))
|
(dbgmsg (strcat "c:ILS_Eckrad: " (ssg-dbg-fn "C:ILS_ECKRAD")))
|
||||||
(dbgmsg (strcat "EckradEdit: " (ssg-dbg-fn "C:ECKRADEDIT")))
|
(dbgmsg (strcat "c:OMNI_AP60: " (ssg-dbg-fn "C:OMNI_AP60")))
|
||||||
(dbgmsg (strcat "SSG_BLOCKEDIT: " (ssg-dbg-fn "C:SSG_BLOCKEDIT")))
|
(dbgmsg (strcat "c:OMNI_APB_630_90: " (ssg-dbg-fn "C:OMNI_APB_630_90")))
|
||||||
(dbgmsg (strcat "OMNI_AP60: " (ssg-dbg-fn "C:OMNI_AP60")))
|
(dbgmsg (strcat "c:OMNI_W90_Einfach: " (ssg-dbg-fn "C:OMNI_W90_EINFACH")))
|
||||||
(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")))
|
|
||||||
(dbgflush)
|
(dbgflush)
|
||||||
)
|
)
|
||||||
|
|
||||||
(defun ssg-lib-startup ( / lisp-pfad menu-pfad curr-srch menu-datei
|
(defun ssg-lib-startup ( / lisp-pfad menu-pfad curr-srch menu-datei lsp-fb-pfad)
|
||||||
acadver-str ver-punkt ver-major lsp-fb-pfad)
|
|
||||||
|
|
||||||
;; Pfade aus Umgebungsvariablen lesen
|
;; Pfade aus Umgebungsvariablen lesen
|
||||||
(setq lisp-pfad (getenv "DXFM_LISP"))
|
(setq lisp-pfad (getenv "DXFM_LISP"))
|
||||||
@@ -213,28 +110,17 @@
|
|||||||
;; DBLCLKEDIT deaktivieren (Block-Editor nicht bei Doppelklick oeffnen)
|
;; DBLCLKEDIT deaktivieren (Block-Editor nicht bei Doppelklick oeffnen)
|
||||||
(setvar "DBLCLKEDIT" 0)
|
(setvar "DBLCLKEDIT" 0)
|
||||||
|
|
||||||
;; (ssg-lib-load-pyrx menu-pfad) ;; deaktiviert - PyRx/BricsCAD V25.3 Inkompatibilitaet
|
;; LSP-Erweiterungen laden (KreiselInsert, OmniModulInsert)
|
||||||
|
(foreach lsp-fb (list "KreiselInsert" "OmniModulInsert")
|
||||||
;; Fallback: LSP-Routinen laden wenn PyRx nicht aktiv
|
(setq lsp-fb-pfad (strcat lisp-pfad "/" lsp-fb ".lsp"))
|
||||||
(setq acadver-str (getvar "ACADVER"))
|
(if (findfile lsp-fb-pfad)
|
||||||
(setq ver-punkt (vl-string-search "." acadver-str))
|
(progn
|
||||||
(setq ver-major (if ver-punkt (substr acadver-str 1 ver-punkt) "25"))
|
(ssg-lib-safe-load lsp-fb-pfad)
|
||||||
(if (not (member (strcat "rxloaderv" ver-major ".0.brx") (arx)))
|
(dbgmsg (strcat "[LOAD] geladen: " lsp-fb ".lsp"))
|
||||||
(progn
|
(princ (strcat "\n[SSG_LIB] Geladen: " lsp-fb ".lsp"))
|
||||||
(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"))
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
(dbgmsg (strcat "[LOAD] nicht gefunden: " lsp-fb ".lsp"))
|
||||||
)
|
)
|
||||||
(dbgmsg "[FALLBACK] PyRx aktiv - kein LSP-Fallback noetig")
|
|
||||||
)
|
)
|
||||||
(dbgflush)
|
(dbgflush)
|
||||||
|
|
||||||
|
|||||||
@@ -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")
|
|
||||||
@@ -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)
|
|
||||||
@@ -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",
|
|
||||||
}
|
|
||||||
@@ -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.")
|
|
||||||
@@ -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)
|
|
||||||
@@ -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")
|
|
||||||
@@ -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()
|
|
||||||
Reference in New Issue
Block a user