Files
plant2dxf/lib/dxf2lib.py
T
2025-09-08 16:29:31 +02:00

845 lines
32 KiB
Python
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 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)
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:
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)
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(
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)
# Basis­punkt ü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):
"""
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")
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 ===")