""" 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'}")