test mit blöcken erstellen

This commit is contained in:
2025-09-08 16:29:31 +02:00
parent 50f2d5f50e
commit 7c662c9663
+506 -111
View File
@@ -1,6 +1,6 @@
import os import os
import ezdxf import ezdxf
from ezdxf.math import Vec2, BoundingBox from ezdxf.math import Matrix44, Vec3, BoundingBox, Vec2
import math import math
import argparse import argparse
import sys import sys
@@ -11,115 +11,120 @@ from pathlib import Path
import logging import logging
def get_bbox(entities): # def get_bbox(entities, transform_matrix=None):
""" # """
Berechnet die Bounding Box für eine Liste von DXF-Entities. # Berechnet die Bounding Box für eine Liste von DXF-Entities.
Args: # Args:
entities: Liste von DXF-Entities (ezdxf entities) # entities: Liste von DXF-Entities (ezdxf entities)
Returns: # Returns:
Vec2 or None: Zentrum der Bounding Box als Vec2-Objekt oder None, # Vec2 or None: Zentrum der Bounding Box als Vec2-Objekt oder None,
falls keine gültige Geometrie gefunden wurde # falls keine gültige Geometrie gefunden wurde
Note: # Note:
Unterstützt POLYLINE, LWPOLYLINE und andere Entity-Typen. # Unterstützt POLYLINE, LWPOLYLINE und andere Entity-Typen.
Fehlerhafte Entities werden übersprungen und protokolliert. # Fehlerhafte Entities werden übersprungen und protokolliert.
""" # """
min_x, min_y = float('inf'), float('inf') # min_x, min_y = float('inf'), float('inf')
max_x, max_y = float('-inf'), float('-inf') # max_x, max_y = float('-inf'), float('-inf')
for e in entities: # for e in entities:
try: # try:
if e.dxftype() == "POLYLINE": # if e.dxftype() == "POLYLINE":
for vertex in e.vertices: # for vertex in e.vertices:
if hasattr(vertex.dxf, 'location'): # if hasattr(vertex.dxf, 'location'):
x, y = vertex.dxf.location.x, vertex.dxf.location.y # x, y = vertex.dxf.location.x, vertex.dxf.location.y
min_x, min_y = min(min_x, x), min(min_y, 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) # max_x, max_y = max(max_x, x), max(max_y, y)
elif e.dxftype() == "LWPOLYLINE": # elif e.dxftype() == "LWPOLYLINE":
for x, y, *_ in e.get_points("xy"): # for x, y, *_ in e.get_points("xy"):
min_x, min_y = min(min_x, x), min(min_y, 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) # max_x, max_y = max(max_x, x), max(max_y, y)
elif e.dxftype() == "ARC": # elif e.dxftype() == "ARC":
# Handle ARC entities: consider endpoints and cardinal extrema # # Handle ARC entities: consider endpoints and cardinal extrema
try: # try:
cx, cy = e.dxf.center.x, e.dxf.center.y # cx, cy = e.dxf.center.x, e.dxf.center.y
except Exception: # except Exception:
cx, cy = e.dxf.center # cx, cy = e.dxf.center
r = float(e.dxf.radius) # r = float(e.dxf.radius)
start_deg = float(e.dxf.start_angle) # start_deg = float(e.dxf.start_angle)
end_deg = float(e.dxf.end_angle) # end_deg = float(e.dxf.end_angle)
start = math.radians(start_deg % 360) # start = math.radians(start_deg % 360)
end = math.radians(end_deg % 360) # end = math.radians(end_deg % 360)
def in_sweep(a: float, s: float, e_: float) -> bool: # def in_sweep(a: float, s: float, e_: float) -> bool:
# CCW sweep from s to e_ with wrap handling # # CCW sweep from s to e_ with wrap handling
if e_ >= s: # if e_ >= s:
return s <= a <= e_ # return s <= a <= e_
return a >= s or a <= e_ # return a >= s or a <= e_
candidates = [] # candidates = []
# Endpoints # # Endpoints
candidates.append((cx + r * math.cos(start), cy + r * math.sin(start))) # candidates.append((cx + r * math.cos(start), cy + r * math.sin(start)))
candidates.append((cx + r * math.cos(end), cy + r * math.sin(end))) # candidates.append((cx + r * math.cos(end), cy + r * math.sin(end)))
# Cardinal angles 0, 90, 180, 270 deg # # Cardinal angles 0, 90, 180, 270 deg
for ang in (0.0, math.pi/2, math.pi, 3*math.pi/2): # for ang in (0.0, math.pi/2, math.pi, 3*math.pi/2):
if in_sweep(ang, start, end): # if in_sweep(ang, start, end):
candidates.append((cx + r * math.cos(ang), cy + r * math.sin(ang))) # candidates.append((cx + r * math.cos(ang), cy + r * math.sin(ang)))
for px, py in candidates: # for px, py in candidates:
min_x, min_y = min(min_x, px), min(min_y, py) # min_x, min_y = min(min_x, px), min(min_y, py)
max_x, max_y = max(max_x, px), max(max_y, py) # max_x, max_y = max(max_x, px), max(max_y, py)
elif e.dxftype() == "CIRCLE": # elif e.dxftype() == "CIRCLE":
# Handle CIRCLE entities via center and radius # # Handle CIRCLE entities via center and radius
try: # try:
cx, cy = e.dsf.center.x, e.dsf.center.y # cx, cy = e.dsf.center.x, e.dsf.center.y
except Exception: # except Exception:
cx, cy = e.dxf.center # cx, cy = e.dxf.center
r = float(e.dxf.radius) # r = float(e.dxf.radius)
min_x, min_y = min(min_x, cx - r), min(min_y, cy - r) # 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) # max_x, max_y = max(max_x, cx + r), max(max_y, cy + r)
elif e.dxftype() == "LINE" or e.dxftype()== "Line": # elif e.dxftype() == "LINE" or e.dxftype()== "Line":
# Handle simple line entities by their start/end points # # Handle simple line entities by their start/end points
try: # try:
sx, sy = e.dxf.start.x, e.dxf.start.y # sx, sy = e.dxf.start.x, e.dxf.start.y
ex, ey = e.dxf.end.x, e.dxf.end.y # ex, ey = e.dxf.end.x, e.dxf.end.y
except Exception: # except Exception:
# Some ezdxf versions provide tuples # # Some ezdxf versions provide tuples
(sx, sy), (ex, ey) = e.dxf.start, e.dxf.end # (sx, sy), (ex, ey) = e.dxf.start, e.dxf.end
min_x, min_y = min(min_x, sx, ex ), min(min_y, sy, ey) # 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) # max_x, max_y = max(max_x, sx, ex), max(max_y, sy, ey)
elif e.dxftype() == "SPLINE": # elif e.dxftype() == "SPLINE":
# Approximate spline to compute bounding box # # Approximate spline to compute bounding box
points = [] # points = []
try: # try:
points = e.approximate(60) # points = e.approximate(60)
except Exception: # except Exception:
try: # try:
points = list(e.flattening(1.0)) # points = list(e.flattening(1.0))
except Exception: # except Exception:
points = [] # points = []
if points: # if points:
for pt in points: # for pt in points:
try: # try:
px, py = pt.x, pt.y # px, py = pt.x, pt.y
except Exception: # except Exception:
px, py = pt[0], pt[1] # px, py = pt[0], pt[1]
min_x, min_y = min(min_x, px), min(min_y, py) # min_x, min_y = min(min_x, px), min(min_y, py)
max_x, max_y = max(max_x, px), max(max_y, py) # max_x, max_y = max(max_x, px), max(max_y, py)
else: # elif e.dxftype() == 'INSERT':
box = e.bbox() # # INSERT: Block-Inhalt mit Transformation berücksichtigen
if box: # insert_bbox = calculate_insert_bounding_box(e, doc, transform_matrix)
(x1, y1), (x2, y2) = box.extmin, box.extmax # if insert_bbox and insert_bbox.has_data:
min_x, min_y = min(min_x, x1), min(min_y, y1) # bbox.extend(insert_bbox)
max_x, max_y = max(max_x, x2), max(max_y, y2) # else:
except Exception as err: # box = e.bbox()
print(f" BBox Fehler für {e.dxftype()}: {err}") # if box:
continue # (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'): # if min_x == float('inf'):
return None, (0,0) # 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): 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 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: if center is None:
error_msg = f"Keine gültige Geometrie in {filename}" error_msg = f"Keine gültige Geometrie in {filename}"
if logger: 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)) blk = doc.blocks.new(name=name, base_point=(0,0))
for e in entities: # for e in entities:
# Sicherstellen, dass referenzierte Blöcke für INSERT verfügbar sind # # Sicherstellen, dass referenzierte Blöcke für INSERT verfügbar sind
if e.dxftype() == "INSERT": # if e.dxftype() == "INSERT":
handle_insert_entities(doc, src_doc, e) # handle_insert_entities(doc, src_doc, e)
cp = copy_entity(logger, error_files, filename, e, center) cp = copy_entity(logger, error_files, filename, e, center)
if cp: if cp:
blk.add_entity(cp) blk.add_entity(cp)
# Platzierung in Reihen und Spalten # Platzierung in Reihen und Spalten
# Attribut-Definition (ATTDEF) hinzufügen # Attribut-Definition (ATTDEF) hinzufügen
blk.add_attdef( blk.add_attdef(
@@ -394,6 +400,395 @@ def get_cfg_value(section, key, fallback):
return 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__": if __name__ == "__main__":
# Argumentparser für Kommandozeilenoptionen # Argumentparser für Kommandozeilenoptionen
parser = argparse.ArgumentParser(description="SVG/XML zu DXF Konverter") parser = argparse.ArgumentParser(description="SVG/XML zu DXF Konverter")