1120 lines
44 KiB
Python
1120 lines
44 KiB
Python
import argparse
|
|
from pathlib import Path
|
|
|
|
|
|
from error_collector import ErrorCollector, write_json_file
|
|
from utils import (
|
|
check_environment_var, check_file_in_work, dxf_is_binary, get_dxf_file,
|
|
extract_insert_attributes_with_doc as extract_block_attributes,
|
|
draw_symbol_frames
|
|
)
|
|
|
|
|
|
"""
|
|
Dieses Programm liest eine dxf Datei und holt sich vom Layer RENAME die Angaben
|
|
für die Bereiche in der Anlage, welche Motoren, Sensoren und Aktoren welchem
|
|
Unterverteiler zugeordnet werden müssen. (Attribut KENNZEICHNUNG)
|
|
Alle Symbol Templates werden entsprechend der Richtungsangaben in DIRECTION nummeriert
|
|
DIRECTION kann TOP_BOTTOM, BOTTOM_TOP, LEFT_RIGHT oder RIGHT_LEFT sein
|
|
|
|
Ein Renamer Symbol enthält z.B. die folgenden Angaben:
|
|
für POT-RA, POT-MA und POT-UC:
|
|
|
|
NAME1=POT-RA@@
|
|
NAME2=POT-MA@@
|
|
NAME3=POT-UC@@
|
|
KENNZEICHNUNG=A01+UC…
|
|
LAYER_NAME1=ILS_POT-RA
|
|
LAYER_NAME2=ILS_POT-MA
|
|
LAYER_NAME3=ILS_POT-UC
|
|
DIRECTION = LEFT_RIGHT
|
|
|
|
für BG, MB:
|
|
|
|
NAME1=BG-1@@@
|
|
NAME2=MB-1@@@
|
|
NAME3=
|
|
KENNZEICHNUNG=
|
|
LAYER_NAME1=ILS_Eingang
|
|
LAYER_NAME2=ILS_Ausgang
|
|
LAYER_NAME3=
|
|
DIRECTION = LEFT_RIGHT
|
|
|
|
oder nur zur Nummerierung der MA Symbole
|
|
NAME=MA-1@@
|
|
KENNZEICHNUNG=A01+UH00
|
|
LAYER_NAME=ILS_MOTOR
|
|
DIRECTION = TOP_BOTTOM/LEFT_RIGHT
|
|
|
|
Diese Symbole auf dem RENAMER Layer sind immer in einem Block mit einer Polylinie oder einen REchteck.
|
|
Alle Sensoren, Motoren, Aktoren etc. innerhalb der zugehörigen Polylinie nehmen an der Nummerierung teil, falls diese das @ Zeichen enthalten, also noch ein Template sind.
|
|
|
|
"""
|
|
|
|
|
|
def read_config_layers(config_path: Path) -> list:
|
|
"""
|
|
Liest die enumerate.cfg und gibt die Layer zurück, auf denen nach Renamer-Blöcken gesucht werden soll.
|
|
Die Konfiguration hat eine einfache Liste unter [Layers]:
|
|
[Layers]
|
|
RENAMER
|
|
OTHER_LAYER
|
|
"""
|
|
layers = []
|
|
|
|
with open(config_path, 'r', encoding='utf-8') as f:
|
|
in_layers_section = False
|
|
for line in f:
|
|
line = line.strip()
|
|
|
|
# Überspringe leere Zeilen und Kommentare
|
|
if not line or line.startswith('#') or line.startswith(';'):
|
|
continue
|
|
|
|
# Prüfe ob wir in der [Layers] Sektion sind
|
|
if line.startswith('['):
|
|
in_layers_section = line.lower() == '[layers]'
|
|
continue
|
|
|
|
# Wenn in [Layers], füge Layer hinzu
|
|
if in_layers_section:
|
|
layers.append(line)
|
|
|
|
return layers
|
|
|
|
|
|
def is_rectangle_polyline(points):
|
|
"""
|
|
Prüft, ob eine Liste von Punkten ein Rechteck darstellt.
|
|
Ein Rechteck hat genau 4 Eckpunkte (oder 5 wenn geschlossen mit identischem Start/End).
|
|
|
|
Args:
|
|
points: Liste von Punkten (Tupel mit mindestens x, y Koordinaten)
|
|
|
|
Returns:
|
|
True wenn es ein Rechteck ist, False sonst
|
|
"""
|
|
if len(points) < 4:
|
|
return False
|
|
|
|
# Prüfe ob erster und letzter Punkt identisch sind (geschlossene Polylinie)
|
|
first = (points[0][0], points[0][1])
|
|
last = (points[-1][0], points[-1][1])
|
|
|
|
if abs(first[0] - last[0]) < 0.001 and abs(first[1] - last[1]) < 0.001:
|
|
# Geschlossene Polylinie - sollte 5 Punkte haben (4 Ecken + 1 Schließung)
|
|
if len(points) == 5:
|
|
return True
|
|
# Wenn mehr als 5 Punkte, ist es kein einfaches Rechteck
|
|
return False
|
|
else:
|
|
# Offene Polylinie - sollte genau 4 Punkte haben
|
|
return len(points) == 4
|
|
|
|
|
|
def get_boundary_geometry(doc, insert):
|
|
"""
|
|
Sucht im Block nach einem Rechteck oder einer geschlossenen Polylinie.
|
|
Unterstützt zweistufige Blockstruktur (äußerer Block mit LWPOLYLINE + INSERT zu Attribut-Block).
|
|
Gibt die Eckpunkte zurück.
|
|
|
|
Returns:
|
|
Tupel (transformed_points, is_rectangle) oder (None, None) wenn nichts gefunden
|
|
"""
|
|
block_layout = doc.blocks.get(insert.dxf.name)
|
|
|
|
# Suche nach LWPOLYLINE, POLYLINE oder geschlossenen Linien
|
|
for entity in block_layout:
|
|
if entity.dxftype() == 'LWPOLYLINE':
|
|
# Get points and transform them
|
|
points = list(entity.get_points())
|
|
|
|
# Prüfe ob geschlossen (entweder Flag gesetzt oder erster == letzter Punkt)
|
|
is_closed = entity.closed or (entity.dxf.flags & 1)
|
|
if not is_closed and len(points) > 1:
|
|
# Prüfe ob erster und letzter Punkt gleich sind
|
|
first = points[0][:2]
|
|
last = points[-1][:2]
|
|
if abs(first[0] - last[0]) < 0.001 and abs(first[1] - last[1]) < 0.001:
|
|
is_closed = True
|
|
|
|
if is_closed or len(points) >= 4:
|
|
# Transform points relative to insert position
|
|
insert_point = insert.dxf.insert
|
|
transformed_points = []
|
|
for p in points:
|
|
# Einfache Translation (ohne Rotation/Skalierung für ersten Ansatz)
|
|
transformed_points.append((
|
|
insert_point[0] + p[0],
|
|
insert_point[1] + p[1]
|
|
))
|
|
|
|
# Prüfe ob es ein Rechteck ist
|
|
is_rect = is_rectangle_polyline(points)
|
|
return transformed_points, is_rect
|
|
|
|
elif entity.dxftype() == 'POLYLINE':
|
|
if entity.is_closed:
|
|
points = [(v.dxf.location.x, v.dxf.location.y) for v in entity.vertices]
|
|
insert_point = insert.dxf.insert
|
|
transformed_points = []
|
|
for p in points:
|
|
transformed_points.append((
|
|
insert_point[0] + p[0],
|
|
insert_point[1] + p[1]
|
|
))
|
|
|
|
# Prüfe ob es ein Rechteck ist
|
|
is_rect = is_rectangle_polyline(points)
|
|
return transformed_points, is_rect
|
|
|
|
return None, None
|
|
|
|
|
|
def point_in_polygon(point, polygon):
|
|
"""
|
|
Prüft, ob ein Punkt innerhalb eines Polygons liegt (Ray-casting Algorithmus).
|
|
"""
|
|
x, y = point
|
|
n = len(polygon)
|
|
inside = False
|
|
|
|
p1x, p1y = polygon[0]
|
|
for i in range(1, n + 1):
|
|
p2x, p2y = polygon[i % n]
|
|
if y > min(p1y, p2y):
|
|
if y <= max(p1y, p2y):
|
|
if x <= max(p1x, p2x):
|
|
if p1y != p2y:
|
|
xinters = (y - p1y) * (p2x - p1x) / (p2y - p1y) + p1x
|
|
if p1x == p2x or x <= xinters:
|
|
inside = not inside
|
|
p1x, p1y = p2x, p2y
|
|
|
|
return inside
|
|
|
|
|
|
def find_symbols_in_boundary(doc, msp, boundary, target_layers, attributes, error_collector=None):
|
|
"""
|
|
Findet alle Symbole (INSERT-Blöcke) innerhalb des angegebenen Bereichs auf den Ziel-Layern.
|
|
"""
|
|
symbols = []
|
|
|
|
# Bestimme welche Layer durchsucht werden sollen
|
|
search_layers = []
|
|
for i in range(1, 4): # NAME1, NAME2, NAME3
|
|
layer_key = f"LAYER_NAME{i}"
|
|
if layer_key in attributes and attributes[layer_key]:
|
|
search_layers.append(attributes[layer_key])
|
|
|
|
# Falls nur NAME/LAYER_NAME vorhanden
|
|
if "LAYER_NAME" in attributes and attributes["LAYER_NAME"]:
|
|
search_layers.append(attributes["LAYER_NAME"])
|
|
|
|
# Durchsuche alle INSERT-Blöcke
|
|
for entity in msp.query('INSERT'):
|
|
if entity.dxf.layer not in search_layers:
|
|
continue
|
|
|
|
# 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)
|
|
|
|
if not symbol_attribs:
|
|
continue
|
|
|
|
# Hole Position des Symbols
|
|
pos = entity.dxf.insert
|
|
point = (pos[0], pos[1])
|
|
|
|
# Prüfe ob innerhalb des Bereichs
|
|
if point_in_polygon(point, boundary):
|
|
# Prüfe ob es ein Template ist (enthält @)
|
|
has_template = False
|
|
for value in symbol_attribs.values():
|
|
if '@' in str(value):
|
|
has_template = True
|
|
break
|
|
|
|
if has_template:
|
|
symbols.append({
|
|
'entity': entity,
|
|
'position': point,
|
|
'attributes': symbol_attribs,
|
|
'layer': entity.dxf.layer
|
|
})
|
|
|
|
return symbols
|
|
|
|
|
|
def sort_symbols_by_direction(symbols, direction):
|
|
"""
|
|
Sortiert die Symbole nach der angegebenen Richtung.
|
|
|
|
Unterstützte Richtungen (erste Angabe = Hauptsortierung):
|
|
TOP_BOTTOM/LEFT_RIGHT: Zeilenweise von oben nach unten, dann links nach rechts (primär Y, sekundär X)
|
|
TOP_BOTTOM/RIGHT_LEFT: Zeilenweise von oben nach unten, dann rechts nach links (primär Y, sekundär -X)
|
|
BOTTOM_TOP/LEFT_RIGHT: Zeilenweise von unten nach oben, dann links nach rechts (primär Y, sekundär X)
|
|
BOTTOM_TOP/RIGHT_LEFT: Zeilenweise von unten nach oben, dann rechts nach links (primär Y, sekundär -X)
|
|
|
|
LEFT_RIGHT/TOP_BOTTOM: Spaltenweise von links nach rechts, dann oben nach unten (primär X, sekundär -Y)
|
|
LEFT_RIGHT/BOTTOM_TOP: Spaltenweise von links nach rechts, dann unten nach oben (primär X, sekundär Y)
|
|
RIGHT_LEFT/TOP_BOTTOM: Spaltenweise von rechts nach links, dann oben nach unten (primär -X, sekundär -Y)
|
|
RIGHT_LEFT/BOTTOM_TOP: Spaltenweise von rechts nach links, dann unten nach oben (primär -X, sekundär Y)
|
|
|
|
Einfache Richtungen:
|
|
TOP_BOTTOM → TOP_BOTTOM/LEFT_RIGHT (zeilenweise von oben nach unten)
|
|
BOTTOM_TOP → BOTTOM_TOP/LEFT_RIGHT (zeilenweise von unten nach oben)
|
|
LEFT_RIGHT: Spaltenweise von links nach rechts (sortiert nach X, dann Y)
|
|
RIGHT_LEFT: Spaltenweise von rechts nach links (sortiert nach -X, dann Y)
|
|
|
|
Args:
|
|
symbols: Liste von Symbol-Dictionaries mit 'position' (x, y)
|
|
direction: DIRECTION-Attribut (z.B. "LEFT_RIGHT", "TOP_BOTTOM/LEFT_RIGHT", "LEFT_RIGHT/TOP_BOTTOM")
|
|
|
|
Returns:
|
|
Sortierte Liste von Symbolen
|
|
"""
|
|
# Kombinierte Richtungen (vollständig spezifiziert) - Y/X Reihenfolge
|
|
if direction == "TOP_BOTTOM/LEFT_RIGHT" or direction == "TOP_BOTTOM":
|
|
# Von oben nach unten (-Y), in jeder Zeile von links nach rechts (X)
|
|
return sorted(symbols, key=lambda s: (-s['position'][1], s['position'][0]))
|
|
elif direction == "LEFT_RIGHT/TOP_BOTTOM" :
|
|
# Von links nach rechts (X), in jeder Spalte von oben nach unten (Y)
|
|
return sorted(symbols, key=lambda s: (s['position'][0], -s['position'][1]))
|
|
elif direction == "LEFT_RIGHT/BOTTOM_TOP":
|
|
# Von links nach rechts (X), in jeder Spalte von unten nach oben (Y)
|
|
return sorted(symbols, key=lambda s: (s['position'][0], s['position'][1]))
|
|
elif direction == "TOP_BOTTOM/RIGHT_LEFT":
|
|
# Von oben nach unten (-Y), in jeder Zeile von rechts nach links (-X)
|
|
return sorted(symbols, key=lambda s: (-s['position'][1], -s['position'][0]))
|
|
elif direction == "BOTTOM_TOP/LEFT_RIGHT" or direction == "BOTTOM_TOP":
|
|
# Von unten nach oben (Y), in jeder Zeile von links nach rechts (X)
|
|
return sorted(symbols, key=lambda s: (s['position'][1], s['position'][0]))
|
|
elif direction == "BOTTOM_TOP/RIGHT_LEFT":
|
|
# Von unten nach oben (Y), in jeder Zeile von rechts nach links (-X)
|
|
return sorted(symbols, key=lambda s: (s['position'][1], -s['position'][0]))
|
|
elif direction == "RIGHT_LEFT/BOTTOM_TOP":
|
|
# Von unten nach oben (Y), in jeder Zeile von rechts nach links (-X)
|
|
return sorted(symbols, key=lambda s: (-s['position'][0], s['position'][1]))
|
|
elif direction == "RIGHT_LEFT/TOP_BOTTOM":
|
|
# Von rechts nach links (-X), in jeder Spalte von oben nach unten (Y)
|
|
return sorted(symbols, key=lambda s: (-s['position'][0], -s['position'][1]))
|
|
|
|
# Einfache Richtungen
|
|
elif direction == "LEFT_RIGHT":
|
|
# Spaltenweise von links nach rechts: Sortiere nach X, dann Y
|
|
return sorted(symbols, key=lambda s: (s['position'][0], s['position'][1]))
|
|
elif direction == "RIGHT_LEFT":
|
|
# Spaltenweise von rechts nach links: Sortiere nach -X, dann Y
|
|
return sorted(symbols, key=lambda s: (-s['position'][0], s['position'][1]))
|
|
else:
|
|
# Fallback: TOP_BOTTOM/LEFT_RIGHT
|
|
return sorted(symbols, key=lambda s: (-s['position'][1], s['position'][0]))
|
|
|
|
|
|
def enumerate_symbols(symbols, attributes):
|
|
"""
|
|
Nummeriert die Symbole durch und ersetzt die @@ Platzhalter.
|
|
"""
|
|
counter = 1
|
|
renamed = []
|
|
|
|
for symbol in symbols:
|
|
layer = symbol['layer']
|
|
|
|
# Finde das passende NAME-Template für diesen Layer
|
|
name_template = None
|
|
for i in range(1, 4):
|
|
layer_key = f"LAYER_NAME{i}"
|
|
name_key = f"NAME{i}"
|
|
if layer_key in attributes and attributes[layer_key] == layer:
|
|
if name_key in attributes:
|
|
name_template = attributes[name_key]
|
|
break
|
|
|
|
# Falls nur NAME/LAYER_NAME vorhanden
|
|
if not name_template and "LAYER_NAME" in attributes and attributes["LAYER_NAME"] == layer:
|
|
if "NAME" in attributes:
|
|
name_template = attributes["NAME"]
|
|
|
|
if not name_template:
|
|
continue
|
|
|
|
# Ersetze @-Zeichen durch Zahlen
|
|
# Zähle wie viele @ im Template sind
|
|
at_count = name_template.count('@')
|
|
number_str = oct(counter)[2:].zfill(at_count)
|
|
|
|
# Aktualisiere Attribute im Symbol
|
|
for attrib in symbol['entity'].attribs:
|
|
old_value = attrib.dxf.text
|
|
if '@' in old_value:
|
|
# Ersetze @ durch die Nummer
|
|
new_value = old_value.replace('@' * at_count, number_str)
|
|
attrib.dxf.text = new_value
|
|
|
|
renamed.append({
|
|
'position': symbol['position'],
|
|
'layer': layer,
|
|
'old_value': old_value,
|
|
'new_value': new_value
|
|
})
|
|
|
|
counter += 1
|
|
|
|
return renamed
|
|
|
|
|
|
def get_layer_names_from_attributes(attributes):
|
|
"""
|
|
Extrahiert alle LAYER_NAME Attribute aus einem Renamer-Block.
|
|
|
|
Args:
|
|
attributes: Dictionary mit Block-Attributen
|
|
|
|
Returns:
|
|
Liste der Layer-Namen (z.B. ['ILS_Eingang', 'ILS_Ausgang'])
|
|
"""
|
|
layer_names = []
|
|
if "LAYER_NAME" in attributes and attributes["LAYER_NAME"]:
|
|
layer_names.append(attributes["LAYER_NAME"])
|
|
for i in range(1, 4):
|
|
layer_key = f"LAYER_NAME{i}"
|
|
if layer_key in attributes and attributes[layer_key]:
|
|
layer_names.append(attributes[layer_key])
|
|
return layer_names
|
|
|
|
|
|
def get_renamer_config_key(attributes):
|
|
"""
|
|
Erstellt einen eindeutigen Schlüssel für die Renamer-Block-Konfiguration.
|
|
Blöcke mit demselben Schlüssel werden gruppiert und gemeinsam nummeriert.
|
|
|
|
Args:
|
|
attributes: Dictionary mit Block-Attributen
|
|
|
|
Returns:
|
|
Tuple mit (NAME-Patterns, LAYER_NAMEs, DIRECTION, KENNZEICHNUNG)
|
|
"""
|
|
# Sammle NAME-Patterns (normalisiert: NAME oder NAME1-3)
|
|
name_patterns = []
|
|
if "NAME" in attributes and attributes["NAME"]:
|
|
name_patterns.append(("NAME", attributes["NAME"]))
|
|
for i in range(1, 4):
|
|
name_key = f"NAME{i}"
|
|
if name_key in attributes and attributes[name_key]:
|
|
name_patterns.append((name_key, attributes[name_key]))
|
|
|
|
# Sammle LAYER_NAMEs (normalisiert: LAYER_NAME oder LAYER_NAME1-3)
|
|
layer_names = []
|
|
if "LAYER_NAME" in attributes and attributes["LAYER_NAME"]:
|
|
layer_names.append(("LAYER_NAME", attributes["LAYER_NAME"]))
|
|
for i in range(1, 4):
|
|
layer_key = f"LAYER_NAME{i}"
|
|
if layer_key in attributes and attributes[layer_key]:
|
|
layer_names.append((layer_key, attributes[layer_key]))
|
|
|
|
# DIRECTION und KENNZEICHNUNG
|
|
direction = attributes.get("DIRECTION", "")
|
|
kennzeichnung = attributes.get("KENNZEICHNUNG", "")
|
|
|
|
# Erstelle einen eindeutigen Schlüssel
|
|
# Sortiere die Listen für konsistente Keys
|
|
name_patterns.sort()
|
|
layer_names.sort()
|
|
|
|
return (tuple(name_patterns), tuple(layer_names), direction, kennzeichnung)
|
|
|
|
|
|
def validate_direction_with_geometry(direction, is_rectangle):
|
|
"""
|
|
Validiert die DIRECTION-Angabe gegen die tatsächliche Geometrie.
|
|
|
|
Args:
|
|
direction: DIRECTION-Attribut (z.B. "LEFT_RIGHT", "POLYLINE_PATH")
|
|
is_rectangle: True wenn die Polylinie ein Rechteck ist, False sonst
|
|
|
|
Returns:
|
|
Tupel (is_valid, error_message):
|
|
- is_valid: True wenn Kombination gültig ist
|
|
- error_message: Fehlermeldung wenn ungültig, sonst None
|
|
"""
|
|
# POLYLINE_PATH-Richtungen erwarten komplexe Polylinien (keine Rechtecke)
|
|
polyline_path_directions = ["POLYLINE_PATH"]
|
|
|
|
# Rechteck/Standard-Richtungen erwarten einfache Rechtecke
|
|
rectangle_directions = [
|
|
"TOP_BOTTOM", "BOTTOM_TOP", "LEFT_RIGHT", "RIGHT_LEFT",
|
|
"TOP_BOTTOM/LEFT_RIGHT", "TOP_BOTTOM/RIGHT_LEFT",
|
|
"BOTTOM_TOP/RIGHT_LEFT", "BOTTOM_TOP/LEFT_RIGHT",
|
|
"LEFT_RIGHT/TOP_BOTTOM", "RIGHT_LEFT/TOP_BOTTOM",
|
|
"LEFT_RIGHT/BOTTOM_TOP", "RIGHT_LEFT/BOTTOM_TOP"
|
|
]
|
|
|
|
if direction in polyline_path_directions:
|
|
if is_rectangle:
|
|
return False, f"DIRECTION '{direction}' erfordert eine komplexe Polylinie, aber ein Rechteck wurde gefunden"
|
|
return True, None
|
|
|
|
elif direction in rectangle_directions:
|
|
if not is_rectangle:
|
|
return False, f"DIRECTION '{direction}' erfordert ein Rechteck, aber eine komplexe Polylinie wurde gefunden"
|
|
return True, None
|
|
|
|
else:
|
|
# Unbekannte DIRECTION - gebe Warnung, aber breche nicht ab
|
|
return True, f"Unbekannte DIRECTION '{direction}' - Verarbeitung wird fortgesetzt"
|
|
|
|
|
|
def collect_and_group_renamer_blocks(doc, msp, renamer_layers, error_collector):
|
|
"""
|
|
Erster Pass: Sammelt und gruppiert alle Renamer-Blöcke nach Konfiguration.
|
|
|
|
Returns:
|
|
Dictionary mit {config_key: [(insert, boundary, attributes, is_rectangle), ...]}
|
|
"""
|
|
renamer_groups = {} # {config_key: [(insert, boundary, attributes, is_rectangle), ...]}
|
|
|
|
for layer in renamer_layers:
|
|
print(f"Durchsuche Layer: {layer}")
|
|
|
|
# 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)
|
|
|
|
if not attributes:
|
|
print(f" Block ohne Attribute gefunden an Position {insert.dxf.insert}")
|
|
continue
|
|
|
|
# Prüfe ob es ein Renamer-Block ist
|
|
has_name = "NAME" in attributes or "NAME1" in attributes
|
|
has_direction = "DIRECTION" in attributes
|
|
|
|
if not (has_name and has_direction):
|
|
continue
|
|
|
|
block_name = attributes.get('NAME', attributes.get('NAME1', 'UNKNOWN'))
|
|
print(f" Renamer-Block gefunden: {block_name}")
|
|
print(f" Direction: {attributes.get('DIRECTION', 'UNKNOWN')}")
|
|
print(f" Kennzeichnung: {attributes.get('KENNZEICHNUNG', 'N/A')}")
|
|
|
|
# Finde Boundary (Rechteck oder Polylinie)
|
|
boundary, is_rectangle = get_boundary_geometry(doc, insert)
|
|
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})
|
|
continue
|
|
|
|
print(f" Boundary gefunden mit {len(boundary)} Punkten (Rechteck: {is_rectangle})")
|
|
print(f" Eckpunkte: {', '.join([f'({p[0]:.2f}, {p[1]:.2f})' for p in boundary])}")
|
|
|
|
# Validiere DIRECTION gegen Geometrie
|
|
direction = attributes.get("DIRECTION", "LEFT_RIGHT")
|
|
is_valid, validation_error = validate_direction_with_geometry(direction, is_rectangle)
|
|
|
|
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})
|
|
continue
|
|
elif validation_error:
|
|
# Warnung bei unbekannter DIRECTION, aber fortfahren
|
|
print(f" WARNUNG: {validation_error}")
|
|
error_collector.add_warnings({"unknown_direction": validation_error})
|
|
|
|
# Erstelle Konfigurationsschlüssel und gruppiere
|
|
config_key = get_renamer_config_key(attributes)
|
|
if config_key not in renamer_groups:
|
|
renamer_groups[config_key] = []
|
|
renamer_groups[config_key].append((insert, boundary, attributes, is_rectangle))
|
|
|
|
return renamer_groups
|
|
|
|
|
|
def find_template_symbols_in_geometry(doc, msp, group_blocks, error_collector):
|
|
"""
|
|
Findet alle Symbole mit '@' die geometrisch in den Renamer-Bereichen liegen,
|
|
unabhängig vom Layer.
|
|
|
|
Args:
|
|
doc: DXF-Dokument
|
|
msp: Modelspace
|
|
group_blocks: Liste von (insert, boundary, attributes, is_rectangle) Tupeln
|
|
error_collector: ErrorCollector für Fehlerbehandlung
|
|
|
|
Returns:
|
|
Liste von Dictionaries mit 'layer', 'position', 'io' für jedes gefundene Symbol
|
|
"""
|
|
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)
|
|
if not symbol_attribs:
|
|
continue
|
|
|
|
# Prüfe ob irgendein Attribut "@" enthält
|
|
has_template = any('@' in str(value) for value in symbol_attribs.values())
|
|
|
|
if has_template:
|
|
pos = entity.dxf.insert
|
|
point = (pos[0], pos[1])
|
|
if point_in_polygon(point, boundary):
|
|
symbols_in_geometry.append({
|
|
'layer': entity.dxf.layer,
|
|
'position': point,
|
|
'io': symbol_attribs.get('IO', 'N/A')
|
|
})
|
|
return symbols_in_geometry
|
|
|
|
|
|
def create_no_symbols_found_error_message(block_name, direction, layer_names, group_blocks,
|
|
doc, msp, error_collector):
|
|
"""
|
|
Erstellt eine detaillierte Fehlermeldung wenn keine Symbole in einem Renamer-Bereich gefunden wurden.
|
|
|
|
Args:
|
|
block_name: Name des Renamer-Blocks
|
|
direction: DIRECTION-Attribut
|
|
layer_names: Liste der erwarteten Layer-Namen
|
|
group_blocks: Liste von (insert, boundary, attributes, is_rectangle) Tupeln
|
|
doc: DXF-Dokument
|
|
msp: Modelspace
|
|
error_collector: ErrorCollector für Fehlerbehandlung
|
|
|
|
Returns:
|
|
Fehlermeldungs-String
|
|
"""
|
|
renamer_positions = [f"({insert.dxf.insert[0]:.1f}, {insert.dxf.insert[1]:.1f})"
|
|
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)
|
|
|
|
# Erstelle Fehlermeldung basierend auf gefundenen Symbolen
|
|
if symbols_in_geometry and not layer_names:
|
|
# Es gibt Symbole im Bereich, aber keine LAYER_NAME Attribute definiert
|
|
return (
|
|
f"Renamer-Block-Gruppe '{block_name}' (DIRECTION: {direction}) "
|
|
f"hat keine passenden Symbole gefunden. "
|
|
f"Es wurden {len(symbols_in_geometry)} Symbol(e) mit '@' geometrisch im Bereich gefunden, "
|
|
f"aber der Renamer-Block hat KEINE LAYER_NAME Attribute (LAYER_NAME1, LAYER_NAME2, LAYER_NAME3) definiert. "
|
|
f"Ohne diese Attribute können Symbole nicht zugeordnet werden. "
|
|
f"Renamer-Blöcke an Positionen: {', '.join(renamer_positions)}"
|
|
)
|
|
elif symbols_in_geometry and layer_names:
|
|
# Es gibt Symbole im Bereich, aber sie sind auf falschen Layern
|
|
found_layers = set(s['layer'] for s in symbols_in_geometry)
|
|
return (
|
|
f"Renamer-Block-Gruppe '{block_name}' (DIRECTION: {direction}) "
|
|
f"hat keine passenden Symbole gefunden. "
|
|
f"Es wurden {len(symbols_in_geometry)} Symbol(e) mit '@' geometrisch im Bereich gefunden, "
|
|
f"aber diese sind auf Layer: {', '.join(found_layers)}. "
|
|
f"Erwartet werden Layer: {', '.join(layer_names)}. "
|
|
f"Prüfen Sie die LAYER_NAME Attribute im Renamer-Block. "
|
|
f"Renamer-Blöcke an Positionen: {', '.join(renamer_positions)}"
|
|
)
|
|
else:
|
|
# Keine Symbole im Bereich gefunden (weder geometrisch noch auf richtigen Layern)
|
|
return (
|
|
f"Renamer-Block-Gruppe '{block_name}' (DIRECTION: {direction}) "
|
|
f"hat keine passenden Symbole gefunden. "
|
|
f"Gesucht auf Layer: {', '.join(layer_names) if layer_names else 'N/A'}. "
|
|
f"Renamer-Blöcke an Positionen: {', '.join(renamer_positions)}"
|
|
)
|
|
|
|
|
|
def collect_symbols_from_group(doc, msp, renamer_layers, group_blocks, block_name, error_collector):
|
|
"""
|
|
Sammelt alle Symbole aus allen Boundaries einer Renamer-Gruppe.
|
|
Prüft auf Duplikate (Symbole in mehreren Bereichen).
|
|
|
|
Args:
|
|
doc: DXF-Dokument
|
|
msp: Modelspace
|
|
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
|
|
|
|
Returns:
|
|
Liste aller eindeutigen Symbole
|
|
"""
|
|
all_symbols = []
|
|
seen_entities = {} # {entity_id: (symbol, [list of renamer block positions])}
|
|
|
|
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)
|
|
|
|
# Füge Symbole hinzu, aber nur wenn das Entity noch nicht gesehen wurde
|
|
for symbol in symbols:
|
|
entity_id = id(symbol['entity']) # Eindeutige ID des Entity-Objekts
|
|
renamer_pos = insert.dxf.insert
|
|
|
|
if entity_id not in seen_entities:
|
|
seen_entities[entity_id] = (symbol, [renamer_pos])
|
|
all_symbols.append(symbol)
|
|
else:
|
|
# Symbol wurde bereits in einem anderen Bereich gefunden - Fehler
|
|
existing_symbol, existing_positions = seen_entities[entity_id]
|
|
existing_positions.append(renamer_pos)
|
|
symbol_pos = symbol['position']
|
|
io_value = symbol['attributes'].get('IO', 'N/A')
|
|
error_msg = (
|
|
f"Symbol an Position ({symbol_pos[0]:.1f}, {symbol_pos[1]:.1f}) "
|
|
f"mit IO='{io_value}' liegt in mehreren Renamer-Bereichen der Gruppe '{block_name}'. "
|
|
f"Gefunden in Renamer-Blöcken an Positionen: "
|
|
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})
|
|
|
|
return all_symbols
|
|
|
|
|
|
def process_renamer_groups(doc, msp, renamer_layers, renamer_groups, error_collector):
|
|
"""
|
|
Zweiter Pass: Verarbeitet jede Renamer-Block-Gruppe.
|
|
Sammelt Symbole, sortiert sie und nummeriert sie.
|
|
|
|
Returns:
|
|
Liste aller umbenannten Symbole
|
|
"""
|
|
all_renamed = []
|
|
|
|
for config_key, group_blocks in renamer_groups.items():
|
|
if not group_blocks:
|
|
continue
|
|
|
|
# Verwende die Attribute des ersten Blocks als Referenz (alle in der Gruppe haben identische Attribute)
|
|
reference_attributes = group_blocks[0][2]
|
|
direction = reference_attributes.get("DIRECTION", "LEFT_RIGHT")
|
|
block_name = reference_attributes.get('NAME', reference_attributes.get('NAME1', 'UNKNOWN'))
|
|
|
|
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)
|
|
|
|
print(f" Gesamt {len(all_symbols)} eindeutige Template-Symbole aus {len(group_blocks)} Blöcken")
|
|
|
|
if not all_symbols:
|
|
# 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
|
|
)
|
|
print(f" FEHLER: {error_msg}")
|
|
error_collector.add_errors({"no_symbols_found": error_msg})
|
|
continue
|
|
|
|
# Sortiere alle Symbole zusammen nach Richtung
|
|
sorted_symbols = sort_symbols_by_direction(all_symbols, direction)
|
|
|
|
# Nummeriere alle Symbole als eine Sequenz
|
|
renamed = enumerate_symbols(sorted_symbols, reference_attributes)
|
|
all_renamed.extend(renamed)
|
|
|
|
print(f" {len(renamed)} Symbole nummeriert")
|
|
|
|
return all_renamed
|
|
|
|
|
|
def find_symbols_with_at_in_io(doc, msp, error_collector):
|
|
"""
|
|
Findet alle Symbole mit "@" im IO-Attribut.
|
|
|
|
Args:
|
|
doc: DXF-Dokument
|
|
msp: Modelspace
|
|
error_collector: ErrorCollector für Fehlerbehandlung
|
|
|
|
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)
|
|
if not symbol_attribs:
|
|
continue
|
|
|
|
# Prüfe ob IO-Attribut "@" enthält
|
|
io_value = symbol_attribs.get('IO', '')
|
|
if '@' in str(io_value):
|
|
pos = entity.dxf.insert
|
|
point = (pos[0], pos[1])
|
|
symbols_with_at.append({
|
|
'entity': entity,
|
|
'position': point,
|
|
'attributes': symbol_attribs,
|
|
'layer': entity.dxf.layer,
|
|
'io': io_value
|
|
})
|
|
return symbols_with_at
|
|
|
|
|
|
def check_symbol_in_boundary(symbol, boundary, attributes, renamer_pos):
|
|
"""
|
|
Prüft ob ein Symbol geometrisch und vom Layer her in einem Renamer-Bereich liegt.
|
|
|
|
Args:
|
|
symbol: Dictionary mit Symbol-Informationen
|
|
boundary: Polygon-Punkte des Renamer-Bereichs
|
|
attributes: Attribute des Renamer-Blocks
|
|
renamer_pos: Position des Renamer-Blocks
|
|
|
|
Returns:
|
|
Tuple (in_boundary, wrong_layer_info):
|
|
- in_boundary: True wenn Symbol geometrisch und vom Layer her im Bereich liegt
|
|
- wrong_layer_info: Dictionary mit Fehlerinformationen wenn Layer falsch ist, sonst None
|
|
"""
|
|
# Prüfe ob Symbol geometrisch innerhalb des Bereichs liegt
|
|
if not point_in_polygon(symbol['position'], boundary):
|
|
return False, None
|
|
|
|
# Symbol liegt geometrisch im Bereich - prüfe Layer
|
|
search_layers = get_layer_names_from_attributes(attributes)
|
|
|
|
if symbol['layer'] in search_layers:
|
|
# Layer passt auch - alles OK
|
|
return True, None
|
|
else:
|
|
# Symbol liegt geometrisch im Bereich, aber Layer passt nicht
|
|
block_name = attributes.get('NAME', attributes.get('NAME1', 'UNKNOWN'))
|
|
wrong_layer_info = {
|
|
'block_name': block_name,
|
|
'renamer_pos': renamer_pos,
|
|
'expected_layers': search_layers,
|
|
'attributes': attributes
|
|
}
|
|
return False, wrong_layer_info
|
|
|
|
|
|
def create_wrong_layer_warning(symbol, wrong_layer_info):
|
|
"""
|
|
Erstellt eine Warnung wenn ein Symbol im Bereich liegt, aber der Layer nicht passt.
|
|
|
|
Args:
|
|
symbol: Dictionary mit Symbol-Informationen
|
|
wrong_layer_info: Dictionary mit Fehlerinformationen
|
|
|
|
Returns:
|
|
Warnmeldungs-String
|
|
"""
|
|
if wrong_layer_info['expected_layers']:
|
|
return (
|
|
f"Symbol an Position ({symbol['position'][0]:.1f}, {symbol['position'][1]:.1f}) "
|
|
f"mit IO='{symbol['io']}' liegt geometrisch im Renamer-Bereich '{wrong_layer_info['block_name']}' "
|
|
f"(Renamer-Position: ({wrong_layer_info['renamer_pos'][0]:.1f}, {wrong_layer_info['renamer_pos'][1]:.1f})), "
|
|
f"aber das Symbol ist auf Layer '{symbol['layer']}', erwartet wird: {', '.join(wrong_layer_info['expected_layers'])}. "
|
|
f"Prüfen Sie die LAYER_NAME Attribute im Renamer-Block."
|
|
)
|
|
else:
|
|
return (
|
|
f"Symbol an Position ({symbol['position'][0]:.1f}, {symbol['position'][1]:.1f}) "
|
|
f"mit IO='{symbol['io']}' liegt geometrisch im Renamer-Bereich '{wrong_layer_info['block_name']}' "
|
|
f"(Renamer-Position: ({wrong_layer_info['renamer_pos'][0]:.1f}, {wrong_layer_info['renamer_pos'][1]:.1f})), "
|
|
f"aber der Renamer-Block hat KEINE LAYER_NAME Attribute definiert. "
|
|
f"Symbole können nur gefunden werden, wenn LAYER_NAME1/LAYER_NAME2/LAYER_NAME3 Attribute gesetzt sind."
|
|
)
|
|
|
|
|
|
def check_symbols_in_boundaries(doc, msp, renamer_groups, error_collector):
|
|
"""
|
|
Dritter Pass: Prüft ob alle Symbole mit "@" im IO in einem Renamer-Bereich liegen.
|
|
"""
|
|
print("\n" + "="*60)
|
|
print("Prüfe ob alle Symbole mit '@' im IO in einem Renamer-Bereich liegen")
|
|
print("="*60)
|
|
|
|
# Sammle alle Renamer-Boundaries
|
|
all_boundaries = []
|
|
for group_blocks in renamer_groups.values():
|
|
for insert, boundary, attributes, is_rectangle in group_blocks:
|
|
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)
|
|
|
|
# Prüfe für jedes Symbol, ob es in mindestens einem Renamer-Bereich liegt
|
|
symbols_not_in_any_boundary = []
|
|
for symbol in symbols_with_at:
|
|
in_any_boundary = False
|
|
wrong_layer_info = None
|
|
|
|
for boundary, attributes, renamer_pos in all_boundaries:
|
|
is_in_boundary, layer_info = check_symbol_in_boundary(symbol, boundary, attributes, renamer_pos)
|
|
|
|
if is_in_boundary:
|
|
# Layer passt auch - alles OK
|
|
in_any_boundary = True
|
|
break
|
|
elif layer_info is not None:
|
|
# Symbol liegt geometrisch im Bereich, aber Layer passt nicht
|
|
wrong_layer_info = layer_info
|
|
|
|
if not in_any_boundary:
|
|
if wrong_layer_info:
|
|
# 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})
|
|
# NICHT als Fehler hinzufügen - Warnung reicht aus
|
|
else:
|
|
# Fehler: Symbol liegt weder geometrisch noch vom Layer her in einem Renamer-Bereich
|
|
symbols_not_in_any_boundary.append(symbol)
|
|
|
|
# Melde Fehler für Symbole, die in keinem Bereich liegen
|
|
if symbols_not_in_any_boundary:
|
|
for symbol in symbols_not_in_any_boundary:
|
|
error_msg = (
|
|
f"Symbol an Position ({symbol['position'][0]:.1f}, {symbol['position'][1]:.1f}) "
|
|
f"mit IO='{symbol['io']}' auf Layer '{symbol['layer']}' "
|
|
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})
|
|
else:
|
|
print(f" Alle {len(symbols_with_at)} Symbole mit '@' im IO liegen in einem Renamer-Bereich.")
|
|
|
|
|
|
def process_renamer_blocks(doc, msp, renamer_layers, error_collector):
|
|
"""
|
|
Verarbeitet alle Renamer-Blöcke auf den angegebenen Layern.
|
|
Gruppiert Blöcke mit identischer Konfiguration und nummeriert alle Symbole
|
|
aus allen Blöcken einer Gruppe gemeinsam.
|
|
|
|
Returns:
|
|
Tuple (all_renamed, renamer_groups):
|
|
- all_renamed: Liste der umbenannten Symbole
|
|
- 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)
|
|
|
|
# Zweiter Pass: Verarbeite jede Gruppe
|
|
all_renamed = process_renamer_groups(doc, msp, renamer_layers, renamer_groups, error_collector)
|
|
|
|
# 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)
|
|
|
|
return all_renamed, renamer_groups
|
|
|
|
|
|
def collect_all_symbols_from_groups(doc, msp, renamer_groups, renamer_layers, error_collector):
|
|
"""
|
|
Sammelt alle Symbole aus allen Renamer-Gruppen für Visualisierungszwecke.
|
|
|
|
Args:
|
|
doc: DXF-Dokument
|
|
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
|
|
|
|
Returns:
|
|
Liste aller gefundenen Symbole mit 'entity', 'position', 'attributes', 'layer'
|
|
"""
|
|
all_symbols = []
|
|
seen_entities = set() # Verhindere Duplikate
|
|
|
|
for config_key, group_blocks in renamer_groups.items():
|
|
if not group_blocks:
|
|
continue
|
|
|
|
reference_attributes = group_blocks[0][2]
|
|
|
|
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)
|
|
|
|
# Füge Symbole hinzu, aber nur wenn das Entity noch nicht gesehen wurde
|
|
for symbol in symbols:
|
|
entity_id = id(symbol['entity'])
|
|
if entity_id not in seen_entities:
|
|
seen_entities.add(entity_id)
|
|
all_symbols.append(symbol)
|
|
|
|
return all_symbols
|
|
|
|
|
|
if __name__ == '__main__':
|
|
# Lese Umgebungsvariablen vor argparse, um Config-Info in Hilfe anzuzeigen
|
|
out_dir = check_environment_var('PROJECT_DATA')
|
|
work_dir = check_environment_var('PROJECT_WORK')
|
|
config_dir = check_environment_var("PROJECT_CFG")
|
|
|
|
# Versuche Config zu lesen für --help Ausgabe
|
|
config_path = Path(config_dir) / "enumerate.cfg"
|
|
available_layers = []
|
|
if config_path.exists():
|
|
try:
|
|
available_layers = read_config_layers(config_path)
|
|
except Exception:
|
|
pass # Ignoriere Fehler beim Lesen für --help
|
|
|
|
# Erstelle Epilog mit verfügbaren Layern
|
|
epilog_text = "\nKonfigurierte Renamer-Layer:\n"
|
|
if available_layers:
|
|
epilog_text += " " + ", ".join(available_layers) + "\n"
|
|
epilog_text += f"\n(aus Konfigurationsdatei: {config_path})"
|
|
else:
|
|
epilog_text += " (keine Layer konfiguriert oder Config-Datei nicht gefunden)\n"
|
|
epilog_text += f" Standard-Pfad: {config_path}"
|
|
|
|
parser = argparse.ArgumentParser(
|
|
description='Nummeriert Symbole in DXF-Dateien basierend auf Renamer-Blöcken',
|
|
prog='create_numbers',
|
|
epilog=epilog_text,
|
|
formatter_class=argparse.RawDescriptionHelpFormatter
|
|
)
|
|
parser.add_argument('-f', '--filename', action='store', required=True, help='DXF-Datei die verarbeitet werden soll', metavar='myfile.dxf')
|
|
parser.add_argument('-e', '--errorfile', action='store', required=False, help='JSON-Datei für Fehler und Warnungen', metavar='errors.json')
|
|
parser.add_argument('-i', '--info', action='store', help='Schreibe Ergebnisse der Nummerierung in eine JSON-Datei')
|
|
parser.add_argument('-d', '--dryrun', action='store_true', help='Symbole nicht in der DXF-Datei überschreiben, nur Ausgabe auf Konsole')
|
|
parser.add_argument('--show_symbol_frames', action='store_true', help='Zeichne Rahmen um jedes Symbol, um die Grenze des Symbols zu visualisieren')
|
|
|
|
args = parser.parse_args()
|
|
|
|
# Initialisiere ErrorCollector
|
|
error_collector = ErrorCollector()
|
|
|
|
filename = Path(args.filename)
|
|
if not filename.suffix == ".dxf":
|
|
print("Nur für .dxf Dateien verfügbar")
|
|
exit(1)
|
|
|
|
(dxf_path, dexists) = check_file_in_work(work_dir, filename)
|
|
if dexists == False:
|
|
print(f"Datei nicht gefunden: {filename}")
|
|
parser.print_help()
|
|
exit(1)
|
|
|
|
# Config-Datei existiert bereits (wurde für --help geprüft)
|
|
if not config_path.exists():
|
|
print(f"Konfigurationsdatei nicht gefunden: {config_path}")
|
|
exit(1)
|
|
|
|
renamer_layers = read_config_layers(config_path)
|
|
print(f"Konfigurierte Layer: {renamer_layers}")
|
|
|
|
if not renamer_layers:
|
|
print("Keine Layer in der Konfiguration gefunden")
|
|
exit(1)
|
|
|
|
# Lese DXF-Datei
|
|
if dxf_is_binary(dxf_path):
|
|
print("DXF-Datei ist binär. Lese komplette Datei. Achten Sie auf RAM-Nutzung.")
|
|
doc = get_dxf_file(dxf_path)
|
|
msp = doc.modelspace()
|
|
else:
|
|
print("DXF-Datei ist ASCII.")
|
|
doc = get_dxf_file(dxf_path)
|
|
msp = doc.modelspace()
|
|
|
|
# Verarbeite Renamer-Blöcke
|
|
print("\n" + "="*60)
|
|
print("Starte Verarbeitung der Renamer-Blöcke")
|
|
print("="*60 + "\n")
|
|
|
|
renamed_symbols, renamer_groups = process_renamer_blocks(doc, msp, renamer_layers, error_collector)
|
|
|
|
print("\n" + "="*60)
|
|
print(f"Verarbeitung abgeschlossen: {len(renamed_symbols)} Symbole nummeriert")
|
|
print("="*60 + "\n")
|
|
|
|
# Ausgabe der Ergebnisse
|
|
if renamed_symbols:
|
|
print("Nummerierte Symbole:")
|
|
for item in renamed_symbols:
|
|
print(f" {item['old_value']} -> {item['new_value']} (Layer: {item['layer']}, Pos: {item['position']})")
|
|
|
|
# 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)
|
|
if all_symbols:
|
|
draw_symbol_frames(doc, msp, all_symbols)
|
|
else:
|
|
print("\nKeine Symbole für Rahmen-Zeichnung gefunden")
|
|
|
|
# Speichere DXF-Datei wenn nicht dry-run
|
|
if not args.dryrun:
|
|
output_path = dxf_path.parent / f"{dxf_path.stem}_numbered{dxf_path.suffix}"
|
|
doc.saveas(output_path)
|
|
print(f"\nNummerierte DXF-Datei gespeichert: {output_path}")
|
|
else:
|
|
print("\nDry-run Modus: DXF-Datei wurde nicht überschrieben")
|
|
|
|
# Schreibe JSON-Ausgabe wenn gewünscht
|
|
if args.info:
|
|
# Formatiere die Ausgabe für bessere Lesbarkeit in JSON
|
|
formatted_symbols = []
|
|
for symbol in renamed_symbols:
|
|
formatted_symbols.append({
|
|
'old_value': symbol['old_value'],
|
|
'new_value': symbol['new_value'],
|
|
'layer': symbol['layer'],
|
|
'position': {
|
|
'x': symbol['position'][0],
|
|
'y': symbol['position'][1]
|
|
}
|
|
})
|
|
|
|
output_data = {
|
|
'renamed_symbols': formatted_symbols,
|
|
'total_count': len(formatted_symbols)
|
|
}
|
|
|
|
info_path = Path(args.info)
|
|
|
|
# Wenn nur ein Dateiname ohne Pfad angegeben wurde, speichere im work-Ordner
|
|
if info_path.parent == Path('.'):
|
|
output_dir = dxf_path.parent
|
|
output_filename = info_path.name
|
|
else:
|
|
# Absoluter oder relativer Pfad mit Verzeichnis angegeben
|
|
output_dir = info_path.parent
|
|
output_filename = info_path.name
|
|
|
|
write_json_file(output_data, output_dir, output_filename)
|
|
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 args.errorfile:
|
|
error_data = error_collector.get_all_issues()
|
|
error_path = Path(args.errorfile)
|
|
|
|
# Wenn nur ein Dateiname ohne Pfad angegeben wurde, speichere im work-Ordner
|
|
if error_path.parent == Path('.'):
|
|
output_dir = dxf_path.parent
|
|
output_filename = error_path.name
|
|
else:
|
|
# Absoluter oder relativer Pfad mit Verzeichnis angegeben
|
|
output_dir = error_path.parent
|
|
output_filename = error_path.name
|
|
|
|
write_json_file(error_data, output_dir, output_filename)
|
|
print(f"Fehler/Warnungen gespeichert: {output_dir / output_filename}")
|
|
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()
|
|
write_json_file(error_data, dxf_path.parent, error_filename)
|
|
print(f"Fehler/Warnungen gespeichert: {dxf_path.parent / error_filename}")
|
|
else:
|
|
print("\nKeine Fehler oder Warnungen - keine Fehlerdatei wird geschrieben")
|
|
|
|
# Exit-Code basierend auf Fehlern
|
|
if error_collector.has_errors():
|
|
print("\n!!! Es sind Fehler aufgetreten !!!")
|
|
exit(1)
|
|
elif error_collector.has_errors_or_warnings():
|
|
print("\n(Warnungen vorhanden, aber keine kritischen Fehler)")
|
|
exit(0)
|
|
else:
|
|
exit(0)
|