924 lines
34 KiB
Python
924 lines
34 KiB
Python
import os
|
||
import ezdxf
|
||
from ezdxf.math import Matrix44, Vec3, BoundingBox, Vec2
|
||
import math
|
||
import argparse
|
||
import sys
|
||
import shutil
|
||
import configparser
|
||
from utils import check_environment_var, setup_logger
|
||
from pathlib import Path
|
||
import logging
|
||
|
||
|
||
# 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)
|
||
|
||
# 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)
|
||
|
||
# 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)
|
||
# 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)
|
||
|
||
# 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):
|
||
"""
|
||
Erstellt eine DXF-Block-Bibliothek aus einzelnen DXF-Dateien.
|
||
|
||
Diese Funktion liest alle DXF-Dateien aus einem Verzeichnis und erstellt
|
||
daraus eine Bibliothek mit Blöcken. Die Blöcke werden in einem Raster
|
||
angeordnet und mit Beschriftungen versehen. Fehlerhafte Dateien werden
|
||
protokolliert.
|
||
|
||
Args:
|
||
input_dir (str): Verzeichnis mit den zu verarbeitenden DXF-Dateien
|
||
output_file (str): Pfad zur zu erstellenden Bibliotheks-DXF-Datei
|
||
config: ConfigParser-Objekt mit den Konfigurationswerten
|
||
logger: Optionaler Logger für Logging-Ausgaben
|
||
|
||
Note:
|
||
- Unterstützte Entity-Typen: LINE, LWPOLYLINE, POLYLINE, SPLINE, CIRCLE, ARC, INSERT
|
||
- Blöcke werden zentriert und in einem 20x20 Raster angeordnet
|
||
- Erstellt eine Log-Datei mit Zeitstempel für fehlerhafte Dateien
|
||
- Automatische Erstellung des Output-Verzeichnisses falls nötig
|
||
"""
|
||
doc = ezdxf.new()
|
||
msp = doc.modelspace()
|
||
|
||
x_offset = 0
|
||
y_offset = 0
|
||
blocks_in_row = 0
|
||
|
||
error_files = []
|
||
processed_files = []
|
||
max_blockspacing_x = 0
|
||
max_blockspacing_y = 0
|
||
|
||
for filename in os.listdir(input_dir):
|
||
if filename =='821104043.dxf':
|
||
test = 2
|
||
if not filename.lower().endswith(".dxf"):
|
||
continue
|
||
|
||
|
||
filepath = os.path.join(input_dir, filename)
|
||
name = os.path.splitext(filename)[0]
|
||
|
||
try:
|
||
src_doc = ezdxf.readfile(filepath)
|
||
src_msp = src_doc.modelspace()
|
||
entities = list(src_msp)
|
||
allowed_types = {"LINE", "LWPOLYLINE", "POLYLINE", "SPLINE", "CIRCLE", "ARC", "INSERT"}
|
||
filtered_entities = []
|
||
|
||
for e in entities:
|
||
if e.dxftype() in allowed_types:
|
||
filtered_entities.append(e)
|
||
else:
|
||
logger.info(f"{e.dxftype()} is nicht in der erlaubten Liste. Diese befindet sich in {filename}")
|
||
|
||
|
||
entities = filtered_entities
|
||
|
||
|
||
except Exception as e:
|
||
error_msg = f"Fehler beim Lesen von {filename}: {e}"
|
||
if logger:
|
||
logger.error(error_msg)
|
||
else:
|
||
print(error_msg)
|
||
error_files.append((filename, error_msg))
|
||
continue
|
||
|
||
# Sicherstellen, dass die im Quell-DXF verwendeten Layer im Zieldokument existieren
|
||
try:
|
||
used_layer_names = {e.dxf.layer for e in entities if hasattr(e.dxf, "layer")}
|
||
for layer_name in used_layer_names:
|
||
if layer_name and layer_name not in doc.layers:
|
||
try:
|
||
src_layer = src_doc.layers.get(layer_name)
|
||
doc.layers.add(
|
||
name=layer_name,
|
||
color=getattr(src_layer.dxf, "color", None),
|
||
linetype=getattr(src_layer.dxf, "linetype", None),
|
||
lineweight=getattr(src_layer.dxf, "lineweight", None),
|
||
)
|
||
except Exception:
|
||
# Fallback: Layer mit Standardwerten anlegen
|
||
doc.layers.add(name=layer_name)
|
||
except Exception:
|
||
pass
|
||
|
||
|
||
# center, ausdehnung = get_bbox(entities)
|
||
boundingbox, ausdehnung, center = calculate_block_bounding_box(filtered_entities, doc,src_doc, filename)
|
||
if center is None:
|
||
error_msg = f"Keine gültige Geometrie in {filename}"
|
||
if logger:
|
||
logger.error(error_msg)
|
||
else:
|
||
print(error_msg)
|
||
error_files.append((filename, error_msg))
|
||
continue
|
||
|
||
if name in doc.blocks:
|
||
doc.blocks.delete_block(name)
|
||
|
||
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)
|
||
for e in entities:
|
||
cp = copy_entity(logger, error_files, filename, e, center)
|
||
if cp:
|
||
blk.add_entity(cp)
|
||
add_bounding_box_to_modelspace(blk, boundingbox, centered=True)
|
||
# Platzierung in Reihen und Spalten
|
||
# Attribut-Definition (ATTDEF) hinzufügen
|
||
blk.add_attdef(
|
||
tag="NAME",
|
||
insert=(0.2, 0.2), # Position relativ zum Blockursprung
|
||
|
||
)
|
||
|
||
# Blockreferenz-Layer bestimmen
|
||
blockref_layer = None
|
||
try:
|
||
#Konfiguration erlaubt expliziten Layernamen
|
||
cfg_layer = None
|
||
try:
|
||
cfg_layer = config.get("dxf2lib", "blockref_layer")
|
||
except Exception:
|
||
cfg_layer = None
|
||
|
||
if cfg_layer and cfg_layer.strip():
|
||
blockref_layer = cfg_layer.strip()
|
||
else:
|
||
# Dominanten Ursprungs-Layer verwenden (Mehrheit der Entities)
|
||
layer_counts = {}
|
||
for e in entities:
|
||
ln = getattr(e.dxf, "layer", None)
|
||
if not ln:
|
||
continue
|
||
layer_counts[ln] = layer_counts.get(ln, 0) + 1
|
||
if layer_counts:
|
||
blockref_layer = max(layer_counts.items(), key=lambda kv: kv[1])[0]
|
||
|
||
if blockref_layer and blockref_layer not in doc.layers:
|
||
# Sicherstellen, dass der Layer existiert
|
||
try:
|
||
src_layer = src_doc.layers.get(blockref_layer)
|
||
doc.layers.add(
|
||
name=blockref_layer,
|
||
color=getattr(src_layer.dxf, "color", None),
|
||
linetype=getattr(src_layer.dxf, "linetype", None),
|
||
lineweight=getattr(src_layer.dxf, "lineweight", None),
|
||
)
|
||
except Exception:
|
||
doc.layers.add(name=blockref_layer)
|
||
except Exception:
|
||
blockref_layer = None
|
||
section = "dxf2lib"
|
||
text_height = get_cfg_value(section, "text_height", DEFAULTS["text_height"])
|
||
extra_block_space_x = get_cfg_value(section, "extra_block_space_x", DEFAULTS["extra_block_space_x"])
|
||
blocks_per_row = get_cfg_value(section, "blocks_per_row", DEFAULTS["blocks_per_row"])
|
||
extra_text_space_y = get_cfg_value(section, "extra_text_space_y", DEFAULTS["extra_text_space_y"])
|
||
|
||
ausdehnung_x, ausdehung_y = ausdehnung[0], ausdehnung[1]
|
||
|
||
block_spacing_y = ausdehung_y + 400
|
||
block_spacing_x = ausdehnung_x + extra_block_space_x
|
||
max_blockspacing_x = max(max_blockspacing_x, block_spacing_x)
|
||
max_blockspacing_y = max(max_blockspacing_y, block_spacing_y)
|
||
x_offset += max_blockspacing_x
|
||
# Blockreferenz mit optionalem Layer einfügen (Entity-Layer bleiben erhalten)
|
||
if blockref_layer:
|
||
msp.add_blockref(name, insert=(x_offset, y_offset), dxfattribs={"layer": blockref_layer})
|
||
else:
|
||
msp.add_blockref(name, insert=(x_offset, y_offset))
|
||
# Text mit Blocknamen über dem Block
|
||
|
||
# Werte aus Config holen (Block: [dxf2lib])
|
||
|
||
msp.add_text(name, dxfattribs={'height': text_height, 'insert': (x_offset -ausdehnung_x/2 , y_offset + ausdehung_y/2 + extra_text_space_y)})
|
||
|
||
processed_files.append(filename)
|
||
blocks_in_row += 1
|
||
# Abstand zwischen Blöcken in einer Reihe
|
||
if blocks_in_row == blocks_per_row:
|
||
|
||
blocks_in_row = 0
|
||
x_offset = 0
|
||
y_offset -= max_blockspacing_y # Neue Zeile, nach unten versetzt
|
||
max_blockspacing_y = 0
|
||
max_blockspacing_x = 0
|
||
|
||
if logger:
|
||
logger.info(f"Bibliotheks-DXF gespeichert: {output_file}")
|
||
logger.info(f"Verarbeitete Dateien: {len(processed_files)}")
|
||
logger.info(f"Fehlerhafte Dateien: {len(error_files)}")
|
||
|
||
if error_files:
|
||
logger.warning(f"{len(error_files)} Dateien konnten nicht verarbeitet werden:")
|
||
for filename, error_msg in error_files:
|
||
logger.error(f"{filename}: {error_msg}")
|
||
else:
|
||
logger.info("Keine fehlerhaften Dateien gefunden.")
|
||
else:
|
||
print(f"Bibliotheks-DXF gespeichert: {output_file}")
|
||
print(f"Verarbeitete Dateien: {len(processed_files)}")
|
||
print(f"Fehlerhafte Dateien: {len(error_files)}")
|
||
|
||
if error_files:
|
||
print(f"Warnung: {len(error_files)} Dateien konnten nicht verarbeitet werden.")
|
||
|
||
output_dir = output_file.parent
|
||
if not output_dir.exists():
|
||
output_dir.mkdir(parents=True, exist_ok=True)
|
||
|
||
doc.saveas(output_file)
|
||
|
||
|
||
def copy_entity(logger, error_files, filename, e, center):
|
||
try:
|
||
cp = e.copy()
|
||
# Geometrie verschieben!
|
||
cp.translate(-center.x, -center.y,0)
|
||
# Ursprungs-Layer übernehmen
|
||
if hasattr(e.dxf, "layer"):
|
||
cp.dxf.layer = e.dxf.layer
|
||
if (hasattr(e.dxf, "color")):
|
||
cp.dxf.color = e.dxf.color
|
||
return cp
|
||
|
||
except Exception as err:
|
||
error_msg = f"Fehler beim Verarbeiten von Entity {e.dxftype()} in {filename}: {err}"
|
||
if logger:
|
||
logger.error(error_msg)
|
||
else:
|
||
print(error_msg)
|
||
error_files.append((filename, error_msg))
|
||
return None
|
||
|
||
def handle_insert_entities(doc, src_doc, e):
|
||
|
||
try:
|
||
ref_name = e.dxf.name
|
||
if ref_name not in doc.blocks and ref_name in src_doc.blocks:
|
||
src_blk = src_doc.blocks[ref_name]
|
||
dst_blk = doc.blocks.new(name=ref_name)
|
||
# Basispunkt übernehmen, falls vorhanden
|
||
try:
|
||
dst_blk.block.dxf.base_point = src_blk.block.dxf.base_point
|
||
|
||
except Exception:
|
||
pass
|
||
for ent in src_blk:
|
||
dst_blk.add_entity(ent.copy())
|
||
|
||
|
||
except Exception:
|
||
pass
|
||
|
||
|
||
# Standardwerte (falls nicht in der Config)
|
||
DEFAULTS = {
|
||
"text_height": 20, # Schriftgröße des Texts (in DXF-Einheiten)
|
||
"blocks_per_row": 20, # Anzahl Blöcke pro Zeile im Raster
|
||
"extra_block_space_x" : 50, # Extra Platz damit sich Blöcke nicht überlappen
|
||
"extra_text_space_y" : 50 # Abstand der Überschrift über dem Symbol
|
||
|
||
}
|
||
def get_cfg_value(section, key, fallback):
|
||
try:
|
||
return int(config.get(section, key))
|
||
except Exception:
|
||
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, src_doc, filename):
|
||
"""
|
||
Berechnet die Bounding Box eines Blocks inklusive aller INSERTs
|
||
"""
|
||
|
||
bbox = BoundingBox()
|
||
|
||
for entity in block:
|
||
entity_bbox = get_entity_bounding_box(entity, doc,src_doc,filename)
|
||
if entity_bbox and entity_bbox.has_data:
|
||
bbox.extend(entity_bbox)
|
||
|
||
|
||
return bbox, (bbox.extmax.x -bbox.extmin.x, bbox.extmax.y -bbox.extmin.y),bbox.center
|
||
|
||
|
||
def get_entity_bounding_box(e, doc,src_doc,filename, transform_matrix=None,):
|
||
"""
|
||
Berechnet die Bounding Box einer einzelnen Entity
|
||
Berücksichtigt INSERTs mit ihren Block-Inhalten
|
||
"""
|
||
|
||
bbox = BoundingBox()
|
||
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])
|
||
bbox.extend([end])
|
||
|
||
elif e.dxftype() == 'CIRCLE':
|
||
# Kreis durch Punktabtastung (robust bei Transformationen)
|
||
center = Vec3(e.dxf.center)
|
||
radius = float(e.dxf.radius)
|
||
angles = (0.0, math.pi / 2, math.pi, 3 * math.pi / 2)
|
||
points = []
|
||
for ang in angles:
|
||
px = center.x + radius * math.cos(ang)
|
||
py = center.y + radius * math.sin(ang)
|
||
p = Vec3(px, py, center.z)
|
||
if transform_matrix:
|
||
p = transform_matrix.transform(p)
|
||
points.append(p)
|
||
if points:
|
||
bbox.extend(points)
|
||
|
||
elif e.dxftype() == 'ARC':
|
||
# Bogen durch Punktabtastung zwischen Start- und Endwinkel
|
||
center = Vec3(e.dxf.center)
|
||
radius = 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 von s nach e_ mit Wrap-Handling
|
||
if e_ >= s:
|
||
return s <= a <= e_
|
||
return a >= s or a <= e_
|
||
|
||
angles = [start, end]
|
||
for ang in (0.0, math.pi / 2, math.pi, 3 * math.pi / 2):
|
||
if in_sweep(ang, start, end):
|
||
angles.append(ang)
|
||
|
||
points = []
|
||
for ang in angles:
|
||
px = center.x + radius * math.cos(ang)
|
||
py = center.y + radius * math.sin(ang)
|
||
p = Vec3(px, py, center.z)
|
||
if transform_matrix:
|
||
p = transform_matrix.transform(p)
|
||
points.append(p)
|
||
if points:
|
||
bbox.extend(points)
|
||
|
||
elif e.dxftype() == 'LWPOLYLINE':
|
||
# Nutze virtuelle Entities (Linien/Bögen), inkl. Bulge-Unterstützung
|
||
for ve in e.virtual_entities():
|
||
ve_bbox = get_entity_bounding_box(ve, doc, src_doc, filename, transform_matrix)
|
||
if ve_bbox and ve_bbox.has_data:
|
||
bbox.extend(ve_bbox)
|
||
|
||
elif e.dxftype() == 'POLYLINE':
|
||
# 2D/3D Polylines ebenfalls über virtuelle Entities
|
||
for ve in e.virtual_entities():
|
||
ve_bbox = get_entity_bounding_box(ve, doc, src_doc, filename, transform_matrix)
|
||
if ve_bbox and ve_bbox.has_data:
|
||
bbox.extend(ve_bbox)
|
||
|
||
elif e.dxftype() == 'SPLINE':
|
||
# Approximation der Spline-Kurve
|
||
try:
|
||
pts = list(e.approximate(60))
|
||
except Exception:
|
||
try:
|
||
pts = list(e.flattening(1.0))
|
||
except Exception:
|
||
pts = []
|
||
sampled = []
|
||
for pt in pts:
|
||
try:
|
||
vx, vy = pt.x, pt.y
|
||
except Exception:
|
||
vx, vy = pt[0], pt[1]
|
||
v = Vec3(vx, vy, 0)
|
||
if transform_matrix:
|
||
v = transform_matrix.transform(v)
|
||
sampled.append(v)
|
||
if sampled:
|
||
bbox.extend(sampled)
|
||
|
||
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,src_doc,filename, 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 {e.dxftype()}: {e}")
|
||
|
||
|
||
return bbox
|
||
|
||
def extract_scaling(matrix):
|
||
sx = math.sqrt(matrix[0, 0]**2 + matrix[0, 1]**2 + matrix[0, 2]**2)
|
||
sy = math.sqrt(matrix[1, 0]**2 + matrix[1, 1]**2 + matrix[1, 2]**2)
|
||
return sx, sy,
|
||
|
||
def calculate_insert_bounding_box(insert_entity, doc,src_doc,filename,parent_transform=None):
|
||
"""
|
||
Berechnet die Bounding Box eines INSERTs inklusive Block-Inhalt
|
||
"""
|
||
|
||
try:
|
||
# Block-Definition finden
|
||
block_name = insert_entity.dxf.name
|
||
src_blk = src_doc.blocks[block_name]
|
||
|
||
if block_name in doc.blocks:
|
||
dst_blk = doc.blocks[block_name]
|
||
|
||
new_insert = False
|
||
|
||
else:
|
||
dst_blk = doc.blocks.new(name=block_name)
|
||
new_insert = True
|
||
if block_name not in src_doc.blocks:
|
||
print(f"Warnung: Block '{block_name}' ")
|
||
return BoundingBox()
|
||
|
||
block_def = src_blk
|
||
|
||
|
||
# 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,src_doc,filename, combined_transform)
|
||
if entity_bbox and entity_bbox.has_data:
|
||
if new_insert:
|
||
dst_blk.add_entity(block_entity.copy())
|
||
block_bbox.extend(entity_bbox)
|
||
|
||
return block_bbox
|
||
|
||
except Exception as e:
|
||
print(f"Fehler bei INSERT Bounding Box: {e},{filename}")
|
||
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, centered=False):
|
||
"""
|
||
Fügt die Bounding Box als Hilfslinien zum Modelspace hinzu.
|
||
Wenn centered=True, wird das Rechteck um den Ursprung (0,0) zentriert und
|
||
ein roter Punkt in die Mitte gesetzt.
|
||
"""
|
||
if not bbox.has_data:
|
||
return
|
||
|
||
# Bounding Box Rechteck zeichnen
|
||
min_pt = bbox.extmin
|
||
max_pt = bbox.extmax
|
||
width = max_pt.x - min_pt.x
|
||
height = max_pt.y - min_pt.y
|
||
|
||
if centered:
|
||
# Rechteck um (0,0) zentriert
|
||
left = -width / 2.0
|
||
right = width / 2.0
|
||
bottom = -height / 2.0
|
||
top = height / 2.0
|
||
bbox_points = [
|
||
(left, bottom),
|
||
(right, bottom),
|
||
(right, top),
|
||
(left, top),
|
||
(left, bottom)
|
||
]
|
||
# Roter Punkt in der Mitte
|
||
msp.add_circle(center=(0.0, 0.0), radius=max(0.5, min(width, height) * 0.01),
|
||
dxfattribs={"layer": "BOUNDING_BOX", "color": 1})
|
||
else:
|
||
# Ursprüngliche, nicht-zentrierte Bounding Box
|
||
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
|
||
|
||
# Text mit Abmessungen
|
||
text_pos = Vec3(bbox_points[0][0], (bbox_points[2][1] if centered else 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(bbox_points[0][0], (bbox_points[2][1] if centered else 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")
|
||
parser.add_argument('-i', '--input', type=str, help='Input-Verzeichnis mit SVG/XML-Dateien')
|
||
parser.add_argument('-n', '--name', required=False, type=str, help='Name der zu erzeugenden Bibliothek (optional, wird sonst abgefragt)', default="test")
|
||
|
||
if len(sys.argv) == 2 and sys.argv[1] in ("-h", "--help"):
|
||
parser.print_help()
|
||
sys.exit(0)
|
||
|
||
args = parser.parse_args()
|
||
|
||
if not args.name:
|
||
args.name = input("Bitte Namen der zu erzeugenden Bibliothek eingeben: ").strip()
|
||
if not args.name:
|
||
print("Fehler: Kein Name angegeben. Beende.")
|
||
sys.exit(1)
|
||
|
||
# Verzeichnisse über Umgebungsvariablen oder Fallback
|
||
if args.input:
|
||
INPUT_DIR = Path(args.input)
|
||
print(f"Verwende Input-Verzeichnis: {INPUT_DIR} \n")
|
||
else:
|
||
INPUT_DIR = check_environment_var("PROJECT_DATA") / "dxf"
|
||
print(f"Kein Input-Verzeichnis angegeben, verwende Standard: {INPUT_DIR} \n")
|
||
|
||
OUTPUT_FILE = check_environment_var("PROJECT_DATA") / "block_libraries" / f"{args.name}.dxf"
|
||
|
||
# Prüfe und erstelle log-Verzeichnis falls nötig
|
||
log_dir = check_environment_var("PROJECT_LOG")
|
||
if not log_dir.exists():
|
||
log_dir.mkdir(parents=True, exist_ok=True)
|
||
print(f"Log-Verzeichnis erstellt: {log_dir}")
|
||
|
||
# Logger Setup
|
||
log_file = Path(os.environ['PROJECT_LOG']) / 'dxf2lib.log'
|
||
file_handler = logging.FileHandler(str(log_file), 'a', 'utf-8')
|
||
logger = setup_logger(log_dir, name='dxf2lib')
|
||
logger.info("=== DXF2LIB Verarbeitung gestartet ===")
|
||
logger.info(f"Input-Verzeichnis: {INPUT_DIR}")
|
||
logger.info(f"Output-Datei: {OUTPUT_FILE}")
|
||
logger.addHandler(file_handler)
|
||
|
||
# Lade Config-Datei
|
||
config = configparser.ConfigParser()
|
||
config_path = check_environment_var("PROJECT_CFG") / "allgemein.cfg"
|
||
config.read(config_path, encoding="utf-8")
|
||
logger.info(f"Config-Datei geladen: {config_path}")
|
||
|
||
# Erstelle die Block-Bibliothek
|
||
logger.info(f"Erstelle Block-Bibliothek aus {INPUT_DIR}...")
|
||
create_block_library(INPUT_DIR, OUTPUT_FILE, config, logger)
|
||
logger.info("=== DXF2LIB Verarbeitung abgeschlossen ===")
|