From c30d2c51b909ee8c600537fe3436ab3c8e1c3512 Mon Sep 17 00:00:00 2001 From: Michael Stangl Date: Mon, 1 Jun 2026 10:22:24 +0200 Subject: [PATCH] =?UTF-8?q?Python=20Routinen=20mit=20PyRx=20als=20Ersatz?= =?UTF-8?q?=20f=C3=BCr=20die=20.lsp=20Routinen=20verwendet.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bin/on_start.lsp | 13 + lib/elemente/__init__.py | 0 lib/elemente/eckrad.py | 180 +++++++++ lib/elemente/kreisel.py | 801 +++++++++++++++++++++++++++++++++++++++ lib/elemente/utils.py | 194 ++++++++++ requirements.txt | 3 + 6 files changed, 1191 insertions(+) create mode 100644 lib/elemente/__init__.py create mode 100644 lib/elemente/eckrad.py create mode 100644 lib/elemente/kreisel.py create mode 100644 lib/elemente/utils.py diff --git a/bin/on_start.lsp b/bin/on_start.lsp index 2336836..62081bd 100644 --- a/bin/on_start.lsp +++ b/bin/on_start.lsp @@ -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.") ) ) diff --git a/lib/elemente/__init__.py b/lib/elemente/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/elemente/eckrad.py b/lib/elemente/eckrad.py new file mode 100644 index 0000000..ba18ee4 --- /dev/null +++ b/lib/elemente/eckrad.py @@ -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) diff --git a/lib/elemente/kreisel.py b/lib/elemente/kreisel.py new file mode 100644 index 0000000..c3bb170 --- /dev/null +++ b/lib/elemente/kreisel.py @@ -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", + } diff --git a/lib/elemente/utils.py b/lib/elemente/utils.py new file mode 100644 index 0000000..f923ef5 --- /dev/null +++ b/lib/elemente/utils.py @@ -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() diff --git a/requirements.txt b/requirements.txt index bc55c26..969f874 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,9 @@ # Python-Abhaengigkeiten # Installieren mit: pip install -r requirements.txt +# PyRx - Python fuer AutoCAD/BricsCAD (in-process API) +cad-pyrx >= 1.2.0 + # Beispiel-Abhaengigkeiten – anpassen nach Bedarf: # pydantic >= 2.0.0 # pytest >= 9.0.0