246 lines
8.2 KiB
Python
246 lines
8.2 KiB
Python
# elemente/utils.py
|
|
# Gemeinsame Hilfsfunktionen fuer alle ILS-Elemente (Kreisel, Eckrad, etc.)
|
|
# ============================================
|
|
|
|
import os
|
|
import json
|
|
import math
|
|
|
|
import PyDb as Db
|
|
import PyGe as Ge
|
|
|
|
# Globale Zaehler pro Komponententyp (pro Sitzung)
|
|
_letzte_nummern = {}
|
|
|
|
|
|
def get_data_path():
|
|
"""Daten-Pfad aus Umgebungsvariable DXFM_DATA lesen."""
|
|
dp = os.environ.get("DXFM_DATA", "")
|
|
if not dp:
|
|
print("\nFEHLER: DXFM_DATA nicht gesetzt!")
|
|
return None
|
|
return dp.replace("\\", "/").rstrip("/")
|
|
|
|
|
|
def load_json_data(rel_path):
|
|
"""JSON-Datei relativ zum DXFM_DATA-Pfad laden.
|
|
|
|
Parameter:
|
|
rel_path - relativer Pfad, z.B. "json/omniflo_boegen.json"
|
|
Rueckgabe: geparste Daten oder leere Liste bei Fehler
|
|
"""
|
|
dp = get_data_path()
|
|
if not dp:
|
|
return []
|
|
path = f"{dp}/{rel_path}"
|
|
try:
|
|
with open(path, "r", encoding="utf-8") as f:
|
|
return json.load(f)
|
|
except (FileNotFoundError, json.JSONDecodeError) as e:
|
|
print(f"\nFEHLER beim Laden von {path}: {e}")
|
|
return []
|
|
|
|
|
|
def get_block_path():
|
|
"""Block-Pfad aus Umgebungsvariable DXFM_BLOCKS lesen."""
|
|
bp = os.environ.get("DXFM_BLOCKS", "")
|
|
if not bp:
|
|
bp = "C:/Users/y.wang/Documents/dxfmakros/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 ensure_block_from_dxf(db, dxf_path, block_name=None):
|
|
"""DXF-Datei als Block-Definition laden falls nicht vorhanden.
|
|
|
|
Parameter:
|
|
db - aktuelle Datenbank
|
|
dxf_path - absoluter Pfad zur DXF-Datei
|
|
block_name - Name der Block-Definition (default: Dateiname ohne .dxf)
|
|
Rueckgabe: True wenn erfolgreich, False bei Fehler
|
|
"""
|
|
if block_name is None:
|
|
block_name = os.path.splitext(os.path.basename(dxf_path))[0]
|
|
bt = Db.BlockTable(db.blockTableId(), Db.OpenMode.kForRead)
|
|
if not bt.has(block_name):
|
|
if not os.path.isfile(dxf_path):
|
|
print(f"\nFehler: DXF nicht gefunden: {dxf_path}")
|
|
return False
|
|
aux_db = Db.Database(False, True)
|
|
aux_db.dxfIn(dxf_path)
|
|
db.insert(aux_db, block_name)
|
|
return True
|
|
|
|
|
|
def create_attrib_defs(btr, attrib_defs, text_height=50.0):
|
|
"""Unsichtbare Attribut-Definitionen in einer BlockTableRecord erzeugen.
|
|
|
|
Parameter:
|
|
btr - Db.BlockTableRecord (kForWrite)
|
|
attrib_defs - Liste von (TAG, DEFAULT) Tupeln
|
|
text_height - Texthoehe der ATTDEFs
|
|
"""
|
|
y_pos = 0.0
|
|
for tag, default in attrib_defs:
|
|
attdef = Db.AttributeDefinition()
|
|
attdef.setPosition(Ge.Point3d(0, y_pos, 0))
|
|
attdef.setHeight(text_height)
|
|
attdef.setTextString(default)
|
|
attdef.setPrompt(tag)
|
|
attdef.setTag(tag)
|
|
attdef.setInvisible(True)
|
|
btr.appendAcDbEntity(attdef)
|
|
y_pos -= text_height * 2.0
|
|
|
|
|
|
def insert_block_with_attribs(db, bname, insert_point, rotation_deg, attrib_values):
|
|
"""Block einfuegen und Attribute aus dict setzen.
|
|
|
|
Parameter:
|
|
db - aktuelle Datenbank
|
|
bname - Name der Blockdefinition
|
|
insert_point - Ge.Point3d
|
|
rotation_deg - Drehwinkel in Grad
|
|
attrib_values - dict {TAG: Wert}
|
|
Rueckgabe: ObjectId der eingefuegten BlockReference
|
|
"""
|
|
bt = Db.BlockTable(db.blockTableId(), Db.OpenMode.kForRead)
|
|
block_id = bt.getAt(bname)
|
|
model = Db.BlockTableRecord(db.modelSpaceId(), Db.OpenMode.kForWrite)
|
|
|
|
bref = Db.BlockReference(insert_point, block_id)
|
|
bref.setRotation(math.radians(rotation_deg))
|
|
model.appendAcDbEntity(bref)
|
|
|
|
# Attribute aus Block-Definition erzeugen und Werte setzen
|
|
btr = Db.BlockTableRecord(block_id, Db.OpenMode.kForRead)
|
|
for ent_id in btr:
|
|
ent = Db.Entity(ent_id, Db.OpenMode.kForRead)
|
|
if ent.isKindOf(Db.AttributeDefinition.desc()):
|
|
attdef = Db.AttributeDefinition(ent_id, Db.OpenMode.kForRead)
|
|
attref = Db.AttributeReference()
|
|
attref.setPropertiesFrom(attdef)
|
|
attref.setTag(attdef.tag())
|
|
attref.setInvisible(attdef.isInvisible())
|
|
attref.setPosition(attdef.position())
|
|
attref.setHeight(attdef.height())
|
|
val = attrib_values.get(attdef.tag(), attdef.textString())
|
|
attref.setTextString(val)
|
|
bref.appendAttribute(attref)
|
|
|
|
return bref.objectId()
|