utils.py mit eingecheckt. Beispieldatei aus Config erzeugen.
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
@echo off
|
||||||
|
CALL manage_interpreter.bat activate_interpreter
|
||||||
|
python %PROJECT_LIB%\create_example.py %*
|
||||||
|
CALL manage_interpreter.bat deactivate_interpreter
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
[Layers]
|
||||||
|
ILS_RENAMER
|
||||||
+146
-8
@@ -10,6 +10,7 @@ für verschiedene Testfälle (Nummerierung, SPS, Erdung, etc.).
|
|||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import ezdxf
|
import ezdxf
|
||||||
from ezdxf.math import Vec3
|
from ezdxf.math import Vec3
|
||||||
@@ -17,16 +18,44 @@ from ezdxf.math import Vec3
|
|||||||
# Umgebungsvariablen
|
# Umgebungsvariablen
|
||||||
PROJECT = os.getenv('PROJECT', Path(__file__).parent.parent.absolute())
|
PROJECT = os.getenv('PROJECT', Path(__file__).parent.parent.absolute())
|
||||||
PROJECT_TEST = os.getenv('PROJECT_TEST', os.path.join(PROJECT, 'testdata'))
|
PROJECT_TEST = os.getenv('PROJECT_TEST', os.path.join(PROJECT, 'testdata'))
|
||||||
|
PROJECT_CFG = os.getenv('PROJECT_CFG', os.path.join(PROJECT, 'cfg'))
|
||||||
|
|
||||||
|
|
||||||
class TestDataGenerator:
|
class TestDataGenerator:
|
||||||
"""Generiert Test-DXF-Dateien für verschiedene Szenarien"""
|
"""Generiert Test-DXF-Dateien für verschiedene Szenarien"""
|
||||||
|
|
||||||
def __init__(self, filename, test_type):
|
def __init__(self, filename, test_type, config_file=None):
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
self.test_type = test_type
|
self.test_type = test_type
|
||||||
self.doc = None
|
self.doc = None
|
||||||
self.msp = None
|
self.msp = None
|
||||||
|
self.config = None
|
||||||
|
|
||||||
|
# Lade Config-Datei falls angegeben
|
||||||
|
if config_file:
|
||||||
|
self.load_config(config_file)
|
||||||
|
|
||||||
|
def load_config(self, config_file):
|
||||||
|
"""
|
||||||
|
Lädt die JSON-Konfigurationsdatei
|
||||||
|
|
||||||
|
Args:
|
||||||
|
config_file: Pfad zur JSON-Config-Datei
|
||||||
|
"""
|
||||||
|
config_path = Path(config_file)
|
||||||
|
if not config_path.is_absolute():
|
||||||
|
config_path = Path(PROJECT_CFG) / config_file
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(config_path, 'r', encoding='utf-8') as f:
|
||||||
|
self.config = json.load(f)
|
||||||
|
print(f"Config geladen: {config_path}")
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f"Warnung: Config-Datei nicht gefunden: {config_path}")
|
||||||
|
self.config = None
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
print(f"Fehler beim Parsen der Config-Datei: {e}")
|
||||||
|
self.config = None
|
||||||
|
|
||||||
def create_document(self):
|
def create_document(self):
|
||||||
"""Erstellt ein neues DXF-Dokument"""
|
"""Erstellt ein neues DXF-Dokument"""
|
||||||
@@ -437,11 +466,7 @@ class TestDataGenerator:
|
|||||||
"""
|
"""
|
||||||
Generiert Testdaten für Nummerierung-Szenario
|
Generiert Testdaten für Nummerierung-Szenario
|
||||||
|
|
||||||
Layout:
|
Verwendet entweder die JSON-Config oder Hard-coded Defaults
|
||||||
- 3x MA-1@@ rechts oben nebeneinander
|
|
||||||
- 1x MA-1@@ links unten
|
|
||||||
- 3x MA-2@@ rechts unten (leicht versetzt)
|
|
||||||
- 3 Renamer-Rahmen (2x Rechteck, 1x Polylinie)
|
|
||||||
"""
|
"""
|
||||||
print("Generiere Nummerierung-Testdaten...")
|
print("Generiere Nummerierung-Testdaten...")
|
||||||
|
|
||||||
@@ -452,6 +477,113 @@ class TestDataGenerator:
|
|||||||
print("Fehler: io-Block konnte nicht erstellt werden")
|
print("Fehler: io-Block konnte nicht erstellt werden")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Wenn Config geladen ist, verwende sie, ansonsten Hard-coded Defaults
|
||||||
|
if self.config and 'test_scenes' in self.config and 'Nummerierung1' in self.config['test_scenes']:
|
||||||
|
print("Verwende JSON-Config für Nummerierung1...")
|
||||||
|
self._generate_nummerierung_from_config()
|
||||||
|
else:
|
||||||
|
print("Verwende Hard-coded Defaults für Nummerierung...")
|
||||||
|
self._generate_nummerierung_hardcoded()
|
||||||
|
|
||||||
|
def _generate_ma_group(self, group, ma_defaults, width_per_char, fixed_height, spacing_factor, y_offsets_list):
|
||||||
|
"""
|
||||||
|
Generiert eine Gruppe von MA-Symbolen
|
||||||
|
|
||||||
|
Args:
|
||||||
|
group: Dict mit Gruppen-Config (name, count, layout_type, base_x, base_y, spacing)
|
||||||
|
ma_defaults: Dict mit MA-Default-Attributen
|
||||||
|
width_per_char: Breite pro Zeichen
|
||||||
|
fixed_height: Fixe Symbol-Höhe
|
||||||
|
spacing_factor: Spacing-Faktor zwischen Symbolen
|
||||||
|
y_offsets_list: Liste von Y-Offsets für horizontal_offset Layout
|
||||||
|
"""
|
||||||
|
name = group['name']
|
||||||
|
count = group['count']
|
||||||
|
layout_type = group['layout_type']
|
||||||
|
base_x = group['base_x']
|
||||||
|
base_y = group['base_y']
|
||||||
|
|
||||||
|
# Extrahiere IO-Pattern aus Name (z.B. "MA-1@@_top" -> "MA-1@@")
|
||||||
|
io_pattern = name.split('_')[0]
|
||||||
|
|
||||||
|
# Berechne Symbol-Ausdehnung
|
||||||
|
symbol_width = len(io_pattern) * width_per_char
|
||||||
|
|
||||||
|
# Berechne Spacing
|
||||||
|
if layout_type in ['horizontal', 'horizontal_offset']:
|
||||||
|
actual_spacing = symbol_width * spacing_factor
|
||||||
|
else:
|
||||||
|
actual_spacing = 0
|
||||||
|
|
||||||
|
# Hole Attribute aus ma_defaults
|
||||||
|
attributes = ma_defaults.get('attributes', {})
|
||||||
|
layer = ma_defaults.get('layer', 'ILS_MOTOR')
|
||||||
|
|
||||||
|
# Generiere Symbole basierend auf Layout-Typ
|
||||||
|
for i in range(count):
|
||||||
|
if layout_type == 'single':
|
||||||
|
x, y = base_x, base_y
|
||||||
|
elif layout_type == 'horizontal':
|
||||||
|
x = base_x + i * actual_spacing
|
||||||
|
y = base_y
|
||||||
|
elif layout_type == 'horizontal_offset':
|
||||||
|
x = base_x + i * actual_spacing
|
||||||
|
y_offset = y_offsets_list[i % len(y_offsets_list)]
|
||||||
|
y = base_y + y_offset
|
||||||
|
else:
|
||||||
|
print(f"Unbekannter Layout-Typ: {layout_type}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Erstelle Block-Referenz
|
||||||
|
blockref = self.msp.add_blockref(
|
||||||
|
'io',
|
||||||
|
insert=(x, y),
|
||||||
|
dxfattribs={'layer': layer}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Setze Attribute (dynamisch IO und BEZEICHNUNG)
|
||||||
|
attrib_values = attributes.copy()
|
||||||
|
attrib_values['IO'] = io_pattern
|
||||||
|
attrib_values['BEZEICHNUNG'] = f"Motor {io_pattern}"
|
||||||
|
|
||||||
|
blockref.add_auto_attribs(attrib_values)
|
||||||
|
|
||||||
|
def _generate_nummerierung_from_config(self):
|
||||||
|
"""Generiert Nummerierung-Testdaten aus JSON-Config"""
|
||||||
|
scene = self.config['test_scenes']['Nummerierung1']
|
||||||
|
ma_defaults = self.config.get('ma_defaults', {})
|
||||||
|
general = self.config.get('general', {})
|
||||||
|
|
||||||
|
# Hole Dimensions aus Config
|
||||||
|
dimensions = ma_defaults.get('dimensions', {})
|
||||||
|
width_per_char = dimensions.get('width_per_char', 201.49)
|
||||||
|
fixed_height = dimensions.get('fixed_height', 380.94)
|
||||||
|
|
||||||
|
# Hole Layout-Einstellungen
|
||||||
|
layout = general.get('layout', {})
|
||||||
|
spacing_factor = layout.get('symbol_spacing_factor', 1.2)
|
||||||
|
y_offsets_list = layout.get('horizontal_offset_y_offsets', [0, -50, 50])
|
||||||
|
|
||||||
|
# Generiere MA-Gruppen aus Config
|
||||||
|
for group in scene.get('ma_groups', []):
|
||||||
|
self._generate_ma_group(group, ma_defaults, width_per_char, fixed_height, spacing_factor, y_offsets_list)
|
||||||
|
|
||||||
|
# Generiere Renamer-Rahmen aus Config
|
||||||
|
for frame_config in scene.get('renaming_frames', []):
|
||||||
|
self.create_renamer_frame(
|
||||||
|
insert_point=(frame_config['x'], frame_config['y']),
|
||||||
|
width=frame_config['width'],
|
||||||
|
height=frame_config['height'],
|
||||||
|
name=frame_config['name'],
|
||||||
|
kennzeichnung=frame_config['kennzeichnung'],
|
||||||
|
layer_name=frame_config['layer_name'],
|
||||||
|
direction=frame_config['direction'],
|
||||||
|
use_polyline=frame_config.get('use_polyline', False)
|
||||||
|
)
|
||||||
|
|
||||||
|
def _generate_nummerierung_hardcoded(self):
|
||||||
|
"""Generiert Nummerierung-Testdaten mit Hard-coded Werten (Fallback)"""
|
||||||
|
|
||||||
# Berechne Symbol-Ausdehnung für korrekte Positionierung
|
# Berechne Symbol-Ausdehnung für korrekte Positionierung
|
||||||
ma1_width, ma1_height = self.calculate_symbol_extent('MA-1@@')
|
ma1_width, ma1_height = self.calculate_symbol_extent('MA-1@@')
|
||||||
ma2_width, ma2_height = self.calculate_symbol_extent('MA-2@@')
|
ma2_width, ma2_height = self.calculate_symbol_extent('MA-2@@')
|
||||||
@@ -535,7 +667,6 @@ class TestDataGenerator:
|
|||||||
'ARTIKELBEZEICHN': 'E-Teile für SEW Motor ASE1-HAN10ES-BG',
|
'ARTIKELBEZEICHN': 'E-Teile für SEW Motor ASE1-HAN10ES-BG',
|
||||||
'KENNZEICHNUNG': '=A01+UH00',
|
'KENNZEICHNUNG': '=A01+UH00',
|
||||||
'TEXT-D': '',
|
'TEXT-D': '',
|
||||||
'TEXT-E': '',
|
|
||||||
'TEXT-ES': '',
|
'TEXT-ES': '',
|
||||||
'TEXT-F': '',
|
'TEXT-F': '',
|
||||||
'ID': '',
|
'ID': '',
|
||||||
@@ -642,6 +773,13 @@ def main():
|
|||||||
help='Ausgabeverzeichnis (Standard: testdata)'
|
help='Ausgabeverzeichnis (Standard: testdata)'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'--config',
|
||||||
|
type=str,
|
||||||
|
default='create_tests.json',
|
||||||
|
help='JSON-Konfigurationsdatei (Standard: create_tests.json)'
|
||||||
|
)
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
# Stelle sicher, dass Dateiname .dxf Endung hat
|
# Stelle sicher, dass Dateiname .dxf Endung hat
|
||||||
@@ -649,7 +787,7 @@ def main():
|
|||||||
args.filename += '.dxf'
|
args.filename += '.dxf'
|
||||||
|
|
||||||
# Generiere Testdaten
|
# Generiere Testdaten
|
||||||
generator = TestDataGenerator(args.filename, args.type)
|
generator = TestDataGenerator(args.filename, args.type, config_file=args.config)
|
||||||
|
|
||||||
if generator.generate():
|
if generator.generate():
|
||||||
generator.save(args.output_dir)
|
generator.save(args.output_dir)
|
||||||
|
|||||||
+191
@@ -0,0 +1,191 @@
|
|||||||
|
"""
|
||||||
|
Common utility functions for the cable routing system.
|
||||||
|
|
||||||
|
This module contains frequently used helper functions for:
|
||||||
|
- File and path operations
|
||||||
|
- Environment variable handling
|
||||||
|
- JSON operations
|
||||||
|
- DXF file handling
|
||||||
|
- Dictionary operations
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Tuple
|
||||||
|
|
||||||
|
import ezdxf
|
||||||
|
from ezdxf.lldxf.const import DXFStructureError
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# File Operations
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
def check_file_in_work(work_dir, filename) -> Tuple[Path, bool]:
|
||||||
|
"""
|
||||||
|
Check if a file exists, either at the given path or in the work directory.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
work_dir: Working directory to check if file not found at filename path (str or Path)
|
||||||
|
filename: Path to the file to check (str or Path)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple of (resolved_path, exists_flag)
|
||||||
|
- resolved_path: Path where file was found (or would be)
|
||||||
|
- exists_flag: True if file exists, False otherwise
|
||||||
|
"""
|
||||||
|
work_dir = Path(work_dir)
|
||||||
|
filename = Path(filename)
|
||||||
|
|
||||||
|
fexists = True
|
||||||
|
if not filename.exists():
|
||||||
|
mypath = work_dir.joinpath(filename)
|
||||||
|
if not mypath.exists():
|
||||||
|
fexists = False
|
||||||
|
else:
|
||||||
|
mypath = filename
|
||||||
|
return mypath, fexists
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Environment Variable Operations
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
def check_environment_var(env_str: str) -> Path:
|
||||||
|
"""
|
||||||
|
Get and validate an environment variable as a Path.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
env_str: Name of the environment variable
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Path object from the environment variable
|
||||||
|
|
||||||
|
Exits:
|
||||||
|
Exits the program if the environment variable is not set or empty
|
||||||
|
"""
|
||||||
|
out_path = os.environ.get(env_str)
|
||||||
|
if out_path:
|
||||||
|
return Path(out_path)
|
||||||
|
else:
|
||||||
|
print(f"Umgebungsvariable {env_str} ist nicht gesetzt oder leer.")
|
||||||
|
exit()
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# JSON Operations
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
def to_json(d: Any, pretty: bool = True) -> str:
|
||||||
|
"""
|
||||||
|
Convert a Python object to a JSON string.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
d: Object to convert to JSON
|
||||||
|
pretty: If True, format with indentation for readability
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSON string representation of the object
|
||||||
|
|
||||||
|
Note:
|
||||||
|
Uses ensure_ascii=False to properly display German umlauts (ä, ö, ü)
|
||||||
|
"""
|
||||||
|
return json.dumps(d, indent=2 if pretty else None, ensure_ascii=False, default=str)
|
||||||
|
|
||||||
|
|
||||||
|
def load_json(jsonfilename: str) -> dict:
|
||||||
|
"""
|
||||||
|
Load JSON data from a file.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
jsonfilename: Path to the JSON file
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Parsed JSON data as a dictionary
|
||||||
|
"""
|
||||||
|
with open(jsonfilename, encoding='utf-8') as fh:
|
||||||
|
return json.load(fh)
|
||||||
|
|
||||||
|
|
||||||
|
def write_results(jsn_results: str, out_dir: Path, filename: str) -> None:
|
||||||
|
"""
|
||||||
|
Write JSON results to a file.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
jsn_results: JSON string to write
|
||||||
|
out_dir: Output directory path
|
||||||
|
filename: Name of the output file
|
||||||
|
"""
|
||||||
|
print("writing results to a json file ...")
|
||||||
|
outfile = Path(out_dir) / filename
|
||||||
|
with open(outfile, 'w', encoding='utf-8') as fh:
|
||||||
|
fh.write(jsn_results)
|
||||||
|
print("done")
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# DXF File Operations
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
def dxf_is_binary(dxf_path: Path) -> bool:
|
||||||
|
"""
|
||||||
|
Check if a DXF file is in binary format.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
dxf_path: Path to the DXF file
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if the file is a binary DXF, False otherwise
|
||||||
|
"""
|
||||||
|
with open(dxf_path, 'rb') as f:
|
||||||
|
header = f.read(22)
|
||||||
|
return b'AutoCAD Binary DXF' in header
|
||||||
|
|
||||||
|
|
||||||
|
def get_dxf_file(filepath: Path):
|
||||||
|
"""
|
||||||
|
Load a DXF file using ezdxf.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filepath: Path to the DXF file
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ezdxf Drawing object
|
||||||
|
|
||||||
|
Exits:
|
||||||
|
Exits with code 1 for I/O errors
|
||||||
|
Exits with code 2 for invalid/corrupted DXF files
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
print("reading file ..", end='')
|
||||||
|
doc = ezdxf.filemanagement.readfile(filepath)
|
||||||
|
print("done")
|
||||||
|
except IOError:
|
||||||
|
print("Not a DXF file or a generic I/O error.")
|
||||||
|
sys.exit(1)
|
||||||
|
except DXFStructureError:
|
||||||
|
print("Invalid or corrupted DXF file.")
|
||||||
|
sys.exit(2)
|
||||||
|
return doc
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Dictionary Operations
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
def merge_two_dicts(x: dict, y: dict) -> dict:
|
||||||
|
"""
|
||||||
|
Merge two dictionaries, with values from y overwriting those in x.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
x: First dictionary (base)
|
||||||
|
y: Second dictionary (override values)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
New dictionary with merged values
|
||||||
|
"""
|
||||||
|
z = x.copy()
|
||||||
|
z.update(y)
|
||||||
|
return z
|
||||||
Reference in New Issue
Block a user