Files
dxfmakros/lib/elemente/omniflo.py
T

401 lines
13 KiB
Python

# omniflo.py
# Omniflo Kern-Modul: JSON-Daten, DXF-Insert, Aluprofil, TEF-Dummies
# Ersetzt den Kern von OmniModulInsert.lsp
# ============================================
import os
import math
from datetime import datetime
import PyRx as Rx
import PyGe as Ge
import PyDb as Db
import PyEd as Ed
from elemente.utils import (
get_data_path,
load_json_data,
get_block_path,
read_block_attribs,
ensure_block_from_dxf,
)
# ============================================================
# JSON-Daten Cache
# ============================================================
_boegen_data = None
_weichen_data = None
def load_boegen():
"""omniflo_boegen.json laden und cachen."""
global _boegen_data
if _boegen_data is not None:
return _boegen_data
_boegen_data = load_json_data("json/omniflo_boegen.json")
print(f"\n[OMNI] {len(_boegen_data)} Boegen geladen.")
return _boegen_data
def load_weichen():
"""omniflo_weichen.json laden und cachen."""
global _weichen_data
if _weichen_data is not None:
return _weichen_data
_weichen_data = load_json_data("json/omniflo_weichen.json")
print(f"\n[OMNI] {len(_weichen_data)} Weichen geladen.")
return _weichen_data
def filter_by(data, key, value):
"""Eintraege nach key/value filtern."""
return [e for e in data if e.get(key) == value]
def get_by_sivasnr(data, sivasnr):
"""Eintrag anhand Sivasnr suchen."""
s = str(sivasnr)
for e in data:
if str(e.get("Sivasnr", "")) == s:
return e
return None
def sivasnr_to_str(val):
"""SivasNr-Wert als String zurueckgeben."""
if val is None:
return ""
if isinstance(val, float):
return str(int(val))
return str(val)
# ============================================================
# DXF-Block einfuegen
# ============================================================
def insert_dxf_block(sivasnr_str):
"""DXF aus data/omniflo/ als Block einfuegen.
User waehlt Einfuegepunkt und Drehwinkel.
Rueckgabe: Einfuegepunkt oder None bei Abbruch.
"""
dp = get_data_path()
if not dp:
return None
dxf_path = f"{dp}/omniflo/{sivasnr_str}.dxf"
ed = Ed.Editor()
res_pt = ed.getPoint("\nEinfuegepunkt waehlen: ")
if res_pt[0] != Ed.PromptStatus.eOk:
print("\n[OMNI] Abgebrochen.")
return None
pt = res_pt[1]
res_ang = ed.getAngle("\nDrehwinkel <0>: ", pt)
angle_rad = res_ang[1] if res_ang[0] == Ed.PromptStatus.eOk else 0.0
db = Db.HostApplicationServices().workingDatabase()
# DXF als Block-Definition laden
if not ensure_block_from_dxf(db, dxf_path, sivasnr_str):
return None
# Block einfuegen
bt = Db.BlockTable(db.blockTableId(), Db.OpenMode.kForRead)
block_id = bt.getAt(sivasnr_str)
model = Db.BlockTableRecord(db.modelSpaceId(), Db.OpenMode.kForWrite)
bref = Db.BlockReference(pt, block_id)
bref.setRotation(angle_rad)
model.appendAcDbEntity(bref)
print(f"\n[OMNI] {sivasnr_str} eingefuegt.")
return pt
def insert_from_entry(eintrag):
"""Eintrag aus Dialog-Ergebnis einfuegen (DXF anhand Sivasnr)."""
if not eintrag:
return None
sivasnr_str = sivasnr_to_str(eintrag.get("Sivasnr"))
profil = eintrag.get("ProfilTyp", "?")
print(f"\n[OMNI] Einfuegen: {profil} ({sivasnr_str})")
return insert_dxf_block(sivasnr_str)
# ============================================================
# Aluprofil-Fahrstrecke einfuegen
# ============================================================
def insert_aluprofil(blockname, laengemax):
"""Aluprofil-Fahrstrecke einfuegen (AP60/AP110/APG110).
Ablauf:
1. Quell-Block (.dwg) laden und Attribute lesen
2. Nutzer waehlt Einfuegepunkt -> Text wird platziert
3. Nutzer waehlt Endpunkt -> Laengenvalidierung
4. Compound-Block aus Linie + ATTDEFs wird erzeugt
5. Text wird gedreht und oberhalb der Linie positioniert
"""
ed = Ed.Editor()
db = Db.HostApplicationServices().workingDatabase()
block_path = get_block_path()
text_height = 100.0
text_gap = 20.0
# Quell-Block laden und Attribute lesen
dwg_path = block_path + blockname + ".dwg"
if not os.path.isfile(dwg_path):
print(f"\n[OMNI] FEHLER: {dwg_path} nicht gefunden!")
return
src_block_name = f"_SRC_{blockname}"
bt = Db.BlockTable(db.blockTableId(), Db.OpenMode.kForRead)
if not bt.has(src_block_name):
aux_db = Db.Database(False, True)
aux_db.readDwg(dwg_path)
db.insert(aux_db, src_block_name)
# Attribute aus Quell-Block lesen
src_attribs = {}
bt = Db.BlockTable(db.blockTableId(), Db.OpenMode.kForRead)
if bt.has(src_block_name):
btr = Db.BlockTableRecord(bt.getAt(src_block_name), Db.OpenMode.kForRead)
for ent_id in btr:
ent = Db.Entity(ent_id, Db.OpenMode.kForRead)
if ent.isKindOf(Db.AttributeDefinition.desc()):
attdef = Db.AttributeDefinition(ent_id, Db.OpenMode.kForRead)
src_attribs[attdef.tag()] = attdef.textString()
# LAENGEMAX aus Quell-Attribut ueberschreiben falls vorhanden
if "LAENGEMAX" in src_attribs and src_attribs["LAENGEMAX"]:
try:
laengemax = float(src_attribs["LAENGEMAX"])
except ValueError:
pass
# 1. Einfuegepunkt
res_pt = ed.getPoint("\nEinfuegepunkt waehlen: ")
if res_pt[0] != Ed.PromptStatus.eOk:
print("\n[OMNI] Abgebrochen.")
return
pt = res_pt[1]
z_val = pt.z if pt.z != 0 else 0.0
# Text-Entity am Einfuegepunkt erzeugen
model = Db.BlockTableRecord(db.modelSpaceId(), Db.OpenMode.kForWrite)
text_ent = Db.Text()
text_ent.setPosition(Ge.Point3d(pt.x, pt.y, z_val))
text_ent.setHeight(text_height)
text_ent.setTextString(blockname)
text_ent.setRotation(0.0)
model.appendAcDbEntity(text_ent)
# 2. Endpunkt-Schleife mit Laengenvalidierung
while True:
res_end = ed.getPoint("\nEndpunkt der Fahrstrecke waehlen: ", pt)
if res_end[0] != Ed.PromptStatus.eOk:
text_ent.upgradeOpen()
text_ent.erase()
print("\n[OMNI] Abgebrochen.")
return
pt2 = res_end[1]
dx = pt2.x - pt.x
dy = pt2.y - pt.y
laenge = math.sqrt(dx * dx + dy * dy)
if laenge > laengemax:
print(f"\n[OMNI] Fahrstreckenlaenge {laenge:.0f} mm ueberschreitet "
f"Maximum von {laengemax:.0f} mm. Bitte neu definieren!")
continue
angle = math.atan2(dy, dx)
laenge_str = f"{laenge:.0f}"
# 3. Compound-Block erzeugen: Linie + Attribute
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
bname = f"{blockname}_{timestamp}"
bt = Db.BlockTable(db.blockTableId(), Db.OpenMode.kForRead)
if bt.has(bname):
bname = f"{bname}_2"
bt.upgradeOpen()
new_btr = Db.BlockTableRecord()
new_btr.setName(bname)
bt.add(new_btr)
# Linie von (0,0) bis (laenge, 0)
line = Db.Line(Ge.Point3d(0, 0, 0), Ge.Point3d(laenge, 0, 0))
new_btr.appendAcDbEntity(line)
# Attribute aus Quell-Block als ATTDEFs
y_pos = 0.0
attdef_height = 50.0
for tag, default in src_attribs.items():
attdef = Db.AttributeDefinition()
attdef.setPosition(Ge.Point3d(0, y_pos, 0))
attdef.setHeight(attdef_height)
attdef.setTextString(default)
attdef.setPrompt(tag)
attdef.setTag(tag)
attdef.setInvisible(True)
new_btr.appendAcDbEntity(attdef)
y_pos -= attdef_height * 2.0
# Block einfuegen
block_id = bt.getAt(bname)
bref = Db.BlockReference(pt, block_id)
bref.setRotation(angle)
model.appendAcDbEntity(bref)
# Attribute setzen: Quell-Werte + LAENGE/A aktualisieren
attrib_values = dict(src_attribs)
attrib_values["LAENGE"] = laenge_str
attrib_values["A"] = laenge_str
btr_def = Db.BlockTableRecord(block_id, Db.OpenMode.kForRead)
for ent_id in btr_def:
ent = Db.Entity(ent_id, Db.OpenMode.kForRead)
if ent.isKindOf(Db.AttributeDefinition.desc()):
ad = Db.AttributeDefinition(ent_id, Db.OpenMode.kForRead)
attref = Db.AttributeReference()
attref.setPropertiesFrom(ad)
attref.setTag(ad.tag())
attref.setInvisible(ad.isInvisible())
attref.setPosition(ad.position())
attref.setHeight(ad.height())
val = attrib_values.get(ad.tag(), ad.textString())
attref.setTextString(val)
bref.appendAttribute(attref)
# 4. Text aktualisieren: oberhalb der Linie positionieren
perp_x = -math.sin(angle) * text_gap
perp_y = math.cos(angle) * text_gap
text_pt = Ge.Point3d(pt.x + perp_x, pt.y + perp_y, z_val)
text_ent.upgradeOpen()
text_ent.setPosition(text_pt)
text_ent.setRotation(angle)
text_ent.setTextString(f"{blockname} L={laenge_str}")
print(f"\n[OMNI] {bname} eingefuegt. Laenge={laenge_str} mm")
break
# ============================================================
# Aluprofil-Commands
# ============================================================
@Rx.command("OMNI_AP60")
def cmd_omni_ap60():
insert_aluprofil("AP60", 7000.0)
@Rx.command("OMNI_AP110")
def cmd_omni_ap110():
insert_aluprofil("AP110", 6000.0)
@Rx.command("OMNI_APG110")
def cmd_omni_apg110():
insert_aluprofil("APG110", 6000.0)
# ============================================================
# TEF / KettenFoerderer Dummy-Commands
# ============================================================
@Rx.command("OMNI_TEF_UmlenkR")
def cmd_tef_umlenkr():
print("\n[DUMMY] Umlenkspannst. rechts fuer TEF links")
@Rx.command("OMNI_TEF_UmlenkL")
def cmd_tef_umlenkl():
print("\n[DUMMY] Umlenkspannst. links fuer TEF rechts")
@Rx.command("OMNI_TEF_AntriebL")
def cmd_tef_antriebl():
print("\n[DUMMY] Antriebst. links fuer TEF links")
@Rx.command("OMNI_TEF_AntriebR")
def cmd_tef_antriebr():
print("\n[DUMMY] Antriebst. rechts fuer TEF rechts")
@Rx.command("OMNI_TEF_Gerade")
def cmd_tef_gerade():
print("\n[DUMMY] TEF Gerade")
@Rx.command("OMNI_KettenFoerderer")
def cmd_kettenfoerderer():
print("\n[DUMMY] KettenFoerderer")
# ============================================================
# Info-Commands
# ============================================================
@Rx.command("OMNI_LOAD")
def cmd_omni_load():
"""Daten neu laden (Cache leeren)."""
global _boegen_data, _weichen_data
_boegen_data = None
_weichen_data = None
load_boegen()
load_weichen()
@Rx.command("OMNI_INFO_BOGEN")
def cmd_omni_info_bogen():
"""Bogen-Info anhand SivasId anzeigen."""
ed = Ed.Editor()
res = ed.getString("\nSivasId des Bogens eingeben: ")
if res[0] != Ed.PromptStatus.eOk or not res[1]:
print("\nAbgebrochen.")
return
sid = res[1]
try:
sid_val = int(sid)
except ValueError:
sid_val = sid
eintrag = get_by_sivasnr(load_boegen(), sid_val)
if eintrag:
print(f"\n ProfilTyp: {eintrag.get('ProfilTyp', '?')}")
print(f" Radius: {eintrag.get('Radius', '?')}")
print(f" KurvenWinkel: {eintrag.get('KurvenWinkel', '?')}")
print(f" Breite: {eintrag.get('Breite', '?')}")
print(f" Laenge: {eintrag.get('Länge', '?')}")
print(f" Antriebsart: {eintrag.get('Antriebsart', '?')}")
else:
print(f"\nKein Bogen mit SivasId '{sid}' gefunden.")
@Rx.command("OMNI_INFO_WEICHE")
def cmd_omni_info_weiche():
"""Weichen-Info anhand SivasId anzeigen."""
ed = Ed.Editor()
res = ed.getString("\nSivasId der Weiche eingeben: ")
if res[0] != Ed.PromptStatus.eOk or not res[1]:
print("\nAbgebrochen.")
return
sid = res[1]
try:
sid_val = int(sid)
except ValueError:
sid_val = sid
eintrag = get_by_sivasnr(load_weichen(), sid_val)
if eintrag:
print(f"\n ProfilTyp: {eintrag.get('ProfilTyp', '?')}")
print(f" WeichenTyp: {eintrag.get('WeichenTyp', '?')}")
print(f" KurvenWinkel: {eintrag.get('KurvenWinkel', '?')}")
print(f" Schaltungstyp: {eintrag.get('Schaltungstyp', '?')}")
print(f" Breite: {eintrag.get('Breite', '?')}")
print(f" Laenge: {eintrag.get('Länge', '?')}")
print(f" KurvenRichtung: {eintrag.get('KurvenRichtung', '?')}")
wkl = eintrag.get("WeichenkörperLänge")
if wkl:
print(f" WK-Laenge: {wkl}")
else:
print(f"\nKeine Weiche mit SivasId '{sid}' gefunden.")