This commit is contained in:
2025-09-04 18:19:00 +02:00
2 changed files with 7540 additions and 42 deletions
File diff suppressed because it is too large Load Diff
+63 -42
View File
@@ -1,6 +1,7 @@
import os import os
import ezdxf import ezdxf
from ezdxf.math import Vec2 from ezdxf.math import Vec2
import math
import argparse import argparse
import sys import sys
import shutil import shutil
@@ -38,7 +39,47 @@ def get_bbox(entities):
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":
# 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() == "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), min(min_y, sy)
max_x, max_y = max(max_x, ex), max(max_y, ey)
else: else:
# Für andere Entity-Typen: Standard bbox() verwenden # Für andere Entity-Typen: Standard bbox() verwenden
box = e.bbox() box = e.bbox()
if box: if box:
@@ -50,9 +91,9 @@ def get_bbox(entities):
continue continue
if min_x == float('inf'): if min_x == float('inf'):
return None return None, (0,0)
return Vec2((min_x + max_x) / 2, (min_y + max_y) / 2) 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):
@@ -85,7 +126,7 @@ def create_block_library(input_dir, output_file, config, logger=None):
error_files = [] error_files = []
processed_files = [] processed_files = []
max_blockspacing_y = 0
for filename in os.listdir(input_dir): for filename in os.listdir(input_dir):
if not filename.lower().endswith(".dxf"): if not filename.lower().endswith(".dxf"):
continue continue
@@ -109,7 +150,7 @@ def create_block_library(input_dir, output_file, config, logger=None):
error_files.append((filename, error_msg)) error_files.append((filename, error_msg))
continue continue
center = get_bbox(entities) center, ausdehnung = get_bbox(entities)
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:
@@ -143,32 +184,27 @@ def create_block_library(input_dir, output_file, config, logger=None):
# Werte aus Config holen (Block: [dxf2lib]) # Werte aus Config holen (Block: [dxf2lib])
section = "dxf2lib" section = "dxf2lib"
text_y = y_offset + get_cfg_value(section, "text_offset_y", DEFAULTS["text_offset_y"])
text_offset_x = get_cfg_value(section, "text_offset_x", DEFAULTS["text_offset_x"])
max_len = get_cfg_value(section, "max_len", DEFAULTS["max_len"])
text_height = get_cfg_value(section, "text_height", DEFAULTS["text_height"]) text_height = get_cfg_value(section, "text_height", DEFAULTS["text_height"])
line_spacing = get_cfg_value(section, "line_spacing", DEFAULTS["line_spacing"]) extra_block_space_x = get_cfg_value(section, "extra_block_space_x", DEFAULTS["extra_block_space_x"])
block_spacing_x = get_cfg_value(section, "block_spacing_x", DEFAULTS["block_spacing_x"])
blocks_per_row = get_cfg_value(section, "blocks_per_row", DEFAULTS["blocks_per_row"]) blocks_per_row = get_cfg_value(section, "blocks_per_row", DEFAULTS["blocks_per_row"])
block_spacing_y = get_cfg_value(section, "block_spacing_y", DEFAULTS["block_spacing_y"]) extra_text_space_y = get_cfg_value(section, "extra_text_space_y", DEFAULTS["extra_text_space_y"])
if len(name) > max_len: ausdehnung_x, ausdehung_y = ausdehnung[0], ausdehnung[1]
first_line = name[:max_len] block_spacing_y = ausdehung_y
second_line = name[max_len:] block_spacing_x = ausdehnung_x + extra_block_space_x
# obere Zeile max_blockspacing_y = max(max_blockspacing_y, block_spacing_y)
msp.add_text(first_line, dxfattribs={'height': text_height, 'insert': (x_offset + text_offset_x, text_y)})
# untere Zeile, etwas niedriger (y kleiner) msp.add_text(name, dxfattribs={'height': text_height, 'insert': (x_offset -ausdehnung_x/2 , y_offset + ausdehung_y/2 + extra_text_space_y)})
msp.add_text(second_line, dxfattribs={'height': text_height, 'insert': (x_offset + text_offset_x, text_y - line_spacing)})
else:
msp.add_text(name, dxfattribs={'height': text_height, 'insert': (x_offset + text_offset_x, text_y)})
processed_files.append(filename) processed_files.append(filename)
blocks_in_row += 1 blocks_in_row += 1
x_offset += block_spacing_x # Abstand zwischen Blöcken in einer Reihe x_offset += block_spacing_x # Abstand zwischen Blöcken in einer Reihe
if blocks_in_row == blocks_per_row: if blocks_in_row == blocks_per_row:
blocks_in_row = 0 blocks_in_row = 0
x_offset = 0 x_offset = 0
y_offset -= block_spacing_y # Neue Zeile, nach unten versetzt y_offset -= max_blockspacing_y # Neue Zeile, nach unten versetzt
max_blockspacing_y = 0
if logger: if logger:
logger.info(f"Bibliotheks-DXF gespeichert: {output_file}") logger.info(f"Bibliotheks-DXF gespeichert: {output_file}")
@@ -198,16 +234,12 @@ def create_block_library(input_dir, output_file, config, logger=None):
# Standardwerte (falls nicht in der Config) # Standardwerte (falls nicht in der Config)
DEFAULTS = { DEFAULTS = {
"text_offset_y": 175, # Vertikaler Abstand des Texts über dem Block (in DXF-Einheiten)
"text_offset_x": -150, # Horizontaler Abstand des Texts links vom Block (in DXF-Einheiten)
"max_len": 9, # Maximale Zeichenlänge pro Textzeile (bei Überschreitung wird umgebrochen)
"text_height": 20, # Schriftgröße des Texts (in DXF-Einheiten) "text_height": 20, # Schriftgröße des Texts (in DXF-Einheiten)
"line_spacing": 22, # Abstand zwischen zwei Textzeilen bei Umbrüchen (in DXF-Einheiten)
"block_spacing_x": 350, # Horizontaler Abstand zwischen Blöcken in einer Reihe (in DXF-Einheiten)
"blocks_per_row": 20, # Anzahl Blöcke pro Zeile im Raster "blocks_per_row": 20, # Anzahl Blöcke pro Zeile im Raster
"block_spacing_y": 500, # Vertikaler Abstand zwischen Zeilen im Raster (in DXF-Einheiten) "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): def get_cfg_value(section, key, fallback):
try: try:
return int(config.get(section, key)) return int(config.get(section, key))
@@ -218,9 +250,8 @@ 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")
parser.add_argument('-i', '--input', type=str, help='Input-Verzeichnis mit SVG/XML-Dateien') 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)') parser.add_argument('-n', '--name', required=False, type=str, help='Name der zu erzeugenden Bibliothek (optional, wird sonst abgefragt)', default="test")
parser.add_argument('-k', '--keep', action="store_true", help='Behalte converted_dxf Ordner. Sonst Löschen nach erfolgreicher Bibliothekserstellung')
if len(sys.argv) == 2 and sys.argv[1] in ("-h", "--help"): if len(sys.argv) == 2 and sys.argv[1] in ("-h", "--help"):
parser.print_help() parser.print_help()
sys.exit(0) sys.exit(0)
@@ -238,10 +269,10 @@ if __name__ == "__main__":
INPUT_DIR = Path(args.input) INPUT_DIR = Path(args.input)
print(f"Verwende Input-Verzeichnis: {INPUT_DIR} \n") print(f"Verwende Input-Verzeichnis: {INPUT_DIR} \n")
else: else:
INPUT_DIR = check_environment_var("PROJECT_WORK") / "converted_dxfs" INPUT_DIR = check_environment_var("PROJECT_DATA") / "dxf"
print(f"Kein Input-Verzeichnis angegeben, verwende Standard: {INPUT_DIR} \n") print(f"Kein Input-Verzeichnis angegeben, verwende Standard: {INPUT_DIR} \n")
OUTPUT_FILE = check_environment_var("PROJECT_DATA") / "block_libaries" / f"{args.name}.dxf" OUTPUT_FILE = check_environment_var("PROJECT_DATA") / "block_libraries" / f"{args.name}.dxf"
# Prüfe und erstelle log-Verzeichnis falls nötig # Prüfe und erstelle log-Verzeichnis falls nötig
log_dir = check_environment_var("PROJECT_LOG") log_dir = check_environment_var("PROJECT_LOG")
@@ -264,14 +295,4 @@ if __name__ == "__main__":
# Erstelle die Block-Bibliothek # Erstelle die Block-Bibliothek
logger.info(f"Erstelle Block-Bibliothek aus {INPUT_DIR}...") logger.info(f"Erstelle Block-Bibliothek aus {INPUT_DIR}...")
create_block_library(INPUT_DIR, OUTPUT_FILE, config, logger) create_block_library(INPUT_DIR, OUTPUT_FILE, config, logger)
if not args.keep:
logger.info(f"Keep-Argument nicht gesetzt, lösche Input-Verzeichnis: {INPUT_DIR}")
try:
if INPUT_DIR.exists():
shutil.rmtree(INPUT_DIR)
logger.info(f"Verzeichnis '{INPUT_DIR}' wurde gelöscht.")
except Exception as err:
logger.warning(f"Konnte '{INPUT_DIR}' nicht löschen: {err}")
logger.info("=== DXF2LIB Verarbeitung abgeschlossen ===") logger.info("=== DXF2LIB Verarbeitung abgeschlossen ===")