943 lines
35 KiB
Python
943 lines
35 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
|
|
|
|
# Umgebungsvariablen
|
|
PROJECT = os.getenv('PROJECT', Path(__file__).parent.parent.absolute())
|
|
PROJECT_TEST = os.getenv('PROJECT_TEST', os.path.join(PROJECT, 'testdata'))
|
|
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
|
|
|
|
# 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': '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 save(self, output_dir=None):
|
|
"""Speichert das DXF-Dokument"""
|
|
if output_dir is None:
|
|
output_dir = PROJECT_TEST
|
|
|
|
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 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'):
|
|
"""
|
|
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 - würde später für BG/MG/POT verwendet
|
|
# Placeholder für Multi-Layer Frame
|
|
pass
|
|
|
|
# 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
|
|
|
|
# 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("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
|
|
# Der Insert-Punkt soll an der oberen linken Ecke des TEXT-D liegen
|
|
# TEXT-D ist bei (772.87, 110.44) relativ zum Block, Höhe 63.75
|
|
# Die obere linke Ecke von TEXT-D ist bei (772.87, 110.44 + 63.75) = (772.87, 174.19) relativ zum Block
|
|
# Daher muss der Block um diese Werte verschoben werden
|
|
text_d_x_offset = 772.87
|
|
text_d_y_offset = 110.44 + 63.75 # Basis + Höhe = obere linke Ecke
|
|
block_insert_x = x - text_d_x_offset
|
|
block_insert_y = y - text_d_y_offset
|
|
|
|
blockref = self.msp.add_blockref(
|
|
'io',
|
|
insert=(block_insert_x, block_insert_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, 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]
|
|
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', []):
|
|
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
|
|
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
|
|
ma1_width, ma1_height = self.calculate_symbol_extent('MA-1@@')
|
|
ma2_width, ma2_height = self.calculate_symbol_extent('MA-2@@')
|
|
|
|
# Abstand zwischen Symbolen (20% der Breite als Puffer)
|
|
spacing = ma1_width * 1.2
|
|
|
|
# Definiere Positionen
|
|
# Rechts oben: 3x MA-1@@ nebeneinander
|
|
top_right_y = 1000
|
|
top_right_x_start = 1500
|
|
|
|
# Der Insert-Punkt soll an der oberen linken Ecke des TEXT-D liegen
|
|
text_d_x_offset = 772.87
|
|
text_d_y_offset = 110.44 + 63.75 # Basis + Höhe = obere linke Ecke
|
|
|
|
for i in range(3):
|
|
x = top_right_x_start + i * spacing
|
|
block_insert_x = x - text_d_x_offset
|
|
block_insert_y = top_right_y - text_d_y_offset
|
|
blockref = self.msp.add_blockref(
|
|
'io',
|
|
# insert=(block_insert_x, block_insert_y),
|
|
insert=(x, y),
|
|
dxfattribs={'layer': 'ILS_MOTOR'}
|
|
)
|
|
# Erzeuge Attribut-Instanzen aus ATTDEFs mit Werten
|
|
blockref.add_auto_attribs({
|
|
'SPS': '1',
|
|
'IO': 'MA-1@@',
|
|
'VERW': 'Motor',
|
|
'BEZEICHNUNG': 'Motor MA-1@@',
|
|
'ARTIKELNR': '790902001',
|
|
'ARTIKELBEZEICHN': 'E-Teile für SEW Motor ASE1-HAN10ES-BG',
|
|
'KENNZEICHNUNG': '=A01+UH00',
|
|
'TEXT-D': '',
|
|
'TEXT-E': '',
|
|
'TEXT-ES': '',
|
|
'TEXT-F': '',
|
|
'TEXT-I': '',
|
|
'ID': '',
|
|
'REALE_POSITION': 'x',
|
|
})
|
|
|
|
# Links unten: 1x MA-1@@
|
|
left_bottom_x = 500
|
|
left_bottom_y = 200
|
|
# Der Insert-Punkt soll an der oberen linken Ecke des TEXT-D liegen
|
|
text_d_x_offset = 772.87
|
|
text_d_y_offset = 110.44 + 63.75 # Basis + Höhe = obere linke Ecke
|
|
block_insert_x = left_bottom_x - text_d_x_offset
|
|
block_insert_y = left_bottom_y - text_d_y_offset
|
|
blockref = self.msp.add_blockref(
|
|
'io',
|
|
#insert=(block_insert_x, block_insert_y),
|
|
insert=(left_bottom_x, left_bottom_y),
|
|
dxfattribs={'layer': 'ILS_MOTOR'}
|
|
)
|
|
blockref.add_auto_attribs({
|
|
'SPS': '1',
|
|
'IO': 'MA-1@@',
|
|
'VERW': 'Motor',
|
|
'BEZEICHNUNG': 'Motor MA-1@@',
|
|
'ARTIKELNR': '790902001',
|
|
'ARTIKELBEZEICHN': 'E-Teile für SEW Motor ASE1-HAN10ES-BG',
|
|
'KENNZEICHNUNG': '=A01+UH00',
|
|
'TEXT-D': '',
|
|
'TEXT-E': '',
|
|
'TEXT-F': '',
|
|
'TEXT-ES': '',
|
|
'ID': '',
|
|
'REALE_POSITION': 'x'
|
|
})
|
|
|
|
# Rechts unten: 3x MA-2@@ leicht versetzt
|
|
bottom_right_y_start = 400
|
|
bottom_right_x_start = top_right_x_start
|
|
y_offsets = [0, -50, 50] # Leichte vertikale Versetzung (angepasst an Symbol-Höhe)
|
|
|
|
# Der Insert-Punkt soll an der oberen linken Ecke des TEXT-D liegen
|
|
text_d_x_offset = 772.87
|
|
text_d_y_offset = 110.44 + 63.75 # Basis + Höhe = obere linke Ecke
|
|
|
|
for i in range(3):
|
|
x = bottom_right_x_start + i * spacing
|
|
y = bottom_right_y_start + y_offsets[i]
|
|
block_insert_x = x - text_d_x_offset
|
|
block_insert_y = y - text_d_y_offset
|
|
blockref = self.msp.add_blockref(
|
|
'io',
|
|
#insert=(block_insert_x, block_insert_y),
|
|
insert=(x, y),
|
|
dxfattribs={'layer': 'ILS_MOTOR'}
|
|
)
|
|
blockref.add_auto_attribs({
|
|
'SPS': '1',
|
|
'IO': 'MA-2@@',
|
|
'VERW': 'Motor',
|
|
'BEZEICHNUNG': 'Motor MA-2@@',
|
|
'ARTIKELNR': '790902001',
|
|
'ARTIKELBEZEICHN': 'E-Teile für SEW Motor ASE1-HAN10ES-BG',
|
|
'KENNZEICHNUNG': '=A01+UH00',
|
|
'TEXT-D': '',
|
|
'TEXT-ES': '',
|
|
'TEXT-F': '',
|
|
'ID': '',
|
|
'REALE_POSITION': 'x'
|
|
})
|
|
|
|
# Renamer-Rahmen erstellen (mit angepassten Größen basierend auf Symbol-Ausdehnung)
|
|
# Puffer für Rahmen
|
|
frame_padding = 100
|
|
|
|
# Rahmen 1: Rechts oben (Rechteck) - umschließt die 3 MA-1@@
|
|
frame1_width = 3 * spacing + ma1_width * 0.2
|
|
frame1_height = ma1_height + 2 * frame_padding
|
|
self.create_renamer_frame(
|
|
insert_point=(top_right_x_start - frame_padding, top_right_y + frame_padding),
|
|
width=frame1_width,
|
|
height=frame1_height,
|
|
name='MA-1@@',
|
|
kennzeichnung='A01+UH00',
|
|
layer_name='ILS_MOTOR',
|
|
direction='LEFT_RIGHT',
|
|
use_polyline=False
|
|
)
|
|
|
|
# Rahmen 2: Links unten (Polylinie) - umschließt das einzelne MA-1@@
|
|
# Schmaler machen - nur 15% breiter als das Symbol
|
|
frame2_padding = 50 # Kleinerer Puffer für schmalen Rahmen
|
|
frame2_width = ma1_width * 1.15
|
|
frame2_height = ma1_height + 2 * frame2_padding
|
|
self.create_renamer_frame(
|
|
insert_point=(left_bottom_x - frame2_padding, left_bottom_y - frame2_padding),
|
|
width=frame2_width,
|
|
height=frame2_height,
|
|
name='MA-1@@',
|
|
kennzeichnung='A01+UH00',
|
|
layer_name='ILS_MOTOR',
|
|
direction='TOP_BOTTOM/LEFT_RIGHT',
|
|
use_polyline=True
|
|
)
|
|
|
|
# Rahmen 3: Rechts unten (Rechteck) - umschließt die 3 MA-2@@
|
|
# Berücksichtige die vertikalen Offsets (max offset = 50)
|
|
frame3_width = 3 * spacing + ma2_width * 0.2
|
|
frame3_height = ma2_height + 2 * frame_padding + 100 # Extra für y_offsets
|
|
self.create_renamer_frame(
|
|
insert_point=(bottom_right_x_start - frame_padding, bottom_right_y_start - frame_padding - 50),
|
|
width=frame3_width,
|
|
height=frame3_height,
|
|
name='MA-2@@',
|
|
kennzeichnung='A01+UH00',
|
|
layer_name='ILS_MOTOR',
|
|
direction='TOP_BOTTOM',
|
|
use_polyline=False
|
|
)
|
|
|
|
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"""
|
|
parser = argparse.ArgumentParser(
|
|
description='Erzeugt Test-DXF-Dateien für verschiedene Testszenarien'
|
|
)
|
|
|
|
parser.add_argument(
|
|
'--scene',
|
|
type=str,
|
|
required=True,
|
|
help='Name der Testszene (z.B. Nummerierung1, Nummerierung2). Muss in der Config existieren.'
|
|
)
|
|
|
|
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.'
|
|
)
|
|
|
|
parser.add_argument(
|
|
'--output-dir',
|
|
type=str,
|
|
default=None,
|
|
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()
|
|
|
|
# Bestimme Filename: entweder angegeben oder aus scene ableiten
|
|
if args.filename:
|
|
filename = args.filename
|
|
else:
|
|
filename = args.scene + '.dxf'
|
|
|
|
# Stelle sicher, dass Dateiname .dxf Endung hat
|
|
if not filename.endswith('.dxf'):
|
|
filename += '.dxf'
|
|
|
|
# Generiere Testdaten
|
|
generator = TestDataGenerator(filename, args.scene, config_file=args.config)
|
|
|
|
if generator.generate():
|
|
generator.save(args.output_dir)
|
|
return 0
|
|
else:
|
|
print("Fehler bei der Generierung der Testdaten")
|
|
return 1
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|