Integration der Funktionalität des Python Skripts in ein Lisp Skript, um den Umweg über DXF zu vermeiden und direkt in der Zeichnung arbeiten zu können

This commit is contained in:
2026-05-07 12:44:12 +02:00
parent e45d1bca9d
commit 690938157f
14 changed files with 1005533 additions and 136 deletions
+145
View File
@@ -0,0 +1,145 @@
# DRT-Verbindungszähler - DXF-Analyse-Tool
Ein Python-basiertes Werkzeug zur automatischen Erkennung und Markierung von Schnittpunkten zwischen Linien in DXF-Dateien.
## Überblick
Dieses Projekt analysiert DXF-Dateien und identifiziert Verbindungs- bzw. Schnittpunkte zwischen Linien auf verschiedenen Layern. Die gefundenen Schnittpunkte werden mit Kreisen markiert und in einer separaten DXF-Datei sowie einem Textbericht ausgegeben.
## Projektstruktur
```text
verbindungszaehler/
├── verbindungszaehler.bat # Hauptskript zum Starten der Analyse
├── config/
│ └── config.cfg # Standardkonfiguration
├── scripts/
│ └── setenv.bat # Umgebungsvariablen und Verzeichnisstruktur
├── src/
│ └── lib/
│ ├── analyze.py # Python-Analyseskript
│ └── dxf_analyzer.py # Kernlogik
├── input/ # Eingabeverzeichnis für DXF-Dateien
└── output/ # Ausgabeverzeichnis für Ergebnisse
```
## Voraussetzungen
- Windows-Betriebssystem
- Python 3.x installiert und im PATH verfügbar
- Python-Bibliothek für DXF-Verarbeitung `ezdxf`
- Modul `dxf_analyzer.py` mit der Klasse `IntersectionAnalyzer`
## Installation
1. Projekt in ein beliebiges Verzeichnis entpacken
2. Sicherstellen, dass Python installiert ist
3. Benötigte Python-Pakete installieren (z.B. `pip install ezdxf`)
4. DXF-Dateien im `input/`-Verzeichnis ablegen
## Verwendung
### Methode 1: Alle Dateien im Input-Verzeichnis analysieren
Doppelklick auf `verbindungszaehler.bat` oder per Kommandozeile:
```batch
verbindungszaehler.bat
```
Alle DXF-Dateien im `input/`-Verzeichnis werden automatisch mit den Einstellungen aus `config.cfg` analysiert.
### Methode 2: Einzelne Datei per Drag & Drop
Ziehen Sie eine DXF-Datei direkt auf die `verbindungszaehler.bat` und lassen Sie sie los. Die Datei wird sofort mit den Einstellungen aus `config/config.cfg` analysiert.
### Methode 3: Einzelne Datei per Kommandozeile
```batch
verbindungszaehler.bat "C:\Pfad\zur\datei.dxf"
```
## Konfiguration
### config.cfg
Die Datei `config.example.cfg` enthält alle Einstellungen für die Analyse. Für individuelle Parameter muss eine Kopie von `config.example.cfg` erstellt und in `config.cfg` umbenannt werden. Diese dient nun als Konfigurationsdatei des Projekts.
Passen Sie diese Werte in `config.cfg` nach Bedarf an:
```cfg
LAYERS=D-1,D-5
TOLERANCE=0.06
MARK_RADIUS=150
```
**Parameter-Erklärung:**
- **LAYERS**: Kommagetrennte Liste der zu analysierenden Layer (z.B. "D-1,D-5,D-3")
- **TOLERANCE**: Maximaler Abstand in DXF-Einheiten, bei dem zwei Punkte als identisch gelten (für ungenaue Zeichnungen)
- **MARK_RADIUS**: Radius der Kreise in DXF-Einheiten, die Schnittpunkte markieren
**Hinweis:** Nach Änderungen in `config.cfg` werden die neuen Werte bei der nächsten Ausführung automatisch verwendet.
## Ausgabe
Das Tool erzeugt für jede analysierte DXF-Datei zwei Dateien im `output/`-Verzeichnis:
1. **[Dateiname]_markiert_[Datum]_[Zeit].dxf**
DXF-Datei mit eingezeichneten Kreisen an allen gefundenen Schnittpunkten
2. **[Dateiname]_analyse_[Datum]_[Zeit].txt**
Textbericht mit Details zu den gefundenen Verbindungen (Koordinaten, Anzahl, etc.)
**Beispiel:**
```text
zeichnung_markiert_20241202_14-30-25.dxf
zeichnung_analyse_20241202_14-30-25.txt
```
## Funktionsweise
1. **setenv.bat** richtet die Verzeichnisstruktur ein und lädt die Konfigurationswerte aus `config.cfg` im `config/`-Ordner
2. **verbindungszaehler.bat** verarbeitet die angegebene Datei (oder alle Dateien im Input-Ordner) und ruft das Python-Skript auf
3. **analyze.py** lädt die DXF-Datei und übergibt sie zusammen mit den Konfigurationsparametern an den `IntersectionAnalyzer`
4. Der Analyzer findet Schnittpunkte zwischen Linien auf den angegebenen Layern innerhalb der definierten Toleranz
5. Gefundene Schnittpunkte werden mit Kreisen (Radius aus `MARK_RADIUS`) markiert
6. Ergebnisse werden als neue DXF-Datei und Textbericht gespeichert
## Workflow-Beispiel
1. DXF-Dateien in den `input/`-Ordner kopieren
2. `config.cfg` im `config/`-Ordner öffnen und gewünschte Layer-Namen, Toleranz und Markierungsradius eintragen
3. `verbindungszaehler.bat` per Doppelklick starten **oder** eine einzelne DXF-Datei auf die Batch-Datei ziehen
4. Ergebnisse im `output/`-Ordner überprüfen: **Bevor die Anzahl der Verbindungen weiter verwendet wird, sind die markierten Punkte im Ausgabelayout zwingend auf Fehler zu überprüfen!**
## Hinweise
- Stellen Sie sicher, dass die Layer-Namen in `config.cfg` exakt mit denen in der DXF-Datei übereinstimmen (Groß-/Kleinschreibung beachten)
- Bei sehr großen Dateien kann die Analyse einige Zeit in Anspruch nehmen
- Die Toleranz sollte an die Genauigkeit Ihrer Zeichnungen angepasst werden (typische Werte: 0.01 bis 0.1)
- Der Markierungsradius sollte groß genug sein, um in der Zeichnung sichtbar zu sein, aber klein genug, um Details nicht zu verdecken
- Ausgabedateien werden nicht überschrieben, da jede Datei einen eindeutigen Zeitstempel erhält
## Fehlerbehebung
##### "Datei nicht gefunden"
Überprüfen Sie den Pfad zur DXF-Datei oder legen Sie Dateien im `input/`-Verzeichnis ab.
##### "Keine Schnittpunkte gefunden"
- Prüfen Sie, ob die in `config.cfg` angegebenen Layer in der DXF-Datei existieren
- Erhöhen Sie den TOLERANCE-Wert in `config.cfg`, falls Linien nicht exakt verbunden sind
##### Python-Fehler
Stellen Sie sicher, dass alle erforderlichen Python-Bibliotheken installiert sind.
##### Drag & Drop funktioniert nicht
Stellen Sie sicher, dass Sie die Datei auf `verbindungszaehler.bat` (nicht auf `setenv.bat`) ziehen.
## Autor
Simon Hensch
+8
View File
@@ -0,0 +1,8 @@
# Alle Linien, die auf den angegebenen Layern liegen, werden in der Berechnung berücksichtigt
LAYERS=D-1,D-5
# Toleranz der Schnittpunkte insbesondere bei T-Verbindungen in Millimetern. Beim Überschreiten der Toleranz wird die Verbindung nicht gezählt.
TOLERANCE=15
# Alle erkannten Verbindungen werden mit einem Kreis des Radius in Millimetern markiert.
MARK_RADIUS=150
+8
View File
@@ -0,0 +1,8 @@
# Alle Linien, die auf den angegebenen Layern liegen, werden in der Berechnung berücksichtigt
LAYERS=D-1,D-5
# Toleranz der Schnittpunkte insbesondere bei T-Verbindungen in Millimetern. Beim Überschreiten der Toleranz wird die Verbindung nicht gezählt.
TOLERANCE=15
# Alle erkannten Verbindungen werden mit einem Kreis des Radius in Millimetern markiert.
MARK_RADIUS=150
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,241 @@
============================================================
DXF SCHNITTPUNKT-ANALYSE ERGEBNIS - 16.04.2026 09:25:49
============================================================
Eingabedatei: C:\Users\s.hensch\Git\drt-verbindungszaehler\input\500658_10_1_ohne_statLager_Verbindungselemente_20260416_SH.dxf
Analysierte Layer: D-1, D-5
Anzahl analysierter Linien: 164
------------------------------------------------------------
SCHNITTPUNKT-STATISTIK
------------------------------------------------------------
Kreuzung : 6 ( 2.9%)
T-Verbindung : 201 ( 95.7%)
Eckverbindung : 3 ( 1.4%)
GESAMT : 210
------------------------------------------------------------
DETAILS DER SCHNITTPUNKTE
------------------------------------------------------------
Kreuzung (6 Stück):
----------------------------------------
1. X= 79401.213, Y= 397.810
2. X= 83401.213, Y= 397.810
3. X= 87401.213, Y= 397.810
4. X= 91401.213, Y= 397.810
5. X= 73501.687, Y= 10597.810
6. X=134124.793, Y= 10391.879
T-Verbindung (201 Stück):
----------------------------------------
1. X=138308.726, Y= 63200.924
2. X=138308.726, Y= 52442.951
3. X=138308.726, Y= 54442.951
4. X=138308.726, Y= 56442.951
5. X=138308.726, Y= 58442.951
6. X=138308.726, Y= 60442.951
7. X=138308.726, Y= 62086.693
8. X=128510.648, Y= 63200.924
9. X=128510.648, Y= 52441.370
10. X=128510.648, Y= 54441.370
11. X=128510.648, Y= 56441.370
12. X=128510.648, Y= 58441.370
13. X=128510.648, Y= 60441.370
14. X=128510.648, Y= 61565.879
15. X=128510.648, Y= 61160.000
16. X=139558.726, Y= 63200.924
17. X=130960.648, Y= 63200.924
18. X=133410.648, Y= 63200.924
19. X=135860.648, Y= 63200.924
20. X=126279.842, Y= 52441.370
21. X=126279.842, Y= 54441.370
22. X=126279.842, Y= 56441.370
23. X=126279.842, Y= 58441.370
24. X=126279.842, Y= 60441.370
25. X=126279.842, Y= 61565.879
26. X=126279.842, Y= 51780.000
27. X=126279.842, Y= 53878.216
28. X=139558.726, Y= 52442.951
29. X=139558.726, Y= 54442.951
30. X=139558.726, Y= 56442.951
31. X=139558.726, Y= 58442.951
32. X=139558.726, Y= 60442.951
33. X=139558.726, Y= 62086.693
34. X= 77401.213, Y= 397.810
35. X= 81401.213, Y= 397.810
36. X= 85401.213, Y= 397.810
37. X= 89401.213, Y= 397.810
38. X= 93401.213, Y= 397.810
39. X= 95088.723, Y= 397.810
40. X= 77401.153, Y= 1547.810
41. X= 79401.153, Y= 1547.810
42. X= 81401.153, Y= 1547.810
43. X= 83401.153, Y= 1547.810
44. X= 85401.153, Y= 1547.810
45. X= 87401.153, Y= 1547.810
46. X= 89401.153, Y= 1547.810
47. X= 91401.153, Y= 1547.810
48. X= 93401.153, Y= 1547.810
49. X= 95088.663, Y= 1547.810
50. X= 95180.000, Y= 5747.810
51. X= 95977.004, Y= 5747.810
52. X= 97051.644, Y= 8147.810
53. X= 95180.000, Y= 8147.810
54. X= 95977.004, Y= 8147.810
55. X= 75394.609, Y= 10597.810
56. X= 77400.687, Y= 10597.810
57. X= 79400.687, Y= 10597.810
58. X= 81400.687, Y= 10597.810
59. X= 83400.687, Y= 10597.810
60. X= 85400.687, Y= 10597.810
61. X= 87400.687, Y= 10597.810
62. X= 89400.687, Y= 10597.810
63. X= 91400.687, Y= 10597.810
64. X= 93400.687, Y= 10597.810
65. X= 73501.687, Y= 11896.713
66. X= 75394.609, Y= 11896.810
67. X= 77400.620, Y= 11896.914
68. X= 79400.620, Y= 11897.017
69. X= 81400.620, Y= 11897.120
70. X= 83400.620, Y= 11897.223
71. X= 85400.620, Y= 11897.326
72. X= 87400.620, Y= 11897.429
73. X= 89400.620, Y= 11897.532
74. X= 91400.620, Y= 11897.635
75. X= 93400.620, Y= 11897.738
76. X=130051.644, Y= 8191.879
77. X=130051.644, Y= 6943.978
78. X=132251.644, Y= 8191.879
79. X=132251.644, Y= 6943.978
80. X=125651.644, Y= 7421.473
81. X=127851.644, Y= 7421.473
82. X=133383.730, Y= 8191.879
83. X=133383.730, Y= 6943.978
84. X=147324.793, Y= 11803.549
85. X=147324.793, Y= 10926.143
86. X=151424.793, Y= 11803.549
87. X=151424.793, Y= 10926.143
88. X=155924.793, Y= 11803.549
89. X=155924.793, Y= 10926.143
90. X=149424.793, Y= 11803.549
91. X=153724.793, Y= 11803.549
92. X=160424.793, Y= 11803.549
93. X=164924.793, Y= 11803.549
94. X=169424.793, Y= 11803.549
95. X=173924.793, Y= 11803.549
96. X=178424.793, Y= 11803.549
97. X=158124.793, Y= 11803.549
98. X=162624.793, Y= 11803.549
99. X=167124.793, Y= 11803.549
100. X=171624.793, Y= 11803.549
101. X=176124.793, Y= 11803.549
102. X=149424.793, Y= 10926.143
103. X=153724.793, Y= 10926.143
104. X=160424.793, Y= 10926.143
105. X=164924.793, Y= 10926.143
106. X=169424.793, Y= 10926.143
107. X=173924.793, Y= 10926.143
108. X=178424.793, Y= 10926.143
109. X=158124.793, Y= 10926.143
110. X=162624.793, Y= 10926.143
111. X=167124.793, Y= 10926.143
112. X=171624.793, Y= 10926.143
113. X=176124.793, Y= 10926.143
114. X=156485.708, Y= 13474.648
115. X=158485.708, Y= 13474.648
116. X=160485.708, Y= 13474.648
117. X=162485.708, Y= 13474.648
118. X=164485.708, Y= 13474.648
119. X=166485.708, Y= 13474.648
120. X=168485.708, Y= 13474.648
121. X=170485.708, Y= 13474.648
122. X=172485.708, Y= 13474.648
123. X=174485.708, Y= 13474.648
124. X=176485.708, Y= 13474.648
125. X=178005.866, Y= 13474.648
126. X=156485.708, Y= 12404.648
127. X=158485.708, Y= 12404.648
128. X=160485.708, Y= 12404.648
129. X=162485.708, Y= 12404.648
130. X=164485.708, Y= 12404.648
131. X=166485.708, Y= 12404.648
132. X=168485.708, Y= 12404.648
133. X=170485.708, Y= 12404.648
134. X=172485.708, Y= 12404.648
135. X=174485.708, Y= 12404.648
136. X=176485.708, Y= 12404.648
137. X=178005.866, Y= 12404.648
138. X=130960.648, Y= 61160.000
139. X=120387.520, Y= 51780.000
140. X=121006.726, Y= 51780.000
141. X=123465.272, Y= 51780.000
142. X=125006.726, Y= 51780.000
143. X=120387.520, Y= 53878.216
144. X=121006.726, Y= 53878.216
145. X=123465.272, Y= 53878.216
146. X=125006.726, Y= 53878.216
147. X=120387.520, Y= 52829.108
148. X=121006.726, Y= 52829.108
149. X=144732.392, Y= 53286.846
150. X=144732.392, Y= 55286.846
151. X=144732.392, Y= 57286.846
152. X=144732.392, Y= 59286.846
153. X=144732.392, Y= 60686.578
154. X=146632.860, Y= 53286.846
155. X=146632.860, Y= 55286.846
156. X=146632.860, Y= 57286.846
157. X=146632.860, Y= 59286.846
158. X=146632.860, Y= 60686.578
159. X=145682.626, Y= 53286.846
160. X=145682.626, Y= 52819.128
161. X= 76800.000, Y= 65085.000
162. X= 78800.000, Y= 65085.000
163. X= 80800.000, Y= 65085.000
164. X= 82800.000, Y= 65085.000
165. X= 84800.000, Y= 65085.000
166. X= 72300.000, Y= 65085.000
167. X= 74800.000, Y= 65085.000
168. X= 73361.274, Y= 65085.000
169. X= 76800.000, Y= 64015.000
170. X= 78800.000, Y= 64015.000
171. X= 80800.000, Y= 64015.000
172. X= 82800.000, Y= 64015.000
173. X= 84800.000, Y= 64015.000
174. X= 72300.000, Y= 64015.000
175. X= 74800.000, Y= 64015.000
176. X= 73361.274, Y= 64015.000
177. X= 85849.346, Y= 64015.000
178. X=125651.644, Y= 1588.300
179. X=125651.644, Y= 3588.300
180. X=125651.644, Y= 5588.300
181. X=125651.644, Y= 630.889
182. X=127851.644, Y= 1588.300
183. X=127851.644, Y= 3588.300
184. X=127851.644, Y= 5588.300
185. X=127851.644, Y= 630.889
186. X= 95180.000, Y= 7190.000
187. X= 95977.004, Y= 7190.000
188. X=126280.000, Y= 1588.300
189. X=126280.000, Y= 630.889
190. X=147880.000, Y= 64274.648
191. X=149880.000, Y= 64274.648
192. X=151880.000, Y= 64274.648
193. X=153880.000, Y= 64274.648
194. X=155880.000, Y= 64274.648
195. X=147202.099, Y= 64274.648
196. X=147880.000, Y= 63204.648
197. X=149880.000, Y= 63204.648
198. X=151880.000, Y= 63204.648
199. X=153880.000, Y= 63204.648
200. X=155880.000, Y= 63204.648
201. X=147202.099, Y= 63204.648
Eckverbindung (3 Stück):
----------------------------------------
1. X=144732.392, Y= 52819.128
2. X=146632.860, Y= 52819.128
3. X= 85849.346, Y= 65085.000
+26
View File
@@ -0,0 +1,26 @@
@echo off
REM Basisverzeichnis bestimmen
pushd %~dp0\..
set PROJECT=%cd%
set SCRIPT_DIR=%PROJECT%\src\lib
set CONFIG_DIR=%PROJECT%\config
set INPUT_DIR=%PROJECT%\input
set OUTPUT_DIR=%PROJECT%\output
if not exist "%SCRIPT_DIR%" mkdir "%SCRIPT_DIR%"
if not exist "%INPUT_DIR%" mkdir "%INPUT_DIR%"
if not exist "%OUTPUT_DIR%" mkdir "%OUTPUT_DIR%"
REM Defaults aus cfg-Datei laden
if exist "%CONFIG_DIR%\config.cfg" (
for /f "usebackq tokens=1,2 delims==" %%A in ("%CONFIG_DIR%\config.cfg") do (
set %%A=%%B
)
)
set PATH=%SCRIPT_DIR%;%PATH%
popd
goto :eof
+52
View File
@@ -0,0 +1,52 @@
import sys
from pathlib import Path
from dxf_analyzer import IntersectionAnalyzer
from datetime import datetime
def main():
if len(sys.argv) < 2:
print("Bitte eine DXF-Datei angeben.")
sys.exit(1)
dxf_file = Path(sys.argv[1])
if not dxf_file.exists():
print(f"Datei nicht gefunden: {dxf_file}")
sys.exit(1)
# Kommandozeilenparameter auslesen oder Standardwerte verwenden
layers = sys.argv[2] if len(sys.argv) > 2 else "D-1,D-5"
tolerance = float(sys.argv[3]) if len(sys.argv) > 3 else 0.04
mark_radius = float(sys.argv[4]) if len(sys.argv) > 4 else 100
layer_list = [layer.strip() for layer in layers.split(",")]
base_name = dxf_file.stem
script_dir = Path(__file__).resolve().parent
output_dir = (script_dir / ".." / ".." / "output").resolve()
output_dir.mkdir(parents=True, exist_ok=True)
datum = datetime.now().strftime("%Y%m%d_%H-%M-%S")
output_dxf = output_dir / f"{base_name}_markiert_{datum}.dxf"
output_txt = output_dir / f"{base_name}_analyse_{datum}.txt"
analyzer = IntersectionAnalyzer(
dxf_path=str(dxf_file),
layer_names=layer_list,
tolerance=tolerance,
mark_radius=mark_radius
)
success = analyzer.analyze(
output_dxf=str(output_dxf),
output_txt=str(output_txt)
)
if success:
print(f"Ergebnis-DXF: {output_dxf}")
print(f"Analyse-Textdatei: {output_txt}")
else:
print("Analyse konnte nicht erfolgreich abgeschlossen werden.")
if __name__ == "__main__":
main()
+406
View File
@@ -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
+39
View File
@@ -0,0 +1,39 @@
@echo off
setlocal enabledelayedexpansion
REM Umgebungsvariablen laden immer relativ zum Batch-Datei-Verzeichnis
call "%~dp0\scripts\setenv.bat"
REM Kommandozeilenparameter prüfen und Defaults verwenden, falls Parameter fehlen
if not "%~2"=="" (
set LAYERS=%~2
) else if not defined LAYERS (
set LAYERS=D-1,D-5
)
if not "%~3"=="" (
set TOLERANCE=%~3
) else if not defined TOLERANCE (
set TOLERANCE=0.05
)
if not "%~4"=="" (
set MARK_RADIUS=%~4
) else if not defined MARK_RADIUS (
set MARK_RADIUS=200
)
REM Analyse starten
if "%~1"=="" (
echo Keine Datei angegeben. Suche in input\ ...
for %%f in ("%INPUT_DIR%\*.dxf") do (
echo Starte Analyse fuer %%f mit Layer=!LAYERS!, Tolerance=!TOLERANCE!, Mark Radius=!MARK_RADIUS!
python "%SCRIPT_DIR%\analyze.py" "%%f" "!LAYERS!" "!TOLERANCE!" "!MARK_RADIUS!"
)
) else (
echo Starte Analyse fuer %~1 mit Layer=%LAYERS%, Tolerance=%TOLERANCE%, Mark Radius=%MARK_RADIUS%
python "%SCRIPT_DIR%\analyze.py" "%~1" "%LAYERS%" "%TOLERANCE%" "%MARK_RADIUS%"
)
echo Fertig.
pause