diff --git a/CLAUDE.md b/CLAUDE.md index f07dec8..3d5df5f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -228,7 +228,7 @@ Abhaengigkeiten: `ezdxf`, Standard-Library. Benoetigen Umgebungsvariablen (`DXFM <<<<<<< Updated upstream ======= | `hw_symbole.py` | HW-Planung: setzt die E-Geraete-Symbole aus dem Plan `_hw_symbole.json` (HW_SYMBOLE, HwPlanung.lsp) in die `_HW.dxf` (`hw_materialfluss.hw_dokument_oeffnen`: vorhandene Datei ergaenzen statt ueberschreiben). Entfernt vorher nur die erzeugten Symbole der Praefixe des Plans (XDATA `SSG_HW_SYM`), baut die Blockdefinitionen bei jedem Lauf neu aus `cfg/hw_symbole.json` (`block_aufbauen`: Rahmen, ATTDEFs, Textstil, Layer), **Lage** per `platzieren`: Marke `REALE_POSITION` mit x genau auf dem Zielpunkt, Kasten daneben, Symbole eines Bauteils luekenlos uebereinander (Reihenfolge `benoetigt`), Gruppe um das Ziel zentriert; Ueberlappung mit schon gesetzten Rahmen (2D-Bounding-Boxen, `platzierung` in hw_symbole.json) wird NUR in y ausgewichen, x bleibt auf dem Ziel; Plan immer vollstaendig, eingefuegt nur `praefixe`. IO = Vorlage, Platzhalter-ID in VERW/BEZEICHNUNG ersetzt (`attribut_werte`). Test `tests/test_hw_symbole.py` (inkl. Abdeckung io_regeln -> hw_symbole und Kette Symbole -> Renamer-Polylinie -> create_numbers). Aufruf: `python hw_symbole.py --symbole cfg/hw_symbole.json` | -| `create_numbers.py` | Nummerierung der Symbol-Vorlagen (`@`) entlang von Renamer-Bereichen (Rechteck mit Richtung oder `POLYLINE_PATH`), Layer/Parameter aus `cfg/enumerate.cfg`; Fehler/Warnungen ueber `error_collector.py`. 2026-11-03 aus kabellaengen umgezogen (dort entfernt), die benoetigten Helfer aus kabellaengen/lib/utils.py stehen direkt im Modul, `--all-examples` entfallen. Nummer wird OKTAL gebildet (`oct(counter)`, so gewollt - nicht auf dezimal "korrigieren"). Genutzt von HW_NUMMERIEREN. Test `tests/test_create_numbers.py`. Aufruf: `python create_numbers.py --filename [--config ...] [--info ...] [--errorfile ...] [--dryrun\|--overwrite]` | +| `create_numbers.py` | Nummerierung der Symbol-Vorlagen (`@`) entlang von Renamer-Bereichen (Rechteck mit Richtung oder `POLYLINE_PATH`), Layer/Parameter aus `cfg/enumerate.cfg`; Fehler/Warnungen als zwei einfache Listen `(art, text)` (`neue_meldungen`/`meldungen_als_dict`, Format der Fehlerdatei unveraendert) - KEINE Kopie von kabellaengens `error_collector.py`/`utils.py`; die wenigen Helfer daraus stehen einmalig im Modul. 2026-11-03 aus kabellaengen umgezogen (dort entfernt), die benoetigten Helfer aus kabellaengen/lib/utils.py stehen direkt im Modul, `--all-examples` entfallen. Nummer wird OKTAL gebildet (`oct(counter)`, so gewollt - nicht auf dezimal "korrigieren"). Genutzt von HW_NUMMERIEREN. Test `tests/test_create_numbers.py`. Aufruf: `python create_numbers.py --filename [--config ...] [--info ...] [--errorfile ...] [--dryrun\|--overwrite]` | | `lager_yaml_export.py` | Lagerplanung: `_lager_raw.json` (LAGER_EXPORT) -> `_drawing.yaml` (Overlay im Schema der RFQ-Hand-YAML, nur Zeichnungsfelder, plus `_provenance`/`_conflicts`/`_warnings`) und optional `--nachweis` HTML. `--hand ` gleicht ab (Hand gewinnt; Konflikte bei > 1 % bzw. 0,005). Aggregiert nur, Regeln leben in LISP. Braucht `pyyaml`. Test `tests/test_lager_yaml.py`. Aufruf: `python lager_yaml_export.py [--hand ...] [--nachweis ...]` | >>>>>>> Stashed changes | `dxf_scan_components.py` | Erkennt ILS-/Omniflo-Komponenten in einer **fremden** Projektzeichnung (ohne unsere Wrapper-Bloecke) und schreibt sie im JSON-Schema aus `doc/json-input-lisp.md` (Nachfolger des geloeschten `tests/testdata/object_data.md`) — Grundlage fuer Nachbau-Testfaelle wie `tests/testdata/mubea.json`. Liest binaere DXF im GB-Bereich streamend per mmap (ezdxf-DOM waere zu gross), loest INSERTs inkl. OCS/Extrusionsrichtung rekursiv in Weltkoordinaten auf, paart AN8/SP8 zu Kreiseln und Antrieb/Umlenkung zu Foerderern. Gefaellestrecken, die als 3D-LINE auf einem Staustrecken-Layer gezeichnet sind (`GF_LINIE_LAYER`, z.B. `6-SP`), werden in einem zweiten Lesedurchgang zu Modus-1-Eintraegen: Start/Ende werden auf die Ketten der anschliessenden Kreisel verlaengert (gezeichnet ist nur die Staustrecke; `deltaL` reicht von AS- bis ES-Ursprung), AS-Seite aus dem Ausschleuselement am oberen Ende; dieses und Separatoren auf der Achse entfallen als Einzel-Insert (die GF baut beide selbst). Sitzt der AS am UNTEREN Ende, ist es ein Steigfoerderer und wird nur gemeldet (`gf_eintraege_aus_linien`, Test `tests/test_dxf_scan_gf.py`). `--ohne-hoehen` setzt alle Z auf 0, `--kreisel-hoehe X Y Z` gibt einem Kreisel seine Hoehe - die daran haengenden GF bekommen daraus `start_mm.z`/`hoehe_ende_mm`, den Winkel rechnet der Treiber (`dematic_arvato:gf-geometrie`: atan ueber die HORIZONTALE Laenge, ES-Laenge UND -Abfall bei der Einlaufneigung gemessen und iteriert, Staustrecke daraus - die flache ES-Laenge liess steile GF ~144 mm vor der Kette enden - NICHT `gf-winkel-berechnen`, dessen asin bei steilen GF wie im Showroom ~50 statt ~37 Grad liefert; `dematic_arvato:gf-endhoehe-pruefen` meldet `warnung` bei > 5 mm Abweichung von HOEHE_BIS). Flache 2D-Plaene: `--flach` (waagrechte Staustrecke mit AS an genau einem Ende = GF, zerstueckelte Linien werden vereint, ES am unteren Ende wird verbraucht, AS/ES-Seite ohne Namensangabe aus der Lage zur Fahrtrichtung), `--kreisel-achse X1 Y1 X2 Y2` (Kreisel ohne AN8/SP8), Einzelteile auf einer Kreisel-Kette erben dessen Anwender-Hoehe (`sensor_hoehen_von_kreiseln`). Kreisel-Paarung mit Tausch-Nachbesserung auf minimale Abstandssumme (zwei Kreisel auf einer Achse). Genutzt fuer `tests/testdata/showroom_ils.json` (`TEST_SHOWROOM_ILS`). Aufruf: `python dxf_scan_components.py [--fenster X0 Y0 X1 Y1]` — ohne `--fenster` liefert eine Anlagenzeichnung leicht ueber 1300 Eintraege, zuviel fuer einen Testlauf | diff --git a/doc/hw_planung.md b/doc/hw_planung.md index 9e22e25..303f55e 100644 --- a/doc/hw_planung.md +++ b/doc/hw_planung.md @@ -185,9 +185,14 @@ einer Gruppe bekommt `...010`. ## Herkunft -`create_numbers.py`, `error_collector.py`, `cfg/enumerate.cfg` und der Test +`create_numbers.py`, `cfg/enumerate.cfg` und der Test `tests/test_create_numbers.py` sind am 2026-11-03 aus dem Kabeltool-Repo -`kabellaengen` hierher umgezogen (dort entfernt). Die Hilfsfunktionen aus -`kabellaengen/lib/utils.py` stehen direkt in `create_numbers.py`. Die Testszenen +`kabellaengen` hierher umgezogen (dort entfernt). Es gibt nur diesen einen +Codestand: die benoetigten Helfer aus `kabellaengen/lib/utils.py` stehen direkt +in `create_numbers.py`, die Fehlersammlung `error_collector.py` ist durch zwei +einfache Listen ersetzt (gleiches Format der Fehlerdatei; gegen das Original an +sechs Testzeichnungen verglichen: identische Nummern und Fehlerdateien). +Bewusst doppelt bleibt nur die Konfiguration `cfg/io_regeln.json` (gekuerzt) - +beide Repos werden von verschiedenen Abteilungen gepflegt. Die Testszenen `Nummerierung1/2`, `BGMG`, `POT` (Erzeugung per `kabellaengen/lib/create_example.py`) bleiben in kabellaengen; der Modus `--all-examples` ist darum hier entfallen. diff --git a/lib/create_numbers.py b/lib/create_numbers.py index 5bd7dd3..52c6cfb 100644 --- a/lib/create_numbers.py +++ b/lib/create_numbers.py @@ -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__': diff --git a/lib/error_collector.py b/lib/error_collector.py deleted file mode 100644 index 3278329..0000000 --- a/lib/error_collector.py +++ /dev/null @@ -1,121 +0,0 @@ -""" -ErrorCollector - Gemeinsame Klasse zum Sammeln von Fehlern und Warnungen - -Diese Klasse wird von getpositions.py und drawdxf.py verwendet, um Fehler -und Warnungen während der Verarbeitung zu sammeln und zu verwalten. -""" - -import json -from pathlib import Path - - -class ErrorCollector: - """Sammelt alle Fehler und Warnungen während der Verarbeitung.""" - - def __init__(self): - self.errors = {} - self.warnings = {} - - def add_errors(self, error_dict): - """Fügt Fehler aus einem Dictionary hinzu.""" - for key, value in error_dict.items(): - if key in self.errors: - # Key existiert bereits - Verhalten hängt vom Typ ab - existing = self.errors[key] - - if isinstance(existing, dict) and isinstance(value, dict): - # Beide sind Dictionaries → Merge/Update - existing.update(value) - elif isinstance(existing, list): - # Existierender Wert ist Liste - if isinstance(value, list): - existing.extend(value) - else: - existing.append(value) - else: - # Existierender Wert ist weder dict noch list → Mache Liste daraus - if isinstance(value, list): - self.errors[key] = [existing] + value - else: - self.errors[key] = [existing, value] - else: - # Neuer Key - behalte Typ des Wertes - if isinstance(value, (dict, list)): - # Dictionary oder Liste → direkt speichern - self.errors[key] = value - else: - # Primitiver Wert (String, etc.) → als Liste speichern - self.errors[key] = [value] - - def add_warnings(self, warning_dict): - """Fügt Warnungen aus einem Dictionary hinzu.""" - for key, value in warning_dict.items(): - if key in self.warnings: - # Key existiert bereits - Verhalten hängt vom Typ ab - existing = self.warnings[key] - - if isinstance(existing, dict) and isinstance(value, dict): - # Beide sind Dictionaries → Merge/Update - existing.update(value) - elif isinstance(existing, list): - # Existierender Wert ist Liste - if isinstance(value, list): - existing.extend(value) - else: - existing.append(value) - else: - # Existierender Wert ist weder dict noch list → Mache Liste daraus - if isinstance(value, list): - self.warnings[key] = [existing] + value - else: - self.warnings[key] = [existing, value] - else: - # Neuer Key - behalte Typ des Wertes - if isinstance(value, (dict, list)): - # Dictionary oder Liste → direkt speichern - self.warnings[key] = value - else: - # Primitiver Wert (String, etc.) → als Liste speichern - self.warnings[key] = [value] - - def has_errors_or_warnings(self): - """Prüft ob Fehler oder Warnungen vorhanden sind.""" - return bool(self.errors) or bool(self.warnings) - - def has_errors(self): - """Prüft ob Fehler vorhanden sind.""" - return bool(self.errors) - - def get_all_issues(self): - """Gibt alle Fehler und Warnungen als Dictionary zurück.""" - result = {} - if self.errors: - result["errors"] = self.errors - if self.warnings: - result["warnings"] = self.warnings - return result - - def write_errorfile(self, work_dir: Path, filename: str): - """Schreibt Fehlerdatei nur wenn Fehler oder Warnungen vorhanden sind.""" - if self.has_errors(): - print("Fehler gefunden. Schreibe Error-Datei.") - all_issues = self.get_all_issues() - write_json_file(all_issues, work_dir, filename) - else: - print("Keine Fehler gefunden. Keine Error-Datei geschrieben.") - - -def write_json_file(data: dict, out_dir: Path, filename: str) -> None: - """ - Schreibt Daten in eine JSON-Datei. - - Args: - data: Dictionary mit zu schreibenden Daten - out_dir: Ausgabeverzeichnis - filename: Dateiname für die JSON-Datei - """ - print("writing data to a json file ...") - outfile = out_dir / filename if isinstance(out_dir, Path) else Path(out_dir) / filename - with open(outfile, 'w', encoding='utf-8') as fh: - json.dump(data, fh, indent=2, ensure_ascii=False, default=str) - print("done")