290 lines
11 KiB
Python
290 lines
11 KiB
Python
import os
|
|
import subprocess
|
|
import sys
|
|
import argparse
|
|
import time
|
|
import ezdxf
|
|
from svg.path import parse_path, Line, Arc, CubicBezier, QuadraticBezier, Close
|
|
import xml.etree.ElementTree as ET
|
|
import re
|
|
from pathlib import Path
|
|
|
|
from utils import check_environment_var, setup_logger
|
|
|
|
class SVGtoDXFConverter:
|
|
def __init__(self, logger=None):
|
|
# Standard DXF-Farben (AutoCAD Color Index)
|
|
self.color_map = {
|
|
'red': 1,
|
|
'yellow': 2,
|
|
'green': 3,
|
|
'cyan': 4,
|
|
'blue': 5,
|
|
'magenta': 6,
|
|
'white': 7,
|
|
'gray': 8,
|
|
'grey': 8,
|
|
'black': 7
|
|
}
|
|
self.logger = logger
|
|
|
|
def svg_to_dxf(self, svg_file: str, dxf_file: str):
|
|
"""Konvertiert eine SVG-Datei zu DXF (AutoCAD 2018)"""
|
|
try:
|
|
# DXF-Dokument für AutoCAD 2018 erstellen
|
|
doc = ezdxf.new('R2018')
|
|
msp = doc.modelspace()
|
|
|
|
# SVG parsen
|
|
tree = ET.parse(svg_file)
|
|
root = tree.getroot()
|
|
|
|
# SVG-Namespace definieren
|
|
namespaces = {
|
|
'svg': 'http://www.w3.org/2000/svg',
|
|
'': 'http://www.w3.org/2000/svg' # Default namespace
|
|
}
|
|
|
|
# Alle path-Elemente finden und konvertieren
|
|
for path_elem in root.iter():
|
|
if path_elem.tag.endswith('path') or path_elem.tag == 'path':
|
|
self.convert_path_element(path_elem, msp)
|
|
|
|
# DXF speichern
|
|
doc.saveas(dxf_file)
|
|
if self.logger:
|
|
self.logger.info(f"DXF-Datei erfolgreich erstellt: {dxf_file}")
|
|
else:
|
|
print(f"DXF-Datei erfolgreich erstellt: {dxf_file}")
|
|
except Exception as e:
|
|
if self.logger:
|
|
self.logger.error(f"Fehler beim Parsen der SVG-Datei {svg_file}: {e}")
|
|
else:
|
|
print(f"Fehler beim Parsen der SVG-Datei {svg_file}: {e}")
|
|
|
|
def convert_path_element(self, path_elem, msp):
|
|
"""Konvertiert ein SVG path-Element zu DXF-Entities"""
|
|
d = path_elem.get('d')
|
|
if not d:
|
|
return
|
|
|
|
# Farbe aus stroke-Attribut extrahieren
|
|
color_index = self.get_color_from_stroke(path_elem.get('stroke', 'black'))
|
|
|
|
try:
|
|
path = parse_path(d)
|
|
self.convert_path_to_dxf(path, msp, color_index)
|
|
except Exception as e:
|
|
if self.logger:
|
|
self.logger.warning(f"Fehler beim Parsen des Pfads: {e}")
|
|
else:
|
|
print(f"Warnung: Fehler beim Parsen des Pfads: {e}")
|
|
|
|
def get_color_from_stroke(self, stroke_value: str) -> int:
|
|
"""Konvertiert SVG stroke-Farbe zu DXF Color Index"""
|
|
if not stroke_value or stroke_value == 'none':
|
|
return 7 # Standard: weiß/schwarz
|
|
|
|
stroke_value = stroke_value.lower().strip()
|
|
|
|
# Direkte Farbnamen
|
|
if stroke_value in self.color_map:
|
|
return self.color_map[stroke_value]
|
|
|
|
# Hex-Farben zu RGB und dann zu nächster DXF-Farbe
|
|
if stroke_value.startswith('#'):
|
|
return self.hex_to_dxf_color(stroke_value)
|
|
|
|
# RGB-Farben
|
|
rgb_match = re.match(r'rgb\((\d+),\s*(\d+),\s*(\d+)\)', stroke_value)
|
|
if rgb_match:
|
|
r, g, b = map(int, rgb_match.groups())
|
|
return self.rgb_to_dxf_color(r, g, b)
|
|
|
|
return 7 # Fallback: weiß/schwarz
|
|
|
|
def hex_to_dxf_color(self, hex_color: str) -> int:
|
|
"""Konvertiert Hex-Farbe zu DXF Color Index"""
|
|
hex_color = hex_color.lstrip('#')
|
|
if len(hex_color) == 6:
|
|
r = int(hex_color[0:2], 16)
|
|
g = int(hex_color[2:4], 16)
|
|
b = int(hex_color[4:6], 16)
|
|
return self.rgb_to_dxf_color(r, g, b)
|
|
return 7
|
|
|
|
def rgb_to_dxf_color(self, r: int, g: int, b: int) -> int:
|
|
"""Konvertiert RGB-Werte zu nächstem DXF Color Index"""
|
|
# Vereinfachte Zuordnung zu Hauptfarben
|
|
if r > 200 and g < 100 and b < 100:
|
|
return 1 # Rot
|
|
elif r > 200 and g > 200 and b < 100:
|
|
return 2 # Gelb
|
|
elif r < 100 and g > 200 and b < 100:
|
|
return 3 # Grün
|
|
elif r < 100 and g > 200 and b > 200:
|
|
return 4 # Cyan
|
|
elif r < 100 and g < 100 and b > 200:
|
|
return 5 # Blau
|
|
elif r > 200 and g < 100 and b > 200:
|
|
return 6 # Magenta
|
|
elif r > 200 and g > 200 and b > 200:
|
|
return 7 # Weiß
|
|
else:
|
|
return 8 # Grau
|
|
|
|
def convert_path_to_dxf(self, path, msp, color_index: int):
|
|
"""Konvertiert SVG-Pfad zu DXF-Entities"""
|
|
current_point = None
|
|
|
|
for segment in path:
|
|
if isinstance(segment, Line):
|
|
start = (segment.start.real, -segment.start.imag) # Y-Achse umkehren
|
|
end = (segment.end.real, -segment.end.imag)
|
|
line = msp.add_line(start, end)
|
|
line.dxf.color = color_index
|
|
current_point = end
|
|
|
|
elif isinstance(segment, Arc):
|
|
# Arc zu Polyline approximieren
|
|
points = self.arc_to_points(segment)
|
|
if len(points) > 1:
|
|
for i in range(len(points) - 1):
|
|
start = (points[i].real, -points[i].imag)
|
|
end = (points[i+1].real, -points[i+1].imag)
|
|
line = msp.add_line(start, end)
|
|
line.dxf.color = color_index
|
|
current_point = (points[-1].real, -points[-1].imag)
|
|
|
|
elif isinstance(segment, (CubicBezier, QuadraticBezier)):
|
|
# Bezier-Kurven zu Polyline approximieren
|
|
points = self.bezier_to_points(segment)
|
|
if len(points) > 1:
|
|
for i in range(len(points) - 1):
|
|
start = (points[i].real, -points[i].imag)
|
|
end = (points[i+1].real, -points[i+1].imag)
|
|
line = msp.add_line(start, end)
|
|
line.dxf.color = color_index
|
|
current_point = (points[-1].real, -points[-1].imag)
|
|
|
|
elif isinstance(segment, Close):
|
|
# Pfad schließen - zurück zum Startpunkt
|
|
if current_point and len(path) > 0:
|
|
start_point = (path[0].start.real, -path[0].start.imag)
|
|
if current_point != start_point:
|
|
line = msp.add_line(current_point, start_point)
|
|
line.dxf.color = color_index
|
|
|
|
def arc_to_points(self, arc, num_points: int = 20):
|
|
"""Approximiert einen Bogen durch Punkte"""
|
|
points = []
|
|
for i in range(num_points + 1):
|
|
t = i / num_points
|
|
point = arc.point(t)
|
|
points.append(point)
|
|
return points
|
|
|
|
def bezier_to_points(self, bezier, num_points: int = 20):
|
|
"""Approximiert eine Bezier-Kurve durch Punkte"""
|
|
points = []
|
|
for i in range(num_points + 1):
|
|
t = i / num_points
|
|
point = bezier.point(t)
|
|
points.append(point)
|
|
return points
|
|
|
|
# Batch-Konvertierung für mehrere Dateien
|
|
def batch_convert(svg_files: list, output_dir: str = '.', logger=None):
|
|
"""Konvertiert mehrere SVG-Dateien"""
|
|
output_dir = Path(output_dir) # Sicherstellen, dass es ein Path ist
|
|
converter = SVGtoDXFConverter(logger=logger)
|
|
|
|
for svg_file in svg_files:
|
|
try:
|
|
# Ausgabedateiname generieren
|
|
base_name = Path(svg_file).stem
|
|
dxf_file = output_dir / f"{base_name}.dxf"
|
|
|
|
converter.svg_to_dxf(svg_file, dxf_file)
|
|
if logger:
|
|
logger.info(f"✓ {svg_file} -> {dxf_file}")
|
|
else:
|
|
print(f"✓ {svg_file} -> {dxf_file}")
|
|
|
|
except Exception as e:
|
|
if logger:
|
|
logger.error(f"✗ Fehler bei {svg_file}: {e}")
|
|
else:
|
|
print(f"✗ Fehler bei {svg_file}: {e}")
|
|
|
|
|
|
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('-o', '--output', type=str, help='Output-Verzeichnis für DXF-Dateien')
|
|
parser.add_argument('-k', '--inkscape', action='store_true', help='Batch-Konvertierung über Inkscape')
|
|
|
|
if len(sys.argv) == 2 and sys.argv[1] in ("-h", "--help"):
|
|
parser.print_help()
|
|
sys.exit(0)
|
|
|
|
args = parser.parse_args()
|
|
|
|
# Verzeichnisse über Umgebungsvariablen oder Fallback
|
|
if args.input:
|
|
INPUT_DIR = Path(args.input)
|
|
print(f"Input-Verzeichnis: {INPUT_DIR} \n")
|
|
else:
|
|
INPUT_DIR = check_environment_var("PROJECT_DATA") / "svg"
|
|
print(f"Kein Input-Verzeichnis angegeben, verwende Standard: {INPUT_DIR} \n")
|
|
|
|
if args.output:
|
|
OUTPUT_DIR = Path(args.output)
|
|
print(f"Output-Verzeichnis: {OUTPUT_DIR} \n")
|
|
else:
|
|
OUTPUT_DIR = check_environment_var("PROJECT_WORK") / "converted_dxfs"
|
|
print(f"Kein Output-Verzeichnis angegeben, verwende Standard: {OUTPUT_DIR} \n")
|
|
|
|
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
|
|
|
files = [f for f in INPUT_DIR.iterdir() if f.suffix.lower() in (".svg", ".xml")]
|
|
files = [str(f) for f in files] # batch_convert erwartet str
|
|
|
|
log_dir = check_environment_var("PROJECT_LOG")
|
|
logger = setup_logger(log_dir, name='svg2dxf')
|
|
logger.info("=== SVG2DXF Verarbeitung gestartet ===")
|
|
logger.info(f"Input-Verzeichnis: {INPUT_DIR}")
|
|
logger.info(f"Output-Verzeichnis: {OUTPUT_DIR}")
|
|
|
|
|
|
if args.inkscape:
|
|
INKSCAPE = "inkscape" # ggf. vollständiger Pfad angeben
|
|
for filename in files:
|
|
input_path = INPUT_DIR / filename
|
|
output_path = OUTPUT_DIR / (Path(filename).stem + ".dxf")
|
|
if logger:
|
|
logger.info(f"Konvertiere: {input_path} → {output_path}")
|
|
else:
|
|
print(f"Konvertiere: {input_path} → {output_path}")
|
|
try:
|
|
subprocess.run([
|
|
INKSCAPE,
|
|
str(input_path),
|
|
"--export-type=dxf",
|
|
f"--export-filename={output_path}"
|
|
], check=True)
|
|
except subprocess.CalledProcessError as e:
|
|
if logger:
|
|
logger.error(f"[FEHLER] Inkscape-Absturz bei {filename}: {e}")
|
|
else:
|
|
print(f"[FEHLER] Inkscape-Absturz bei {filename}: {e}")
|
|
time.sleep(0.5) # kleine Pause zur Entlastung von RAM/GTK
|
|
|
|
else:
|
|
batch_convert(files, str(OUTPUT_DIR), logger=logger)
|
|
|
|
logger.info("=== SVG2DXF Verarbeitung abgeschlossen ===")
|
|
|
|
|