refactor: create_numbers ohne Kopie von error_collector

Fehler/Warnungen als zwei einfache Listen (neue_meldungen,
meldungen_als_dict) statt der aus kabellaengen kopierten
ErrorCollector-Klasse; lib/error_collector.py entfernt. Format der
Fehlerdatei unveraendert, an sechs Testzeichnungen gegen das Original
verglichen (identische Nummern und Fehlerdateien).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Michael StanglandClaude Opus 5.5 committed 2026-11-03 17:00:42 +01:00
1 parent 8abb6b3c65
commit 4100cbfb97
4 files changed
+97 -184

No files matched your search

+88 -59
View File
@@ -1,4 +1,5 @@
import argparse
import json
import os
import sys
from pathlib import Path
@@ -6,7 +7,35 @@ from pathlib import Path
import ezdxf
from ezdxf.lldxf.const import DXFStructureError
from error_collector import ErrorCollector, write_json_file
# ============================================================================
# Meldungen: zwei einfache Listen von (art, text) - Fehler und Warnungen.
# Ersetzt die Fehlersammler-Klasse ErrorCollector aus kabellaengen (lib/error_collector.py),
# damit keine Kopie davon in diesem Repo liegt.
# ============================================================================
def neue_meldungen():
"""Leere Meldungssammlung: {"errors": [(art, text), ...], "warnings": [...]}."""
return {"errors": [], "warnings": []}
def meldungen_als_dict(meldungen):
"""Fuer die Fehlerdatei nach Art gruppiert, Format wie bisher:
{"errors": {art: [text, ...]}, "warnings": {...}} - leere Teile entfallen."""
erg = {}
for teil in ("errors", "warnings"):
gruppen = {}
for art, text in meldungen[teil]:
gruppen.setdefault(art, []).append(text)
if gruppen:
erg[teil] = gruppen
return erg
def write_json_file(data, out_dir, filename):
"""data als JSON (UTF-8, eingerueckt) nach out_dir/filename schreiben."""
with open(Path(out_dir) / filename, 'w', encoding='utf-8') as fh:
json.dump(data, fh, indent=2, ensure_ascii=False, default=str)
# ============================================================================
@@ -37,7 +66,7 @@ def get_dxf_file(filepath: Path):
return doc
def extract_block_attributes(doc, insert, error_collector=None) -> dict:
def extract_block_attributes(doc, insert, meldungen=None) -> dict:
"""
Alle Attribute eines INSERT als {tag: text}. Unterstuetzt die zweistufige
Blockstruktur der Renamer-Bloecke (aeusserer Block mit LWPOLYLINE und einem
@@ -64,9 +93,9 @@ def extract_block_attributes(doc, insert, error_collector=None) -> dict:
attributes[attrib.dxf.tag] = attrib.dxf.text
break
except Exception as e:
if error_collector:
if meldungen:
error_msg = f"Fehler beim Extrahieren der Attribute aus Block '{insert.dxf.name}': {e}"
error_collector.add_warnings({"attribute_extraction_error": error_msg})
meldungen["warnings"].append(("attribute_extraction_error", error_msg))
return attributes
@@ -377,7 +406,7 @@ def point_near_polyline(point, polyline_points, max_distance=500.0):
return min_dist_sq <= (max_distance * max_distance)
def find_symbols_in_boundary(doc, msp, boundary, target_layers, attributes, error_collector=None, polyline_max_distance=500.0):
def find_symbols_in_boundary(doc, msp, boundary, target_layers, attributes, meldungen=None, polyline_max_distance=500.0):
"""
Findet alle Symbole (INSERT-Blöcke) innerhalb des angegebenen Bereichs auf den Ziel-Layern.
@@ -411,7 +440,7 @@ def find_symbols_in_boundary(doc, msp, boundary, target_layers, attributes, erro
# Prüfe ob der Block Attribute hat (direkt oder in zweistufiger Struktur)
# Verwende extract_block_attributes um beides zu unterstützen
symbol_attribs = extract_block_attributes(doc, entity, error_collector)
symbol_attribs = extract_block_attributes(doc, entity, meldungen)
if not symbol_attribs:
continue
@@ -758,7 +787,7 @@ def validate_direction_with_geometry(direction, is_rectangle):
return True, f"Unbekannte DIRECTION '{direction}' - Verarbeitung wird fortgesetzt"
def collect_and_group_renamer_blocks(doc, msp, renamer_layers, error_collector):
def collect_and_group_renamer_blocks(doc, msp, renamer_layers, meldungen):
"""
Erster Pass: Sammelt und gruppiert alle Renamer-Blöcke nach Konfiguration.
@@ -773,7 +802,7 @@ def collect_and_group_renamer_blocks(doc, msp, renamer_layers, error_collector):
# Finde alle INSERT-Blöcke auf diesem Layer
for insert in msp.query(f'INSERT[layer=="{layer}"]'):
# Extrahiere Attribute (unterstützt zweistufige Blockstruktur)
attributes = extract_block_attributes(doc, insert, error_collector)
attributes = extract_block_attributes(doc, insert, meldungen)
if not attributes:
print(f" Block ohne Attribute gefunden an Position {insert.dxf.insert}")
@@ -796,7 +825,7 @@ def collect_and_group_renamer_blocks(doc, msp, renamer_layers, error_collector):
if boundary is None:
error_msg = f"Keine Polylinie/Rechteck im Renamer-Block '{block_name}' an Position {insert.dxf.insert} gefunden"
print(f" FEHLER: {error_msg}")
error_collector.add_errors({"missing_boundary": error_msg})
meldungen["errors"].append(("missing_boundary", error_msg))
continue
print(f" Boundary gefunden mit {len(boundary)} Punkten (Rechteck: {is_rectangle})")
@@ -809,12 +838,12 @@ def collect_and_group_renamer_blocks(doc, msp, renamer_layers, error_collector):
if not is_valid:
error_msg = f"Renamer-Block '{block_name}' an Position {insert.dxf.insert}: {validation_error}"
print(f" FEHLER: {error_msg}")
error_collector.add_errors({"direction_geometry_mismatch": error_msg})
meldungen["errors"].append(("direction_geometry_mismatch", error_msg))
continue
elif validation_error:
# Warnung bei unbekannter DIRECTION, aber fortfahren
print(f" WARNUNG: {validation_error}")
error_collector.add_warnings({"unknown_direction": validation_error})
meldungen["warnings"].append(("unknown_direction", validation_error))
# Erstelle Konfigurationsschlüssel und gruppiere
config_key = get_renamer_config_key(attributes)
@@ -825,7 +854,7 @@ def collect_and_group_renamer_blocks(doc, msp, renamer_layers, error_collector):
return renamer_groups
def find_template_symbols_in_geometry(doc, msp, group_blocks, error_collector):
def find_template_symbols_in_geometry(doc, msp, group_blocks, meldungen):
"""
Findet alle Symbole mit '@' die geometrisch in den Renamer-Bereichen liegen,
unabhängig vom Layer.
@@ -834,7 +863,7 @@ def find_template_symbols_in_geometry(doc, msp, group_blocks, error_collector):
doc: DXF-Dokument
msp: Modelspace
group_blocks: Liste von (insert, boundary, attributes, is_rectangle) Tupeln
error_collector: ErrorCollector für Fehlerbehandlung
meldungen: Meldungen (neue_meldungen) fuer Fehler/Warnungen
Returns:
Liste von Dictionaries mit 'layer', 'position', 'io' für jedes gefundene Symbol
@@ -842,7 +871,7 @@ def find_template_symbols_in_geometry(doc, msp, group_blocks, error_collector):
symbols_in_geometry = []
for insert, boundary, attributes, is_rectangle in group_blocks:
for entity in msp.query('INSERT'):
symbol_attribs = extract_block_attributes(doc, entity, error_collector)
symbol_attribs = extract_block_attributes(doc, entity, meldungen)
if not symbol_attribs:
continue
@@ -862,7 +891,7 @@ def find_template_symbols_in_geometry(doc, msp, group_blocks, error_collector):
def create_no_symbols_found_error_message(block_name, direction, layer_names, group_blocks,
doc, msp, error_collector):
doc, msp, meldungen):
"""
Erstellt eine detaillierte Fehlermeldung wenn keine Symbole in einem Renamer-Bereich gefunden wurden.
@@ -873,7 +902,7 @@ def create_no_symbols_found_error_message(block_name, direction, layer_names, gr
group_blocks: Liste von (insert, boundary, attributes, is_rectangle) Tupeln
doc: DXF-Dokument
msp: Modelspace
error_collector: ErrorCollector für Fehlerbehandlung
meldungen: Meldungen (neue_meldungen) fuer Fehler/Warnungen
Returns:
Fehlermeldungs-String
@@ -882,7 +911,7 @@ def create_no_symbols_found_error_message(block_name, direction, layer_names, gr
for insert, _, _, _ in group_blocks]
# Prüfe ob es Symbole mit @ gibt, die geometrisch im Bereich liegen (egal auf welchem Layer)
symbols_in_geometry = find_template_symbols_in_geometry(doc, msp, group_blocks, error_collector)
symbols_in_geometry = find_template_symbols_in_geometry(doc, msp, group_blocks, meldungen)
# Erstelle Fehlermeldung basierend auf gefundenen Symbolen
if symbols_in_geometry and not layer_names:
@@ -917,7 +946,7 @@ def create_no_symbols_found_error_message(block_name, direction, layer_names, gr
)
def collect_symbols_from_group(doc, msp, renamer_layers, group_blocks, block_name, error_collector, polyline_max_distance=500.0):
def collect_symbols_from_group(doc, msp, renamer_layers, group_blocks, block_name, meldungen, polyline_max_distance=500.0):
"""
Sammelt alle Symbole aus allen Boundaries einer Renamer-Gruppe.
Prüft auf Duplikate (Symbole in mehreren Bereichen).
@@ -928,7 +957,7 @@ def collect_symbols_from_group(doc, msp, renamer_layers, group_blocks, block_nam
renamer_layers: Layer auf denen nach Renamer-Blöcken gesucht wird
group_blocks: Liste von (insert, boundary, attributes, is_rectangle) Tupeln
block_name: Name des Renamer-Blocks (für Fehlermeldungen)
error_collector: ErrorCollector für Fehlerbehandlung
meldungen: Meldungen (neue_meldungen) fuer Fehler/Warnungen
polyline_max_distance: Maximale Distanz zur Polylinie (aus Config)
Returns:
@@ -939,7 +968,7 @@ def collect_symbols_from_group(doc, msp, renamer_layers, group_blocks, block_nam
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, polyline_max_distance)
symbols = find_symbols_in_boundary(doc, msp, boundary, renamer_layers, attributes, meldungen, polyline_max_distance)
# Füge Symbole hinzu, aber nur wenn das Entity noch nicht gesehen wurde
for symbol in symbols:
@@ -962,12 +991,12 @@ def collect_symbols_from_group(doc, msp, renamer_layers, group_blocks, block_nam
f"{', '.join([f'({p[0]:.1f}, {p[1]:.1f})' for p in existing_positions])}"
)
print(f" FEHLER: {error_msg}")
error_collector.add_errors({"symbol_in_multiple_boundaries": error_msg})
meldungen["errors"].append(("symbol_in_multiple_boundaries", error_msg))
return all_symbols
def process_renamer_groups(doc, msp, renamer_layers, renamer_groups, error_collector, polyline_max_distance=500.0):
def process_renamer_groups(doc, msp, renamer_layers, renamer_groups, meldungen, polyline_max_distance=500.0):
"""
Zweiter Pass: Verarbeitet jede Renamer-Block-Gruppe.
Sammelt Symbole, sortiert sie und nummeriert sie.
@@ -992,7 +1021,7 @@ def process_renamer_groups(doc, msp, renamer_layers, renamer_groups, error_colle
print(f"\n Verarbeite Gruppe: {block_name} ({len(group_blocks)} Renamer-Blöcke)")
# Sammle Symbole aus ALLEN Boundaries dieser Gruppe
all_symbols = collect_symbols_from_group(doc, msp, renamer_layers, group_blocks, block_name, error_collector, polyline_max_distance)
all_symbols = collect_symbols_from_group(doc, msp, renamer_layers, group_blocks, block_name, meldungen, polyline_max_distance)
print(f" Gesamt {len(all_symbols)} eindeutige Template-Symbole aus {len(group_blocks)} Blöcken")
@@ -1000,10 +1029,10 @@ def process_renamer_groups(doc, msp, renamer_layers, renamer_groups, error_colle
# Fehlermeldung: Keine Symbole gefunden
layer_names = get_layer_names_from_attributes(reference_attributes)
error_msg = create_no_symbols_found_error_message(
block_name, direction, layer_names, group_blocks, doc, msp, error_collector
block_name, direction, layer_names, group_blocks, doc, msp, meldungen
)
print(f" FEHLER: {error_msg}")
error_collector.add_errors({"no_symbols_found": error_msg})
meldungen["errors"].append(("no_symbols_found", error_msg))
continue
# Für POLYLINE_PATH: Hole die Polylinie vom ersten Block
@@ -1029,21 +1058,21 @@ def process_renamer_groups(doc, msp, renamer_layers, renamer_groups, error_colle
return all_renamed
def find_symbols_with_at_in_io(doc, msp, error_collector):
def find_symbols_with_at_in_io(doc, msp, meldungen):
"""
Findet alle Symbole mit "@" im IO-Attribut.
Args:
doc: DXF-Dokument
msp: Modelspace
error_collector: ErrorCollector für Fehlerbehandlung
meldungen: Meldungen (neue_meldungen) fuer Fehler/Warnungen
Returns:
Liste von Dictionaries mit Symbol-Informationen
"""
symbols_with_at = []
for entity in msp.query('INSERT'):
symbol_attribs = extract_block_attributes(doc, entity, error_collector)
symbol_attribs = extract_block_attributes(doc, entity, meldungen)
if not symbol_attribs:
continue
@@ -1138,7 +1167,7 @@ def create_wrong_layer_warning(symbol, wrong_layer_info):
)
def check_symbols_in_boundaries(doc, msp, renamer_groups, error_collector, polyline_max_distance=500.0):
def check_symbols_in_boundaries(doc, msp, renamer_groups, meldungen, polyline_max_distance=500.0):
"""
Dritter Pass: Prüft ob alle Symbole mit "@" im IO in einem Renamer-Bereich liegen.
@@ -1156,7 +1185,7 @@ def check_symbols_in_boundaries(doc, msp, renamer_groups, error_collector, polyl
all_boundaries.append((boundary, attributes, insert.dxf.insert))
# Finde alle Symbole mit "@" im IO-Attribut
symbols_with_at = find_symbols_with_at_in_io(doc, msp, error_collector)
symbols_with_at = find_symbols_with_at_in_io(doc, msp, meldungen)
# Prüfe für jedes Symbol, ob es in mindestens einem Renamer-Bereich liegt
symbols_not_in_any_boundary = []
@@ -1180,7 +1209,7 @@ def check_symbols_in_boundaries(doc, msp, renamer_groups, error_collector, polyl
# Warnung: Symbol liegt geometrisch im Bereich, aber Layer stimmt nicht
warning_msg = create_wrong_layer_warning(symbol, wrong_layer_info)
print(f" WARNUNG: {warning_msg}")
error_collector.add_warnings({"symbol_wrong_layer": warning_msg})
meldungen["warnings"].append(("symbol_wrong_layer", warning_msg))
# NICHT als Fehler hinzufügen - Warnung reicht aus
else:
# Fehler: Symbol liegt weder geometrisch noch vom Layer her in einem Renamer-Bereich
@@ -1195,7 +1224,7 @@ def check_symbols_in_boundaries(doc, msp, renamer_groups, error_collector, polyl
f"enthält '@' im IO-Attribut, liegt aber in keinem Renamer-Bereich."
)
print(f" FEHLER: {error_msg}")
error_collector.add_errors({"symbol_with_at_not_in_boundary": error_msg})
meldungen["errors"].append(("symbol_with_at_not_in_boundary", error_msg))
else:
print(f" Alle {len(symbols_with_at)} Symbole mit '@' im IO liegen in einem Renamer-Bereich.")
@@ -1231,14 +1260,14 @@ def rectangles_overlap(boundary1, boundary2):
return not no_overlap
def check_overlapping_renamer_blocks(renamer_groups, error_collector):
def check_overlapping_renamer_blocks(renamer_groups, meldungen):
"""
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
meldungen: Meldungen (neue_meldungen) fuer Fehler/Warnungen
"""
print("\n" + "="*60)
print("Prüfe auf überlappende Renamer-Bereiche")
@@ -1287,12 +1316,12 @@ def check_overlapping_renamer_blocks(renamer_groups, error_collector):
f"Symbole könnten mehrfach nummeriert werden."
)
print(f" {error_msg}")
error_collector.add_errors({"overlapping_renamer_blocks": error_msg})
meldungen["errors"].append(("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, polyline_max_distance=500.0):
def process_renamer_blocks(doc, msp, renamer_layers, meldungen, polyline_max_distance=500.0):
"""
Verarbeitet alle Renamer-Blöcke auf den angegebenen Layern.
Gruppiert Blöcke mit identischer Konfiguration und nummeriert alle Symbole
@@ -1307,21 +1336,21 @@ def process_renamer_blocks(doc, msp, renamer_layers, error_collector, polyline_m
- renamer_groups: Dictionary der gruppierten 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, meldungen)
# Prüfe auf überlappende Renamer-Bereiche
check_overlapping_renamer_blocks(renamer_groups, error_collector)
check_overlapping_renamer_blocks(renamer_groups, meldungen)
# Zweiter Pass: Verarbeite jede Gruppe
all_renamed = process_renamer_groups(doc, msp, renamer_layers, renamer_groups, error_collector, polyline_max_distance)
all_renamed = process_renamer_groups(doc, msp, renamer_layers, renamer_groups, meldungen, polyline_max_distance)
# 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, polyline_max_distance)
check_symbols_in_boundaries(doc, msp, renamer_groups, meldungen, polyline_max_distance)
return all_renamed, renamer_groups
def collect_all_symbols_from_groups(doc, msp, renamer_groups, renamer_layers, error_collector, polyline_max_distance=500.0):
def collect_all_symbols_from_groups(doc, msp, renamer_groups, renamer_layers, meldungen, polyline_max_distance=500.0):
"""
Sammelt alle Symbole aus allen Renamer-Gruppen für Visualisierungszwecke.
@@ -1330,7 +1359,7 @@ def collect_all_symbols_from_groups(doc, msp, renamer_groups, renamer_layers, er
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
meldungen: Meldungen (neue_meldungen) fuer Fehler/Warnungen
polyline_max_distance: Maximale Distanz zur Polylinie (aus Config)
Returns:
@@ -1347,7 +1376,7 @@ def collect_all_symbols_from_groups(doc, msp, renamer_groups, renamer_layers, er
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, polyline_max_distance)
symbols = find_symbols_in_boundary(doc, msp, boundary, renamer_layers, attributes, meldungen, polyline_max_distance)
# Füge Symbole hinzu, aber nur wenn das Entity noch nicht gesehen wurde
for symbol in symbols:
@@ -1371,12 +1400,12 @@ def process_single_file(dxf_path, work_dir, renamer_layers, polyline_max_distanc
args: Argparse-Argumente
Returns:
Tuple (success, error_collector):
Tuple (success, meldungen):
- success: True wenn erfolgreich verarbeitet
- error_collector: ErrorCollector mit gesammelten Fehlern/Warnungen
- meldungen: Meldungen (neue_meldungen) mit gesammelten Fehlern/Warnungen
"""
# Initialisiere ErrorCollector für diese Datei
error_collector = ErrorCollector()
# Meldungen fuer diese Datei
meldungen = neue_meldungen()
print(f"\n{'='*80}")
print(f"Verarbeite Datei: {dxf_path.name}")
@@ -1397,7 +1426,7 @@ def process_single_file(dxf_path, work_dir, renamer_layers, polyline_max_distanc
print("Starte Verarbeitung der Renamer-Blöcke")
print("="*60 + "\n")
renamed_symbols, renamer_groups = process_renamer_blocks(doc, msp, renamer_layers, error_collector, polyline_max_distance)
renamed_symbols, renamer_groups = process_renamer_blocks(doc, msp, renamer_layers, meldungen, polyline_max_distance)
print("\n" + "="*60)
print(f"Verarbeitung abgeschlossen: {len(renamed_symbols)} Symbole nummeriert")
@@ -1411,7 +1440,7 @@ def process_single_file(dxf_path, work_dir, renamer_layers, polyline_max_distanc
# 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, polyline_max_distance)
all_symbols = collect_all_symbols_from_groups(doc, msp, renamer_groups, renamer_layers, meldungen, polyline_max_distance)
if all_symbols:
draw_symbol_frames(doc, msp, all_symbols)
else:
@@ -1467,9 +1496,9 @@ def process_single_file(dxf_path, work_dir, renamer_layers, polyline_max_distanc
print(f"Ergebnisse in JSON gespeichert: {output_dir / output_filename}")
# Schreibe Fehler-Datei nur wenn Fehler oder Warnungen vorhanden sind
if error_collector.has_errors_or_warnings():
if (meldungen["errors"] or meldungen["warnings"]):
if args.errorfile:
error_data = error_collector.get_all_issues()
error_data = meldungen_als_dict(meldungen)
error_path = Path(args.errorfile)
# Wenn nur ein Dateiname ohne Pfad angegeben wurde, speichere im work-Ordner
@@ -1486,19 +1515,19 @@ def process_single_file(dxf_path, work_dir, renamer_layers, polyline_max_distanc
else:
# Keine explizite Fehlerdatei angegeben, generiere eine basierend auf Input-Datei
error_filename = f"{dxf_path.stem}_errors.json"
error_data = error_collector.get_all_issues()
error_data = meldungen_als_dict(meldungen)
write_json_file(error_data, dxf_path.parent, error_filename)
print(f"Fehler/Warnungen gespeichert: {dxf_path.parent / error_filename}")
# Status-Meldung
if error_collector.has_errors():
if meldungen["errors"]:
print("\n!!! Es sind Fehler aufgetreten !!!")
return False, error_collector
elif error_collector.has_errors_or_warnings():
return False, meldungen
elif (meldungen["errors"] or meldungen["warnings"]):
print("\n(Warnungen vorhanden, aber keine kritischen Fehler)")
return True, error_collector
return True, meldungen
else:
return True, error_collector
return True, meldungen
def main(argv=None):
@@ -1552,9 +1581,9 @@ def main(argv=None):
print(f"Datei nicht gefunden: {dxf_path}")
return 1
_success, error_collector = process_single_file(dxf_path, dxf_path.parent, renamer_layers,
_success, meldungen = process_single_file(dxf_path, dxf_path.parent, renamer_layers,
polyline_max_distance, args)
return 1 if error_collector.has_errors() else 0
return 1 if meldungen["errors"] else 0
if __name__ == '__main__':