diff --git a/cfg/enumerate.cfg b/cfg/enumerate.cfg index ecc7884..c55029c 100644 --- a/cfg/enumerate.cfg +++ b/cfg/enumerate.cfg @@ -1,3 +1,8 @@ [Layers] ILS_RENAMER Omniflo_RENAMER + +[Parameters] +# Maximale Distanz (in DXF-Einheiten) eines Symbols zur Polylinie bei POLYLINE_PATH +# Symbole die weiter entfernt sind, werden nicht zur Nummerierung berücksichtigt +polyline_max_distance = 500.0 diff --git a/lib/create_numbers.py b/lib/create_numbers.py index 7a01915..f835feb 100644 --- a/lib/create_numbers.py +++ b/lib/create_numbers.py @@ -83,6 +83,50 @@ def read_config_layers(config_path: Path) -> list: return layers +def read_config_parameters(config_path: Path) -> dict: + """ + Liest die enumerate.cfg und gibt die Parameter zurück. + Die Konfiguration hat Key-Value-Paare unter [Parameters]: + [Parameters] + polyline_max_distance = 500.0 + + Returns: + Dictionary mit Parametern und ihren Werten + """ + parameters = { + 'polyline_max_distance': 500.0 # Default-Wert + } + + with open(config_path, 'r', encoding='utf-8') as f: + in_parameters_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 [Parameters] Sektion sind + if line.startswith('['): + in_parameters_section = line.lower() == '[parameters]' + continue + + # Wenn in [Parameters], parse Key-Value-Paare + if in_parameters_section and '=' in line: + key, value = line.split('=', 1) + key = key.strip() + value = value.strip() + + # Versuche den Wert als Float zu parsen + try: + parameters[key] = float(value) + except ValueError: + # Wenn nicht als Float parsbar, speichere als String + parameters[key] = value + + return parameters + + def is_rectangle_polyline(points): """ Prüft, ob eine Liste von Punkten ein Rechteck darstellt. @@ -194,9 +238,63 @@ def point_in_polygon(point, polygon): return inside -def find_symbols_in_boundary(doc, msp, boundary, target_layers, attributes, error_collector=None): +def point_near_polyline(point, polyline_points, max_distance=500.0): + """ + Prüft ob ein Punkt in der Nähe einer Polylinie liegt. + + Args: + point: (x, y) Punkt + polyline_points: Liste von (x, y) Punkten der Polylinie + max_distance: Maximale Distanz zum nächsten Punkt auf der Polylinie + + Returns: + True wenn Punkt innerhalb von max_distance zur Polylinie liegt + """ + if not polyline_points or len(polyline_points) < 2: + return False + + min_dist_sq = float('inf') + + # Prüfe Distanz zu jedem Segment der Polylinie + for i in range(len(polyline_points) - 1): + p1 = polyline_points[i] + p2 = polyline_points[i + 1] + + # Berechne die Projektion des Punktes auf das Segment + dx = p2[0] - p1[0] + dy = p2[1] - p1[1] + segment_length_sq = dx * dx + dy * dy + + if segment_length_sq == 0: + # Segment ist ein Punkt + dist_sq = (point[0] - p1[0])**2 + (point[1] - p1[1])**2 + min_dist_sq = min(min_dist_sq, dist_sq) + continue + + # Parameter t für die Projektion (0 = p1, 1 = p2) + t = ((point[0] - p1[0]) * dx + (point[1] - p1[1]) * dy) / segment_length_sq + t = max(0.0, min(1.0, t)) # Clampe auf [0, 1] + + # Nächster Punkt auf dem Segment + proj_x = p1[0] + t * dx + proj_y = p1[1] + t * dy + + # Distanz vom Punkt zur Projektion + dist_sq = (point[0] - proj_x)**2 + (point[1] - proj_y)**2 + min_dist_sq = min(min_dist_sq, dist_sq) + + 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): """ Findet alle Symbole (INSERT-Blöcke) innerhalb des angegebenen Bereichs auf den Ziel-Layern. + + Für POLYLINE_PATH: Symbole in der Nähe der Polylinie + Für Rechtecke: Symbole innerhalb des Polygons + + Args: + polyline_max_distance: Maximale Distanz zur Polylinie (aus Config) """ symbols = [] @@ -211,6 +309,10 @@ def find_symbols_in_boundary(doc, msp, boundary, target_layers, attributes, erro if "LAYER_NAME" in attributes and attributes["LAYER_NAME"]: search_layers.append(attributes["LAYER_NAME"]) + # Prüfe ob es POLYLINE_PATH ist + direction = attributes.get("DIRECTION", "") + is_polyline_path = (direction == "POLYLINE_PATH") + # Durchsuche alle INSERT-Blöcke for entity in msp.query('INSERT'): if entity.dxf.layer not in search_layers: @@ -228,7 +330,15 @@ def find_symbols_in_boundary(doc, msp, boundary, target_layers, attributes, erro point = (pos[0], pos[1]) # Prüfe ob innerhalb des Bereichs - if point_in_polygon(point, boundary): + is_in_boundary = False + if is_polyline_path: + # Für POLYLINE_PATH: Prüfe Nähe zur Polylinie + is_in_boundary = point_near_polyline(point, boundary, max_distance=polyline_max_distance) + else: + # Für Rechtecke: Prüfe ob innerhalb des Polygons + is_in_boundary = point_in_polygon(point, boundary) + + if is_in_boundary: # Prüfe ob es ein Template ist (enthält @) has_template = False for value in symbol_attribs.values(): @@ -247,7 +357,77 @@ def find_symbols_in_boundary(doc, msp, boundary, target_layers, attributes, erro return symbols -def sort_symbols_by_direction(symbols, direction): +def calculate_position_along_polyline(symbol_pos, polyline_points): + """ + Berechnet die Position eines Symbols entlang einer Polylinie. + + Die Position wird als kumulative Distanz vom Start der Polylinie bis zum + nächstgelegenen Punkt auf der Polylinie zum Symbol berechnet. + + Args: + symbol_pos: (x, y) Position des Symbols + polyline_points: Liste von (x, y) Punkten der Polylinie + + Returns: + Float: Kumulative Distanz entlang der Polylinie + """ + if not polyline_points or len(polyline_points) < 2: + return 0.0 + + min_dist = float('inf') + best_segment_idx = 0 + best_proj_dist = 0.0 + + # Finde das nächste Segment und die Projektion auf diesem Segment + for i in range(len(polyline_points) - 1): + p1 = polyline_points[i] + p2 = polyline_points[i + 1] + + # Berechne die Projektion des Symbols auf das Segment + # Vektor von p1 zu p2 + dx = p2[0] - p1[0] + dy = p2[1] - p1[1] + segment_length_sq = dx * dx + dy * dy + + if segment_length_sq == 0: + # Segment ist ein Punkt + dist_sq = (symbol_pos[0] - p1[0])**2 + (symbol_pos[1] - p1[1])**2 + if dist_sq < min_dist: + min_dist = dist_sq + best_segment_idx = i + best_proj_dist = 0.0 + continue + + # Parameter t für die Projektion (0 = p1, 1 = p2) + t = ((symbol_pos[0] - p1[0]) * dx + (symbol_pos[1] - p1[1]) * dy) / segment_length_sq + t = max(0.0, min(1.0, t)) # Clampe auf [0, 1] + + # Nächster Punkt auf dem Segment + proj_x = p1[0] + t * dx + proj_y = p1[1] + t * dy + + # Distanz vom Symbol zur Projektion + dist_sq = (symbol_pos[0] - proj_x)**2 + (symbol_pos[1] - proj_y)**2 + + if dist_sq < min_dist: + min_dist = dist_sq + best_segment_idx = i + best_proj_dist = t * (segment_length_sq ** 0.5) + + # Berechne kumulative Distanz bis zum besten Segment + Projektion auf dem Segment + cumulative_dist = 0.0 + for i in range(best_segment_idx): + p1 = polyline_points[i] + p2 = polyline_points[i + 1] + segment_length = ((p2[0] - p1[0])**2 + (p2[1] - p1[1])**2) ** 0.5 + cumulative_dist += segment_length + + cumulative_dist += best_proj_dist + + return cumulative_dist + + +def sort_symbols_by_direction(symbols, direction, polyline_points=None): """ Sortiert die Symbole nach der angegebenen Richtung. @@ -268,13 +448,33 @@ def sort_symbols_by_direction(symbols, direction): LEFT_RIGHT: Spaltenweise von links nach rechts (sortiert nach X, dann Y) RIGHT_LEFT: Spaltenweise von rechts nach links (sortiert nach -X, dann Y) + Spezielle Richtungen: + POLYLINE_PATH: Nummerierung entlang einer Polylinie in Reihenfolge der Polylinien-Punkte + 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") + direction: DIRECTION-Attribut (z.B. "LEFT_RIGHT", "TOP_BOTTOM/LEFT_RIGHT", "POLYLINE_PATH") + polyline_points: Optional - Liste von (x, y) Punkten für POLYLINE_PATH Richtung Returns: Sortierte Liste von Symbolen """ + # POLYLINE_PATH: Sortiere entlang der Polylinie + if direction == "POLYLINE_PATH": + if not polyline_points or len(polyline_points) < 2: + print(" WARNUNG: POLYLINE_PATH ohne gültige Polylinie - verwende Fallback-Sortierung") + return sorted(symbols, key=lambda s: (-s['position'][1], s['position'][0])) + + # Berechne Position entlang der Polylinie für jedes Symbol + symbols_with_dist = [] + for symbol in symbols: + dist = calculate_position_along_polyline(symbol['position'], polyline_points) + symbols_with_dist.append((dist, symbol)) + + # Sortiere nach Distanz entlang der Polylinie + symbols_with_dist.sort(key=lambda x: x[0]) + return [symbol for dist, symbol in symbols_with_dist] + # 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) @@ -711,8 +911,19 @@ def process_renamer_groups(doc, msp, renamer_layers, renamer_groups, error_colle error_collector.add_errors({"no_symbols_found": error_msg}) continue + # Für POLYLINE_PATH: Hole die Polylinie vom ersten Block + polyline_points = None + if direction == "POLYLINE_PATH": + if group_blocks: + insert, boundary, attributes, is_rectangle = group_blocks[0] + if boundary and not is_rectangle: + polyline_points = boundary + print(f" POLYLINE_PATH: Verwende Polylinie mit {len(polyline_points)} Punkten") + else: + print(f" WARNUNG: POLYLINE_PATH angegeben, aber keine Polylinie gefunden (is_rectangle={is_rectangle})") + # Sortiere alle Symbole zusammen nach Richtung - sorted_symbols = sort_symbols_by_direction(all_symbols, direction) + sorted_symbols = sort_symbols_by_direction(all_symbols, direction, polyline_points) # Nummeriere alle Symbole als eine Sequenz renamed = enumerate_symbols(sorted_symbols, reference_attributes) @@ -772,8 +983,17 @@ def check_symbol_in_boundary(symbol, boundary, attributes, renamer_pos): - 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 + direction = attributes.get("DIRECTION", "") + is_polyline_path = (direction == "POLYLINE_PATH") + + if is_polyline_path: + # Für POLYLINE_PATH: Prüfe Nähe zur Polylinie + if not point_near_polyline(symbol['position'], boundary, max_distance=500.0): + return False, None + else: + # Für Rechtecke: Prüfe ob innerhalb des Polygons + 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)