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:
1 parent
8abb6b3c65
commit
4100cbfb97
4 files changed
+97
-184
No files matched your search
@@ -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
@@ -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
@@ -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__':
|
||||
|
||||
@@ -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")
|
||||
Reference in new issue
Block a user