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 ===")