point_near_polyline impl. um Symbole entlang einer Polylinie zu nummerieren

This commit is contained in:
2026-02-05 17:50:32 +01:00
parent e600dfbae8
commit b7d98cbf14
2 changed files with 232 additions and 7 deletions
+5
View File
@@ -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
+227 -7
View File
@@ -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)