Zusätzliche Abfragen für Fehler durch überlappende Renaming Frames eingebaut

This commit is contained in:
2026-02-05 17:36:18 +01:00
parent 31084defdb
commit e600dfbae8
3 changed files with 436 additions and 30 deletions
+320 -21
View File
@@ -883,7 +883,7 @@ class TestDataGenerator:
# Generiere Renamer-Rahmen aus Config
for frame_config in scene.get('renaming_frames', []):
frame_type = frame_config.get('type', 'rectangle')
frame_type = frame_config.get('type', 'rectangle')
if frame_type == 'polyline_path':
# Polyline-Pfad Frame
@@ -933,6 +933,245 @@ class TestDataGenerator:
# 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.
@@ -1347,15 +1586,21 @@ def main():
parser.add_argument(
'--scene',
type=str,
required=True,
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.'
help='Name der zu erzeugenden Testdatei (z.B. Nummerierung1.dxf). Falls nicht angegeben, wird scene + .dxf verwendet. Wird bei --all ignoriert.'
)
parser.add_argument(
@@ -1380,29 +1625,83 @@ def main():
args = parser.parse_args()
# Bestimme Filename: entweder angegeben oder aus scene ableiten
if args.filename:
filename = args.filename
# 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:
filename = args.scene + '.dxf'
# Einzelne Szene
scenes_to_generate = [args.scene]
# Stelle sicher, dass Dateiname .dxf Endung hat
if not filename.endswith('.dxf'):
filename += '.dxf'
# Generiere alle Szenen
success_count = 0
error_count = 0
# Generiere Testdaten
generator = TestDataGenerator(filename, args.scene, config_file=args.config)
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'
if generator.generate():
# Zeichne Symbol-Rahmen wenn gewünscht
if args.show_symbol_frames:
generator.draw_frames_for_generated_symbols()
# Stelle sicher, dass Dateiname .dxf Endung hat
if not filename.endswith('.dxf'):
filename += '.dxf'
generator.save(args.output_dir)
return 0
else:
print("Fehler bei der Generierung der Testdaten")
return 1
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__':
+110 -3
View File
@@ -881,6 +881,98 @@ def check_symbols_in_boundaries(doc, msp, renamer_groups, error_collector):
print(f" Alle {len(symbols_with_at)} Symbole mit '@' im IO liegen in einem Renamer-Bereich.")
def rectangles_overlap(boundary1, boundary2):
"""
Prüft ob sich zwei Rechteck-Boundaries überlappen.
Args:
boundary1: Liste von (x, y) Punkten (mindestens 4 Eckpunkte)
boundary2: Liste von (x, y) Punkten (mindestens 4 Eckpunkte)
Returns:
True wenn sich die Rechtecke überlappen
"""
# Extrahiere Min/Max für beide Rechtecke
b1_xs = [p[0] for p in boundary1]
b1_ys = [p[1] for p in boundary1]
b1_left, b1_right = min(b1_xs), max(b1_xs)
b1_bottom, b1_top = min(b1_ys), max(b1_ys)
b2_xs = [p[0] for p in boundary2]
b2_ys = [p[1] for p in boundary2]
b2_left, b2_right = min(b2_xs), max(b2_xs)
b2_bottom, b2_top = min(b2_ys), max(b2_ys)
# Prüfe ob keine Überlappung
no_overlap = (b1_right <= b2_left or # B1 ist links von B2
b2_right <= b1_left or # B2 ist links von B1
b1_top <= b2_bottom or # B1 ist unter B2
b2_top <= b1_bottom) # B2 ist unter B1
return not no_overlap
def check_overlapping_renamer_blocks(renamer_groups, error_collector):
"""
Prüft ob sich Renamer-Rechtecke überlappen.
Polyline-Pfade (DIRECTION=POLYLINE_PATH) werden nicht geprüft.
Args:
renamer_groups: Dictionary mit gruppierten Renamer-Blöcken
error_collector: ErrorCollector für Fehlerbehandlung
"""
print("\n" + "="*60)
print("Prüfe auf überlappende Renamer-Bereiche")
print("="*60)
# Sammle alle Rechteck-Renamer (ohne POLYLINE_PATH)
rectangle_renamers = []
for config_key, group_blocks in renamer_groups.items():
for insert, boundary, attributes, is_rectangle in group_blocks:
direction = attributes.get("DIRECTION", "")
block_name = attributes.get('NAME', attributes.get('NAME1', 'UNKNOWN'))
# Überspringe POLYLINE_PATH
if direction == 'POLYLINE_PATH':
continue
# Nur Rechtecke prüfen
if is_rectangle:
rectangle_renamers.append({
'name': block_name,
'position': insert.dxf.insert,
'boundary': boundary,
'direction': direction,
'is_rectangle': is_rectangle
})
# Prüfe alle Paare auf Überlappung
overlaps_found = []
for i in range(len(rectangle_renamers)):
for j in range(i + 1, len(rectangle_renamers)):
r1 = rectangle_renamers[i]
r2 = rectangle_renamers[j]
if rectangles_overlap(r1['boundary'], r2['boundary']):
overlaps_found.append((r1, r2))
# Melde Fehler
if overlaps_found:
print(f"\nFEHLER: {len(overlaps_found)} Überlappung(en) zwischen Renamer-Bereichen gefunden:")
for r1, r2 in overlaps_found:
error_msg = (
f"Renamer-Bereich '{r1['name']}' an Position "
f"({r1['position'][0]:.1f}, {r1['position'][1]:.1f}) "
f"überlappt mit Renamer-Bereich '{r2['name']}' an Position "
f"({r2['position'][0]:.1f}, {r2['position'][1]:.1f}). "
f"Symbole könnten mehrfach nummeriert werden."
)
print(f" {error_msg}")
error_collector.add_errors({"overlapping_renamer_blocks": error_msg})
else:
print(f"\nOK: Keine Überlappungen zwischen {len(rectangle_renamers)} Renamer-Rechtecken gefunden")
def process_renamer_blocks(doc, msp, renamer_layers, error_collector):
"""
Verarbeitet alle Renamer-Blöcke auf den angegebenen Layern.
@@ -895,6 +987,9 @@ def process_renamer_blocks(doc, msp, renamer_layers, error_collector):
# Erster Pass: Sammle und gruppiere alle Renamer-Blöcke
renamer_groups = collect_and_group_renamer_blocks(doc, msp, renamer_layers, error_collector)
# Prüfe auf überlappende Renamer-Bereiche
check_overlapping_renamer_blocks(renamer_groups, error_collector)
# Zweiter Pass: Verarbeite jede Gruppe
all_renamed = process_renamer_groups(doc, msp, renamer_layers, renamer_groups, error_collector)
@@ -975,10 +1070,15 @@ 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('-i', '--info', 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('--overwrite', action='store_true', help='Überschreibt die Originaldatei anstatt eine neue Datei mit Suffix _numbered zu erstellen')
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: --overwrite und --dryrun können nicht zusammen verwendet werden
if args.overwrite and args.dryrun:
parser.error("--overwrite und --dryrun können nicht zusammen verwendet werden")
# Initialisiere ErrorCollector
error_collector = ErrorCollector()
@@ -1042,9 +1142,16 @@ if __name__ == '__main__':
# Speichere DXF-Datei wenn nicht dry-run
if not args.dryrun:
output_path = dxf_path.parent / f"{dxf_path.stem}_numbered{dxf_path.suffix}"
doc.saveas(output_path)
print(f"\nNummerierte DXF-Datei gespeichert: {output_path}")
if args.overwrite:
# Überschreibe die Originaldatei
output_path = dxf_path
doc.saveas(output_path)
print(f"\nOriginaldatei überschrieben: {output_path}")
else:
# Erstelle neue Datei mit _numbered Suffix
output_path = dxf_path.parent / f"{dxf_path.stem}_numbered{dxf_path.suffix}"
doc.saveas(output_path)
print(f"\nNummerierte DXF-Datei gespeichert: {output_path}")
else:
print("\nDry-run Modus: DXF-Datei wurde nicht überschrieben")