""" DXF Schnittpunkt-Analysator Analysiert Linienschnittpunkte in DXF-Dateien und klassifiziert diese nach Typ """ import ezdxf from ezdxf.math import Vec3 from collections import defaultdict import sys from pathlib import Path from math import sin, cos, pi from datetime import datetime class IntersectionAnalyzer: def __init__(self, dxf_path, layer_names, tolerance=0.1, mark_radius=50): """ Initialisiert den Analysator Args: dxf_path: Pfad zur DXF-Datei layer_names: Liste der zu analysierenden Layer tolerance: Toleranz für Punktvergleiche in mm (Standard: 0.01) """ self.dxf_path = dxf_path self.layer_names = layer_names self.tolerance = tolerance self.mark_radius = mark_radius self.doc = None self.lines = [] self.intersections = { 'Kreuzung': [], 'T-Verbindung': [], 'Eckverbindung': [] } self.debug_info = [] def load_dxf(self): """Lädt die DXF-Datei""" try: self.doc = ezdxf.readfile(self.dxf_path) print(f"DXF-Datei erfolgreich geladen: {self.dxf_path}") return True except Exception as e: print(f"Fehler beim Laden der DXF-Datei: {e}") return False def extract_lines(self): """Extrahiert alle Linien aus den angegebenen Layern""" msp = self.doc.modelspace() for entity in msp: if entity.dxftype() == 'LINE' and entity.dxf.layer in self.layer_names: start = Vec3(entity.dxf.start.x, entity.dxf.start.y, 0) # Z ignorieren end = Vec3(entity.dxf.end.x, entity.dxf.end.y, 0) # Debug: Überprüfe auf extrem kurze Linien (Punkte) length = start.distance(end) if length < 1e-10: print(f"WARNUNG: Punkt statt Linie gefunden bei ({start.x}, {start.y})") continue self.lines.append((start, end, entity)) print(f"Anzahl gefundener Linien: {len(self.lines)}") print(f"Verwendete Toleranz: {self.tolerance} mm") # Debug: Zeige alle Linien mit ihren Koordinaten #print("\n=== LINIEN-DETAILS ===") #for i, (start, end, entity) in enumerate(self.lines): #print(f"Linie {i}: Start=({start.x:.3f}, {start.y:.3f}) End=({end.x:.3f}, {end.y:.3f}) Länge={start.distance(end):.3f}") def calculate_intersection(self, line1, line2): p1, p2 = line1[0], line1[1] p3, p4 = line2[0], line2[1] # Richtungsvektoren d1 = p2 - p1 d2 = p4 - p3 # Kreuzprodukt zur Prüfung auf Parallelität/Kollinearität cross = d1.x * d2.y - d1.y * d2.x # Kollinear oder parallel if abs(cross) < 1e-12: for pt in (p1, p2): if self.point_on_segment(pt, (p3, p4)): return pt for pt in (p3, p4): if self.point_on_segment(pt, (p1, p2)): return pt return None # Parametrisierte Schnittpunktberechnung denom = (p1.x - p2.x)*(p3.y - p4.y) - (p1.y - p2.y)*(p3.x - p4.x) t = ((p1.x - p3.x)*(p3.y - p4.y) - (p1.y - p3.y)*(p3.x - p4.x)) / denom u = -((p1.x - p2.x)*(p1.y - p3.y) - (p1.y - p2.y)*(p1.x - p3.x)) / denom # Schnittpunkt auf den Segmenten ± Toleranz if -self.tolerance <= t <= 1 + self.tolerance and -self.tolerance <= u <= 1 + self.tolerance: x = p1.x + t * d1.x y = p1.y + t * d1.y intersection = Vec3(x, y, 0) dist1 = self.distance_point_to_segment(intersection, (p1, p2)) dist2 = self.distance_point_to_segment(intersection, (p3, p4)) if dist1 <= self.tolerance and dist2 <= self.tolerance: return intersection # Prüfen auf T-Verbindung: Endpunkt von line1 nahe line2 for pt in (p1, p2): dist = self.distance_point_to_segment(pt, (p3, p4)) if 0 < dist <= self.tolerance: return pt # Prüfen auf T-Verbindung: Endpunkt von line2 nahe line1 for pt in (p3, p4): dist = self.distance_point_to_segment(pt, (p1, p2)) if 0 < dist <= self.tolerance: return pt # Keine Kreuzung oder T-Verbindung return None def distance_point_to_segment(self, pt, segment): """Abstand eines Punktes zu einem Liniensegment""" start, end = segment seg_vec = end - start seg_len_sq = seg_vec.x**2 + seg_vec.y**2 if seg_len_sq < 1e-12: return pt.distance(start) t = max(0, min(1, ((pt - start).dot(seg_vec)) / seg_len_sq)) proj = start + seg_vec * t return pt.distance(proj) def point_on_segment(self, pt, segment): ax, ay = segment[0].x, segment[0].y bx, by = segment[1].x, segment[1].y px, py = pt.x, pt.y # Richtungsvektoren vx, vy = bx - ax, by - ay wx, wy = px - ax, py - ay # Kreuzprodukt zur Kollinearitätsprüfung cross = vx * wy - vy * wx if abs(cross) > self.tolerance: return False # Skalarprodukt für Projektion dot = wx * vx + wy * vy if dot < -self.tolerance: return False # Länge des Segments im Quadrat seg_len_sq = vx * vx + vy * vy if dot > seg_len_sq + self.tolerance: return False return True def is_endpoint(self, point, line): start, end = line[0], line[1] return (start.distance(point) <= self.tolerance or end.distance(point) <= self.tolerance) def classify_intersection(self, point, line1, line2): """ Klassifiziert den Schnittpunkt Returns: 'Kreuzung', 'T-Verbindung' oder 'Eckverbindung' """ line1_ends = self.is_endpoint(point, line1) line2_ends = self.is_endpoint(point, line2) if line1_ends and line2_ends: return 'Eckverbindung' elif line1_ends or line2_ends: return 'T-Verbindung' else: return 'Kreuzung' def find_intersections(self): """Findet und klassifiziert alle Schnittpunkte""" print("\n=== ANALYSIERE SCHNITTPUNKTE ===\n") processed_pairs = set() for i, line1 in enumerate(self.lines): for j, line2 in enumerate(self.lines): if i >= j: continue pair = (i, j) if pair in processed_pairs: continue # Prüfe ob Endpunkte übereinstimmen (mit Toleranz) l1_start, l1_end = line1[0], line1[1] l2_start, l2_end = line2[0], line2[1] # Finde übereinstimmende Endpunkte matches = [] dist_ss = l1_start.distance(l2_start) dist_se = l1_start.distance(l2_end) dist_es = l1_end.distance(l2_start) dist_ee = l1_end.distance(l2_end) if dist_ss < self.tolerance: matches.append(('start', 'start', l1_start)) if dist_se < self.tolerance: matches.append(('start', 'end', l1_start)) if dist_es < self.tolerance: matches.append(('end', 'start', l1_end)) if dist_ee < self.tolerance: matches.append(('end', 'end', l1_end)) # Debug: Zeige nahe Punkte, die knapp außerhalb der Toleranz liegen min_dist = min(dist_ss, dist_se, dist_es, dist_ee) #if self.tolerance <= min_dist < self.tolerance * 10: #print(f"FAST-MATCH Linie {i}-{j}: min_dist={min_dist:.6f} (Toleranz={self.tolerance})") # Wenn Endpunkte übereinstimmen, ist es eine Eckverbindung if matches: for match_type, _, point in matches: self.intersections['Eckverbindung'].append(point) processed_pairs.add(pair) #print(f"DEBUG: Eckverbindung Linie {i}-{j} bei ({point.x:.3f}, {point.y:.3f})") continue # Berechne geometrischen Schnittpunkt point = self.calculate_intersection(line1, line2) if point is not None: intersection_type = self.classify_intersection(point, line1, line2) self.intersections[intersection_type].append(point) processed_pairs.add(pair) # Erweiterte Debug-Info l1_ends = self.is_endpoint(point, line1) l2_ends = self.is_endpoint(point, line2) #print(f"DEBUG: {intersection_type} Linie {i}-{j} bei ({point.x:.3f}, {point.y:.3f}) [L1_end={l1_ends}, L2_end={l2_ends}]") total = sum(len(v) for v in self.intersections.values()) print(f"\n=== ZUSAMMENFASSUNG ===") print(f"Gefundene Schnittpunkte: {total}") for itype, points in self.intersections.items(): print(f" {itype}: {len(points)}") def create_marking_layers(self): """Erstellt Layer für die Markierungen""" layer_names = { 'Kreuzung': 'D-KREUZUNG', 'T-Verbindung': 'D-T_VERBINDUNG', 'Eckverbindung': 'D-ECKVERBINDUNG' } colors = { 'Kreuzung': 1, # Rot 'T-Verbindung': 3, # Grün 'Eckverbindung': 5 # Blau } for itype, layer_name in layer_names.items(): if layer_name not in self.doc.layers: self.doc.layers.add( layer_name, color=colors[itype] ) return layer_names def add_marking_circles(self, layer_names): """Fügt Markierungskreise an allen Schnittpunkten hinzu""" msp = self.doc.modelspace() radius = self.mark_radius for itype, points in self.intersections.items(): layer_name = layer_names[itype] for point in points: msp.add_circle( center=(point.x, point.y), radius=radius, dxfattribs={'layer': layer_name} ) print("\nMarkierungskreise hinzugefügt") def save_results(self, output_dxf, output_txt): """Speichert die Ergebnisse mit Fehlerhandling, falls die Dateien blockiert sind.""" from pathlib import Path print(f"\nSpeichern in {output_dxf}...") # DXF speichern try: self.doc.saveas(output_dxf) print(f"\nMarkierte DXF-Datei gespeichert: {output_dxf}") except PermissionError: print("\nFEHLER: Die DXF-Datei konnte nicht gespeichert werden.") print(f"Grund: '{Path(output_dxf).name}' ist vermutlich noch geöffnet.") print("Bitte schließen und speichern erneut ausführen.\n") return False except Exception as e: print("\nUnerwarteter Fehler beim Speichern der DXF:") print(e) return False # TXT speichern try: dt = datetime.now().strftime("%d.%m.%Y %H:%M:%S") with open(output_txt, 'w', encoding='utf-8') as f: f.write("=" * 60 + "\n") f.write(f"DXF SCHNITTPUNKT-ANALYSE ERGEBNIS - {dt} \n") f.write("=" * 60 + "\n\n") f.write(f"Eingabedatei: {self.dxf_path}\n") f.write(f"Analysierte Layer: {', '.join(self.layer_names)}\n") f.write(f"Anzahl analysierter Linien: {len(self.lines)}\n\n") f.write("-" * 60 + "\n") f.write("SCHNITTPUNKT-STATISTIK\n") f.write("-" * 60 + "\n\n") total = sum(len(v) for v in self.intersections.values()) for itype in ['Kreuzung', 'T-Verbindung', 'Eckverbindung']: count = len(self.intersections[itype]) percentage = (count / total * 100) if total > 0 else 0 f.write(f"{itype:20s}: {count:5d} ({percentage:5.1f}%)\n") f.write(f"\n{'GESAMT':20s}: {total:5d}\n\n") f.write("-" * 60 + "\n") f.write("DETAILS DER SCHNITTPUNKTE\n") f.write("-" * 60 + "\n\n") for itype in ['Kreuzung', 'T-Verbindung', 'Eckverbindung']: points = self.intersections[itype] f.write(f"\n{itype} ({len(points)} Stück):\n") f.write("-" * 40 + "\n") for idx, point in enumerate(points, 1): f.write(f" {idx:3d}. X={point.x:10.3f}, Y={point.y:10.3f}\n") print(f"Ergebnisdatei gespeichert: {output_txt}") except PermissionError: print("\nFEHLER: Die Analyse-Textdatei konnte nicht gespeichert werden.") print(f"Grund: '{Path(output_txt).name}' ist vermutlich noch geöffnet.") print("Bitte schließen und speichern erneut ausführen.\n") return False except Exception as e: print("\nUnerwarteter Fehler beim Speichern der Textdatei:") print(e) return False return True def analyze(self, output_dxf=None, output_txt=None): """Führt die komplette Analyse durch""" print("\n" + "=" * 60) print("DXF SCHNITTPUNKT-ANALYSE") print("=" * 60 + "\n") if not self.load_dxf(): return False self.extract_lines() if len(self.lines) == 0: print("Keine Linien auf den angegebenen Layern gefunden!") return False self.find_intersections() layer_names = self.create_marking_layers() self.add_marking_circles(layer_names) # Standardausgabedateinamen generieren if output_dxf is None: base = Path(self.dxf_path).stem output_dxf = f"{base}_markiert.dxf" if output_txt is None: base = Path(self.dxf_path).stem output_txt = f"{base}_analyse.txt" # Speichern und Fehler überprüfen ok = self.save_results(output_dxf, output_txt) if not ok: print("\nAnalyse abgebrochen wegen Speicherfehler.\n") return False print("\n" + "=" * 60) print("ANALYSE ERFOLGREICH ABGESCHLOSSEN") print("=" * 60 + "\n") return True