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

+1 -1
View File
@@ -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 `<dwg>_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 <plan.json> <out_HW.dxf> --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 <dxf> [--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 <dxf> [--config ...] [--info ...] [--errorfile ...] [--dryrun\|--overwrite]` |
| `lager_yaml_export.py` | Lagerplanung: `<dwg>_lager_raw.json` (LAGER_EXPORT) -> `<dwg>_drawing.yaml` (Overlay im Schema der RFQ-Hand-YAML, nur Zeichnungsfelder, plus `_provenance`/`_conflicts`/`_warnings`) und optional `--nachweis` HTML. `--hand <yaml>` 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 <raw.json> <out.yaml> [--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 <dxf> <data_dir> <output.json> [--fenster X0 Y0 X1 Y1]` — ohne `--fenster` liefert eine Anlagenzeichnung leicht ueber 1300 Eintraege, zuviel fuer einen Testlauf |
+8 -3
View File
@@ -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.
+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__':
-121
View File
@@ -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")