From 7c662c96635c9521f74bba5b76998df060076c2f Mon Sep 17 00:00:00 2001 From: Paul Wolok Date: Mon, 8 Sep 2025 16:29:31 +0200 Subject: [PATCH] =?UTF-8?q?test=20mit=20bl=C3=B6cken=20erstellen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/dxf2lib.py | 617 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 506 insertions(+), 111 deletions(-) diff --git a/lib/dxf2lib.py b/lib/dxf2lib.py index 4f74158..e0de2e5 100644 --- a/lib/dxf2lib.py +++ b/lib/dxf2lib.py @@ -1,6 +1,6 @@ import os import ezdxf -from ezdxf.math import Vec2, BoundingBox +from ezdxf.math import Matrix44, Vec3, BoundingBox, Vec2 import math import argparse import sys @@ -11,115 +11,120 @@ from pathlib import Path import logging -def get_bbox(entities): - """ - Berechnet die Bounding Box für eine Liste von DXF-Entities. +# def get_bbox(entities, transform_matrix=None): +# """ +# Berechnet die Bounding Box für eine Liste von DXF-Entities. - Args: - entities: Liste von DXF-Entities (ezdxf entities) +# Args: +# entities: Liste von DXF-Entities (ezdxf entities) - Returns: - Vec2 or None: Zentrum der Bounding Box als Vec2-Objekt oder None, - falls keine gültige Geometrie gefunden wurde +# Returns: +# Vec2 or None: Zentrum der Bounding Box als Vec2-Objekt oder None, +# falls keine gültige Geometrie gefunden wurde - Note: - Unterstützt POLYLINE, LWPOLYLINE und andere Entity-Typen. - Fehlerhafte Entities werden übersprungen und protokolliert. - """ - min_x, min_y = float('inf'), float('inf') - max_x, max_y = float('-inf'), float('-inf') - for e in entities: - try: - if e.dxftype() == "POLYLINE": - for vertex in e.vertices: - if hasattr(vertex.dxf, 'location'): - x, y = vertex.dxf.location.x, vertex.dxf.location.y - min_x, min_y = min(min_x, x), min(min_y, y) - max_x, max_y = max(max_x, x), max(max_y, y) - elif e.dxftype() == "LWPOLYLINE": - for x, y, *_ in e.get_points("xy"): - min_x, min_y = min(min_x, x), min(min_y, y) - max_x, max_y = max(max_x, x), max(max_y, y) - elif e.dxftype() == "ARC": - # Handle ARC entities: consider endpoints and cardinal extrema - try: - cx, cy = e.dxf.center.x, e.dxf.center.y - except Exception: - cx, cy = e.dxf.center - r = float(e.dxf.radius) - start_deg = float(e.dxf.start_angle) - end_deg = float(e.dxf.end_angle) - start = math.radians(start_deg % 360) - end = math.radians(end_deg % 360) +# Note: +# Unterstützt POLYLINE, LWPOLYLINE und andere Entity-Typen. +# Fehlerhafte Entities werden übersprungen und protokolliert. +# """ +# min_x, min_y = float('inf'), float('inf') +# max_x, max_y = float('-inf'), float('-inf') +# for e in entities: +# try: +# if e.dxftype() == "POLYLINE": +# for vertex in e.vertices: +# if hasattr(vertex.dxf, 'location'): +# x, y = vertex.dxf.location.x, vertex.dxf.location.y +# min_x, min_y = min(min_x, x), min(min_y, y) +# max_x, max_y = max(max_x, x), max(max_y, y) +# elif e.dxftype() == "LWPOLYLINE": +# for x, y, *_ in e.get_points("xy"): +# min_x, min_y = min(min_x, x), min(min_y, y) +# max_x, max_y = max(max_x, x), max(max_y, y) +# elif e.dxftype() == "ARC": +# # Handle ARC entities: consider endpoints and cardinal extrema +# try: +# cx, cy = e.dxf.center.x, e.dxf.center.y +# except Exception: +# cx, cy = e.dxf.center +# r = float(e.dxf.radius) +# start_deg = float(e.dxf.start_angle) +# end_deg = float(e.dxf.end_angle) +# start = math.radians(start_deg % 360) +# end = math.radians(end_deg % 360) - def in_sweep(a: float, s: float, e_: float) -> bool: - # CCW sweep from s to e_ with wrap handling - if e_ >= s: - return s <= a <= e_ - return a >= s or a <= e_ +# def in_sweep(a: float, s: float, e_: float) -> bool: +# # CCW sweep from s to e_ with wrap handling +# if e_ >= s: +# return s <= a <= e_ +# return a >= s or a <= e_ - candidates = [] - # Endpoints - candidates.append((cx + r * math.cos(start), cy + r * math.sin(start))) - candidates.append((cx + r * math.cos(end), cy + r * math.sin(end))) - # Cardinal angles 0, 90, 180, 270 deg - for ang in (0.0, math.pi/2, math.pi, 3*math.pi/2): - if in_sweep(ang, start, end): - candidates.append((cx + r * math.cos(ang), cy + r * math.sin(ang))) - for px, py in candidates: - min_x, min_y = min(min_x, px), min(min_y, py) - max_x, max_y = max(max_x, px), max(max_y, py) - elif e.dxftype() == "CIRCLE": - # Handle CIRCLE entities via center and radius - try: - cx, cy = e.dsf.center.x, e.dsf.center.y - except Exception: - cx, cy = e.dxf.center - r = float(e.dxf.radius) - min_x, min_y = min(min_x, cx - r), min(min_y, cy - r) - max_x, max_y = max(max_x, cx + r), max(max_y, cy + r) - elif e.dxftype() == "LINE" or e.dxftype()== "Line": - # Handle simple line entities by their start/end points - try: - sx, sy = e.dxf.start.x, e.dxf.start.y - ex, ey = e.dxf.end.x, e.dxf.end.y - except Exception: - # Some ezdxf versions provide tuples - (sx, sy), (ex, ey) = e.dxf.start, e.dxf.end - min_x, min_y = min(min_x, sx, ex ), min(min_y, sy, ey) - max_x, max_y = max(max_x, sx, ex), max(max_y, sy, ey) - elif e.dxftype() == "SPLINE": - # Approximate spline to compute bounding box - points = [] - try: - points = e.approximate(60) - except Exception: - try: - points = list(e.flattening(1.0)) - except Exception: - points = [] - if points: - for pt in points: - try: - px, py = pt.x, pt.y - except Exception: - px, py = pt[0], pt[1] - min_x, min_y = min(min_x, px), min(min_y, py) - max_x, max_y = max(max_x, px), max(max_y, py) - else: - box = e.bbox() - if box: - (x1, y1), (x2, y2) = box.extmin, box.extmax - min_x, min_y = min(min_x, x1), min(min_y, y1) - max_x, max_y = max(max_x, x2), max(max_y, y2) - except Exception as err: - print(f" BBox Fehler für {e.dxftype()}: {err}") - continue +# candidates = [] +# # Endpoints +# candidates.append((cx + r * math.cos(start), cy + r * math.sin(start))) +# candidates.append((cx + r * math.cos(end), cy + r * math.sin(end))) +# # Cardinal angles 0, 90, 180, 270 deg +# for ang in (0.0, math.pi/2, math.pi, 3*math.pi/2): +# if in_sweep(ang, start, end): +# candidates.append((cx + r * math.cos(ang), cy + r * math.sin(ang))) +# for px, py in candidates: +# min_x, min_y = min(min_x, px), min(min_y, py) +# max_x, max_y = max(max_x, px), max(max_y, py) +# elif e.dxftype() == "CIRCLE": +# # Handle CIRCLE entities via center and radius +# try: +# cx, cy = e.dsf.center.x, e.dsf.center.y +# except Exception: +# cx, cy = e.dxf.center +# r = float(e.dxf.radius) +# min_x, min_y = min(min_x, cx - r), min(min_y, cy - r) +# max_x, max_y = max(max_x, cx + r), max(max_y, cy + r) +# elif e.dxftype() == "LINE" or e.dxftype()== "Line": +# # Handle simple line entities by their start/end points +# try: +# sx, sy = e.dxf.start.x, e.dxf.start.y +# ex, ey = e.dxf.end.x, e.dxf.end.y +# except Exception: +# # Some ezdxf versions provide tuples +# (sx, sy), (ex, ey) = e.dxf.start, e.dxf.end +# min_x, min_y = min(min_x, sx, ex ), min(min_y, sy, ey) +# max_x, max_y = max(max_x, sx, ex), max(max_y, sy, ey) +# elif e.dxftype() == "SPLINE": +# # Approximate spline to compute bounding box +# points = [] +# try: +# points = e.approximate(60) +# except Exception: +# try: +# points = list(e.flattening(1.0)) +# except Exception: +# points = [] +# if points: +# for pt in points: +# try: +# px, py = pt.x, pt.y +# except Exception: +# px, py = pt[0], pt[1] +# min_x, min_y = min(min_x, px), min(min_y, py) +# max_x, max_y = max(max_x, px), max(max_y, py) +# elif e.dxftype() == 'INSERT': +# # INSERT: Block-Inhalt mit Transformation berücksichtigen +# insert_bbox = calculate_insert_bounding_box(e, doc, transform_matrix) +# if insert_bbox and insert_bbox.has_data: +# bbox.extend(insert_bbox) +# else: +# box = e.bbox() +# if box: +# (x1, y1), (x2, y2) = box.extmin, box.extmax +# min_x, min_y = min(min_x, x1), min(min_y, y1) +# max_x, max_y = max(max_x, x2), max(max_y, y2) +# except Exception as err: +# print(f" BBox Fehler für {e.dxftype()}: {err}") +# continue - if min_x == float('inf'): - return None, (0,0) +# if min_x == float('inf'): +# return None, (0,0) - return Vec2((min_x + max_x) / 2, (min_y + max_y) / 2), (max_x -min_x, max_y -min_y) +# return Vec2((min_x + max_x) / 2, (min_y + max_y) / 2), (max_x -min_x, max_y -min_y) def create_block_library(input_dir, output_file, config, logger=None): @@ -209,7 +214,8 @@ def create_block_library(input_dir, output_file, config, logger=None): pass - center, ausdehnung = get_bbox(entities) + # center, ausdehnung = get_bbox(entities) + center, ausdehnung = get_entity_bounding_box(entities, doc, transform_matrix=None) if center is None: error_msg = f"Keine gültige Geometrie in {filename}" if logger: @@ -224,13 +230,13 @@ def create_block_library(input_dir, output_file, config, logger=None): blk = doc.blocks.new(name=name, base_point=(0,0)) - for e in entities: - # Sicherstellen, dass referenzierte Blöcke für INSERT verfügbar sind - if e.dxftype() == "INSERT": - handle_insert_entities(doc, src_doc, e) - cp = copy_entity(logger, error_files, filename, e, center) - if cp: - blk.add_entity(cp) + # for e in entities: + # # Sicherstellen, dass referenzierte Blöcke für INSERT verfügbar sind + # if e.dxftype() == "INSERT": + # handle_insert_entities(doc, src_doc, e) + cp = copy_entity(logger, error_files, filename, e, center) + if cp: + blk.add_entity(cp) # Platzierung in Reihen und Spalten # Attribut-Definition (ATTDEF) hinzufügen blk.add_attdef( @@ -394,6 +400,395 @@ def get_cfg_value(section, key, fallback): return fallback + + +def convert_dxf_to_block_with_inserts(input_filename, output_filename, block_name="CONVERTED_BLOCK"): + """ + Konvertiert alle Entities einer DXF-Datei in einen neuen Block + INSERTs werden als Referenzen beibehalten (nicht explodiert) + """ + try: + # Eingabe-DXF laden + input_doc = ezdxf.readfile(input_filename) + print(f"Lade DXF-Datei: {input_filename}") + + # Neue Ausgabe-DXF erstellen + output_doc = ezdxf.new('R2010') + output_doc.header['$INSUNITS'] = 4 # Millimeter + + # Zuerst alle Block-Definitionen kopieren + copied_blocks = copy_block_definitions(input_doc, output_doc) + print(f"Block-Definitionen kopiert: {len(copied_blocks)}") + + # Neuen Hauptblock erstellen + new_block = output_doc.blocks.new(name=block_name) + print(f"Erstelle neuen Block: {block_name}") + + # Alle Entities aus dem Modelspace kopieren + msp = input_doc.modelspace() + entity_count = 0 + insert_count = 0 + + for entity in msp: + if entity.dxftype() == 'INSERT': + # INSERT direkt kopieren (nicht explodieren) + copy_entity_to_block(entity, new_block) + insert_count += 1 + else: + # Normale Entity kopieren + copy_entity_to_block(entity, new_block) + + entity_count += 1 + + # Bounding Box berechnen + bbox = calculate_block_bounding_box(new_block, output_doc) + + # Block im Modelspace der neuen Datei platzieren + output_msp = output_doc.modelspace() + output_msp.add_blockref(block_name, insert=(0, 0)) + + # Bounding Box als Hilfslinien hinzufügen (optional) + if bbox.has_data: + add_bounding_box_to_modelspace(output_msp, bbox) + + # Speichern + output_doc.saveas(output_filename) + + print(f"Konvertierung abgeschlossen:") + print(f" - {entity_count} Entities übertragen") + print(f" - {insert_count} INSERTs als Referenzen beibehalten") + print(f" - Bounding Box: {format_bounding_box(bbox)}") + print(f" - Ausgabe: {output_filename}") + + return bbox + + except FileNotFoundError: + print(f"Fehler: Datei {input_filename} nicht gefunden") + return None + except ezdxf.DXFStructureError as e: + print(f"DXF-Strukturfehler: {e}") + return None + except Exception as e: + print(f"Unerwarteter Fehler: {e}") + return None + + +def copy_block_definitions(source_doc, target_doc): + """ + Kopiert alle Block-Definitionen vom Quell- zum Ziel-Dokument + """ + copied_blocks = [] + + for block_name in source_doc.blocks: + # Standard-Blöcke (MODEL_SPACE, PAPER_SPACE) überspringen + if block_name.startswith('*'): + continue + + source_block = source_doc.blocks[block_name] + + # Prüfen ob Block bereits existiert + if block_name in target_doc.blocks: + print(f"Warnung: Block '{block_name}' existiert bereits, wird übersprungen") + continue + + # Neuen Block in Ziel-Dokument erstellen + target_block = target_doc.blocks.new(name=block_name) + + # Alle Entities des Quell-Blocks kopieren + for entity in source_block: + copy_entity_to_block(entity, target_block) + + copied_blocks.append(block_name) + + return copied_blocks + + +def calculate_block_bounding_box(block, doc): + """ + Berechnet die Bounding Box eines Blocks inklusive aller INSERTs + """ + bbox = BoundingBox() + + for entity in block: + entity_bbox = get_entity_bounding_box(entity, doc) + if entity_bbox and entity_bbox.has_data: + bbox.extend(entity_bbox) + + return bbox + + +def get_entity_bounding_box(entity, doc, transform_matrix=None): + """ + Berechnet die Bounding Box einer einzelnen Entity + Berücksichtigt INSERTs mit ihren Block-Inhalten + """ + bbox = BoundingBox() + for e in entity: + try: + if e.dxftype() == 'LINE': + start = Vec3(e.dxf.start) + end = Vec3(e.dxf.end) + if transform_matrix: + start = transform_matrix.transform(start) + end = transform_matrix.transform(end) + bbox.extend([start, end]) + + elif e.dxftype() == 'CIRCLE': + center = Vec3(e.dxf.center) + radius = e.dxf.radius + if transform_matrix: + center = transform_matrix.transform(center) + # Radius mit durchschnittlicher Skalierung anpassen + scale_factor = (transform_matrix.scale_x + transform_matrix.scale_y) / 2 + radius *= abs(scale_factor) + + bbox.extend([ + Vec3(center.x - radius, center.y - radius, center.z), + Vec3(center.x + radius, center.y + radius, center.z) + ]) + + elif e.dxftype() == 'ARC': + # Vereinfachung: Bounding Box des vollständigen Kreises + center = Vec3(e.dxf.center) + radius = e.dxf.radius + if transform_matrix: + center = transform_matrix.transform(center) + scale_factor = (transform_matrix.scale_x + transform_matrix.scale_y) / 2 + radius *= abs(scale_factor) + + bbox.extend([ + Vec3(center.x - radius, center.y - radius, center.z), + Vec3(center.x + radius, center.y + radius, center.z) + ]) + + elif e.dxftype() == 'LWPOLYLINE': + points = [] + for point in e.get_points(): + pt = Vec3(point[0], point[1], 0) + if transform_matrix: + pt = transform_matrix.transform(pt) + points.append(pt) + if points: + bbox.extend(points) + + elif e.dxftype() == 'POLYLINE': + points = [] + for vertex in e.vertices: + pt = Vec3(vertex.dxf.location) + if transform_matrix: + pt = transform_matrix.transform(pt) + points.append(pt) + if points: + bbox.extend(points) + + elif e.dxftype() == 'TEXT': + # Vereinfachung: Nur Insert-Point berücksichtigen + insert_point = Vec3(e.dxf.insert) + if transform_matrix: + insert_point = transform_matrix.transform(insert_point) + bbox.extend([insert_point]) + + elif e.dxftype() == 'INSERT': + # INSERT: Block-Inhalt mit Transformation berücksichtigen + insert_bbox = calculate_insert_bounding_box(e, doc, transform_matrix) + if insert_bbox and insert_bbox.has_data: + bbox.extend(insert_bbox) + + except Exception as e: + print(f"Fehler bei Bounding Box Berechnung für {entity.dxftype()}: {e}") + + return bbox,( bbox.extmax.x - bbox.extmin.x,bbox.extmax.y -bbox.extmin.y) + + +def calculate_insert_bounding_box(insert_entity, doc, parent_transform=None): + """ + Berechnet die Bounding Box eines INSERTs inklusive Block-Inhalt + """ + try: + # Block-Definition finden + block_name = insert_entity.dxf.name + if block_name not in doc.blocks: + print(f"Warnung: Block '{block_name}' nicht gefunden") + return BoundingBox() + + block_def = doc.blocks[block_name] + + # Transformation der INSERT-Entity berechnen + insert_transform = get_insert_transform_matrix(insert_entity) + + # Mit übergeordneter Transformation kombinieren + if parent_transform: + combined_transform = parent_transform * insert_transform + else: + combined_transform = insert_transform + + # Bounding Box aller Entities im Block berechnen + block_bbox = BoundingBox() + + for block_entity in block_def: + entity_bbox = get_entity_bounding_box(block_entity, doc, combined_transform) + if entity_bbox and entity_bbox.has_data: + block_bbox.extend(entity_bbox) + + return block_bbox + + except Exception as e: + print(f"Fehler bei INSERT Bounding Box: {e}") + return BoundingBox() + + +def get_insert_transform_matrix(insert_entity): + """ + Berechnet die Transformationsmatrix für einen INSERT + """ + # Position + insert_point = Vec3(insert_entity.dxf.insert) + + # Skalierung + xscale = getattr(insert_entity.dxf, 'xscale', 1.0) + yscale = getattr(insert_entity.dxf, 'yscale', 1.0) + zscale = getattr(insert_entity.dxf, 'zscale', 1.0) + + # Rotation (in Radiant umwandeln) + rotation = math.radians(getattr(insert_entity.dxf, 'rotation', 0.0)) + + # Transformationsmatrix erstellen + matrix = Matrix44.chain( + Matrix44.scale(xscale, yscale, zscale), + Matrix44.z_rotate(rotation), + Matrix44.translate(insert_point.x, insert_point.y, insert_point.z) + ) + + return matrix + + +def copy_entity_to_block(entity, target_block): + """ + Kopiert eine Entity in einen Zielblock + """ + try: + # Entity kopieren und zum Block hinzufügen + entity_copy = entity.copy() + target_block.add_entity(entity_copy) + + except Exception as e: + print(f"Fehler beim Kopieren von {entity.dxftype()}: {e}") + + +def add_bounding_box_to_modelspace(msp, bbox): + """ + Fügt die Bounding Box als Hilfslinien zum Modelspace hinzu + """ + if not bbox.has_data: + return + + # Bounding Box Rechteck zeichnen + min_pt = bbox.extmin + max_pt = bbox.extmax + + # Rechteck als LWPOLYLINE + bbox_points = [ + (min_pt.x, min_pt.y), + (max_pt.x, min_pt.y), + (max_pt.x, max_pt.y), + (min_pt.x, max_pt.y), + (min_pt.x, min_pt.y) + ] + + bbox_poly = msp.add_lwpolyline(bbox_points) + bbox_poly.dxf.layer = "BOUNDING_BOX" + bbox_poly.dxf.color = 1 # Rot + + # Bemaßungen hinzufügen + width = max_pt.x - min_pt.x + height = max_pt.y - min_pt.y + + # Text mit Abmessungen + text_pos = Vec3(min_pt.x, max_pt.y + 5, 0) + msp.add_text(f"Breite: {width:.2f} mm", height=3, + dxfattribs={'insert': text_pos, 'layer': "BOUNDING_BOX", 'color': 1}) + + text_pos2 = Vec3(min_pt.x, max_pt.y + 10, 0) + msp.add_text(f"Höhe: {height:.2f} mm", height=3, + dxfattribs={'insert': text_pos2, 'layer': "BOUNDING_BOX", 'color': 1}) + + +def format_bounding_box(bbox): + """ + Formatiert Bounding Box Information für Ausgabe + """ + if not bbox.has_data: + return "Keine gültigen Geometriedaten gefunden" + + min_pt = bbox.extmin + max_pt = bbox.extmax + width = max_pt.x - min_pt.x + height = max_pt.y - min_pt.y + depth = max_pt.z - min_pt.z + + return (f"Min: ({min_pt.x:.2f}, {min_pt.y:.2f}, {min_pt.z:.2f}) " + f"Max: ({max_pt.x:.2f}, {max_pt.y:.2f}, {max_pt.z:.2f}) " + f"Größe: {width:.2f} × {height:.2f} × {depth:.2f} mm") + + +def analyze_source_dxf_with_blocks(filename): + """ + Analysiert die Quell-DXF inklusive Block-Definitionen + """ + try: + doc = ezdxf.readfile(filename) + msp = doc.modelspace() + + entity_types = {} + layer_count = {} + insert_blocks = {} + block_definitions = {} + + # Modelspace analysieren + for entity in msp: + entity_type = entity.dxftype() + entity_types[entity_type] = entity_types.get(entity_type, 0) + 1 + + layer = getattr(entity.dxf, 'layer', '0') + layer_count[layer] = layer_count.get(layer, 0) + 1 + + if entity_type == 'INSERT': + block_name = entity.dxf.name + insert_blocks[block_name] = insert_blocks.get(block_name, 0) + 1 + + # Block-Definitionen analysieren + for block_name in doc.blocks: + if not block_name.startswith('*'): # Keine Standard-Blöcke + block_def = doc.blocks[block_name] + entity_count = len(list(block_def)) + block_definitions[block_name] = entity_count + + print(f"\nAnalyse von {filename}:") + print("=" * 50) + print("Entity-Typen im Modelspace:") + for etype, count in sorted(entity_types.items()): + print(f" {etype}: {count}") + + print(f"\nLayer ({len(layer_count)}):") + for layer, count in sorted(layer_count.items()): + print(f" {layer}: {count} entities") + + if insert_blocks: + print(f"\nINSERT-Verwendungen ({sum(insert_blocks.values())} total):") + for block, count in sorted(insert_blocks.items()): + print(f" {block}: {count}× verwendet") + + if block_definitions: + print(f"\nBlock-Definitionen ({len(block_definitions)}):") + for block, count in sorted(block_definitions.items()): + print(f" {block}: {count} entities") + + return entity_types, layer_count, insert_blocks, block_definitions + + except Exception as e: + print(f"Fehler bei der Analyse: {e}") + return {}, {}, {}, {} + if __name__ == "__main__": # Argumentparser für Kommandozeilenoptionen parser = argparse.ArgumentParser(description="SVG/XML zu DXF Konverter")