Schalter --show_symbol_frames dazugemacht.

This commit is contained in:
2026-01-29 16:45:48 +01:00
parent c4ef6e28f4
commit ae82736960
3 changed files with 442 additions and 34 deletions
+295
View File
@@ -0,0 +1,295 @@
import ezdxf
from ezdxf import colors
from ezdxf.enums import TextEntityAlignment
import math
def create_grounding_symbol(filename="erdung.dxf"):
"""Erstellt ein Schutzerde-Symbol nach DIN EN 60617"""
doc = ezdxf.new('R2010')
msp = doc.modelspace()
# Block erstellen
block = doc.blocks.new(name='ERDUNG')
# Mittelpunkt (relativ zum Block)
cx, cy = 0, 0
# Vertikale Linie (Erdungsleiter)
block.add_line((cx, cy + 60), (cx, cy - 20))
# Drei horizontale Linien (Erdsymbol)
# Obere Linie (längste)
block.add_line((cx - 40, cy - 20), (cx + 40, cy - 20))
# Mittlere Linie
block.add_line((cx - 25, cy - 35), (cx + 25, cy - 35))
# Untere Linie (kürzeste)
block.add_line((cx - 15, cy - 50), (cx + 15, cy - 50))
# Beschriftung PE
block.add_text("PE", dxfattribs={
'height': 12,
'insert': (cx, cy + 70),
'halign': TextEntityAlignment.CENTER
})
# Attribute definieren
block.add_attdef(tag='REALE_POSITION', insert=(cx, cy - 70), dxfattribs={
'height': 8,
'prompt': 'Reale Position:',
'default': 'x',
'halign': TextEntityAlignment.CENTER,
'invisible': 1 # Unsichtbar, nur für Daten
})
block.add_attdef(tag='NAME', insert=(cx, cy - 85), dxfattribs={
'height': 10,
'prompt': 'Name:',
'default': '',
'halign': TextEntityAlignment.CENTER
})
block.add_attdef(tag='KENNZEICHNUNG', insert=(cx, cy - 100), dxfattribs={
'height': 8,
'prompt': 'Kennzeichnung:',
'default': '=A01+UH00-X01',
'halign': TextEntityAlignment.CENTER,
'invisible': 1 # Unsichtbar, nur für Daten
})
# Block-Instanz einfügen
msp.add_blockref('ERDUNG', (125, 125))
doc.saveas(filename)
print(f"{filename} erstellt")
def create_switchboard_symbol(filename="schaltschrank.dxf"):
"""Erstellt ein schematisches Schaltschrank-Symbol für Lagepläne"""
doc = ezdxf.new('R2010')
msp = doc.modelspace()
# Block erstellen
block = doc.blocks.new(name='SCHALTSCHRANK')
# Mittelpunkt (relativ zum Block)
cx, cy = 0, 0
# Rechteck für Schaltschrank (60x80mm)
width, height = 60, 80
x1, y1 = cx - width/2, cy - height/2
x2, y2 = cx + width/2, cy + height/2
# Außenrahmen
block.add_lwpolyline([
(x1, y1), (x2, y1), (x2, y2), (x1, y2), (x1, y1)
], dxfattribs={'closed': True})
# Innere Unterteilung (Fächer)
block.add_line((x1, cy - 15), (x2, cy - 15))
block.add_line((x1, cy + 15), (x2, cy + 15))
# Türgriff-Symbol (vertikale Linie links)
block.add_line((x1 + 5, cy - 25), (x1 + 5, cy + 25))
# Beschriftung
block.add_text("Schaltschrank", dxfattribs={
'height': 10,
'insert': (cx, y2 + 15),
'halign': TextEntityAlignment.CENTER
})
# Attribute definieren
block.add_attdef(tag='REALE_POSITION', insert=(cx, y1 - 15), dxfattribs={
'height': 8,
'prompt': 'Reale Position:',
'default': 'x',
'halign': TextEntityAlignment.CENTER,
'invisible': 1
})
block.add_attdef(tag='NAME', insert=(cx, y1 - 30), dxfattribs={
'height': 10,
'prompt': 'Name:',
'default': '',
'halign': TextEntityAlignment.CENTER
})
block.add_attdef(tag='KENNZEICHNUNG', insert=(cx, y1 - 45), dxfattribs={
'height': 8,
'prompt': 'Kennzeichnung:',
'default': '=A01+UH00-X01',
'halign': TextEntityAlignment.CENTER,
'invisible': 1
})
# Block-Instanz einfügen
msp.add_blockref('SCHALTSCHRANK', (125, 125))
doc.saveas(filename)
print(f"{filename} erstellt")
def create_subdistribution_symbol(filename="unterverteiler.dxf"):
"""Erstellt ein Unterverteiler-Symbol (ähnlich Schaltschrank, kleiner)"""
doc = ezdxf.new('R2010')
msp = doc.modelspace()
# Block erstellen
block = doc.blocks.new(name='UNTERVERTEILER')
# Mittelpunkt (relativ zum Block)
cx, cy = 0, 0
# Rechteck für Unterverteiler (45x60mm - kleiner als Schaltschrank)
width, height = 45, 60
x1, y1 = cx - width/2, cy - height/2
x2, y2 = cx + width/2, cy + height/2
# Außenrahmen
block.add_lwpolyline([
(x1, y1), (x2, y1), (x2, y2), (x1, y2), (x1, y1)
], dxfattribs={'closed': True})
# Innere Unterteilung (weniger Fächer)
block.add_line((x1, cy), (x2, cy))
# Türgriff-Symbol
block.add_line((x1 + 4, cy - 15), (x1 + 4, cy + 15))
# Beschriftung UV (Unterverteiler)
block.add_text("UV", dxfattribs={
'height': 12,
'insert': (cx, cy),
'halign': TextEntityAlignment.CENTER,
'valign': TextEntityAlignment.MIDDLE
})
# Zusätzliche Beschriftung
block.add_text("Unterverteiler", dxfattribs={
'height': 10,
'insert': (cx, y2 + 15),
'halign': TextEntityAlignment.CENTER
})
# Attribute definieren
block.add_attdef(tag='REALE_POSITION', insert=(cx, y1 - 15), dxfattribs={
'height': 8,
'prompt': 'Reale Position:',
'default': 'x',
'halign': TextEntityAlignment.CENTER,
'invisible': 1
})
block.add_attdef(tag='NAME', insert=(cx, y1 - 30), dxfattribs={
'height': 10,
'prompt': 'Name:',
'default': '',
'halign': TextEntityAlignment.CENTER
})
block.add_attdef(tag='KENNZEICHNUNG', insert=(cx, y1 - 45), dxfattribs={
'height': 8,
'prompt': 'Kennzeichnung:',
'default': '=A01+UH00-X01',
'halign': TextEntityAlignment.CENTER,
'invisible': 1
})
# Block-Instanz einfügen
msp.add_blockref('UNTERVERTEILER', (125, 125))
doc.saveas(filename)
print(f"{filename} erstellt")
def create_motor_symbol(filename="motor.dxf"):
"""Erstellt ein allgemeines Motor-Symbol (M im Kreis) nach DIN EN 60617"""
doc = ezdxf.new('R2010')
msp = doc.modelspace()
# Block erstellen
block = doc.blocks.new(name='MOTOR')
# Mittelpunkt (relativ zum Block)
cx, cy = 0, 0
radius = 30
# Kreis
block.add_circle((cx, cy), radius)
# Buchstabe "M" in der Mitte
block.add_text("M", dxfattribs={
'height': 25,
'insert': (cx, cy),
'halign': TextEntityAlignment.CENTER,
'valign': TextEntityAlignment.MIDDLE
})
# Anschlussklemmen (drei Linien für Drehstrom - optional)
# Oben
block.add_line((cx, cy + radius), (cx, cy + radius + 20))
# Links unten
angle1 = math.radians(210)
x1 = cx + radius * math.cos(angle1)
y1 = cy + radius * math.sin(angle1)
block.add_line((x1, y1), (x1 - 15, y1 - 10))
# Rechts unten
angle2 = math.radians(330)
x2 = cx + radius * math.cos(angle2)
y2 = cy + radius * math.sin(angle2)
block.add_line((x2, y2), (x2 + 15, y2 - 10))
# Beschriftung
block.add_text("Motor", dxfattribs={
'height': 10,
'insert': (cx, cy - radius - 25),
'halign': TextEntityAlignment.CENTER
})
# Attribute definieren
block.add_attdef(tag='REALE_POSITION', insert=(cx, cy - radius - 40), dxfattribs={
'height': 8,
'prompt': 'Reale Position:',
'default': 'x',
'halign': TextEntityAlignment.CENTER,
'invisible': 1
})
block.add_attdef(tag='NAME', insert=(cx, cy - radius - 55), dxfattribs={
'height': 10,
'prompt': 'Name:',
'default': '',
'halign': TextEntityAlignment.CENTER
})
block.add_attdef(tag='KENNZEICHNUNG', insert=(cx, cy - radius - 70), dxfattribs={
'height': 8,
'prompt': 'Kennzeichnung:',
'default': '=A01+UH00-X01',
'halign': TextEntityAlignment.CENTER,
'invisible': 1
})
# Block-Instanz einfügen
msp.add_blockref('MOTOR', (125, 125))
doc.saveas(filename)
print(f"{filename} erstellt")
# Hauptprogramm
if __name__ == "__main__":
print("Erstelle DXF Elektrosymbole nach DIN EN 60617 mit Attributen...\n")
create_grounding_symbol()
create_switchboard_symbol()
create_subdistribution_symbol()
create_motor_symbol()
print("\n✓ Alle Symbole wurden erfolgreich erstellt!")
print("\nDateien:")
print(" - erdung.dxf")
print(" - schaltschrank.dxf")
print(" - unterverteiler.dxf")
print(" - motor.dxf")
print("\nJedes Symbol ist als Block mit folgenden Attributen definiert:")
print(" - REALE_POSITION (Standard: 'x', unsichtbar)")
print(" - NAME (Standard: leer, sichtbar unter dem Symbol)")
print(" - KENNZEICHNUNG (Standard: '=A01+UH00-X01', unsichtbar)")
print("\nDie Attribute können in CAD-Programmen bearbeitet werden.")
+89 -29
View File
@@ -14,9 +14,11 @@ import json
from pathlib import Path from pathlib import Path
import ezdxf import ezdxf
from symbol_frames import draw_symbol_frames
# Umgebungsvariablen # Umgebungsvariablen
PROJECT = os.getenv('PROJECT', Path(__file__).parent.parent.absolute()) PROJECT = os.getenv('PROJECT', Path(__file__).parent.parent.absolute())
PROJECT_TEST = os.getenv('PROJECT_TEST', os.path.join(PROJECT, 'testdata')) PROJECT_WORK = os.getenv('PROJECT_WORK', os.path.join(PROJECT, 'work'))
PROJECT_CFG = os.getenv('PROJECT_CFG', os.path.join(PROJECT, 'cfg')) PROJECT_CFG = os.getenv('PROJECT_CFG', os.path.join(PROJECT, 'cfg'))
@@ -284,13 +286,38 @@ class TestDataGenerator:
def save(self, output_dir=None): def save(self, output_dir=None):
"""Speichert das DXF-Dokument""" """Speichert das DXF-Dokument"""
if output_dir is None: if output_dir is None:
output_dir = PROJECT_TEST output_dir = PROJECT_WORK
output_path = os.path.join(output_dir, self.filename) output_path = os.path.join(output_dir, self.filename)
self.doc.saveas(output_path) self.doc.saveas(output_path)
print(f"Test-DXF erstellt: {output_path}") print(f"Test-DXF erstellt: {output_path}")
return 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): def load_block_from_file(self, source_file, block_name):
""" """
Lädt einen Block aus einer anderen DXF-Datei Lädt einen Block aus einer anderen DXF-Datei
@@ -608,15 +635,15 @@ class TestDataGenerator:
return offset_map.get(insert_point, (0, 0)) # Default: left_top_corner return offset_map.get(insert_point, (0, 0)) # Default: left_top_corner
def _generate_ma_group(self, group, ma_defaults, width_per_char, fixed_height, spacing_factor, y_offsets_list, insert_point='left_top_corner'): def _generate_ma_group(self, group, ma_defaults, symbol_width, symbol_height, spacing_factor, y_offsets_list, insert_point='left_top_corner'):
""" """
Generiert eine Gruppe von MA-Symbolen Generiert eine Gruppe von MA-Symbolen
Args: Args:
group: Dict mit Gruppen-Config (name, count, layout_type, base_x, base_y, spacing) group: Dict mit Gruppen-Config (name, count, layout_type, base_x, base_y, spacing)
ma_defaults: Dict mit MA-Default-Attributen ma_defaults: Dict mit MA-Default-Attributen
width_per_char: Breite pro Zeichen symbol_width: Feste Breite des Symbols
fixed_height: Fixe Symbol-Höhe symbol_height: Feste Höhe des Symbols
spacing_factor: Spacing-Faktor zwischen Symbolen spacing_factor: Spacing-Faktor zwischen Symbolen
y_offsets_list: Liste von Y-Offsets für horizontal_offset Layout y_offsets_list: Liste von Y-Offsets für horizontal_offset Layout
insert_point: Einfügepunkt des Blocks (z.B. "left_top_corner", "center") insert_point: Einfügepunkt des Blocks (z.B. "left_top_corner", "center")
@@ -630,9 +657,6 @@ class TestDataGenerator:
# Extrahiere IO-Pattern aus Name (z.B. "MA-1@@_top" -> "MA-1@@") # Extrahiere IO-Pattern aus Name (z.B. "MA-1@@_top" -> "MA-1@@")
io_pattern = name.split('_')[0] io_pattern = name.split('_')[0]
# Berechne Symbol-Ausdehnung
symbol_width = len(io_pattern) * width_per_char
# Berechne Spacing # Berechne Spacing
if layout_type in ['horizontal', 'horizontal_offset']: if layout_type in ['horizontal', 'horizontal_offset']:
actual_spacing = symbol_width * spacing_factor actual_spacing = symbol_width * spacing_factor
@@ -644,7 +668,7 @@ class TestDataGenerator:
layer = ma_defaults.get('layer', 'ILS_MOTOR') layer = ma_defaults.get('layer', 'ILS_MOTOR')
# Berechne Offset basierend auf Einfügepunkt # Berechne Offset basierend auf Einfügepunkt
offset_x, offset_y = self._calculate_insert_offset(insert_point, symbol_width, fixed_height) offset_x, offset_y = self._calculate_insert_offset(insert_point, symbol_width, symbol_height)
# Generiere Symbole basierend auf Layout-Typ # Generiere Symbole basierend auf Layout-Typ
for i in range(count): for i in range(count):
@@ -669,7 +693,7 @@ class TestDataGenerator:
# (da der io-Block bei (0,0) startet und nach oben geht) # (da der io-Block bei (0,0) startet und nach oben geht)
blockref = self.msp.add_blockref( blockref = self.msp.add_blockref(
'io', 'io',
insert=(left_top_x, left_top_y - fixed_height), insert=(left_top_x, left_top_y - symbol_height),
dxfattribs={'layer': layer} dxfattribs={'layer': layer}
) )
@@ -681,10 +705,12 @@ class TestDataGenerator:
blockref.add_auto_attribs(attrib_values) blockref.add_auto_attribs(attrib_values)
# Speichere Symbol-Position für spätere Validierung # Speichere Symbol-Position für spätere Validierung
# Speichere immer die linke obere Ecke # 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({ self.generated_symbols.append({
'x': left_top_x, 'x': actual_insert_x,
'y': left_top_y, 'y': actual_insert_y,
'io': io_pattern, 'io': io_pattern,
'group_name': name, 'group_name': name,
'layer': layer 'layer': layer
@@ -701,10 +727,10 @@ class TestDataGenerator:
ma_defaults = self.config.get('ma_defaults', {}) ma_defaults = self.config.get('ma_defaults', {})
general = self.config.get('general', {}) general = self.config.get('general', {})
# Hole Dimensions aus Config # Hole Dimensions aus Config (feste Werte)
dimensions = ma_defaults.get('dimensions', {}) dimensions = ma_defaults.get('dimensions', {})
width_per_char = dimensions.get('width_per_char', 201.49) symbol_width = dimensions.get('width', 1210)
fixed_height = dimensions.get('fixed_height', 380.94) symbol_height = dimensions.get('height', 381)
# Hole Layout-Einstellungen # Hole Layout-Einstellungen
layout = general.get('layout', {}) layout = general.get('layout', {})
@@ -717,7 +743,7 @@ class TestDataGenerator:
# Generiere MA-Gruppen aus Config # Generiere MA-Gruppen aus Config
for group in scene.get('ma_groups', []): for group in scene.get('ma_groups', []):
self._generate_ma_group(group, ma_defaults, width_per_char, fixed_height, spacing_factor, y_offsets_list, insert_point) self._generate_ma_group(group, ma_defaults, symbol_width, symbol_height, spacing_factor, y_offsets_list, insert_point)
# Generiere Renamer-Rahmen aus Config # Generiere Renamer-Rahmen aus Config
for frame_config in scene.get('renaming_frames', []): for frame_config in scene.get('renaming_frames', []):
@@ -916,10 +942,15 @@ class TestDataGenerator:
print("Prüfe ob alle generierten Symbole in Renamer-Frames liegen") print("Prüfe ob alle generierten Symbole in Renamer-Frames liegen")
print("="*60) print("="*60)
# Hole Symbol-Dimensionen aus Config # Hole Symbol-Dimensionen aus Config (feste Werte)
dimensions = self.config.get('ma_defaults', {}).get('dimensions', {}) dimensions = self.config.get('ma_defaults', {}).get('dimensions', {})
width_per_char = dimensions.get('width_per_char', 201.49) ma_symbol_width = dimensions.get('width', 1210)
fixed_height = dimensions.get('fixed_height', 380.94) 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 # Sammle alle Frame-Boundaries
frame_boundaries = [] frame_boundaries = []
@@ -969,15 +1000,20 @@ class TestDataGenerator:
# Prüfe jedes generierte Symbol # Prüfe jedes generierte Symbol
symbols_outside = [] symbols_outside = []
for symbol in self.generated_symbols: for symbol in self.generated_symbols:
# Berechne Symbol-Dimensionen # Berechne Symbol-Dimensionen basierend auf IO-Text Länge
io_text = symbol['io'] io_text = symbol['io']
symbol_width = len(io_text) * width_per_char char_count = len(io_text)
symbol_height = fixed_height 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 # Berechne Mittelpunkt des Symbols
# symbol['x'], symbol['y'] ist die linke obere Ecke # symbol['x'], symbol['y'] ist die linke untere Ecke
symbol_center_x = symbol['x'] + symbol_width / 2 symbol_center_x = symbol['x'] + symbol_width / 2
symbol_center_y = symbol['y'] - symbol_height / 2 symbol_center_y = symbol['y'] + symbol_height / 2
symbol_point = (symbol_center_x, symbol_center_y) symbol_point = (symbol_center_x, symbol_center_y)
# Radius für Kreis-Test # Radius für Kreis-Test
@@ -1041,7 +1077,8 @@ class TestDataGenerator:
symbol_with_type['symbol_radius'] = symbol_radius symbol_with_type['symbol_radius'] = symbol_radius
symbol_with_type['shift_x'] = best_shift[0] symbol_with_type['shift_x'] = best_shift[0]
symbol_with_type['shift_y'] = best_shift[1] symbol_with_type['shift_y'] = best_shift[1]
symbol_with_type['closest_frame'] = closest_frame.get('name', 'UNKNOWN') if closest_frame else 'UNKNOWN' 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) symbols_outside.append(symbol_with_type)
# Gib Warnungen aus und zeichne Fehlerkreise # Gib Warnungen aus und zeichne Fehlerkreise
@@ -1059,18 +1096,31 @@ class TestDataGenerator:
else: else:
shift_x = symbol.get('shift_x', 0) shift_x = symbol.get('shift_x', 0)
shift_y = symbol.get('shift_y', 0) shift_y = symbol.get('shift_y', 0)
closest_frame = symbol.get('closest_frame', 'UNKNOWN') closest_frame_name = symbol.get('closest_frame_name', 'UNKNOWN')
if shift_x != 0 or shift_y != 0: if shift_x != 0 or shift_y != 0:
print(f"{base_msg}: " print(f"{base_msg}: "
f"Symbol liegt nicht innerhalb des Renamer-Rahmens '{closest_frame}'. " f"Symbol liegt nicht innerhalb des Renamer-Rahmens '{closest_frame_name}'. "
f"Verschieben Sie um dx={shift_x:.1f}, dy={shift_y:.1f}") f"Verschieben Sie um dx={shift_x:.1f}, dy={shift_y:.1f}")
else: else:
print(f"{base_msg}: " print(f"{base_msg}: "
f"Symbol liegt nicht innerhalb des Renamer-Rahmens") f"Symbol liegt nicht innerhalb des Renamer-Rahmens")
# Zeichne Fehlerkreis um das Symbol # Zeichne Fehlerkreis um das Symbol
self._draw_error_circle(symbol['x'], symbol['y'], symbol['symbol_radius']) # 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: else:
print(f"\nOK: Alle {len(self.generated_symbols)} generierten Symbole erfüllen die Frame-Kriterien") print(f"\nOK: Alle {len(self.generated_symbols)} generierten Symbole erfüllen die Frame-Kriterien")
@@ -1186,6 +1236,12 @@ def main():
help=f'JSON-Konfigurationsdatei (Standard: {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() args = parser.parse_args()
# Bestimme Filename: entweder angegeben oder aus scene ableiten # Bestimme Filename: entweder angegeben oder aus scene ableiten
@@ -1202,6 +1258,10 @@ def main():
generator = TestDataGenerator(filename, args.scene, config_file=args.config) generator = TestDataGenerator(filename, args.scene, config_file=args.config)
if generator.generate(): 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) generator.save(args.output_dir)
return 0 return 0
else: else:
+58 -5
View File
@@ -4,6 +4,7 @@ from pathlib import Path
from error_collector import ErrorCollector, write_json_file from error_collector import ErrorCollector, write_json_file
from utils import check_environment_var, check_file_in_work, dxf_is_binary, get_dxf_file from utils import check_environment_var, check_file_in_work, dxf_is_binary, get_dxf_file
from symbol_frames import draw_symbol_frames
""" """
@@ -491,6 +492,7 @@ def collect_and_group_renamer_blocks(doc, msp, renamer_layers, error_collector):
continue continue
print(f" Boundary gefunden mit {len(boundary)} Punkten (Rechteck: {is_rectangle})") print(f" Boundary gefunden mit {len(boundary)} Punkten (Rechteck: {is_rectangle})")
print(f" Eckpunkte: {', '.join([f'({p[0]:.2f}, {p[1]:.2f})' for p in boundary])}")
# Validiere DIRECTION gegen Geometrie # Validiere DIRECTION gegen Geometrie
direction = attributes.get("DIRECTION", "LEFT_RIGHT") direction = attributes.get("DIRECTION", "LEFT_RIGHT")
@@ -679,17 +681,59 @@ def process_renamer_blocks(doc, msp, renamer_layers, error_collector):
Verarbeitet alle Renamer-Blöcke auf den angegebenen Layern. Verarbeitet alle Renamer-Blöcke auf den angegebenen Layern.
Gruppiert Blöcke mit identischer Konfiguration und nummeriert alle Symbole Gruppiert Blöcke mit identischer Konfiguration und nummeriert alle Symbole
aus allen Blöcken einer Gruppe gemeinsam. aus allen Blöcken einer Gruppe gemeinsam.
Returns:
Tuple (all_renamed, renamer_groups):
- all_renamed: Liste der umbenannten Symbole
- renamer_groups: Dictionary der gruppierten Renamer-Blöcke
""" """
# Erster Pass: Sammle und gruppiere alle Renamer-Blöcke # Erster Pass: Sammle und gruppiere alle Renamer-Blöcke
renamer_groups = collect_and_group_renamer_blocks(doc, msp, renamer_layers, error_collector) renamer_groups = collect_and_group_renamer_blocks(doc, msp, renamer_layers, error_collector)
# Zweiter Pass: Verarbeite jede Gruppe # Zweiter Pass: Verarbeite jede Gruppe
all_renamed = process_renamer_groups(doc, msp, renamer_layers, renamer_groups, error_collector) all_renamed = process_renamer_groups(doc, msp, renamer_layers, renamer_groups, error_collector)
# Dritter Pass: Prüfe ob alle Symbole mit "@" im IO in einem Renamer-Bereich liegen # Dritter Pass: Prüfe ob alle Symbole mit "@" im IO in einem Renamer-Bereich liegen
check_symbols_in_boundaries(doc, msp, renamer_groups, error_collector) check_symbols_in_boundaries(doc, msp, renamer_groups, error_collector)
return all_renamed return all_renamed, renamer_groups
def collect_all_symbols_from_groups(doc, msp, renamer_groups, renamer_layers, error_collector):
"""
Sammelt alle Symbole aus allen Renamer-Gruppen für Visualisierungszwecke.
Args:
doc: DXF-Dokument
msp: Modelspace
renamer_groups: Dictionary mit gruppierten Renamer-Blöcken
renamer_layers: Liste der Layer mit Renamer-Blöcken
error_collector: ErrorCollector für Fehlerbehandlung
Returns:
Liste aller gefundenen Symbole mit 'entity', 'position', 'attributes', 'layer'
"""
all_symbols = []
seen_entities = set() # Verhindere Duplikate
for config_key, group_blocks in renamer_groups.items():
if not group_blocks:
continue
reference_attributes = group_blocks[0][2]
for insert, boundary, attributes, is_rectangle in group_blocks:
# Finde Symbole innerhalb dieses Bereichs
symbols = find_symbols_in_boundary(doc, msp, boundary, renamer_layers, attributes, error_collector)
# Füge Symbole hinzu, aber nur wenn das Entity noch nicht gesehen wurde
for symbol in symbols:
entity_id = id(symbol['entity'])
if entity_id not in seen_entities:
seen_entities.add(entity_id)
all_symbols.append(symbol)
return all_symbols
if __name__ == '__main__': if __name__ == '__main__':
@@ -726,6 +770,7 @@ if __name__ == '__main__':
parser.add_argument('-e', '--errorfile', action='store', required=False, help='JSON-Datei für Fehler und Warnungen', metavar='errors.json') parser.add_argument('-e', '--errorfile', action='store', required=False, help='JSON-Datei für Fehler und Warnungen', metavar='errors.json')
parser.add_argument('-w', '--write', action='store', help='Schreibe Ergebnisse der Nummerierung in eine JSON-Datei') parser.add_argument('-w', '--write', action='store', help='Schreibe Ergebnisse der Nummerierung in eine JSON-Datei')
parser.add_argument('-d', '--dryrun', action='store_true', help='Symbole nicht in der DXF-Datei überschreiben, nur Ausgabe auf Konsole') parser.add_argument('-d', '--dryrun', action='store_true', help='Symbole nicht in der DXF-Datei überschreiben, nur Ausgabe auf Konsole')
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() args = parser.parse_args()
@@ -770,7 +815,7 @@ if __name__ == '__main__':
print("Starte Verarbeitung der Renamer-Blöcke") print("Starte Verarbeitung der Renamer-Blöcke")
print("="*60 + "\n") print("="*60 + "\n")
renamed_symbols = process_renamer_blocks(doc, msp, renamer_layers, error_collector) renamed_symbols, renamer_groups = process_renamer_blocks(doc, msp, renamer_layers, error_collector)
print("\n" + "="*60) print("\n" + "="*60)
print(f"Verarbeitung abgeschlossen: {len(renamed_symbols)} Symbole nummeriert") print(f"Verarbeitung abgeschlossen: {len(renamed_symbols)} Symbole nummeriert")
@@ -782,6 +827,14 @@ if __name__ == '__main__':
for item in renamed_symbols: for item in renamed_symbols:
print(f" {item['old_value']} -> {item['new_value']} (Layer: {item['layer']}, Pos: {item['position']})") print(f" {item['old_value']} -> {item['new_value']} (Layer: {item['layer']}, Pos: {item['position']})")
# Zeichne Symbol-Rahmen wenn gewünscht
if args.show_symbol_frames:
all_symbols = collect_all_symbols_from_groups(doc, msp, renamer_groups, renamer_layers, error_collector)
if all_symbols:
draw_symbol_frames(doc, msp, all_symbols)
else:
print("\nKeine Symbole für Rahmen-Zeichnung gefunden")
# Speichere DXF-Datei wenn nicht dry-run # Speichere DXF-Datei wenn nicht dry-run
if not args.dryrun: if not args.dryrun:
output_path = dxf_path.parent / f"{dxf_path.stem}_numbered{dxf_path.suffix}" output_path = dxf_path.parent / f"{dxf_path.stem}_numbered{dxf_path.suffix}"