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:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
+241
@@ -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
|
||||
+479776
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
Binary file not shown.
@@ -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()
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user