Files
dxfmakros/lib/set_koords.py
T

219 lines
6.9 KiB
Python

"""
Erzeugt benannte Koordinatensystem-Bloecke in DXF-Dateien.
Jeder Block enthaelt drei Linien vom Ursprung:
- rot(1) X-Achse, Laenge 1
- gruen(3) Y-Achse, Laenge 2
- blau(5) Z-Achse, Laenge 3
Die 3D-Rotation erfolgt ueber die Transformation des INSERT-Entities.
"""
import math
import ezdxf
from ezdxf.math import Matrix44
# Block-Linien: (color, dx, dy, dz)
KS_LINES = [
(1, 1.0, 0.0, 0.0), # rot, X-Achse, Laenge 1
(3, 0.0, 2.0, 0.0), # gruen, Y-Achse, Laenge 2
(5, 0.0, 0.0, 3.0), # blau, Z-Achse, Laenge 3
]
def _ensure_block(doc, block_name):
"""Erzeugt den Block falls er noch nicht existiert."""
if block_name not in doc.blocks:
blk = doc.blocks.new(name=block_name)
for color, dx, dy, dz in KS_LINES:
blk.add_line(
start=(0, 0, 0),
end=(dx, dy, dz),
dxfattribs={"color": color},
)
return block_name
def _ensure_layer(doc, layer_name="_KOORDINATENSYSTEME"):
"""Stellt sicher, dass der Layer existiert."""
if layer_name not in doc.layers:
doc.layers.add(layer_name)
def insert_ks(msp, name, point, rx=0, ry=0, rz=0):
"""
Erzeugt ein benanntes Koordinatensystem als Block-Referenz.
Args:
msp: Modelspace oder Block des Ziel-Dokuments.
name: Block-Name (z.B. "K1", "K2", "K3", "K4").
point: Einfuegepunkt als (x, y, z) Tupel.
rx: Rotation um X-Achse in Grad.
ry: Rotation um Y-Achse in Grad.
rz: Rotation um Z-Achse in Grad.
Returns:
Das erzeugte INSERT-Entity.
"""
doc = msp.doc
_ensure_layer(doc)
_ensure_block(doc, name)
insert = msp.add_blockref(
name,
insert=point,
dxfattribs={"layer": "_KOORDINATENSYSTEME"},
)
# 3D-Rotation via Transformationsmatrix
m = Matrix44.chain(
Matrix44.translate(-point[0], -point[1], -point[2]),
Matrix44.x_rotate(math.radians(rx)),
Matrix44.y_rotate(math.radians(ry)),
Matrix44.z_rotate(math.radians(rz)),
Matrix44.translate(point[0], point[1], point[2]),
)
insert.transform(m)
return insert
def read_ks(doc, name):
"""
Liest ein Koordinatensystem aus einer DXF-Datei zurueck.
Bestimmt Position und Rotationswinkel (rx, ry, rz) aus den
tatsaechlichen Linienendpunkten des aufgeloesten INSERT-Blocks.
Args:
doc: ezdxf DXF-Dokument.
name: Block-Name (z.B. "K1").
Returns:
Dict mit 'name', 'point', 'rx', 'ry', 'rz' oder None.
"""
msp = doc.modelspace()
for entity in msp:
if entity.dxftype() == "INSERT" and entity.dxf.name == name:
# Linien aus Block-Referenz aufloesen (virtuelle Entities)
lines = [e for e in entity.virtual_entities() if e.dxftype() == "LINE"]
if len(lines) != 3:
return None
# Gemeinsamer Startpunkt = Einfuegepunkt
point = (lines[0].dxf.start[0], lines[0].dxf.start[1], lines[0].dxf.start[2])
# Richtungsvektoren aus den Endpunkten extrahieren
axes = {}
for line in lines:
end = line.dxf.end
dx = end[0] - point[0]
dy = end[1] - point[1]
dz = end[2] - point[2]
length = (dx**2 + dy**2 + dz**2) ** 0.5
axes[line.dxf.color] = (dx / length, dy / length, dz / length)
# X-Achse (color=1), Y-Achse (color=3), Z-Achse (color=5)
x_axis = axes.get(1, (1, 0, 0))
y_axis = axes.get(3, (0, 1, 0))
z_axis = axes.get(5, (0, 0, 1))
# Rotationswinkel aus Rotationsmatrix R = Rz * Ry * Rx
# Spalten von R: x_axis, y_axis, z_axis
# R20 = -sin(ry) = x_axis[2]
# R21 = cos(ry)*sin(rx) = y_axis[2]
# R22 = cos(ry)*cos(rx) = z_axis[2]
# R10 = sin(rz)*cos(ry) = x_axis[1]
# R00 = cos(rz)*cos(ry) = x_axis[0]
ry = math.asin(max(-1, min(1, -x_axis[2])))
cos_ry = math.cos(ry)
if abs(cos_ry) > 1e-6:
rx = math.atan2(y_axis[2] / cos_ry, z_axis[2] / cos_ry)
rz = math.atan2(x_axis[1] / cos_ry, x_axis[0] / cos_ry)
else:
# Gimbal Lock: ry = +/-90, rx und rz nicht unabhaengig
rz = 0
rx = math.atan2(-z_axis[0], y_axis[0])
return {
"name": name,
"point": point,
"rx": math.degrees(rx),
"ry": math.degrees(ry),
"rz": math.degrees(rz),
}
return None
def verify_ks(dxf_path, expected):
"""
Liest KS-Bloecke aus einer DXF-Datei und vergleicht mit Erwartungswerten.
Args:
dxf_path: Pfad zur DXF-Datei.
expected: Liste von Dicts mit 'name', 'point', 'rx', 'ry', 'rz'.
Returns:
True wenn alle Werte uebereinstimmen (Toleranz 0.01).
"""
doc = ezdxf.readfile(dxf_path)
all_ok = True
tol = 0.01
for exp in expected:
result = read_ks(doc, exp["name"])
if result is None:
print(f"FEHLER: Block '{exp['name']}' nicht gefunden")
all_ok = False
continue
errors = []
for i, axis in enumerate(("x", "y", "z")):
if abs(result["point"][i] - exp["point"][i]) > tol:
errors.append(f"{axis}={result['point'][i]:.3f} (erwartet {exp['point'][i]:.3f})")
for angle in ("rx", "ry", "rz"):
diff = abs(result[angle] - exp[angle])
if diff > 360 - tol:
diff = abs(diff - 360)
if diff > tol:
errors.append(f"{angle}={result[angle]:.2f} (erwartet {exp[angle]:.2f})")
if errors:
print(f"FEHLER {exp['name']}: {', '.join(errors)}")
all_ok = False
else:
print(f"OK {exp['name']}: point=({result['point'][0]:.2f},{result['point'][1]:.2f},{result['point'][2]:.2f}) "
f"rx={result['rx']:.2f} ry={result['ry']:.2f} rz={result['rz']:.2f}")
return all_ok
if __name__ == "__main__":
import os
doc = ezdxf.new(dxfversion="R2010")
msp = doc.modelspace()
test_cases = [
{"name": "K1", "point": (0, 0, 0), "rx": 0, "ry": 0, "rz": 0},
{"name": "K2", "point": (10, 0, 0), "rx": 0, "ry": 0, "rz": 90},
{"name": "K3", "point": (20, 0, 0), "rx": 0, "ry": 45, "rz": 0},
{"name": "K4", "point": (30, 0, 0), "rx": 30, "ry": 45, "rz": 60},
]
for tc in test_cases:
insert_ks(msp, tc["name"], tc["point"], tc["rx"], tc["ry"], tc["rz"])
results_dir = os.environ.get("DXFM_RESULTS", "results")
os.makedirs(results_dir, exist_ok=True)
out_path = os.path.join(results_dir, "ks_test.dxf")
doc.saveas(out_path)
print(f"Testdatei gespeichert: {out_path}\n")
print("Verifikation:")
ok = verify_ks(out_path, test_cases)
print(f"\nErgebnis: {'ALLE OK' if ok else 'FEHLER GEFUNDEN'}")