initialer commit
This commit is contained in:
@@ -0,0 +1,406 @@
|
||||
"""
|
||||
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
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user