1709 lines
66 KiB
Python
1709 lines
66 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
create_example.py - Erzeugt Test-DXF-Dateien für verschiedene Testszenarien
|
|
|
|
Dieses Skript generiert synthetische DXF-Testdateien mit definierten Layouts
|
|
für verschiedene Testfälle (Nummerierung, SPS, Erdung, etc.).
|
|
"""
|
|
|
|
import argparse
|
|
import sys
|
|
import os
|
|
import json
|
|
from pathlib import Path
|
|
import ezdxf
|
|
|
|
from utils import draw_symbol_frames
|
|
|
|
# Umgebungsvariablen
|
|
PROJECT = os.getenv('PROJECT', Path(__file__).parent.parent.absolute())
|
|
PROJECT_WORK = os.getenv('PROJECT_WORK', os.path.join(PROJECT, 'work'))
|
|
PROJECT_CFG = os.getenv('PROJECT_CFG', os.path.join(PROJECT, 'cfg'))
|
|
|
|
|
|
class TestDataGenerator:
|
|
"""Generiert Test-DXF-Dateien für verschiedene Szenarien"""
|
|
|
|
def __init__(self, filename, scene_name, config_file=None):
|
|
self.filename = filename
|
|
self.scene_name = scene_name
|
|
self.doc = None
|
|
self.msp = None
|
|
self.config = None
|
|
self.generated_symbols = [] # Liste von (x, y, io_pattern, group_name) für Validierung
|
|
|
|
# Lade Config-Datei (muss immer angegeben werden)
|
|
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"FEHLER: 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):
|
|
"""Erstellt ein neues DXF-Dokument"""
|
|
self.doc = ezdxf.new('R2010')
|
|
self.msp = self.doc.modelspace()
|
|
|
|
# Erstelle textstyle3 für die Attribute
|
|
self.create_textstyle()
|
|
|
|
# Erstelle Layer mit korrekten Farben
|
|
self.create_layers()
|
|
|
|
return self.doc
|
|
|
|
def create_textstyle(self):
|
|
"""Erstellt den textstyle3 für Attribute"""
|
|
if 'textstyle3' not in self.doc.styles:
|
|
self.doc.styles.new('textstyle3', dxfattribs={
|
|
'font': 'arial.ttf',
|
|
'width': 1.0
|
|
})
|
|
|
|
def create_layers(self):
|
|
"""
|
|
Erstellt alle benötigten Layer mit korrekten Farben
|
|
Basiert auf Analyse von easy.dxf
|
|
"""
|
|
# Layer-Definitionen mit Farben
|
|
layer_defs = [
|
|
{'name': '0', 'color': 7}, # Standard-Layer
|
|
{'name': 'ILS_MOTOR', 'color': 7},
|
|
{'name': 'ILS_RENAMER', 'color': 1}, # Rot
|
|
{'name': 'ILS_Eingang', 'color': 7}, # BG-Symbole
|
|
{'name': 'ILS_Ausgang', 'color': 7}, # MB-Symbole
|
|
{'name': 'ILS_POT-Erdung', 'color': 7}, # POT-Symbole
|
|
{'name': 'VALIDATION_ERROR', 'color': 1}, # Rot für Fehlerkreise
|
|
{'name': 'TEXT-D', 'color': -7}, # Ausgeschaltet (negativ)
|
|
{'name': 'TEXT-E', 'color': 7},
|
|
{'name': 'TEXT-ES', 'color': 7},
|
|
{'name': 'TEXT-F', 'color': 7},
|
|
{'name': 'TEXT-I', 'color': 7}
|
|
]
|
|
|
|
for layer_def in layer_defs:
|
|
if layer_def['name'] not in self.doc.layers:
|
|
self.doc.layers.new(layer_def['name'], dxfattribs={
|
|
'color': layer_def['color']
|
|
})
|
|
|
|
def calculate_symbol_extent(self, io_text):
|
|
"""
|
|
Berechnet die ungefähre Ausdehnung eines io-Symbols basierend auf der IO-Länge
|
|
|
|
Basierend auf Analyse von IO=MA0062 (6 Zeichen):
|
|
- Bounding Box: 1208.94 x 380.94 Einheiten
|
|
- ~201.49 Einheiten Breite pro Zeichen
|
|
- ~380.94 Einheiten Höhe (konstant)
|
|
|
|
Args:
|
|
io_text: Text des IO-Attributs (z.B. "MA-1@@")
|
|
|
|
Returns:
|
|
(width, height) in DXF-Einheiten
|
|
"""
|
|
char_count = len(io_text)
|
|
width_per_char = 201.49
|
|
fixed_height = 380.94
|
|
|
|
width = char_count * width_per_char
|
|
height = fixed_height
|
|
|
|
return (width, height)
|
|
|
|
def create_io_block(self):
|
|
"""
|
|
Erstellt den io-Block mit allen Attribut-Definitionen
|
|
Basiert auf dem io-Block aus easy.dxf (IO=MA0062)
|
|
|
|
Returns:
|
|
Block-Definition
|
|
"""
|
|
if 'io' in self.doc.blocks:
|
|
return self.doc.blocks['io']
|
|
|
|
# Erstelle neuen Block
|
|
io_block = self.doc.blocks.new('io')
|
|
|
|
# Attribut-Definitionen basierend auf ILS_Motor-790902001 aus Nummerierung_IO.dxf
|
|
# invisible=True: ID, BEZEICHNUNG, KENNZEICHNUNG, ARTIKELNR, ARTIKELBEZEICHN, TEXT-I
|
|
# invisible=False: IO, VERW, SPS, REALE_POSITION, TEXT-D, TEXT-E, TEXT-ES, TEXT-F
|
|
attrib_defs = [
|
|
{
|
|
'tag': 'REALE_POSITION',
|
|
'insert': (0.0, 230.0, 0.0),
|
|
'height': 50.0,
|
|
'default': 'x',
|
|
'layer': 'ILS_MOTOR',
|
|
'invisible': False
|
|
},
|
|
{
|
|
'tag': 'SPS',
|
|
'insert': (0.0, 130.37, 0.0),
|
|
'height': 125.0,
|
|
'default': '1',
|
|
'layer': 'ILS_MOTOR',
|
|
'invisible': False
|
|
},
|
|
{
|
|
'tag': 'IO',
|
|
'insert': (103.88, 130.37, 0.0),
|
|
'height': 125.0,
|
|
'default': 'MA0000',
|
|
'layer': 'ILS_MOTOR',
|
|
'invisible': False
|
|
},
|
|
{
|
|
'tag': 'TEXT-D',
|
|
'insert': (772.87, 110.44, 0.0),
|
|
'height': 63.75,
|
|
'default': '',
|
|
'layer': 'TEXT-D',
|
|
'invisible': False
|
|
},
|
|
{
|
|
'tag': 'TEXT-E',
|
|
'insert': (772.87, 110.44, 0.0),
|
|
'height': 63.75,
|
|
'default': '',
|
|
'layer': 'TEXT-E',
|
|
'invisible': False
|
|
},
|
|
{
|
|
'tag': 'TEXT-ES',
|
|
'insert': (772.87, 110.44, 0.0),
|
|
'height': 63.75,
|
|
'default': '',
|
|
'layer': 'TEXT-ES',
|
|
'invisible': False
|
|
},
|
|
{
|
|
'tag': 'TEXT-F',
|
|
'insert': (772.87, 110.44, 0.0),
|
|
'height': 63.75,
|
|
'default': '',
|
|
'layer': 'TEXT-F',
|
|
'invisible': False
|
|
},
|
|
{
|
|
'tag': 'TEXT-I',
|
|
'insert': (772.87, 110.44, 0.0),
|
|
'height': 63.75,
|
|
'default': '',
|
|
'layer': 'TEXT-I',
|
|
'invisible': True # Unsichtbar
|
|
},
|
|
{
|
|
'tag': 'VERW',
|
|
'insert': (6.92, 20.78, 0.0),
|
|
'height': 80.0,
|
|
'default': 'ILS-M0000',
|
|
'layer': 'ILS_MOTOR',
|
|
'invisible': False
|
|
},
|
|
{
|
|
'tag': 'ID',
|
|
'insert': (0.0, 0.73, 0.0),
|
|
'height': 120.0,
|
|
'default': '',
|
|
'layer': 'ILS_MOTOR',
|
|
'invisible': True # Unsichtbar
|
|
},
|
|
{
|
|
'tag': 'BEZEICHNUNG',
|
|
'insert': (0.0, -67.05, 0.0),
|
|
'height': 63.75,
|
|
'default': 'Motor MA0000',
|
|
'layer': 'ILS_MOTOR',
|
|
'invisible': True # Unsichtbar
|
|
},
|
|
{
|
|
'tag': 'KENNZEICHNUNG',
|
|
'insert': (0.0, -155.33, 0.0),
|
|
'height': 63.75,
|
|
'default': '=A01+UH00',
|
|
'layer': 'ILS_MOTOR',
|
|
'invisible': True # Unsichtbar
|
|
},
|
|
{
|
|
'tag': 'ARTIKELNR',
|
|
'insert': (0.0, -248.90, 0.0),
|
|
'height': 63.75,
|
|
'default': '790902001',
|
|
'layer': 'ILS_MOTOR',
|
|
'invisible': True # Unsichtbar
|
|
},
|
|
{
|
|
'tag': 'ARTIKELBEZEICHN',
|
|
'insert': (0.0, -332.68, 0.0),
|
|
'height': 63.75,
|
|
'default': 'E-Teile fuer SEW Motor ASE1 - HAN10ES - BG',
|
|
'layer': 'ILS_MOTOR',
|
|
'invisible': True # Unsichtbar
|
|
}
|
|
]
|
|
|
|
# Füge alle Attribut-Definitionen hinzu
|
|
for attrib_def in attrib_defs:
|
|
# Berechne flags: Bit 0 = invisible
|
|
flags = 1 if attrib_def['invisible'] else 0
|
|
|
|
io_block.add_attdef(
|
|
tag=attrib_def['tag'],
|
|
insert=attrib_def['insert'],
|
|
text=attrib_def['default'],
|
|
dxfattribs={
|
|
'height': attrib_def['height'],
|
|
'layer': attrib_def['layer'],
|
|
'color': 256, # BYLAYER
|
|
'style': 'textstyle3',
|
|
'rotation': 0,
|
|
'halign': 0,
|
|
'valign': 0,
|
|
'flags': flags
|
|
}
|
|
)
|
|
|
|
return io_block
|
|
|
|
def create_pot_block(self):
|
|
"""
|
|
Erstellt den ILS_POT-Erdung Block mit allen Attribut-Definitionen
|
|
Basiert auf dem ILS_POT-Erdung Block aus Nummerierung_IO.dxf
|
|
|
|
Returns:
|
|
Block-Definition
|
|
"""
|
|
block_name = 'ILS_POT-Erdung'
|
|
if block_name in self.doc.blocks:
|
|
return self.doc.blocks[block_name]
|
|
|
|
# Erstelle neuen Block
|
|
pot_block = self.doc.blocks.new(block_name)
|
|
|
|
# Hole Attribut-Definitionen aus pot_defaults (falls vorhanden)
|
|
if self.config and 'pot_defaults' in self.config:
|
|
pot_defaults = self.config['pot_defaults']
|
|
attrib_configs = pot_defaults.get('attrib_defs', [])
|
|
else:
|
|
# Fallback: Hard-coded Attribute basierend auf Analyse von Nummerierung_IO.dxf
|
|
attrib_configs = [
|
|
{
|
|
'tag': 'REALE_POSITION',
|
|
'insert': (0.0, 13.0, 0.0),
|
|
'height': 100.0,
|
|
'default': 'x',
|
|
'layer': 'ILS_POT-Erdung',
|
|
'invisible': False
|
|
},
|
|
{
|
|
'tag': 'NAME',
|
|
'insert': (316.17, -179.49, 0.0),
|
|
'height': 128.0,
|
|
'default': 'POT-1@@@',
|
|
'layer': 'ILS_POT-Erdung',
|
|
'invisible': False
|
|
},
|
|
{
|
|
'tag': 'KENNZEICHNUNG',
|
|
'insert': (0.0, -377.38, 0.0),
|
|
'height': 128.0,
|
|
'default': '=A01+UH00-X01',
|
|
'layer': 'ILS_POT-Erdung',
|
|
'invisible': True
|
|
}
|
|
]
|
|
|
|
# Füge alle Attribut-Definitionen hinzu
|
|
for attrib_def in attrib_configs:
|
|
# Berechne flags: Bit 0 = invisible
|
|
flags = 1 if attrib_def['invisible'] else 0
|
|
|
|
# Konvertiere insert-Liste zu Tupel falls nötig
|
|
insert_pos = attrib_def['insert']
|
|
if isinstance(insert_pos, list):
|
|
insert_pos = tuple(insert_pos)
|
|
|
|
pot_block.add_attdef(
|
|
tag=attrib_def['tag'],
|
|
insert=insert_pos,
|
|
text=attrib_def['default'],
|
|
dxfattribs={
|
|
'height': attrib_def['height'],
|
|
'layer': attrib_def['layer'],
|
|
'color': 256, # BYLAYER
|
|
'style': 'textstyle3',
|
|
'rotation': 0,
|
|
'halign': 0,
|
|
'valign': 0,
|
|
'flags': flags
|
|
}
|
|
)
|
|
|
|
return pot_block
|
|
|
|
def save(self, output_dir=None):
|
|
"""Speichert das DXF-Dokument"""
|
|
if output_dir is None:
|
|
output_dir = PROJECT_WORK
|
|
|
|
output_path = os.path.join(output_dir, self.filename)
|
|
self.doc.saveas(output_path)
|
|
print(f"Test-DXF erstellt: {output_path}")
|
|
return output_path
|
|
|
|
def draw_frames_for_generated_symbols(self):
|
|
"""
|
|
Zeichnet Rahmen um alle generierten Symbole zur Visualisierung.
|
|
Konvertiert das interne Format in das von draw_symbol_frames erwartete Format.
|
|
"""
|
|
if not self.generated_symbols:
|
|
print("Keine generierten Symbole zum Zeichnen von Rahmen gefunden")
|
|
return 0
|
|
|
|
# Konvertiere das interne Format in das erwartete Format
|
|
symbols_for_frames = []
|
|
for sym in self.generated_symbols:
|
|
# generated_symbols enthält 'x', 'y' (Einfügepunkt = linke untere Ecke bei left_bottom_corner)
|
|
# draw_symbol_frames erwartet Position = linke untere Ecke des Symbols
|
|
# Wir können die gespeicherten Koordinaten direkt verwenden
|
|
|
|
symbols_for_frames.append({
|
|
'position': (sym['x'], sym['y']), # Position = linke untere Ecke
|
|
'attributes': {'IO': sym['io']}, # IO-Wert für Breitenberechnung
|
|
'layer': sym.get('layer', 'ILS_MOTOR')
|
|
})
|
|
|
|
# Zeichne die Rahmen
|
|
return draw_symbol_frames(self.doc, self.msp, symbols_for_frames)
|
|
|
|
def load_block_from_file(self, source_file, block_name):
|
|
"""
|
|
Lädt einen Block aus einer anderen DXF-Datei
|
|
|
|
Args:
|
|
source_file: Pfad zur Quell-DXF-Datei
|
|
block_name: Name des zu ladenden Blocks
|
|
|
|
Returns:
|
|
Block-Definition oder None
|
|
"""
|
|
try:
|
|
source_doc = ezdxf.readfile(source_file)
|
|
if block_name in source_doc.blocks:
|
|
# Block in das Zieldokument kopieren
|
|
source_block = source_doc.blocks.get(block_name)
|
|
if block_name not in self.doc.blocks:
|
|
new_block = self.doc.blocks.new(block_name)
|
|
# Kopiere alle Entities aus dem Quellblock (inkl. ATTDEF)
|
|
for entity in source_block:
|
|
try:
|
|
new_block.add_foreign_entity(entity, copy=True)
|
|
except Exception as e:
|
|
print(f"Warnung: Konnte Entity nicht kopieren: {e}")
|
|
# Versuche direktes Klonen
|
|
try:
|
|
cloned = entity.copy()
|
|
new_block.add_entity(cloned)
|
|
except:
|
|
pass
|
|
return self.doc.blocks.get(block_name)
|
|
else:
|
|
print(f"Warnung: Block '{block_name}' nicht in {source_file} gefunden")
|
|
return None
|
|
except Exception as e:
|
|
print(f"Fehler beim Laden von Block '{block_name}': {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
return None
|
|
|
|
def create_renamer_attrib_block(self, block_name, attrib_tags, frame_type='MA'):
|
|
"""
|
|
Erstellt einen Block mit ATTDEF-Attributen für Renamer-Frames
|
|
|
|
Args:
|
|
block_name: Name des Attribut-Blocks (z.B. 'RENAMER_ATTRIB_MA')
|
|
attrib_tags: Liste der Attribut-Tags (z.B. ['NAME', 'KENNZEICHNUNG', ...])
|
|
frame_type: 'MA' oder 'MULTI' für Frame-Typ
|
|
|
|
Returns:
|
|
Block-Definition
|
|
"""
|
|
if block_name in self.doc.blocks:
|
|
return self.doc.blocks[block_name]
|
|
|
|
attrib_block = self.doc.blocks.new(block_name)
|
|
|
|
# Hole Positionen aus Config, falls vorhanden
|
|
if self.config and 'general' in self.config:
|
|
general = self.config['general']
|
|
if frame_type == 'MA' and 'ma_frame' in general:
|
|
config_positions = general['ma_frame'].get('attdef_positions', {})
|
|
elif frame_type == 'MULTI' and 'multi_frame' in general:
|
|
config_positions = general['multi_frame'].get('attdef_positions', {})
|
|
else:
|
|
config_positions = {}
|
|
else:
|
|
config_positions = {}
|
|
|
|
# Definiere Standard-Positionen und Werte für Attribute (Fallback)
|
|
default_definitions = {
|
|
'NAME': {'insert': (10, 50), 'default': 'MA@@', 'height': 21.25},
|
|
'NAME1': {'insert': (10, 80), 'default': 'BG1@@@', 'height': 21.25},
|
|
'NAME2': {'insert': (10, 60), 'default': 'MB1@@@', 'height': 21.25},
|
|
'NAME3': {'insert': (10, 40), 'default': '', 'height': 21.25},
|
|
'KENNZEICHNUNG': {'insert': (10, 20), 'default': '=A01+UC0101', 'height': 21.25},
|
|
'LAYER_NAME': {'insert': (10, 0), 'default': 'ILS_MOTOR', 'height': 21.25},
|
|
'LAYER_NAME1': {'insert': (10, -20), 'default': 'ILS_Eingang', 'height': 21.25},
|
|
'LAYER_NAME2': {'insert': (10, -40), 'default': 'ILS_Ausgang', 'height': 21.25},
|
|
'LAYER_NAME3': {'insert': (10, -60), 'default': '', 'height': 21.25},
|
|
'DIRECTION': {'insert': (10, -80), 'default': 'LEFT_RIGHT', 'height': 21.25},
|
|
'ID': {'insert': (10, -100), 'default': '', 'height': 21.25}
|
|
}
|
|
|
|
# Füge nur die angeforderten Attribute hinzu
|
|
for tag in attrib_tags:
|
|
if tag in default_definitions:
|
|
default_def = default_definitions[tag]
|
|
|
|
# Verwende Config-Werte, falls vorhanden, sonst Defaults
|
|
if tag in config_positions and not tag.startswith('_'):
|
|
config_pos = config_positions[tag]
|
|
x = config_pos.get('x', default_def['insert'][0])
|
|
y = config_pos.get('y', default_def['insert'][1])
|
|
height = config_pos.get('height', default_def['height'])
|
|
else:
|
|
x, y = default_def['insert']
|
|
height = default_def['height']
|
|
|
|
attrib_block.add_attdef(
|
|
tag=tag,
|
|
insert=(x, y),
|
|
text=default_def['default'],
|
|
dxfattribs={
|
|
'height': height,
|
|
'layer': 'ILS_RENAMER',
|
|
'color': 1,
|
|
'style': 'textstyle3'
|
|
}
|
|
)
|
|
|
|
return attrib_block
|
|
|
|
def create_renamer_frame(self, insert_point, width, height, name, kennzeichnung,
|
|
layer_name, direction, use_polyline=False, frame_type='MA',
|
|
name2='', name3='', layer_name2='', layer_name3='', id_value=''):
|
|
"""
|
|
Erstellt einen Renamer-Rahmen mit zweistufiger Block-Struktur auf Layer ILS_RENAMER
|
|
|
|
Struktur:
|
|
- Äußerer Block (RENAMER_xxx):
|
|
1. LWPOLYLINE (umschließende Polylinie)
|
|
2. Block-Referenz mit Attributen (RENAMER_ATTRIB_xxx)
|
|
|
|
Args:
|
|
insert_point: (x, y) Position des Rahmens
|
|
width: Breite des Rahmens
|
|
height: Höhe des Rahmens
|
|
name: NAME-Attribut (oder NAME1 für Multi-Layer)
|
|
kennzeichnung: KENNZEICHNUNG-Attribut
|
|
layer_name: LAYER_NAME-Attribut (oder LAYER_NAME1 für Multi-Layer)
|
|
direction: DIRECTION-Attribut
|
|
use_polyline: Wird ignoriert (für Kompatibilität)
|
|
frame_type: 'MA' für einfachen Frame, 'MULTI' für Multi-Layer Frame
|
|
"""
|
|
x, y = insert_point
|
|
|
|
# Bestimme Attribut-Tags basierend auf Frame-Typ
|
|
if frame_type == 'MA':
|
|
attrib_tags = ['NAME', 'KENNZEICHNUNG', 'LAYER_NAME', 'DIRECTION']
|
|
attrib_block_name = 'RENAMER_ATTRIB_MA'
|
|
else: # MULTI
|
|
attrib_tags = ['NAME1', 'NAME2', 'NAME3', 'KENNZEICHNUNG',
|
|
'LAYER_NAME1', 'LAYER_NAME2', 'LAYER_NAME3', 'DIRECTION', 'ID']
|
|
attrib_block_name = 'RENAMER_ATTRIB_MULTI'
|
|
|
|
# Erstelle Attribut-Block (wiederverwendbar) mit Config-Unterstützung
|
|
self.create_renamer_attrib_block(attrib_block_name, attrib_tags, frame_type=frame_type)
|
|
|
|
# Erstelle äußeren Renamer-Block mit eindeutigem Namen (inkl. Position und Größe)
|
|
# Jeder Rahmen bekommt einen eindeutigen Namen basierend auf Position
|
|
outer_block_name = f"RENAMER_{name.replace('@', '').replace('-', '_')}_{int(x)}_{int(y)}"
|
|
if outer_block_name not in self.doc.blocks:
|
|
outer_block = self.doc.blocks.new(outer_block_name)
|
|
|
|
# 1. Füge LWPOLYLINE hinzu (umschließende Polylinie)
|
|
points = [
|
|
(0, 0),
|
|
(width, 0),
|
|
(width, height),
|
|
(0, height),
|
|
(0, 0)
|
|
]
|
|
outer_block.add_lwpolyline(points, dxfattribs={
|
|
'layer': 'ILS_RENAMER',
|
|
'color': 1
|
|
})
|
|
|
|
# 2. Füge Block-Referenz mit Attributen hinzu
|
|
# Position an der linken oberen Ecke des Rahmens
|
|
attrib_insert_pos = (10, height - 30) # Linke obere Ecke mit kleinem Abstand
|
|
attrib_blockref = outer_block.add_blockref(
|
|
attrib_block_name,
|
|
insert=attrib_insert_pos,
|
|
dxfattribs={'layer': 'ILS_RENAMER'}
|
|
)
|
|
|
|
# Setze Attribut-Werte auf der inneren Block-Referenz
|
|
if frame_type == 'MA':
|
|
attrib_blockref.add_auto_attribs({
|
|
'NAME': name,
|
|
'KENNZEICHNUNG': kennzeichnung,
|
|
'LAYER_NAME': layer_name,
|
|
'DIRECTION': direction
|
|
})
|
|
else: # MULTI - für BG/MG/POT mit mehreren Layern
|
|
attrib_blockref.add_auto_attribs({
|
|
'NAME1': name,
|
|
'NAME2': name2,
|
|
'NAME3': name3,
|
|
'KENNZEICHNUNG': kennzeichnung,
|
|
'LAYER_NAME1': layer_name,
|
|
'LAYER_NAME2': layer_name2,
|
|
'LAYER_NAME3': layer_name3,
|
|
'DIRECTION': direction,
|
|
'ID': id_value
|
|
})
|
|
|
|
# Füge Block-Referenz im Modelspace ein
|
|
blockref = self.msp.add_blockref(
|
|
outer_block_name,
|
|
insert=(x, y),
|
|
dxfattribs={'layer': 'ILS_RENAMER'}
|
|
)
|
|
|
|
def create_polyline_renamer_frame(self, path_points, name, kennzeichnung, layer_name, direction):
|
|
"""
|
|
Erstellt einen Renamer-Rahmen mit Polyline-Pfad
|
|
|
|
Struktur:
|
|
- Block (RENAMER_POLYLINE_xxx):
|
|
1. LWPOLYLINE (Pfad durch die Punkte)
|
|
2. Block-Referenz mit Attributen (RENAMER_ATTRIB_MA)
|
|
|
|
Args:
|
|
path_points: Liste von Dictionaries mit 'x' und 'y' Koordinaten
|
|
name: NAME-Attribut
|
|
kennzeichnung: KENNZEICHNUNG-Attribut
|
|
layer_name: LAYER_NAME-Attribut
|
|
direction: DIRECTION-Attribut (z.B. 'POLYLINE_PATH')
|
|
"""
|
|
# Erstelle Attribut-Block (wiederverwendbar)
|
|
attrib_tags = ['NAME', 'KENNZEICHNUNG', 'LAYER_NAME', 'DIRECTION']
|
|
attrib_block_name = 'RENAMER_ATTRIB_MA'
|
|
self.create_renamer_attrib_block(attrib_block_name, attrib_tags, frame_type='MA')
|
|
|
|
# Erstelle eindeutigen Block-Namen basierend auf erstem Punkt
|
|
first_point = path_points[0] if path_points else {'x': 0, 'y': 0}
|
|
block_name = f"RENAMER_POLYLINE_{name.replace('@', '').replace('-', '_')}_{int(first_point['x'])}_{int(first_point['y'])}"
|
|
|
|
if block_name not in self.doc.blocks:
|
|
renamer_block = self.doc.blocks.new(block_name)
|
|
|
|
# 1. Füge LWPOLYLINE hinzu (Pfad)
|
|
# Konvertiere Punkte von Dict zu Tupeln
|
|
points = [(p['x'], p['y']) for p in path_points]
|
|
|
|
renamer_block.add_lwpolyline(points, dxfattribs={
|
|
'layer': 'ILS_RENAMER',
|
|
'color': 1
|
|
})
|
|
|
|
# 2. Füge Block-Referenz mit Attributen hinzu
|
|
# Position neben dem ersten Punkt des Pfades (mit kleinem Offset)
|
|
first_x = points[0][0]
|
|
first_y = points[0][1]
|
|
attrib_insert_pos = (first_x + 10, first_y + 30) # Offset vom ersten Punkt
|
|
attrib_blockref = renamer_block.add_blockref(
|
|
attrib_block_name,
|
|
insert=attrib_insert_pos,
|
|
dxfattribs={'layer': 'ILS_RENAMER'}
|
|
)
|
|
|
|
# Setze Attribut-Werte
|
|
attrib_blockref.add_auto_attribs({
|
|
'NAME': name,
|
|
'KENNZEICHNUNG': kennzeichnung,
|
|
'LAYER_NAME': layer_name,
|
|
'DIRECTION': direction
|
|
})
|
|
|
|
# Füge Block-Referenz im Modelspace ein (am Ursprung, da Pfad absolute Koordinaten hat)
|
|
blockref = self.msp.add_blockref(
|
|
block_name,
|
|
insert=(0, 0),
|
|
dxfattribs={'layer': 'ILS_RENAMER'}
|
|
)
|
|
|
|
def generate_nummerierung(self, scene_name='Nummerierung1'):
|
|
"""
|
|
Generiert Testdaten für Nummerierung-Szenario
|
|
|
|
Verwendet entweder die JSON-Config oder Hard-coded Defaults
|
|
|
|
Args:
|
|
scene_name: Name der Testszene (z.B. 'Nummerierung1', 'Nummerierung2')
|
|
"""
|
|
print(f"Generiere Nummerierung-Testdaten ({scene_name})...")
|
|
|
|
# Erstelle io-Block direkt (ohne Referenzdatei)
|
|
io_block = self.create_io_block()
|
|
|
|
if io_block is None:
|
|
print("Fehler: io-Block konnte nicht erstellt werden")
|
|
return
|
|
|
|
# Prüfe ob POT-Symbole in der Scene verwendet werden
|
|
if self.config and 'test_scenes' in self.config and scene_name in self.config['test_scenes']:
|
|
scene = self.config['test_scenes'][scene_name]
|
|
if 'pot_groups' in scene and scene['pot_groups']:
|
|
print("POT-Symbole erkannt, erstelle ILS_POT-Erdung Block...")
|
|
pot_block = self.create_pot_block()
|
|
if pot_block:
|
|
print(f"POT-Block 'ILS_POT-Erdung' erfolgreich erstellt")
|
|
else:
|
|
print(f"WARNUNG: POT-Block konnte nicht erstellt werden")
|
|
|
|
# Wenn Config geladen ist, verwende sie, ansonsten Hard-coded Defaults
|
|
if self.config and 'test_scenes' in self.config and scene_name in self.config['test_scenes']:
|
|
print(f"Verwende JSON-Config für {scene_name}...")
|
|
self._generate_nummerierung_from_config(scene_name)
|
|
else:
|
|
print("Fehler in Config ... terminating")
|
|
|
|
def _calculate_insert_offset(self, insert_point, symbol_width, symbol_height):
|
|
"""
|
|
Berechnet den Offset vom Einfügepunkt zur linken oberen Ecke
|
|
|
|
Args:
|
|
insert_point: String mit dem Einfügepunkt (z.B. "left_top_corner", "center")
|
|
symbol_width: Breite des Symbols
|
|
symbol_height: Höhe des Symbols
|
|
|
|
Returns:
|
|
(offset_x, offset_y) - Offset vom Einfügepunkt zur linken oberen Ecke
|
|
"""
|
|
offset_map = {
|
|
'left_top_corner': (0, 0),
|
|
'left_bottom_corner': (0, symbol_height),
|
|
'right_top_corner': (-symbol_width, 0),
|
|
'right_bottom_corner': (-symbol_width, symbol_height),
|
|
'center': (-symbol_width / 2, symbol_height / 2),
|
|
'top_center': (-symbol_width / 2, 0),
|
|
'bottom_center': (-symbol_width / 2, symbol_height)
|
|
}
|
|
|
|
return offset_map.get(insert_point, (0, 0)) # Default: left_top_corner
|
|
|
|
def _generate_symbol_group(self, group, symbol_defaults, symbol_type='MA', spacing_factor=1.2, y_offsets_list=[0, -50, 50], insert_point='left_bottom_corner'):
|
|
"""
|
|
Generiert eine Gruppe von Symbolen (MA, BG, MB, POT, etc.)
|
|
|
|
Args:
|
|
group: Dict mit Gruppen-Config (name, count, layout_type, base_x, base_y, spacing)
|
|
symbol_defaults: Dict mit Symbol-Default-Attributen (ma_defaults, bg_defaults, etc.)
|
|
symbol_type: Typ des Symbols ('MA', 'BG', 'MB', 'POT') für BEZEICHNUNG
|
|
spacing_factor: Spacing-Faktor zwischen Symbolen
|
|
y_offsets_list: Liste von Y-Offsets für horizontal_offset Layout
|
|
insert_point: Einfügepunkt des Blocks (z.B. "left_bottom_corner", "center")
|
|
"""
|
|
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]
|
|
|
|
# Hole Dimensionen aus defaults
|
|
dimensions = symbol_defaults.get('dimensions', {})
|
|
symbol_width = dimensions.get('width', 1210)
|
|
symbol_height = dimensions.get('height', 381)
|
|
|
|
# Berechne Spacing: Verwende group['spacing'] falls angegeben, sonst symbol_width * spacing_factor
|
|
if layout_type in ['horizontal', 'horizontal_offset']:
|
|
if 'spacing' in group and group['spacing'] > 0:
|
|
actual_spacing = group['spacing']
|
|
else:
|
|
actual_spacing = symbol_width * spacing_factor
|
|
else:
|
|
actual_spacing = 0
|
|
|
|
# Hole Attribute und Block-Info aus defaults
|
|
attributes = symbol_defaults.get('attributes', {})
|
|
layer = symbol_defaults.get('layer', 'ILS_MOTOR')
|
|
block_name = symbol_defaults.get('block_name', 'io')
|
|
|
|
# Berechne Offset basierend auf Einfügepunkt
|
|
offset_x, offset_y = self._calculate_insert_offset(insert_point, symbol_width, symbol_height)
|
|
|
|
# Bestimme BEZEICHNUNG basierend auf Symbol-Typ
|
|
bezeichnung_prefix = {
|
|
'MA': 'Motor',
|
|
'BG': 'Eingang',
|
|
'MB': 'Ausgang',
|
|
'POT': 'Erdung'
|
|
}.get(symbol_type, 'Symbol')
|
|
|
|
# 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
|
|
|
|
# Berechne die linke obere Ecke basierend auf dem Einfügepunkt
|
|
left_top_x = x + offset_x
|
|
left_top_y = y + offset_y
|
|
|
|
# DXF-Block wird an der linken unteren Ecke eingefügt
|
|
# (da der io-Block bei (0,0) startet und nach oben geht)
|
|
blockref = self.msp.add_blockref(
|
|
block_name,
|
|
insert=(left_top_x, left_top_y - symbol_height),
|
|
dxfattribs={'layer': layer}
|
|
)
|
|
|
|
# Setze Attribute (dynamisch IO/NAME und BEZEICHNUNG)
|
|
attrib_values = attributes.copy()
|
|
|
|
# Für POT-Symbole: Setze NAME statt IO
|
|
if symbol_type == 'POT':
|
|
attrib_values['NAME'] = io_pattern
|
|
else:
|
|
attrib_values['IO'] = io_pattern
|
|
|
|
# Setze BEZEICHNUNG nur wenn im Template vorhanden
|
|
if 'BEZEICHNUNG' in attrib_values:
|
|
attrib_values['BEZEICHNUNG'] = f"{bezeichnung_prefix} {io_pattern}"
|
|
|
|
blockref.add_auto_attribs(attrib_values)
|
|
|
|
# Speichere Symbol-Position für spätere Validierung
|
|
# Speichere den tatsächlichen Einfügepunkt (linke untere Ecke bei left_bottom_corner)
|
|
actual_insert_x = left_top_x
|
|
actual_insert_y = left_top_y - symbol_height
|
|
self.generated_symbols.append({
|
|
'x': actual_insert_x,
|
|
'y': actual_insert_y,
|
|
'io': io_pattern,
|
|
'group_name': name,
|
|
'layer': layer
|
|
})
|
|
|
|
def _generate_nummerierung_from_config(self, scene_name='Nummerierung1'):
|
|
"""
|
|
Generiert Nummerierung-Testdaten aus JSON-Config
|
|
|
|
Args:
|
|
scene_name: Name der Testszene in der Config
|
|
"""
|
|
scene = self.config['test_scenes'][scene_name]
|
|
general = self.config.get('general', {})
|
|
|
|
# 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])
|
|
|
|
# Hole Einfügepunkt aus Config (default: left_bottom_corner)
|
|
ma_frame = general.get('ma_frame', {})
|
|
insert_point = ma_frame.get('insert_point', 'left_bottom_corner')
|
|
|
|
# Generiere MA-Gruppen aus Config
|
|
ma_defaults = self.config.get('ma_defaults', {})
|
|
for group in scene.get('ma_groups', []):
|
|
self._generate_symbol_group(group, ma_defaults, symbol_type='MA',
|
|
spacing_factor=spacing_factor,
|
|
y_offsets_list=y_offsets_list,
|
|
insert_point=insert_point)
|
|
|
|
# Generiere BG-Gruppen aus Config (Eingänge)
|
|
bg_defaults = self.config.get('bg_defaults', {})
|
|
for group in scene.get('bg_groups', []):
|
|
self._generate_symbol_group(group, bg_defaults, symbol_type='BG',
|
|
spacing_factor=spacing_factor,
|
|
y_offsets_list=y_offsets_list,
|
|
insert_point=insert_point)
|
|
|
|
# Generiere MB-Gruppen aus Config (Ausgänge)
|
|
mb_defaults = self.config.get('mb_defaults', {})
|
|
for group in scene.get('mb_groups', []):
|
|
self._generate_symbol_group(group, mb_defaults, symbol_type='MB',
|
|
spacing_factor=spacing_factor,
|
|
y_offsets_list=y_offsets_list,
|
|
insert_point=insert_point)
|
|
|
|
# Generiere POT-Gruppen aus Config (Erdungspunkte)
|
|
pot_defaults = self.config.get('pot_defaults', {})
|
|
for group in scene.get('pot_groups', []):
|
|
self._generate_symbol_group(group, pot_defaults, symbol_type='POT',
|
|
spacing_factor=spacing_factor,
|
|
y_offsets_list=y_offsets_list,
|
|
insert_point=insert_point)
|
|
|
|
# Generiere Renamer-Rahmen aus Config
|
|
for frame_config in scene.get('renaming_frames', []):
|
|
frame_type = frame_config.get('type', 'rectangle')
|
|
|
|
if frame_type == 'polyline_path':
|
|
# Polyline-Pfad Frame
|
|
self.create_polyline_renamer_frame(
|
|
path_points=frame_config['path_points'],
|
|
name=frame_config['name'],
|
|
kennzeichnung=frame_config['kennzeichnung'],
|
|
layer_name=frame_config['layer_name'],
|
|
direction=frame_config['direction']
|
|
)
|
|
else:
|
|
# Standard Rectangle Frame
|
|
# Prüfe ob es ein MULTI-Frame ist (NAME1/NAME2 statt NAME)
|
|
is_multi_frame = 'name2' in frame_config or 'layer_name1' in frame_config
|
|
|
|
if is_multi_frame:
|
|
# MULTI-Frame (BG, MG, POT mit mehreren Layern)
|
|
self.create_renamer_frame(
|
|
insert_point=(frame_config['x'], frame_config['y']),
|
|
width=frame_config['width'],
|
|
height=frame_config['height'],
|
|
name=frame_config.get('name', ''),
|
|
kennzeichnung=frame_config.get('kennzeichnung', ''),
|
|
layer_name=frame_config.get('layer_name1', ''),
|
|
direction=frame_config.get('direction', ''),
|
|
use_polyline=frame_config.get('use_polyline', False),
|
|
frame_type='MULTI',
|
|
name2=frame_config.get('name2', ''),
|
|
name3=frame_config.get('name3', ''),
|
|
layer_name2=frame_config.get('layer_name2', ''),
|
|
layer_name3=frame_config.get('layer_name3', ''),
|
|
id_value=frame_config.get('id', '')
|
|
)
|
|
else:
|
|
# MA-Frame (einfacher Frame)
|
|
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)
|
|
)
|
|
|
|
# Prüfe ob alle generierten Symbole in einem Renamer-Frame liegen
|
|
self._validate_symbols_in_frames(scene)
|
|
|
|
# Prüfe ob sich Renamer-Rechtecke überlappen (außer POLYLINE_PATH)
|
|
self._check_overlapping_frames(scene)
|
|
|
|
def _rectangles_overlap(self, rect1, rect2):
|
|
"""
|
|
Prüft ob sich zwei Rechtecke überlappen.
|
|
|
|
Args:
|
|
rect1: Dictionary mit 'x', 'y', 'width', 'height'
|
|
rect2: Dictionary mit 'x', 'y', 'width', 'height'
|
|
|
|
Returns:
|
|
True wenn sich die Rechtecke überlappen
|
|
"""
|
|
# Rechteck 1
|
|
r1_left = rect1['x']
|
|
r1_right = rect1['x'] + rect1['width']
|
|
r1_bottom = rect1['y']
|
|
r1_top = rect1['y'] + rect1['height']
|
|
|
|
# Rechteck 2
|
|
r2_left = rect2['x']
|
|
r2_right = rect2['x'] + rect2['width']
|
|
r2_bottom = rect2['y']
|
|
r2_top = rect2['y'] + rect2['height']
|
|
|
|
# Prüfe ob keine Überlappung (dann ist das Gegenteil eine Überlappung)
|
|
no_overlap = (r1_right <= r2_left or # R1 ist links von R2
|
|
r2_right <= r1_left or # R2 ist links von R1
|
|
r1_top <= r2_bottom or # R1 ist unter R2
|
|
r2_top <= r1_bottom) # R2 ist unter R1
|
|
|
|
return not no_overlap
|
|
|
|
def _calculate_overlap_area(self, rect1, rect2):
|
|
"""
|
|
Berechnet die Überlappungsfläche zweier Rechtecke.
|
|
|
|
Args:
|
|
rect1: Dictionary mit 'x', 'y', 'width', 'height'
|
|
rect2: Dictionary mit 'x', 'y', 'width', 'height'
|
|
|
|
Returns:
|
|
Überlappungsfläche in Quadrateinheiten
|
|
"""
|
|
# Rechteck 1
|
|
r1_left = rect1['x']
|
|
r1_right = rect1['x'] + rect1['width']
|
|
r1_bottom = rect1['y']
|
|
r1_top = rect1['y'] + rect1['height']
|
|
|
|
# Rechteck 2
|
|
r2_left = rect2['x']
|
|
r2_right = rect2['x'] + rect2['width']
|
|
r2_bottom = rect2['y']
|
|
r2_top = rect2['y'] + rect2['height']
|
|
|
|
# Berechne überlappenden Bereich
|
|
overlap_left = max(r1_left, r2_left)
|
|
overlap_right = min(r1_right, r2_right)
|
|
overlap_bottom = max(r1_bottom, r2_bottom)
|
|
overlap_top = min(r1_top, r2_top)
|
|
|
|
# Wenn keine Überlappung, gib 0 zurück
|
|
if overlap_right <= overlap_left or overlap_top <= overlap_bottom:
|
|
return 0
|
|
|
|
# Berechne Fläche
|
|
overlap_width = overlap_right - overlap_left
|
|
overlap_height = overlap_top - overlap_bottom
|
|
return overlap_width * overlap_height
|
|
|
|
def _calculate_overlap_recommendations(self, frame1, frame2):
|
|
"""
|
|
Berechnet Empfehlungen zur Behebung einer Überlappung zwischen zwei Frames.
|
|
|
|
Args:
|
|
frame1: Dictionary mit 'x', 'y', 'width', 'height', 'name'
|
|
frame2: Dictionary mit 'x', 'y', 'width', 'height', 'name'
|
|
|
|
Returns:
|
|
Liste von Empfehlungs-Strings
|
|
"""
|
|
recommendations = []
|
|
|
|
# Berechne Kanten
|
|
f1_left = frame1['x']
|
|
f1_right = frame1['x'] + frame1['width']
|
|
f1_bottom = frame1['y']
|
|
f1_top = frame1['y'] + frame1['height']
|
|
|
|
f2_left = frame2['x']
|
|
f2_right = frame2['x'] + frame2['width']
|
|
f2_bottom = frame2['y']
|
|
f2_top = frame2['y'] + frame2['height']
|
|
|
|
# Berechne Überlappungsbereiche
|
|
overlap_left = max(f1_left, f2_left)
|
|
overlap_right = min(f1_right, f2_right)
|
|
overlap_bottom = max(f1_bottom, f2_bottom)
|
|
overlap_top = min(f1_top, f2_top)
|
|
|
|
overlap_width = overlap_right - overlap_left
|
|
overlap_height = overlap_top - overlap_bottom
|
|
|
|
# Horizontale Überlappung
|
|
if overlap_width > 0:
|
|
# Frame 1 ist links von Frame 2
|
|
if f1_left < f2_left:
|
|
# Option 1: Frame 1 width reduzieren
|
|
new_width1 = f1_right - f1_left - overlap_width - 1
|
|
recommendations.append(
|
|
f"Reduziere '{frame1['name']}' width von {frame1['width']} auf {new_width1:.0f} "
|
|
f"(um {overlap_width + 1:.0f} Einheiten)"
|
|
)
|
|
# Option 2: Frame 2 nach rechts verschieben
|
|
new_x2 = f2_left + overlap_width + 1
|
|
recommendations.append(
|
|
f"Verschiebe '{frame2['name']}' x von {frame2['x']} auf {new_x2:.0f} "
|
|
f"(um {overlap_width + 1:.0f} Einheiten nach rechts)"
|
|
)
|
|
# Frame 2 ist links von Frame 1
|
|
else:
|
|
# Option 1: Frame 2 width reduzieren
|
|
new_width2 = f2_right - f2_left - overlap_width - 1
|
|
recommendations.append(
|
|
f"Reduziere '{frame2['name']}' width von {frame2['width']} auf {new_width2:.0f} "
|
|
f"(um {overlap_width + 1:.0f} Einheiten)"
|
|
)
|
|
# Option 2: Frame 1 nach rechts verschieben
|
|
new_x1 = f1_left + overlap_width + 1
|
|
recommendations.append(
|
|
f"Verschiebe '{frame1['name']}' x von {frame1['x']} auf {new_x1:.0f} "
|
|
f"(um {overlap_width + 1:.0f} Einheiten nach rechts)"
|
|
)
|
|
|
|
# Vertikale Überlappung
|
|
if overlap_height > 0:
|
|
# Frame 1 ist unter Frame 2
|
|
if f1_bottom < f2_bottom:
|
|
# Option 1: Frame 1 height reduzieren
|
|
new_height1 = f1_top - f1_bottom - overlap_height - 1
|
|
recommendations.append(
|
|
f"Reduziere '{frame1['name']}' height von {frame1['height']} auf {new_height1:.0f} "
|
|
f"(um {overlap_height + 1:.0f} Einheiten)"
|
|
)
|
|
# Option 2: Frame 2 nach oben verschieben
|
|
new_y2 = f2_bottom + overlap_height + 1
|
|
recommendations.append(
|
|
f"Verschiebe '{frame2['name']}' y von {frame2['y']} auf {new_y2:.0f} "
|
|
f"(um {overlap_height + 1:.0f} Einheiten nach oben)"
|
|
)
|
|
# Frame 2 ist unter Frame 1
|
|
else:
|
|
# Option 1: Frame 2 height reduzieren
|
|
new_height2 = f2_top - f2_bottom - overlap_height - 1
|
|
recommendations.append(
|
|
f"Reduziere '{frame2['name']}' height von {frame2['height']} auf {new_height2:.0f} "
|
|
f"(um {overlap_height + 1:.0f} Einheiten)"
|
|
)
|
|
# Option 2: Frame 1 nach oben verschieben
|
|
new_y1 = f1_bottom + overlap_height + 1
|
|
recommendations.append(
|
|
f"Verschiebe '{frame1['name']}' y von {frame1['y']} auf {new_y1:.0f} "
|
|
f"(um {overlap_height + 1:.0f} Einheiten nach oben)"
|
|
)
|
|
|
|
return recommendations
|
|
|
|
def _check_overlapping_frames(self, scene):
|
|
"""
|
|
Prüft ob sich Renamer-Rechtecke überlappen.
|
|
Polyline-Pfade (DIRECTION=POLYLINE_PATH) werden nicht geprüft.
|
|
|
|
Args:
|
|
scene: Scene-Configuration Dictionary
|
|
"""
|
|
print("\n" + "="*60)
|
|
print("Prüfe auf überlappende Renamer-Rechtecke")
|
|
print("="*60)
|
|
|
|
# Sammle alle Rechteck-Frames (ohne POLYLINE_PATH)
|
|
rectangle_frames = []
|
|
for frame_config in scene.get('renaming_frames', []):
|
|
frame_type = frame_config.get('type', 'rectangle')
|
|
direction = frame_config.get('direction', '')
|
|
|
|
# Überspringe POLYLINE_PATH Frames
|
|
if frame_type == 'polyline_path' or direction == 'POLYLINE_PATH':
|
|
continue
|
|
|
|
# Überspringe Frames ohne Positionsangaben
|
|
if 'x' not in frame_config or 'y' not in frame_config:
|
|
continue
|
|
|
|
rectangle_frames.append({
|
|
'name': frame_config.get('name', 'UNKNOWN'),
|
|
'x': frame_config['x'],
|
|
'y': frame_config['y'],
|
|
'width': frame_config.get('width', 0),
|
|
'height': frame_config.get('height', 0),
|
|
'direction': direction
|
|
})
|
|
|
|
# Prüfe alle Paare von Rechtecken auf Überlappung
|
|
overlaps_found = []
|
|
for i in range(len(rectangle_frames)):
|
|
for j in range(i + 1, len(rectangle_frames)):
|
|
frame1 = rectangle_frames[i]
|
|
frame2 = rectangle_frames[j]
|
|
|
|
if self._rectangles_overlap(frame1, frame2):
|
|
overlap_area = self._calculate_overlap_area(frame1, frame2)
|
|
recommendations = self._calculate_overlap_recommendations(frame1, frame2)
|
|
overlaps_found.append({
|
|
'frame1': frame1,
|
|
'frame2': frame2,
|
|
'overlap_area': overlap_area,
|
|
'recommendations': recommendations
|
|
})
|
|
|
|
# Gib Warnungen aus
|
|
if overlaps_found:
|
|
print(f"\nWARNUNG: {len(overlaps_found)} Überlappung(en) zwischen Renamer-Rechtecken gefunden:")
|
|
for overlap in overlaps_found:
|
|
f1 = overlap['frame1']
|
|
f2 = overlap['frame2']
|
|
area = overlap['overlap_area']
|
|
print(f" Frame '{f1['name']}' (Position: {f1['x']}, {f1['y']}, "
|
|
f"Größe: {f1['width']}x{f1['height']}) "
|
|
f"überlappt mit Frame '{f2['name']}' "
|
|
f"(Position: {f2['x']}, {f2['y']}, Größe: {f2['width']}x{f2['height']})")
|
|
print(f" Überlappungsfläche: {area:.1f} Quadrateinheiten")
|
|
print(f" Empfohlene Änderungen (wähle eine):")
|
|
for rec in overlap['recommendations']:
|
|
print(f" - {rec}")
|
|
else:
|
|
print(f"\nOK: Keine Überlappungen zwischen {len(rectangle_frames)} Renamer-Rechtecken gefunden")
|
|
|
|
def _point_in_rectangle(self, point, rect_x, rect_y, rect_width, rect_height):
|
|
"""
|
|
Prüft ob ein Punkt innerhalb eines Rechtecks liegt.
|
|
|
|
Args:
|
|
point: (x, y) Tupel
|
|
rect_x, rect_y: Linke obere Ecke des Rechtecks
|
|
rect_width, rect_height: Breite und Höhe des Rechtecks
|
|
"""
|
|
x, y = point
|
|
return (rect_x <= x <= rect_x + rect_width and
|
|
rect_y <= y <= rect_y + rect_height)
|
|
|
|
def _calculate_shift_to_rectangle(self, point, rect_x, rect_y, rect_width, rect_height):
|
|
"""
|
|
Berechnet den minimalen Verschiebungsvektor, um einen Punkt in ein Rechteck zu verschieben.
|
|
|
|
Args:
|
|
point: (x, y) Tupel
|
|
rect_x, rect_y: Linke untere Ecke des Rechtecks
|
|
rect_width, rect_height: Breite und Höhe des Rechtecks
|
|
|
|
Returns:
|
|
(delta_x, delta_y) - Verschiebungsvektor
|
|
"""
|
|
x, y = point
|
|
delta_x = 0
|
|
delta_y = 0
|
|
|
|
# Berechne Verschiebung in x-Richtung
|
|
if x < rect_x:
|
|
delta_x = rect_x - x
|
|
elif x > rect_x + rect_width:
|
|
delta_x = (rect_x + rect_width) - x
|
|
|
|
# Berechne Verschiebung in y-Richtung
|
|
if y < rect_y:
|
|
delta_y = rect_y - y
|
|
elif y > rect_y + rect_height:
|
|
delta_y = (rect_y + rect_height) - y
|
|
|
|
return (delta_x, delta_y)
|
|
|
|
def _point_in_polygon(self, point, polygon):
|
|
"""
|
|
Prüft, ob ein Punkt innerhalb eines Polygons liegt (Ray-casting Algorithmus).
|
|
"""
|
|
x, y = point
|
|
n = len(polygon)
|
|
inside = False
|
|
|
|
p1x, p1y = polygon[0]
|
|
for i in range(1, n + 1):
|
|
p2x, p2y = polygon[i % n]
|
|
if y > min(p1y, p2y):
|
|
if y <= max(p1y, p2y):
|
|
if x <= max(p1x, p2x):
|
|
if p1y != p2y:
|
|
xinters = (y - p1y) * (p2x - p1x) / (p2y - p1y) + p1x
|
|
if p1x == p2x or x <= xinters:
|
|
inside = not inside
|
|
p1x, p1y = p2x, p2y
|
|
|
|
return inside
|
|
|
|
def _circle_intersects_polyline(self, center, radius, polyline):
|
|
"""
|
|
Prüft ob ein Kreis mit Mittelpunkt 'center' und Radius 'radius'
|
|
irgendein Segment der Polylinie schneidet.
|
|
|
|
Args:
|
|
center: (x, y) Mittelpunkt des Kreises
|
|
radius: Radius des Kreises
|
|
polyline: Liste von (x, y) Punkten der Polylinie
|
|
|
|
Returns:
|
|
True wenn der Kreis die Polylinie schneidet
|
|
"""
|
|
cx, cy = center
|
|
|
|
# Prüfe jedes Segment der Polylinie
|
|
for i in range(len(polyline) - 1):
|
|
p1 = polyline[i]
|
|
p2 = polyline[i + 1]
|
|
|
|
# Berechne den kürzesten Abstand vom Kreis-Mittelpunkt zum Linien-Segment
|
|
dist = self._distance_point_to_segment(center, p1, p2)
|
|
|
|
# Wenn Abstand <= Radius, dann gibt es eine Schnittmenge
|
|
if dist <= radius:
|
|
return True
|
|
|
|
return False
|
|
|
|
def _distance_point_to_segment(self, point, seg_start, seg_end):
|
|
"""
|
|
Berechnet den kürzesten Abstand von einem Punkt zu einem Linien-Segment.
|
|
|
|
Args:
|
|
point: (x, y) Punkt
|
|
seg_start: (x, y) Start des Segments
|
|
seg_end: (x, y) Ende des Segments
|
|
|
|
Returns:
|
|
Kürzester Abstand
|
|
"""
|
|
px, py = point
|
|
x1, y1 = seg_start
|
|
x2, y2 = seg_end
|
|
|
|
# Vektor von seg_start zu seg_end
|
|
dx = x2 - x1
|
|
dy = y2 - y1
|
|
|
|
# Spezialfall: Segment ist ein Punkt
|
|
if dx == 0 and dy == 0:
|
|
return ((px - x1)**2 + (py - y1)**2)**0.5
|
|
|
|
# Parameter t für die Projektion des Punktes auf die Linie
|
|
# t = 0 bedeutet seg_start, t = 1 bedeutet seg_end
|
|
t = max(0, min(1, ((px - x1) * dx + (py - y1) * dy) / (dx * dx + dy * dy)))
|
|
|
|
# Nächster Punkt auf dem Segment
|
|
closest_x = x1 + t * dx
|
|
closest_y = y1 + t * dy
|
|
|
|
# Abstand vom Punkt zum nächsten Punkt auf dem Segment
|
|
return ((px - closest_x)**2 + (py - closest_y)**2)**0.5
|
|
|
|
def _validate_symbols_in_frames(self, scene):
|
|
"""
|
|
Prüft ob alle generierten Symbole aus ma_groups in mindestens einem Renamer-Frame liegen.
|
|
|
|
Für POLYLINE_PATH Frames: Prüft ob die Polylinie einen Kreis um das Symbol schneidet
|
|
Für andere Frames: Prüft ob das Symbol innerhalb des Frames liegt
|
|
|
|
Gibt Warnungen aus für Symbole, die nicht die Kriterien erfüllen.
|
|
"""
|
|
if not self.generated_symbols:
|
|
return
|
|
|
|
print("\n" + "="*60)
|
|
print("Prüfe ob alle generierten Symbole in Renamer-Frames liegen")
|
|
print("="*60)
|
|
|
|
# Hole Symbol-Dimensionen aus Config (feste Werte)
|
|
dimensions = self.config.get('ma_defaults', {}).get('dimensions', {})
|
|
ma_symbol_width = dimensions.get('width', 1210)
|
|
ma_symbol_height = dimensions.get('height', 381)
|
|
|
|
# Hole Multi-Frame Dimensionen
|
|
multi_dimensions = self.config.get('general', {}).get('multi_frame', {}).get('dimensions', {})
|
|
multi_symbol_width = multi_dimensions.get('width', 1410)
|
|
multi_symbol_height = multi_dimensions.get('height', 381)
|
|
|
|
# Sammle alle Frame-Boundaries
|
|
frame_boundaries = []
|
|
for frame_config in scene.get('renaming_frames', []):
|
|
frame_type = frame_config.get('type', 'rectangle')
|
|
direction = frame_config.get('direction', '')
|
|
layer_name = frame_config.get('layer_name', '')
|
|
|
|
if frame_type == 'polyline_path' or direction == 'POLYLINE_PATH':
|
|
# Polyline-Pfad: verwende path_points direkt
|
|
path_points = frame_config.get('path_points', [])
|
|
if path_points:
|
|
polyline = [(p['x'], p['y']) for p in path_points]
|
|
frame_boundaries.append({
|
|
'validation_type': 'polyline_path',
|
|
'polyline': polyline,
|
|
'name': frame_config.get('name', 'UNKNOWN'),
|
|
'layer_name': layer_name,
|
|
'direction': direction
|
|
})
|
|
else:
|
|
# Rechteck: berechne Eckpunkte
|
|
rect_x = frame_config.get('x', 0)
|
|
rect_y = frame_config.get('y', 0)
|
|
rect_width = frame_config.get('width', 0)
|
|
rect_height = frame_config.get('height', 0)
|
|
|
|
# Erstelle Rechteck als Polygon (4 Eckpunkte)
|
|
polygon = [
|
|
(rect_x, rect_y),
|
|
(rect_x + rect_width, rect_y),
|
|
(rect_x + rect_width, rect_y + rect_height),
|
|
(rect_x, rect_y + rect_height)
|
|
]
|
|
frame_boundaries.append({
|
|
'validation_type': 'containment',
|
|
'boundary': polygon,
|
|
'rect_x': rect_x,
|
|
'rect_y': rect_y,
|
|
'rect_width': rect_width,
|
|
'rect_height': rect_height,
|
|
'name': frame_config.get('name', 'UNKNOWN'),
|
|
'layer_name': layer_name,
|
|
'direction': direction
|
|
})
|
|
|
|
# Prüfe jedes generierte Symbol
|
|
symbols_outside = []
|
|
for symbol in self.generated_symbols:
|
|
# Berechne Symbol-Dimensionen basierend auf IO-Text Länge
|
|
io_text = symbol['io']
|
|
char_count = len(io_text)
|
|
if char_count >= 7:
|
|
symbol_width = multi_symbol_width # Multi-Frame (BG/MB/POT)
|
|
symbol_height = multi_symbol_height
|
|
else:
|
|
symbol_width = ma_symbol_width # MA-Frame
|
|
symbol_height = ma_symbol_height
|
|
|
|
# Berechne Mittelpunkt des Symbols
|
|
# symbol['x'], symbol['y'] ist die linke untere Ecke
|
|
symbol_center_x = symbol['x'] + symbol_width / 2
|
|
symbol_center_y = symbol['y'] + symbol_height / 2
|
|
symbol_point = (symbol_center_x, symbol_center_y)
|
|
|
|
# Radius für Kreis-Test
|
|
symbol_radius = max(symbol_width, symbol_height) / 2
|
|
|
|
in_any_frame = False
|
|
validation_type_used = None
|
|
|
|
closest_frame = None
|
|
min_shift_distance = float('inf')
|
|
best_shift = (0, 0)
|
|
|
|
for frame in frame_boundaries:
|
|
# Prüfe ob Symbol auf dem richtigen Layer ist
|
|
if frame['layer_name'] and symbol['layer'] != frame['layer_name']:
|
|
continue
|
|
|
|
# Speichere den Validierungstyp für Fehlermeldung
|
|
if validation_type_used is None:
|
|
validation_type_used = frame['validation_type']
|
|
|
|
# Wähle Validierungsmethode basierend auf Frame-Typ
|
|
if frame['validation_type'] == 'polyline_path':
|
|
# POLYLINE_PATH: Prüfe ob Polylinie den Kreis um das Symbol schneidet
|
|
if self._circle_intersects_polyline(symbol_point, symbol_radius, frame['polyline']):
|
|
in_any_frame = True
|
|
break
|
|
# Für Polyline-Pfade: Berechne minimalen Abstand zur Polylinie
|
|
# (vereinfacht: nicht implementiert, da komplex)
|
|
else:
|
|
# Containment: Prüfe ob Symbol innerhalb des Frames liegt
|
|
if self._point_in_rectangle(
|
|
symbol_point,
|
|
frame['rect_x'],
|
|
frame['rect_y'],
|
|
frame['rect_width'],
|
|
frame['rect_height']
|
|
):
|
|
in_any_frame = True
|
|
break
|
|
|
|
# Berechne Verschiebung für diesen Frame
|
|
shift_x, shift_y = self._calculate_shift_to_rectangle(
|
|
symbol_point,
|
|
frame['rect_x'],
|
|
frame['rect_y'],
|
|
frame['rect_width'],
|
|
frame['rect_height']
|
|
)
|
|
shift_distance = (shift_x**2 + shift_y**2)**0.5
|
|
|
|
# Speichere den nächsten Frame
|
|
if shift_distance < min_shift_distance:
|
|
min_shift_distance = shift_distance
|
|
best_shift = (shift_x, shift_y)
|
|
closest_frame = frame
|
|
|
|
if not in_any_frame:
|
|
symbol_with_type = symbol.copy()
|
|
symbol_with_type['validation_type'] = validation_type_used
|
|
symbol_with_type['symbol_radius'] = symbol_radius
|
|
symbol_with_type['shift_x'] = best_shift[0]
|
|
symbol_with_type['shift_y'] = best_shift[1]
|
|
symbol_with_type['closest_frame_name'] = closest_frame.get('name', 'UNKNOWN') if closest_frame else 'UNKNOWN'
|
|
symbol_with_type['closest_frame_dict'] = closest_frame # Speichere das gesamte Dictionary
|
|
symbols_outside.append(symbol_with_type)
|
|
|
|
# Gib Warnungen aus und zeichne Fehlerkreise
|
|
if symbols_outside:
|
|
print(f"\nWARNUNG: {len(symbols_outside)} Symbol(e) erfüllen nicht die Frame-Kriterien:")
|
|
for symbol in symbols_outside:
|
|
base_msg = (f" Symbol IO='{symbol['io']}' aus Gruppe '{symbol['group_name']}' "
|
|
f"an Position ({symbol['x']:.1f}, {symbol['y']:.1f}) "
|
|
f"auf Layer '{symbol['layer']}'")
|
|
|
|
if symbol.get('validation_type') == 'polyline_path':
|
|
print(f"{base_msg}: "
|
|
f"Die Polylinie schneidet nicht den Umkreis des Symbols "
|
|
f"(Radius: {symbol['symbol_radius']:.1f})")
|
|
else:
|
|
shift_x = symbol.get('shift_x', 0)
|
|
shift_y = symbol.get('shift_y', 0)
|
|
closest_frame_name = symbol.get('closest_frame_name', 'UNKNOWN')
|
|
|
|
if shift_x != 0 or shift_y != 0:
|
|
print(f"{base_msg}: "
|
|
f"Symbol liegt nicht innerhalb des Renamer-Rahmens '{closest_frame_name}'. "
|
|
f"Verschieben Sie um dx={shift_x:.1f}, dy={shift_y:.1f}")
|
|
else:
|
|
print(f"{base_msg}: "
|
|
f"Symbol liegt nicht innerhalb des Renamer-Rahmens")
|
|
|
|
# Zeichne Fehlerkreis um das Symbol
|
|
# Berechne Symbol-Zentrum (symbol['x'], symbol['y'] = linke untere Ecke)
|
|
io_text = symbol['io']
|
|
char_count = len(io_text)
|
|
if char_count >= 7:
|
|
sym_width = multi_symbol_width
|
|
sym_height = multi_symbol_height
|
|
else:
|
|
sym_width = ma_symbol_width
|
|
sym_height = ma_symbol_height
|
|
|
|
symbol_center_x = symbol['x'] + sym_width / 2
|
|
symbol_center_y = symbol['y'] + sym_height / 2
|
|
|
|
self._draw_error_circle(symbol_center_x, symbol_center_y, symbol['symbol_radius'])
|
|
else:
|
|
print(f"\nOK: Alle {len(self.generated_symbols)} generierten Symbole erfüllen die Frame-Kriterien")
|
|
|
|
def _draw_error_circle(self, center_x, center_y, radius):
|
|
"""
|
|
Zeichnet einen roten Kreis um ein fehlerhaftes Symbol in die DXF-Datei.
|
|
|
|
Args:
|
|
center_x: X-Koordinate des Kreismittelpunkts
|
|
center_y: Y-Koordinate des Kreismittelpunkts
|
|
radius: Radius des Kreises
|
|
"""
|
|
self.msp.add_circle(
|
|
center=(center_x, center_y),
|
|
radius=radius,
|
|
dxfattribs={
|
|
'layer': 'VALIDATION_ERROR',
|
|
'color': 1 # Rot
|
|
}
|
|
)
|
|
|
|
|
|
|
|
def generate(self):
|
|
"""Generiert die Testdaten basierend auf dem Test-Typ"""
|
|
self.create_document()
|
|
|
|
# Prüfe ob Scene in Config existiert
|
|
if not self.config:
|
|
print("FEHLER: Keine Config-Datei geladen")
|
|
return False
|
|
|
|
if 'test_scenes' not in self.config:
|
|
print("FEHLER: Config enthält keinen 'test_scenes' Abschnitt")
|
|
return False
|
|
|
|
if self.scene_name not in self.config['test_scenes']:
|
|
print(f"FEHLER: Scene '{self.scene_name}' nicht in Config gefunden")
|
|
print(f"Verfügbare Scenes: {', '.join(self.config['test_scenes'].keys())}")
|
|
return False
|
|
|
|
# Generiere nur Nummerierung (andere Test-Typen werden später unterstützt)
|
|
self.generate_nummerierung(self.scene_name)
|
|
|
|
return True
|
|
|
|
|
|
def main():
|
|
"""Hauptfunktion mit Argparse"""
|
|
# Versuche Config zu lesen für --help Ausgabe
|
|
default_config = 'create_tests.json'
|
|
config_path = Path(PROJECT_CFG) / default_config
|
|
available_scenes = []
|
|
scene_descriptions = {}
|
|
|
|
if config_path.exists():
|
|
try:
|
|
with open(config_path, 'r', encoding='utf-8') as f:
|
|
config = json.load(f)
|
|
if 'test_scenes' in config:
|
|
for scene_name, scene_data in config['test_scenes'].items():
|
|
available_scenes.append(scene_name)
|
|
if 'description' in scene_data:
|
|
scene_descriptions[scene_name] = scene_data['description']
|
|
except Exception:
|
|
pass # Ignoriere Fehler beim Lesen für --help
|
|
|
|
# Erstelle Epilog mit verfügbaren Szenen
|
|
epilog_text = "\nVerfügbare Test-Szenen:\n"
|
|
if available_scenes:
|
|
for scene in sorted(available_scenes):
|
|
desc = scene_descriptions.get(scene, '')
|
|
if desc:
|
|
epilog_text += f" {scene}: {desc}\n"
|
|
else:
|
|
epilog_text += f" {scene}\n"
|
|
epilog_text += f"\n(aus Konfigurationsdatei: {config_path})"
|
|
else:
|
|
epilog_text += " (keine Szenen konfiguriert oder Config-Datei nicht gefunden)\n"
|
|
epilog_text += f" Standard-Pfad: {config_path}"
|
|
|
|
parser = argparse.ArgumentParser(
|
|
description='Erzeugt Test-DXF-Dateien für verschiedene Testszenarien',
|
|
epilog=epilog_text,
|
|
formatter_class=argparse.RawDescriptionHelpFormatter
|
|
)
|
|
|
|
parser.add_argument(
|
|
'--scene',
|
|
type=str,
|
|
default=None,
|
|
help='Name der Testszene (z.B. Nummerierung1, Nummerierung2). Muss in der Config existieren.'
|
|
)
|
|
|
|
parser.add_argument(
|
|
'--all',
|
|
action='store_true',
|
|
help='Erzeugt alle konfigurierten Testszenen'
|
|
)
|
|
|
|
parser.add_argument(
|
|
'--filename',
|
|
type=str,
|
|
default=None,
|
|
help='Name der zu erzeugenden Testdatei (z.B. Nummerierung1.dxf). Falls nicht angegeben, wird scene + .dxf verwendet. Wird bei --all ignoriert.'
|
|
)
|
|
|
|
parser.add_argument(
|
|
'--output-dir',
|
|
type=str,
|
|
default=None,
|
|
help='Ausgabeverzeichnis (Standard: testdata)'
|
|
)
|
|
|
|
parser.add_argument(
|
|
'--config',
|
|
type=str,
|
|
default=default_config,
|
|
help=f'JSON-Konfigurationsdatei (Standard: {default_config})'
|
|
)
|
|
|
|
parser.add_argument(
|
|
'--show_symbol_frames',
|
|
action='store_true',
|
|
help='Zeichne Rahmen um jedes Symbol, um die Grenze des Symbols zu visualisieren'
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
# Validierung: entweder --scene oder --all muss angegeben sein
|
|
if not args.scene and not args.all:
|
|
parser.error("Entweder --scene oder --all muss angegeben werden")
|
|
|
|
if args.scene and args.all:
|
|
parser.error("--scene und --all können nicht zusammen verwendet werden")
|
|
|
|
# Bestimme welche Szenen zu erzeugen sind
|
|
if args.all:
|
|
# Lade Config, um alle Szenen zu bekommen
|
|
config_path = Path(PROJECT_CFG) / args.config
|
|
if not config_path.exists():
|
|
print(f"FEHLER: Config-Datei nicht gefunden: {config_path}")
|
|
return 1
|
|
|
|
try:
|
|
with open(config_path, 'r', encoding='utf-8') as f:
|
|
config = json.load(f)
|
|
except json.JSONDecodeError as e:
|
|
print(f"FEHLER beim Parsen der Config-Datei: {e}")
|
|
return 1
|
|
|
|
if 'test_scenes' not in config:
|
|
print("FEHLER: Config enthält keinen 'test_scenes' Abschnitt")
|
|
return 1
|
|
|
|
scenes_to_generate = list(config['test_scenes'].keys())
|
|
print(f"Erzeuge alle {len(scenes_to_generate)} konfigurierten Testszenen:")
|
|
for scene in scenes_to_generate:
|
|
print(f" - {scene}")
|
|
print()
|
|
else:
|
|
# Einzelne Szene
|
|
scenes_to_generate = [args.scene]
|
|
|
|
# Generiere alle Szenen
|
|
success_count = 0
|
|
error_count = 0
|
|
|
|
for scene_name in scenes_to_generate:
|
|
# Bestimme Filename: entweder angegeben (nur bei einzelner Szene) oder aus scene ableiten
|
|
if args.filename and not args.all:
|
|
filename = args.filename
|
|
else:
|
|
filename = scene_name + '.dxf'
|
|
|
|
# Stelle sicher, dass Dateiname .dxf Endung hat
|
|
if not filename.endswith('.dxf'):
|
|
filename += '.dxf'
|
|
|
|
print(f"\n{'='*60}")
|
|
print(f"Erzeuge Testszene: {scene_name}")
|
|
print(f"Datei: {filename}")
|
|
print(f"{'='*60}")
|
|
|
|
# Generiere Testdaten
|
|
generator = TestDataGenerator(filename, scene_name, config_file=args.config)
|
|
|
|
if generator.generate():
|
|
# Zeichne Symbol-Rahmen wenn gewünscht
|
|
if args.show_symbol_frames:
|
|
generator.draw_frames_for_generated_symbols()
|
|
|
|
generator.save(args.output_dir)
|
|
success_count += 1
|
|
else:
|
|
print(f"Fehler bei der Generierung von {scene_name}")
|
|
error_count += 1
|
|
|
|
# Zusammenfassung
|
|
print(f"\n{'='*60}")
|
|
print(f"Zusammenfassung:")
|
|
print(f" Erfolgreich: {success_count}")
|
|
print(f" Fehler: {error_count}")
|
|
print(f"{'='*60}")
|
|
|
|
return 0 if error_count == 0 else 1
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|