# 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", }