Merge branch 'main' of http://gitea.schoenenberger.de/mistangl/plant2dxf
This commit is contained in:
+4
-8
@@ -1,6 +1,6 @@
|
|||||||
|
chcp 1252 > nul
|
||||||
@echo off
|
@echo off
|
||||||
REM falls Umlaute in den Pfaden sind:
|
|
||||||
chcp 65001 > nul
|
|
||||||
REM ~dp0 steht für das Verzeichnis, in der diese Datei liegt
|
REM ~dp0 steht für das Verzeichnis, in der diese Datei liegt
|
||||||
pushd %~dp0\..
|
pushd %~dp0\..
|
||||||
|
|
||||||
@@ -12,15 +12,11 @@ set PROJECT_LIB=%PROJECT%\lib
|
|||||||
set PROJECT_DATA=%PROJECT%\data
|
set PROJECT_DATA=%PROJECT%\data
|
||||||
set PROJECT_WORK=%PROJECT%\work
|
set PROJECT_WORK=%PROJECT%\work
|
||||||
set PROJECT_TEST=%PROJECT%\testdata
|
set PROJECT_TEST=%PROJECT%\testdata
|
||||||
|
set PROJECT_LOG=%PROJECT%\log
|
||||||
|
|
||||||
set SIVAS_TEILESTAMM=\\195.243.223.3\sivas\jit\programme\KSbExcelExportSivasTeilestamm.exe
|
|
||||||
set SIVAS_EXCEL_EXPORT_DIR=%PROJECT_DATA%
|
|
||||||
|
|
||||||
if not exist %PROJECT%\work mkdir %PROJECT%\work
|
|
||||||
if not exist %PROJECT%\data mkdir %PROJECT%\data
|
if not exist %PROJECT%\data mkdir %PROJECT%\data
|
||||||
|
if not exist %PROJECT%\log mkdir %PROJECT%\log
|
||||||
|
|
||||||
set PROJECT_IO_RESULTS=%PROJECT_WORK%
|
|
||||||
set PROJECT_BOM_RESULTS=%PROJECT_WORK%
|
|
||||||
set INSTALL_DIR="%ONEDRIVE%\Desktop\Kabeltool"
|
set INSTALL_DIR="%ONEDRIVE%\Desktop\Kabeltool"
|
||||||
|
|
||||||
set PATH=%PROJECT_BIN%;%PATH%
|
set PATH=%PROJECT_BIN%;%PATH%
|
||||||
|
|||||||
@@ -7,3 +7,23 @@ libfile = ils_lib.dxf
|
|||||||
[Omniflo]
|
[Omniflo]
|
||||||
libfile = omniflo_lib.dxf
|
libfile = omniflo_lib.dxf
|
||||||
|
|
||||||
|
[dxf2lib]
|
||||||
|
# Text-Positionierung und Formatierung
|
||||||
|
# Vertikaler Abstand des Texts über dem Block (in DXF-Einheiten)
|
||||||
|
text_offset_y = 175
|
||||||
|
# Horizontaler Abstand des Texts links vom Block (in DXF-Einheiten)
|
||||||
|
text_offset_x = -150
|
||||||
|
# Maximale Zeichenlänge pro Textzeile (bei Überschreitung wird umgebrochen)
|
||||||
|
max_len = 9
|
||||||
|
# Schriftgröße des Texts (in DXF-Einheiten)
|
||||||
|
text_height = 20
|
||||||
|
# Abstand zwischen zwei Textzeilen bei Umbrüchen (in DXF-Einheiten)
|
||||||
|
line_spacing = 22
|
||||||
|
|
||||||
|
# Block-Anordnung im Raster
|
||||||
|
# Horizontaler Abstand zwischen Blöcken in einer Reihe (in DXF-Einheiten)
|
||||||
|
block_spacing_x = 350
|
||||||
|
# Anzahl Blöcke pro Zeile im Raster
|
||||||
|
blocks_per_row = 20
|
||||||
|
# Vertikaler Abstand zwischen Zeilen im Raster (in DXF-Einheiten)
|
||||||
|
block_spacing_y = 500
|
||||||
|
|||||||
+205
-85
@@ -4,15 +4,26 @@ from ezdxf.math import Vec2
|
|||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
import shutil
|
import shutil
|
||||||
|
import configparser
|
||||||
|
from utils import check_environment_var, setup_logger
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
def check_environment_var(env_str: str, fallback: str) -> str:
|
|
||||||
out_path = os.environ.get(env_str)
|
|
||||||
if out_path:
|
|
||||||
return out_path
|
|
||||||
print(f"[WARN] Umgebungsvariable {env_str} ist nicht gesetzt oder leer. Verwende '{fallback}'.")
|
|
||||||
return fallback
|
|
||||||
|
|
||||||
def get_bbox(entities):
|
def get_bbox(entities):
|
||||||
|
"""
|
||||||
|
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')
|
min_x, min_y = float('inf'), float('inf')
|
||||||
max_x, max_y = float('-inf'), float('-inf')
|
max_x, max_y = float('-inf'), float('-inf')
|
||||||
for e in entities:
|
for e in entities:
|
||||||
@@ -43,6 +54,166 @@ def get_bbox(entities):
|
|||||||
|
|
||||||
return Vec2((min_x + max_x) / 2, (min_y + max_y) / 2)
|
return Vec2((min_x + max_x) / 2, (min_y + max_y) / 2)
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
- 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 = []
|
||||||
|
|
||||||
|
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"}
|
||||||
|
filtered_entities = [e for e in entities if e.dxftype() in allowed_types]
|
||||||
|
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
|
||||||
|
|
||||||
|
center = get_bbox(entities)
|
||||||
|
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:
|
||||||
|
try:
|
||||||
|
cp = e.copy()
|
||||||
|
cp.translate(-center.x, -center.y, 0) # Geometrie verschieben!
|
||||||
|
blk.add_entity(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))
|
||||||
|
|
||||||
|
# Platzierung in Reihen und Spalten
|
||||||
|
msp.add_blockref(name, insert=(x_offset, y_offset))
|
||||||
|
# Text mit Blocknamen über dem Block
|
||||||
|
|
||||||
|
# Werte aus Config holen (Block: [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"])
|
||||||
|
line_spacing = get_cfg_value(section, "line_spacing", DEFAULTS["line_spacing"])
|
||||||
|
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"])
|
||||||
|
block_spacing_y = get_cfg_value(section, "block_spacing_y", DEFAULTS["block_spacing_y"])
|
||||||
|
|
||||||
|
if len(name) > max_len:
|
||||||
|
first_line = name[:max_len]
|
||||||
|
second_line = name[max_len:]
|
||||||
|
# obere Zeile
|
||||||
|
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(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)
|
||||||
|
blocks_in_row += 1
|
||||||
|
x_offset += block_spacing_x # Abstand zwischen Blöcken in einer Reihe
|
||||||
|
if blocks_in_row == blocks_per_row:
|
||||||
|
blocks_in_row = 0
|
||||||
|
x_offset = 0
|
||||||
|
y_offset -= block_spacing_y # Neue Zeile, nach unten versetzt
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
# Standardwerte (falls nicht in der Config)
|
||||||
|
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)
|
||||||
|
"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
|
||||||
|
"block_spacing_y": 500, # Vertikaler Abstand zwischen Zeilen im Raster (in DXF-Einheiten)
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_cfg_value(section, key, fallback):
|
||||||
|
try:
|
||||||
|
return int(config.get(section, key))
|
||||||
|
except Exception:
|
||||||
|
return fallback
|
||||||
|
|
||||||
if __name__ == "__main__":
|
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")
|
||||||
@@ -64,94 +235,43 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
# Verzeichnisse über Umgebungsvariablen oder Fallback
|
# Verzeichnisse über Umgebungsvariablen oder Fallback
|
||||||
if args.input:
|
if args.input:
|
||||||
INPUT_DIR = 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 = os.path.join(check_environment_var("PROJECT_WORK", "."), "converted_dxfs")
|
INPUT_DIR = check_environment_var("PROJECT_WORK") / "converted_dxfs"
|
||||||
print(f"Kein Input-Verzeichnis angegeben, verwende Standard: {INPUT_DIR} \n")
|
print(f"Kein Input-Verzeichnis angegeben, verwende Standard: {INPUT_DIR} \n")
|
||||||
|
|
||||||
OUTPUT_FILE = os.path.join(check_environment_var("PROJECT_DATA", "."),"block_libaries", f"{args.name}.dxf")
|
OUTPUT_FILE = check_environment_var("PROJECT_DATA") / "block_libaries" / f"{args.name}.dxf"
|
||||||
|
|
||||||
doc = ezdxf.new()
|
# Prüfe und erstelle log-Verzeichnis falls nötig
|
||||||
msp = doc.modelspace()
|
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}")
|
||||||
|
|
||||||
x_offset = 0
|
# Logger Setup
|
||||||
y_offset = 0
|
logger = setup_logger(log_dir, name='dxf2lib')
|
||||||
blocks_in_row = 0
|
logger.info("=== DXF2LIB Verarbeitung gestartet ===")
|
||||||
|
logger.info(f"Input-Verzeichnis: {INPUT_DIR}")
|
||||||
|
logger.info(f"Output-Datei: {OUTPUT_FILE}")
|
||||||
|
|
||||||
for filename in os.listdir(INPUT_DIR):
|
# Lade Config-Datei
|
||||||
if not filename.lower().endswith(".dxf"):
|
config = configparser.ConfigParser()
|
||||||
continue
|
config_path = check_environment_var("PROJECT_CFG") / "allgemein.cfg"
|
||||||
|
config.read(config_path, encoding="utf-8")
|
||||||
filepath = os.path.join(INPUT_DIR, filename)
|
logger.info(f"Config-Datei geladen: {config_path}")
|
||||||
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"}
|
|
||||||
filtered_entities = [e for e in entities if e.dxftype() in allowed_types]
|
|
||||||
entities = filtered_entities
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Fehler beim Lesen von {filename}: {e}")
|
|
||||||
continue
|
|
||||||
|
|
||||||
center = get_bbox(entities)
|
|
||||||
if center is None:
|
|
||||||
print(f"Keine gültige Geometrie in {filename}")
|
|
||||||
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:
|
|
||||||
try:
|
|
||||||
cp = e.copy()
|
|
||||||
cp.translate(-center.x, -center.y, 0) # Geometrie verschieben!
|
|
||||||
blk.add_entity(cp)
|
|
||||||
except Exception as err:
|
|
||||||
print(f"Fehler in {filename}: {err}")
|
|
||||||
|
|
||||||
# Platzierung in Reihen und Spalten
|
|
||||||
msp.add_blockref(name, insert=(x_offset, y_offset))
|
|
||||||
# Text mit Blocknamen über dem Block
|
|
||||||
text_y = y_offset + 175 # 175 Einheiten über dem Block
|
|
||||||
max_len = 9
|
|
||||||
text_height = 20
|
|
||||||
line_spacing = 22 # Abstand zwischen den zwei Textzeilen
|
|
||||||
|
|
||||||
if len(name) > max_len:
|
|
||||||
first_line = name[:max_len]
|
|
||||||
second_line = name[max_len:]
|
|
||||||
# obere Zeile
|
|
||||||
msp.add_text(first_line, dxfattribs={'height': text_height, 'insert': (x_offset - 150, text_y)})
|
|
||||||
# untere Zeile, etwas niedriger (y kleiner)
|
|
||||||
msp.add_text(second_line, dxfattribs={'height': text_height, 'insert': (x_offset - 150, text_y - line_spacing)})
|
|
||||||
else:
|
|
||||||
msp.add_text(name, dxfattribs={'height': text_height, 'insert': (x_offset - 150, text_y)})
|
|
||||||
blocks_in_row += 1
|
|
||||||
x_offset += 350 # Abstand zwischen Blöcken in einer Reihe
|
|
||||||
if blocks_in_row == 20:
|
|
||||||
blocks_in_row = 0
|
|
||||||
x_offset = 0
|
|
||||||
y_offset -= 500 # Neue Zeile, nach unten versetzt
|
|
||||||
|
|
||||||
print(f"Bibliotheks-DXF gespeichert: {OUTPUT_FILE} \n")
|
|
||||||
|
|
||||||
output_dir = os.path.dirname(OUTPUT_FILE)
|
|
||||||
if not os.path.exists(output_dir):
|
|
||||||
os.makedirs(output_dir, exist_ok=True)
|
|
||||||
|
|
||||||
doc.saveas(OUTPUT_FILE)
|
|
||||||
|
|
||||||
|
# Erstelle die Block-Bibliothek
|
||||||
|
logger.info(f"Erstelle Block-Bibliothek aus {INPUT_DIR}...")
|
||||||
|
create_block_library(INPUT_DIR, OUTPUT_FILE, config, logger)
|
||||||
|
|
||||||
if not args.keep:
|
if not args.keep:
|
||||||
print(f"Keep-Argument nicht gesetzt, lösche Input-Verzeichnis: {INPUT_DIR} \n")
|
logger.info(f"Keep-Argument nicht gesetzt, lösche Input-Verzeichnis: {INPUT_DIR}")
|
||||||
try:
|
try:
|
||||||
if os.path.exists(INPUT_DIR):
|
if INPUT_DIR.exists():
|
||||||
shutil.rmtree(INPUT_DIR)
|
shutil.rmtree(INPUT_DIR)
|
||||||
print(f"Verzeichnis '{INPUT_DIR}' wurde gelöscht.")
|
logger.info(f"Verzeichnis '{INPUT_DIR}' wurde gelöscht.")
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
print(f"[WARN] Konnte '{INPUT_DIR}' nicht löschen: {err}")
|
logger.warning(f"Konnte '{INPUT_DIR}' nicht löschen: {err}")
|
||||||
|
|
||||||
|
logger.info("=== DXF2LIB Verarbeitung abgeschlossen ===")
|
||||||
|
|||||||
+87
-32
@@ -13,12 +13,21 @@ import configparser
|
|||||||
import ezdxf
|
import ezdxf
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import math
|
import math
|
||||||
|
from utils import check_environment_var, setup_logger
|
||||||
|
|
||||||
# --------------------------------------------------------- CFG-Leser für shapes.cfg
|
# --------------------------------------------------------- CFG-Leser für shapes.cfg
|
||||||
def get_shape_cfg(teileart, cfg_path):
|
def get_shape_cfg(teileart, cfg_path, logger=None):
|
||||||
parser = configparser.ConfigParser()
|
parser = configparser.ConfigParser()
|
||||||
with open(cfg_path, encoding='utf-8') as f:
|
try:
|
||||||
parser.read_file(f)
|
with open(cfg_path, encoding='utf-8') as f:
|
||||||
|
parser.read_file(f)
|
||||||
|
except Exception as e:
|
||||||
|
msg = f"Fehler beim Lesen der Config-Datei {cfg_path}: {e}"
|
||||||
|
if logger:
|
||||||
|
logger.error(msg)
|
||||||
|
else:
|
||||||
|
print(msg)
|
||||||
|
return []
|
||||||
section = teileart
|
section = teileart
|
||||||
if section not in parser:
|
if section not in parser:
|
||||||
return []
|
return []
|
||||||
@@ -53,14 +62,6 @@ ATTR_TAG = "TeileId" # Attributtag im Block
|
|||||||
RADIUS = 400 # Radius der Kreiselkreise (mm)
|
RADIUS = 400 # Radius der Kreiselkreise (mm)
|
||||||
|
|
||||||
# --------------------------------------------------------- Hilfsfunktionen
|
# --------------------------------------------------------- Hilfsfunktionen
|
||||||
def check_environment_var(env_str: str) -> Path:
|
|
||||||
"""Liefert Path aus Umgebungsvariable oder beendet mit Fehlermeldung."""
|
|
||||||
out_path = os.environ.get(env_str)
|
|
||||||
if out_path:
|
|
||||||
return Path(out_path)
|
|
||||||
print(f"Umgebungsvariable {env_str} ist nicht gesetzt oder leer.")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
def extract_coords(planquadrat: str) -> tuple[float, float]:
|
def extract_coords(planquadrat: str) -> tuple[float, float]:
|
||||||
"""Extrahiert X/Y Koordinaten aus PlanquadratString."""
|
"""Extrahiert X/Y Koordinaten aus PlanquadratString."""
|
||||||
m = re.search(r"X:(\d+[\.,]?\d*)\s+Y:(\d+[\.,]?\d*)", planquadrat)
|
m = re.search(r"X:(\d+[\.,]?\d*)\s+Y:(\d+[\.,]?\d*)", planquadrat)
|
||||||
@@ -87,8 +88,9 @@ def import_block(block_name: str, from_doc, to_doc) -> None:
|
|||||||
for ent in src:
|
for ent in src:
|
||||||
tgt.add_entity(ent.copy())
|
tgt.add_entity(ent.copy())
|
||||||
|
|
||||||
def berechne_hoehe(csv_path):
|
def berechne_hoehe(csv_path, logger=None):
|
||||||
y_werte = []
|
y_werte = []
|
||||||
|
try:
|
||||||
with csv_path.open(newline="", encoding="utf-8") as fh:
|
with csv_path.open(newline="", encoding="utf-8") as fh:
|
||||||
reader = csv.DictReader(fh, delimiter=';')
|
reader = csv.DictReader(fh, delimiter=';')
|
||||||
for row in reader:
|
for row in reader:
|
||||||
@@ -96,11 +98,23 @@ def berechne_hoehe(csv_path):
|
|||||||
try:
|
try:
|
||||||
_, y = extract_coords(planquadrat)
|
_, y = extract_coords(planquadrat)
|
||||||
y_werte.append(y)
|
y_werte.append(y)
|
||||||
except Exception:
|
except Exception as e:
|
||||||
continue
|
if logger:
|
||||||
if not y_werte:
|
logger.warning(f"Fehler beim Extrahieren der Koordinate aus '{planquadrat}': {e}")
|
||||||
raise ValueError("Keine Y-Koordinaten in der CSV gefunden!")
|
except Exception as e:
|
||||||
return max(y_werte)
|
if logger:
|
||||||
|
logger.error(f"Fehler beim Lesen der CSV-Datei {csv_path}: {e}")
|
||||||
|
else:
|
||||||
|
print(f"Fehler beim Lesen der CSV-Datei {csv_path}: {e}")
|
||||||
|
return 0
|
||||||
|
if not y_werte:
|
||||||
|
msg = "Keine Y-Koordinaten in der CSV gefunden!"
|
||||||
|
if logger:
|
||||||
|
logger.error(msg)
|
||||||
|
else:
|
||||||
|
print(msg)
|
||||||
|
raise ValueError(msg)
|
||||||
|
return max(y_werte)
|
||||||
|
|
||||||
def transform_coords(x: float, y: float, height: float) -> tuple[float, float]:
|
def transform_coords(x: float, y: float, height: float) -> tuple[float, float]:
|
||||||
"""Transformiert Bildschirmkoordinaten (0,0 oben links) ins DXF-KoSy (0,0 unten links)."""
|
"""Transformiert Bildschirmkoordinaten (0,0 oben links) ins DXF-KoSy (0,0 unten links)."""
|
||||||
@@ -329,20 +343,45 @@ def get_libfile_cfg(teileart, cfg_path):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
# --------------------------------------------------------- Hauptfunktion
|
# --------------------------------------------------------- Hauptfunktion
|
||||||
def main(csv_path: Path, default_lib_path: Path, shapes_cfg_path: Path, output_path: Path, verbose=False):
|
def main(csv_path: Path, lib_path: Path, cfg_path: Path,
|
||||||
|
output_path: Path, verbose=False, logger=None):
|
||||||
|
|
||||||
# Bibliotheks-Cache
|
# Bibliothek nur laden, wenn Datei existiert
|
||||||
lib_docs = {}
|
lib_doc = None
|
||||||
# Verzeichnisse
|
if lib_path.exists():
|
||||||
data_dir = check_environment_var("PROJECT_DATA")
|
try:
|
||||||
blocklib_dir = Path(data_dir) / "block_libaries"
|
lib_doc = ezdxf.readfile(lib_path)
|
||||||
|
if verbose:
|
||||||
|
logger.info(f"[INFO] Bibliothek geladen: {lib_path}") if logger else print(f"[INFO] Bibliothek geladen: {lib_path}")
|
||||||
|
except Exception as e:
|
||||||
|
msg = f"Fehler beim Lesen der Bibliothek '{lib_path}': {e}"
|
||||||
|
if logger:
|
||||||
|
logger.error(msg)
|
||||||
|
else:
|
||||||
|
print(msg)
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
msg = f"[INFO] Keine Bibliothek gefunden unter {lib_path}."
|
||||||
|
if logger:
|
||||||
|
logger.warning(msg)
|
||||||
|
else:
|
||||||
|
print(msg)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
# Neue Zielzeichnung (DXF R2018)
|
# Neue Zielzeichnung (DXF R2018)
|
||||||
doc = ezdxf.new(dxfversion="R2018")
|
doc = ezdxf.new(dxfversion="R2018")
|
||||||
msp = doc.modelspace()
|
msp = doc.modelspace()
|
||||||
|
|
||||||
# Höhe bestimmen für Koordinaten-Transformation
|
# Höhe bestimmen für Koordinaten-Transformation
|
||||||
height = berechne_hoehe(csv_path)
|
try:
|
||||||
|
height = berechne_hoehe(csv_path, logger=logger)
|
||||||
|
except Exception as e:
|
||||||
|
msg = f"Fehler bei der Höhenberechnung: {e}"
|
||||||
|
if logger:
|
||||||
|
logger.error(msg)
|
||||||
|
else:
|
||||||
|
print(msg)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
# Verarbeitung der Blöcke
|
# Verarbeitung der Blöcke
|
||||||
with csv_path.open(newline="", encoding="utf-8") as fh:
|
with csv_path.open(newline="", encoding="utf-8") as fh:
|
||||||
@@ -357,7 +396,11 @@ def main(csv_path: Path, default_lib_path: Path, shapes_cfg_path: Path, output_p
|
|||||||
x_screen, y_screen = extract_coords(planquadrat)
|
x_screen, y_screen = extract_coords(planquadrat)
|
||||||
x, y = transform_coords(x_screen, y_screen, height)
|
x, y = transform_coords(x_screen, y_screen, height)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[WARN] {teileid}: {e}")
|
msg = f"[WARN] {teileid}: {e}"
|
||||||
|
if logger:
|
||||||
|
logger.warning(msg)
|
||||||
|
else:
|
||||||
|
print(msg)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Bibliotheksdatei bestimmen
|
# Bibliotheksdatei bestimmen
|
||||||
@@ -385,19 +428,26 @@ def main(csv_path: Path, default_lib_path: Path, shapes_cfg_path: Path, output_p
|
|||||||
# Funktions-Dispatch: handle_<teileart> (mit _ statt Leerzeichen und Punkten, alles klein)
|
# Funktions-Dispatch: handle_<teileart> (mit _ statt Leerzeichen und Punkten, alles klein)
|
||||||
func_name = f'handle_{normalize_func_name(teileart)}'
|
func_name = f'handle_{normalize_func_name(teileart)}'
|
||||||
handler = globals().get(func_name)
|
handler = globals().get(func_name)
|
||||||
symbols = get_shape_cfg(teileart, shapes_cfg_path)
|
symbols = get_shape_cfg(teileart, shapes_cfg_path, logger=logger)
|
||||||
# Mapping für Omniflo-Typen
|
# Mapping für Omniflo-Typen
|
||||||
if func_name.startswith('handle_omniflo'):
|
if func_name.startswith('handle_omniflo'):
|
||||||
handler = globals().get('handle_omniflo')
|
handler = globals().get('handle_omniflo')
|
||||||
if handler:
|
if handler:
|
||||||
handler(msp, teileid, merkmale, x, y, doc, lib_doc, verbose, symbols)
|
handler(msp, teileid, merkmale, x, y, doc, lib_doc, verbose, symbols)
|
||||||
else:
|
else:
|
||||||
print(f"[WARN] Keine Routine für TeileArt '{teileart}'. Überspringe '{teileid}'.")
|
msg = f"[WARN] Keine Routine für TeileArt '{teileart}'. Überspringe '{teileid}'."
|
||||||
continue
|
if logger:
|
||||||
|
logger.warning(msg)
|
||||||
|
else:
|
||||||
|
print(msg)
|
||||||
|
continue
|
||||||
|
|
||||||
# DXF speichern
|
# DXF speichern
|
||||||
doc.saveas(output_path)
|
doc.saveas(output_path)
|
||||||
print(f"[DONE] DXF gespeichert unter: {output_path}")
|
if logger:
|
||||||
|
logger.info(f"[DONE] DXF gespeichert unter: {output_path}")
|
||||||
|
else:
|
||||||
|
print(f"[DONE] DXF gespeichert unter: {output_path}")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
@@ -410,10 +460,14 @@ if __name__ == "__main__":
|
|||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
# Verzeichnisse aus Umgebungsvariablen
|
# Verzeichnisse aus Umgebungsvariablen
|
||||||
|
log_dir = check_environment_var("PROJECT_LOG")
|
||||||
data_dir = check_environment_var("PROJECT_DATA")
|
data_dir = check_environment_var("PROJECT_DATA")
|
||||||
work_dir = check_environment_var("PROJECT_WORK")
|
work_dir = check_environment_var("PROJECT_WORK")
|
||||||
config_dir = check_environment_var("PROJECT_CFG")
|
config_dir = check_environment_var("PROJECT_CFG")
|
||||||
|
|
||||||
|
logger = setup_logger(log_dir, name='plant2dxf')
|
||||||
|
logger.info("=== plant2dxf Verarbeitung gestartet ===")
|
||||||
|
|
||||||
# CSV‑Pfad: nur Dateiname → im WORK‑Ordner suchen
|
# CSV‑Pfad: nur Dateiname → im WORK‑Ordner suchen
|
||||||
if os.sep not in args.file and "/" not in args.file:
|
if os.sep not in args.file and "/" not in args.file:
|
||||||
csv_path = work_dir / args.file
|
csv_path = work_dir / args.file
|
||||||
@@ -425,4 +479,5 @@ if __name__ == "__main__":
|
|||||||
default_lib_path = Path(args.lib) if args.lib else data_dir / "blocks.dxf"
|
default_lib_path = Path(args.lib) if args.lib else data_dir / "blocks.dxf"
|
||||||
output_path = Path(args.output) if args.output else (work_dir / f"{csv_path.stem}.dxf")
|
output_path = Path(args.output) if args.output else (work_dir / f"{csv_path.stem}.dxf")
|
||||||
|
|
||||||
main(csv_path, default_lib_path, cfg_path, output_path, verbose=args.verbose)
|
main(csv_path, default_lib_path, cfg_path, output_path, verbose=args.verbose, logger=logger)
|
||||||
|
logger.info("=== plant2dxf Verarbeitung abgeschlossen ===")
|
||||||
|
|||||||
+256
-30
@@ -3,14 +3,219 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
import argparse
|
import argparse
|
||||||
import time
|
import time
|
||||||
# Hilfsfunktion wie in plant2dxf.py
|
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
|
||||||
|
|
||||||
def check_environment_var(env_str: str, fallback: str) -> str:
|
from utils import check_environment_var, setup_logger
|
||||||
out_path = os.environ.get(env_str)
|
|
||||||
if out_path:
|
class SVGtoDXFConverter:
|
||||||
return out_path
|
def __init__(self, logger=None):
|
||||||
print(f"[WARN] Umgebungsvariable {env_str} ist nicht gesetzt oder leer. Verwende '{fallback}'.")
|
# Standard DXF-Farben (AutoCAD Color Index)
|
||||||
return fallback
|
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__":
|
if __name__ == "__main__":
|
||||||
@@ -18,7 +223,8 @@ if __name__ == "__main__":
|
|||||||
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('-o', '--output', type=str, help='Output-Verzeichnis für DXF-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"):
|
if len(sys.argv) == 2 and sys.argv[1] in ("-h", "--help"):
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
@@ -27,37 +233,57 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
# Verzeichnisse über Umgebungsvariablen oder Fallback
|
# Verzeichnisse über Umgebungsvariablen oder Fallback
|
||||||
if args.input:
|
if args.input:
|
||||||
INPUT_DIR = args.input
|
INPUT_DIR = Path(args.input)
|
||||||
print(f"Input-Verzeichnis: {INPUT_DIR} \n")
|
print(f"Input-Verzeichnis: {INPUT_DIR} \n")
|
||||||
else:
|
else:
|
||||||
INPUT_DIR = os.path.join(check_environment_var("PROJECT_DATA", "."), "svg")
|
INPUT_DIR = check_environment_var("PROJECT_DATA") / "svg"
|
||||||
print(f"Kein Input-Verzeichnis angegeben, verwende Standard: {INPUT_DIR} \n")
|
print(f"Kein Input-Verzeichnis angegeben, verwende Standard: {INPUT_DIR} \n")
|
||||||
|
|
||||||
if args.output:
|
if args.output:
|
||||||
OUTPUT_DIR = args.output
|
OUTPUT_DIR = Path(args.output)
|
||||||
print(f"Output-Verzeichnis: {OUTPUT_DIR} \n")
|
print(f"Output-Verzeichnis: {OUTPUT_DIR} \n")
|
||||||
else:
|
else:
|
||||||
OUTPUT_DIR = os.path.join(check_environment_var("PROJECT_WORK", "."), "converted_dxfs")
|
OUTPUT_DIR = check_environment_var("PROJECT_WORK") / "converted_dxfs"
|
||||||
print(f"Kein Output-Verzeichnis angegeben, verwende Standard: {OUTPUT_DIR} \n")
|
print(f"Kein Output-Verzeichnis angegeben, verwende Standard: {OUTPUT_DIR} \n")
|
||||||
|
|
||||||
INKSCAPE = "inkscape" # ggf. vollständiger Pfad angeben
|
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
|
||||||
|
|
||||||
files = [f for f in os.listdir(INPUT_DIR) if f.lower().endswith((".svg", ".xml"))]
|
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 ===")
|
||||||
|
|
||||||
for filename in files:
|
|
||||||
input_path = os.path.join(INPUT_DIR, filename)
|
|
||||||
output_path = os.path.join(OUTPUT_DIR, os.path.splitext(filename)[0] + ".dxf")
|
|
||||||
|
|
||||||
print(f"Konvertiere: {input_path} → {output_path}")
|
|
||||||
try:
|
|
||||||
subprocess.run([
|
|
||||||
INKSCAPE,
|
|
||||||
input_path,
|
|
||||||
"--export-type=dxf",
|
|
||||||
f"--export-filename={output_path}"
|
|
||||||
], check=True)
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
print(f"[FEHLER] Inkscape-Absturz bei {filename}: {e}")
|
|
||||||
time.sleep(0.5) # kleine Pause zur Entlastung von RAM/GTK
|
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def check_environment_var(env_str: str) -> Path:
|
||||||
|
"""Liefert Path aus Umgebungsvariable oder beendet mit Fehlermeldung."""
|
||||||
|
out_path = os.environ.get(env_str)
|
||||||
|
if out_path:
|
||||||
|
return Path(out_path)
|
||||||
|
msg = f"Umgebungsvariable {env_str} ist nicht gesetzt oder leer."
|
||||||
|
print(msg)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_logger(log_dir: Path, name: str = 'app') -> logging.Logger:
|
||||||
|
"""
|
||||||
|
Erstellt und konfiguriert einen Logger, der sowohl in eine Datei als auch auf die Konsole schreibt.
|
||||||
|
Die Logdatei erhält einen Zeitstempel im Namen und ist UTF-8 kodiert.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
log_dir (Path): Verzeichnis für die Log-Dateien
|
||||||
|
name (str): Name des Loggers (z.B. 'plant2dxf', 'dxf2lib')
|
||||||
|
Returns:
|
||||||
|
logging.Logger: Konfigurierter Logger
|
||||||
|
"""
|
||||||
|
from datetime import datetime
|
||||||
|
logger = logging.getLogger(name)
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
# Vorherige Handler entfernen, um doppelte Logs zu vermeiden
|
||||||
|
for handler in logger.handlers[:]:
|
||||||
|
logger.removeHandler(handler)
|
||||||
|
|
||||||
|
# Log-Verzeichnis anlegen (falls nicht vorhanden)
|
||||||
|
log_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# Logdatei mit Zeitstempel im Namen erzeugen
|
||||||
|
log_file = log_dir / f"{name}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
|
||||||
|
file_handler = logging.FileHandler(log_file, encoding='utf-8')
|
||||||
|
file_handler.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
# Handler für die Konsole
|
||||||
|
console_handler = logging.StreamHandler()
|
||||||
|
console_handler.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
# Einheitliches Log-Format definieren
|
||||||
|
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
|
||||||
|
file_handler.setFormatter(formatter)
|
||||||
|
console_handler.setFormatter(formatter)
|
||||||
|
|
||||||
|
# Handler dem Logger hinzufügen
|
||||||
|
logger.addHandler(file_handler)
|
||||||
|
logger.addHandler(console_handler)
|
||||||
|
return logger
|
||||||
Reference in New Issue
Block a user