Python Routinen mit PyRx als Ersatz für die .lsp Routinen verwendet.
This commit is contained in:
@@ -60,6 +60,19 @@
|
||||
(princ "\n[SSG_LIB] WARNUNG: DXFMAKRO nicht gesetzt, Menue nicht geladen!")
|
||||
)
|
||||
|
||||
;; Python-Module laden (PyRx)
|
||||
(if menu-pfad
|
||||
(foreach pymod '("eckrad" "kreisel")
|
||||
(if (findfile (strcat menu-pfad "/lib/elemente/" pymod ".py"))
|
||||
(progn
|
||||
(command "PYLOAD" (strcat menu-pfad "/lib/elemente/" pymod ".py"))
|
||||
(princ (strcat "\n[SSG_LIB] Python-Modul " pymod ".py geladen."))
|
||||
)
|
||||
(princ (strcat "\n[SSG_LIB] HINWEIS: " pymod ".py nicht gefunden."))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(princ "\n[SSG_LIB] Startup abgeschlossen.")
|
||||
)
|
||||
)
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
# 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
|
||||
|
||||
from elemente.utils import (
|
||||
get_block_path,
|
||||
component_next_number,
|
||||
merge_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)
|
||||
@@ -0,0 +1,801 @@
|
||||
# kreisel.py
|
||||
# ILS Kreisel - PyRx-Implementierung
|
||||
# Ersetzt alle Kreisel-Routinen aus KreiselInsert.lsp
|
||||
# Befehle: KreiselInsert, KreiselConnect, KreiselRedraw,
|
||||
# KreiselQuick, KreiselEdit, KreiselParams,
|
||||
# KreiselLabelSetup, KreiselLabelPos, KreiselLabelHoehe
|
||||
# ============================================
|
||||
|
||||
import os
|
||||
import math
|
||||
|
||||
import PyRx as Rx
|
||||
import PyGe as Ge
|
||||
import PyDb as Db
|
||||
import PyEd as Ed
|
||||
import wx
|
||||
|
||||
# ============================================================
|
||||
# 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_letzte_nr = 0
|
||||
_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
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Hilfsfunktionen
|
||||
# ============================================================
|
||||
|
||||
def get_block_path():
|
||||
"""Block-Pfad aus Umgebungsvariable DXFM_BLOCKS lesen."""
|
||||
bp = os.environ.get("DXFM_BLOCKS", "")
|
||||
if not bp:
|
||||
bp = "C:/Users/y.wang/Documents/dxfmakros/Blocks/"
|
||||
print(f"\nUmgebungsvariable DXFM_BLOCKS nicht gefunden. Nutze Standardpfad: {bp}")
|
||||
bp = bp.replace("\\", "/").rstrip("/") + "/"
|
||||
return bp
|
||||
|
||||
|
||||
def kreisel_next_number(db):
|
||||
"""Naechste freie Kreisel-Nummer ermitteln."""
|
||||
global _kreisel_letzte_nr
|
||||
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("KREISEL_"):
|
||||
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 = _kreisel_letzte_nr
|
||||
|
||||
_kreisel_letzte_nr = max_nr + 1
|
||||
return _kreisel_letzte_nr
|
||||
|
||||
|
||||
def merge_attribs(overrides, attrib_defs):
|
||||
"""Attribut-Defaults mit Ueberschreibungen zusammenfuehren."""
|
||||
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 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]
|
||||
|
||||
|
||||
def ensure_textstyle(db, style_name="Kreisel_Arial", font="Arial"):
|
||||
"""Textstil 'Kreisel_Arial' 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_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_block_from_dwg(db, dwg_name, block_path):
|
||||
"""Externen DWG-Block in die Datenbank laden falls nicht vorhanden."""
|
||||
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
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 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 = kreisel_next_number(db)
|
||||
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
|
||||
y_pos = 0.0
|
||||
text_height = 50.0
|
||||
for tag, default in KREISEL_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)
|
||||
new_btr.appendAcDbEntity(attdef)
|
||||
y_pos -= text_height * 2.0
|
||||
|
||||
print(f"\n-> Neue Blockdefinition: {bname}")
|
||||
else:
|
||||
print(f"\n-> Verwende bestehende Blockdefinition: {bname}")
|
||||
|
||||
# Block am Zielpunkt einfuegen
|
||||
block_id = bt.getAt(bname)
|
||||
model = Db.BlockTableRecord(db.modelSpaceId(), Db.OpenMode.kForWrite)
|
||||
pt = Ge.Point3d(base_point.x, base_point.y, z_hoehe)
|
||||
bref = Db.BlockReference(pt, block_id)
|
||||
bref.setRotation(math.radians(rotation_deg))
|
||||
model.appendAcDbEntity(bref)
|
||||
|
||||
# Attribute 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 = merged.get(attdef.tag(), attdef.textString())
|
||||
attref.setTextString(val)
|
||||
bref.appendAttribute(attref)
|
||||
|
||||
print(f"\n-> Kreisel #{kreisel_nummer} eingefuegt: {bname} (Hoehe={z_hoehe:.0f})")
|
||||
return bref.objectId()
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Befehle
|
||||
# ============================================================
|
||||
|
||||
@Rx.command("KreiselInsert")
|
||||
def cmd_kreisel_insert():
|
||||
"""KreiselInsert: Manuell Position + Parameter."""
|
||||
global _kreisel_typ
|
||||
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.")
|
||||
|
||||
|
||||
@Rx.command("KreiselEdit")
|
||||
def cmd_kreisel_edit():
|
||||
"""KreiselEdit: Bestehenden Kreisel per wxPython-Dialog bearbeiten."""
|
||||
ed = Ed.Editor()
|
||||
db = Db.HostApplicationServices().workingDatabase()
|
||||
|
||||
# 1. Block auswaehlen
|
||||
res = ed.entSel("\nKreisel-Block auswaehlen: ")
|
||||
if res[0] != Ed.PromptStatus.eOk:
|
||||
return
|
||||
ent_id = res[1]
|
||||
|
||||
ent = Db.Entity(ent_id, Db.OpenMode.kForRead)
|
||||
if not ent.isKindOf(Db.BlockReference.desc()):
|
||||
print("\nKein Block ausgewaehlt!")
|
||||
return
|
||||
|
||||
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')}")
|
||||
|
||||
# 2. wxPython-Dialog (ersetzt DCL)
|
||||
dlg = KreiselEditDialog(None, attribs, rotation)
|
||||
result = dlg.ShowModal()
|
||||
|
||||
if result == wx.ID_OK:
|
||||
new_values = dlg.get_values()
|
||||
dlg.Destroy()
|
||||
|
||||
# Attribute aktualisieren
|
||||
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("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",
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
# elemente/utils.py
|
||||
# Gemeinsame Hilfsfunktionen fuer alle ILS-Elemente (Kreisel, Eckrad, etc.)
|
||||
# ============================================
|
||||
|
||||
import os
|
||||
import math
|
||||
|
||||
import PyDb as Db
|
||||
import PyGe as Ge
|
||||
|
||||
# Globale Zaehler pro Komponententyp (pro Sitzung)
|
||||
_letzte_nummern = {}
|
||||
|
||||
|
||||
def get_block_path():
|
||||
"""Block-Pfad aus Umgebungsvariable DXFM_BLOCKS lesen."""
|
||||
bp = os.environ.get("DXFM_BLOCKS", "")
|
||||
if not bp:
|
||||
bp = "C:/Users/y.wang/Documents/dxfmakros/Blocks/"
|
||||
print(f"\nUmgebungsvariable DXFM_BLOCKS nicht gefunden. Nutze Standardpfad: {bp}")
|
||||
bp = bp.replace("\\", "/").rstrip("/") + "/"
|
||||
return bp
|
||||
|
||||
|
||||
def component_next_number(db, prefix):
|
||||
"""Naechste freie Nummer fuer einen Komponententyp ermitteln.
|
||||
|
||||
Scannt alle INSERT-Entities im Modelspace deren Blockname mit
|
||||
`prefix` beginnt (z.B. "ECKRAD_", "KREISEL_") und liest das
|
||||
NUMMER-Attribut aus. Gibt die naechste freie Nummer zurueck.
|
||||
|
||||
Entspricht component-next-number aus KreiselInsert.lsp.
|
||||
|
||||
Parameter:
|
||||
db - aktuelle Datenbank
|
||||
prefix - Blockname-Praefix, z.B. "ECKRAD_" oder "KREISEL_"
|
||||
Rueckgabe: naechste freie Nummer (int)
|
||||
"""
|
||||
global _letzte_nummern
|
||||
prefix_upper = prefix.upper()
|
||||
max_nr = 0
|
||||
|
||||
model = Db.BlockTableRecord(db.modelSpaceId(), Db.OpenMode.kForRead)
|
||||
for obj_id in model:
|
||||
ent = Db.Entity(obj_id, Db.OpenMode.kForRead)
|
||||
if not ent.isKindOf(Db.BlockReference.desc()):
|
||||
continue
|
||||
bref = Db.BlockReference(obj_id, Db.OpenMode.kForRead)
|
||||
btr = Db.BlockTableRecord(bref.blockTableRecord(), Db.OpenMode.kForRead)
|
||||
bname = btr.getName()
|
||||
if not bname.upper().startswith(prefix_upper):
|
||||
continue
|
||||
for att_id in bref.attributeIds():
|
||||
att = Db.AttributeReference(att_id, Db.OpenMode.kForRead)
|
||||
if att.tag().upper() == "NUMMER":
|
||||
try:
|
||||
val = int(att.textString())
|
||||
if val > max_nr:
|
||||
max_nr = val
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if max_nr == 0:
|
||||
max_nr = _letzte_nummern.get(prefix_upper, 0)
|
||||
|
||||
_letzte_nummern[prefix_upper] = max_nr + 1
|
||||
return _letzte_nummern[prefix_upper]
|
||||
|
||||
|
||||
def merge_attribs(overrides, attrib_defs):
|
||||
"""Attribut-Defaults mit Ueberschreibungen zusammenfuehren.
|
||||
|
||||
Parameter:
|
||||
overrides - dict mit Ueberschreibungen (darf None sein)
|
||||
attrib_defs - Liste von (TAG, DEFAULT) Tupeln
|
||||
Rueckgabe: dict mit allen Tags
|
||||
"""
|
||||
result = {tag: default for tag, default in attrib_defs}
|
||||
if overrides:
|
||||
result.update(overrides)
|
||||
return result
|
||||
|
||||
|
||||
def read_block_attribs(bref):
|
||||
"""Alle Attribute einer BlockReference als dict lesen."""
|
||||
attribs = {}
|
||||
for att_id in bref.attributeIds():
|
||||
att = Db.AttributeReference(att_id, Db.OpenMode.kForRead)
|
||||
attribs[att.tag()] = att.textString()
|
||||
return attribs
|
||||
|
||||
|
||||
def ensure_layer(db, layer_name, color_index, set_active=False):
|
||||
"""Layer anlegen falls nicht vorhanden, optional aktiv setzen."""
|
||||
lt = Db.LayerTable(db.layerTableId(), Db.OpenMode.kForRead)
|
||||
if not lt.has(layer_name):
|
||||
lt.upgradeOpen()
|
||||
lr = Db.LayerTableRecord()
|
||||
lr.setName(layer_name)
|
||||
c = Db.Color()
|
||||
c.setColorIndex(int(color_index))
|
||||
lr.setColor(c)
|
||||
lt.add(lr)
|
||||
if set_active:
|
||||
layer_id = lt.getAt(layer_name)
|
||||
db.setClayer(layer_id)
|
||||
|
||||
|
||||
def ensure_textstyle(db, style_name="Kreisel_Arial", font="Arial"):
|
||||
"""Textstil anlegen falls nicht vorhanden."""
|
||||
tst = Db.TextStyleTable(db.textStyleTableId(), Db.OpenMode.kForRead)
|
||||
if not tst.has(style_name):
|
||||
tst.upgradeOpen()
|
||||
ts = Db.TextStyleTableRecord()
|
||||
ts.setName(style_name)
|
||||
ts.setFont(font, False, False, 0, 0)
|
||||
tst.add(ts)
|
||||
return style_name
|
||||
|
||||
|
||||
def ensure_block_from_dwg(db, dwg_name, block_path=None):
|
||||
"""Externen DWG-Block in die Datenbank laden falls nicht vorhanden."""
|
||||
if block_path is None:
|
||||
block_path = get_block_path()
|
||||
bt = Db.BlockTable(db.blockTableId(), Db.OpenMode.kForRead)
|
||||
base_name = os.path.splitext(dwg_name)[0]
|
||||
if not bt.has(base_name):
|
||||
dwg_path = block_path + dwg_name
|
||||
if not os.path.isfile(dwg_path):
|
||||
print(f"\nFehler: {dwg_name} nicht gefunden unter: {dwg_path}")
|
||||
return False
|
||||
aux_db = Db.Database(False, True)
|
||||
aux_db.readDwg(dwg_path)
|
||||
db.insert(aux_db, base_name)
|
||||
return True
|
||||
|
||||
|
||||
def 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()
|
||||
@@ -1,6 +1,9 @@
|
||||
# Python-Abhaengigkeiten
|
||||
# Installieren mit: pip install -r requirements.txt
|
||||
|
||||
# PyRx - Python fuer AutoCAD/BricsCAD (in-process API)
|
||||
cad-pyrx >= 1.2.0
|
||||
|
||||
# Beispiel-Abhaengigkeiten – anpassen nach Bedarf:
|
||||
# pydantic >= 2.0.0
|
||||
# pytest >= 9.0.0
|
||||
|
||||
Reference in New Issue
Block a user