Files
plant2dxf/lib/dxf2lib.py
T

995 lines
33 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
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.
# -*- coding: utf-8 -*-
import os
import ezdxf
from ezdxf.math import Matrix44, Vec3, BoundingBox, Vec2
import math
import argparse
import sys
import configparser
from utils import check_environment_var, setup_logger
from pathlib import Path
import logging
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, TEXT, MTEXT, REGION, …
- 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",
"ATTDEF",
"TEXT",
"MTEXT",
"3DFACE",
"MESH",
"POLYFACE",
"POLYFACEMESH",
"SURFACE",
"3DSURFACE",
"EXTRUDEDSURFACE",
"REVOLVEDSURFACE",
"LOFTEDSURFACE",
"SWEPTSURFACE",
"REGION",
}
filtered_entities = []
att_def = {}
for insert in src_msp.query("INSERT"):
for attrib in insert.attribs:
att_def[attrib.dxf.tag] = attrib.dxf.text
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
boundingbox, ausdehnung, center = calculate_block_bounding_box(
filtered_entities, doc, src_doc, filename, config
)
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:
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
# 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:
test = msp.add_blockref(
name, insert=(x_offset, y_offset), dxfattribs={"layer": blockref_layer}
)
else:
test = msp.add_blockref(name, insert=(x_offset, y_offset))
# Text mit Blocknamen über dem Block
if name == "a" or name == "b":
for tag, value in att_def.items():
tag = tag
value = value
a = test.add_attrib(
tag=tag,
text=value,
)
a.is_invisible = True
# 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
# 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, config):
"""
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, config)
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 _bbox_from_virtual_entities(
entity, doc, src_doc, filename, config, transform_matrix=None
):
"""
Nutzt virtual_entities(), um komplexe Objekte (z.B. SURFACE) in
auswertbare Geometrie zu zerlegen und darauf eine Bounding Box zu
berechnen.
"""
v_bbox = BoundingBox()
if not hasattr(entity, "virtual_entities"):
return v_bbox
try:
for ve in entity.virtual_entities():
ve_bbox = get_entity_bounding_box(
ve, doc, src_doc, filename, config, transform_matrix
)
if ve_bbox and ve_bbox.has_data:
v_bbox.extend(ve_bbox)
except Exception as err:
print(f"Fehler bei virtual_entities() für {entity.dxftype()}: {err}")
return v_bbox
def get_entity_bounding_box(e, doc, src_doc, filename, config, 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
ve_bbox = _bbox_from_virtual_entities(
e, doc, src_doc, filename, config, transform_matrix
)
if ve_bbox.has_data:
bbox.extend(ve_bbox)
elif e.dxftype() == "POLYLINE":
# 2D/3D Polylines ebenfalls über virtuelle Entities
ve_bbox = _bbox_from_virtual_entities(
e, doc, src_doc, filename, config, transform_matrix
)
if ve_bbox.has_data:
bbox.extend(ve_bbox)
elif e.dxftype() == "3DFACE":
# 3DFace: direkte Eckpunkte verwenden
pts = []
try:
pts = [Vec3(p) for p in e.get_points()] # ezdxf API
except Exception:
try:
pts = [Vec3(v.dxf.location) for v in e.vertices]
except Exception:
pts = []
if transform_matrix:
pts = [transform_matrix.transform(p) for p in pts]
if pts:
bbox.extend(pts)
elif e.dxftype() in {"MESH", "POLYFACE", "POLYFACEMESH"}:
# Mesh/Polyface über virtuelle Geometrie auswerten
ve_bbox = _bbox_from_virtual_entities(
e, doc, src_doc, filename, config, transform_matrix
)
if 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() == "ATTDEF":
insert_point = Vec3(e.dxf.insert)
if transform_matrix:
insert_point = transform_matrix.transform(insert_point)
elif e.dxftype() == "TEXT":
insert_point = Vec3(e.dxf.insert)
height = float(getattr(e.dxf, "height", 1.0))
width_factor = float(getattr(e.dxf, "width", 1.0))
rotation = math.radians(getattr(e.dxf, "rotation", 0.0))
text_content = getattr(e.dxf, "text", "") or ""
est_width = max(
len(text_content) * height * 0.6 * width_factor, height * 0.5
)
corners = [
insert_point,
insert_point + Vec3(est_width, 0, 0),
insert_point + Vec3(0, height, 0),
insert_point + Vec3(est_width, height, 0),
]
if rotation != 0:
cos_r, sin_r = math.cos(rotation), math.sin(rotation)
origin = insert_point
rotated = []
for c in corners:
dx, dy = c.x - origin.x, c.y - origin.y
rx = origin.x + dx * cos_r - dy * sin_r
ry = origin.y + dx * sin_r + dy * cos_r
rotated.append(Vec3(rx, ry, c.z))
corners = rotated
for pt in corners:
if transform_matrix:
pt = transform_matrix.transform(pt)
bbox.extend([pt])
elif e.dxftype() == "MTEXT":
insert_point = Vec3(e.dxf.insert)
char_height = float(getattr(e.dxf, "char_height", 1.0))
width = float(getattr(e.dxf, "width", 0.0))
if width <= 0:
width = 10.0 * char_height
text_content = getattr(e.dxf, "text", "") or ""
lines = len(text_content.split("\n")) or 1
box_height = char_height * lines * 1.2
corners = [
insert_point,
insert_point + Vec3(width, 0, 0),
insert_point + Vec3(0, box_height, 0),
insert_point + Vec3(width, box_height, 0),
]
for pt in corners:
if transform_matrix:
pt = transform_matrix.transform(pt)
bbox.extend([pt])
elif e.dxftype() == "REGION":
# Region: Begrenzungsgeometrie über virtual_entities()
ve_bbox = _bbox_from_virtual_entities(
e, doc, src_doc, filename, config, transform_matrix
)
if ve_bbox.has_data:
bbox.extend(ve_bbox)
elif e.dxftype().endswith("SURFACE"):
# Viele Surface-Typen liefern ihre Proxy-Geometrie über virtual_entities()
ve_bbox = _bbox_from_virtual_entities(
e, doc, src_doc, filename, config, transform_matrix
)
if ve_bbox.has_data:
bbox.extend(ve_bbox)
elif e.dxftype() == "INSERT":
# INSERT: Block-Inhalt mit Transformation berücksichtigen
insert_bbox = calculate_insert_bounding_box(
e, doc, src_doc, filename, config, transform_matrix
)
if (
insert_bbox
and insert_bbox.has_data
and e.dxf.layer not in config.get("dxf2lib", "automation_layer")
):
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, config, 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, config, 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") / "omniflo"
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 ===")