utils.py mit eingecheckt. Beispieldatei aus Config erzeugen.

This commit is contained in:
2026-01-23 16:05:22 +01:00
parent d55d71d623
commit 84f8751eff
4 changed files with 343 additions and 8 deletions
+4
View File
@@ -0,0 +1,4 @@
@echo off
CALL manage_interpreter.bat activate_interpreter
python %PROJECT_LIB%\create_example.py %*
CALL manage_interpreter.bat deactivate_interpreter
+2
View File
@@ -0,0 +1,2 @@
[Layers]
ILS_RENAMER
+146 -8
View File
@@ -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
View File
@@ -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