erste Fassung der Beispielerzeugung

This commit is contained in:
2026-01-23 15:39:14 +01:00
parent 3deebb81f3
commit 3a964b9139
+663
View File
@@ -0,0 +1,663 @@
#!/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
from pathlib import Path
import ezdxf
from ezdxf.math import Vec3
# Umgebungsvariablen
PROJECT = os.getenv('PROJECT', Path(__file__).parent.parent.absolute())
PROJECT_TEST = os.getenv('PROJECT_TEST', os.path.join(PROJECT, 'testdata'))
class TestDataGenerator:
"""Generiert Test-DXF-Dateien für verschiedene Szenarien"""
def __init__(self, filename, test_type):
self.filename = filename
self.test_type = test_type
self.doc = None
self.msp = 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):
"""
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', ...])
Returns:
Block-Definition
"""
if block_name in self.doc.blocks:
return self.doc.blocks[block_name]
attrib_block = self.doc.blocks.new(block_name)
# Definiere Standard-Positionen und Werte für Attribute
# Font-Größe auf ein Viertel reduziert (85.0 → 21.25)
attrib_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 attrib_definitions:
attdef = attrib_definitions[tag]
attrib_block.add_attdef(
tag=tag,
insert=attdef['insert'],
text=attdef['default'],
dxfattribs={
'height': attdef['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)
self.create_renamer_attrib_block(attrib_block_name, attrib_tags)
# 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 generate_nummerierung(self):
"""
Generiert Testdaten für Nummerierung-Szenario
Layout:
- 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...")
# 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
# 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
for i in range(3):
x = top_right_x_start + i * spacing
blockref = self.msp.add_blockref(
'io',
insert=(x, top_right_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
blockref = self.msp.add_blockref(
'io',
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)
for i in range(3):
x = bottom_right_x_start + i * spacing
y = bottom_right_y_start + y_offsets[i]
blockref = self.msp.add_blockref(
'io',
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-E': '',
'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()
if self.test_type == 'nummerierung':
self.generate_nummerierung()
elif self.test_type == 'sps':
print("SPS-Testdaten noch nicht implementiert")
elif self.test_type == 'todraw':
print("ToDraw-Testdaten noch nicht implementiert")
elif self.test_type == 'erdung':
print("Erdung-Testdaten noch nicht implementiert")
elif self.test_type == 'translation':
print("Translation-Testdaten noch nicht implementiert")
elif self.test_type == 'hoehe':
print("Höhe-Testdaten noch nicht implementiert")
else:
print(f"Unbekannter Test-Typ: {self.test_type}")
return False
return True
def main():
"""Hauptfunktion mit Argparse"""
parser = argparse.ArgumentParser(
description='Erzeugt Test-DXF-Dateien für verschiedene Testszenarien'
)
parser.add_argument(
'--filename',
type=str,
required=True,
help='Name der zu erzeugenden Testdatei (z.B. Nummerierung1.dxf)'
)
parser.add_argument(
'--type',
type=str,
required=True,
choices=['nummerierung', 'sps', 'todraw', 'erdung', 'translation', 'hoehe'],
help='Typ des Tests'
)
parser.add_argument(
'--output-dir',
type=str,
default=None,
help='Ausgabeverzeichnis (Standard: testdata)'
)
args = parser.parse_args()
# Stelle sicher, dass Dateiname .dxf Endung hat
if not args.filename.endswith('.dxf'):
args.filename += '.dxf'
# Generiere Testdaten
generator = TestDataGenerator(args.filename, args.type)
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())