Compare commits

...

21 Commits

Author SHA1 Message Date
m.stangl 2325316dd3 Claude init ausgeführt 2026-07-09 11:04:38 +02:00
m.stangl 5bfda0abf6 Aufruf der Unittests aus dem Root des PROJECT_DIR. Dann funktionieren die Includes wieder 2026-01-28 18:42:20 +01:00
m.stangl 200145eea7 errorlevel wurde nicht korrekt zurückgemeldet 2026-01-28 18:36:14 +01:00
m.stangl c29f13a4ae Unittests zu allen Elementen erstellt 2026-01-28 18:28:54 +01:00
m.stangl e8c6b17ea9 trenne das Laden der .env pip libraries von dem Setzen der Umgebung. setenv.bat enthält jetzt bei Bedarf den python Interpreter auf dem Share 2026-01-28 15:41:27 +01:00
m.stangl 85132193dd Unittest Aufruf über unittest discover Aufruf 2026-01-28 11:09:49 +01:00
m.stangl 4aae9e153d lib und Elemente Ordner mit __init__ versehen, so dass man beim import die Verzeichnisse durch Punktpfade angeben kann 2026-01-27 19:00:27 +01:00
m.stangl 855a874e4a Unittest Aufruf dazu 2026-01-27 17:03:36 +01:00
m.stangl 829fe76d0f Varioförderer refactored mit Claude. Unittests dazu gebaut. 2026-01-27 16:43:24 +01:00
m.stangl ab415071c2 Rechtsschreibkorrektur 2026-01-27 16:21:27 +01:00
m.stangl c8760fb024 encoding Standard in die erste Zeile als Kommentar eingefügt 2026-01-27 16:16:30 +01:00
m.stangl 5f59433ac8 Methode erzeugt um auf einen Rutsch alle Textdxf Dateien zu erzeugen 2026-01-27 16:11:53 +01:00
m.stangl 79ff6794db settings und formatierungseinstellungen für das Projekt dazu 2026-01-27 16:10:57 +01:00
m.stangl 19282888ee alles neu mit black formatiert. handler_context.py impl. um weniger Übergabeparameter zu haben 2026-01-27 16:09:52 +01:00
m.stangl 4d09aee02b black zur Formatierung und ruff für den import Sortierung zu den Packeten hinzu 2026-01-27 15:58:42 +01:00
m.stangl b58e80b79f überflüssigen Aufruf entfernt 2026-01-27 15:00:41 +01:00
P.wolok e124272c13 Aktualiesung der readme fehlender Elemente 2026-01-27 14:07:10 +01:00
P.wolok 586102cf00 Weichekoerper aktualisiert, weil dort ein Fehler drin war 2026-01-27 14:03:43 +01:00
P.wolok 7f81240559 Aktualisierung Omniflo Blöcke wegen einlesesung stopper und dummy scanner und textdatei mit fehlenden tests 2026-01-27 10:35:21 +01:00
s.hensch d9d398b6fd Merge branch 'main' of http://gitea.schoenenberger.de/mistangl/plant2dxf 2026-01-26 16:14:11 +01:00
s.hensch 9a8f0ff374 Kurve_links_90°_R500_Gefälle um -90° gedreht, um zu testen, ob die Drehung ohne weiteres geändert werden kann 2026-01-26 16:13:51 +01:00
47 changed files with 98915 additions and 55662 deletions
+78
View File
@@ -0,0 +1,78 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## What this project does
`plant2dxf` reads a RuleDesigner CSV export describing all components of a plant/conveyor system (Kreisel, Gefällestrecke, VarioFörderer, Omniflo, etc.) and generates a DXF drawing of the plant by placing and assembling DXF blocks from library files. Positions come from the CSV's `Planquadrat` column (`X:<val> Y:<val>`, in mm); component-specific properties come from the `Merkmale` JSON column; connections between components come from `NachbarIds`.
Full functional documentation (CSV format, config format, per-element-type handler behavior, ezdxf usage patterns) lives in [doc/plant2dxf_dokumentation.md](doc/plant2dxf_dokumentation.md) — read it before making non-trivial changes to handler logic, it is much more detailed than this file.
## Commands
All commands are run via the `.bat` scripts in `bin/` (Windows only; this project has no `.sh` counterparts). Scripts call `setenv.bat` first, which sets `PROJECT`, `PROJECT_BIN`, `PROJECT_CFG`, `PROJECT_DOC`, `PROJECT_LIB`, `PROJECT_DATA`, `PROJECT_WORK`, `PROJECT_TEST`, `PROJECT_LOG` and creates missing `data/`, `work/`, `log/`, `testdata/` folders.
```bat
bin\install_py.bat REM create .venv and pip install lib/requirements.txt (no-op if .venv exists)
bin\manage_interpreter.bat activate REM activate .venv (used internally by other scripts)
bin\run_unittests.bat REM run all *_tests.py under lib\Elemente via unittest discover
bin\plant2dxf.bat -f <csv-datei> [-c shapes.cfg] [-l blocks.dxf] [-o output.dxf] [-v]
```
`run_unittests.bat` runs `python -m unittest discover -t "%PROJECT%" -s lib\Elemente -p "*_tests.py"` from the project root — tests must be discovered from repo root or the `lib.*` imports break. To run a single test file/case directly (with venv already active):
```bat
python -m unittest lib.Elemente.Kreisel_tests -v
python -m unittest lib.Elemente.Kreisel_tests.TestKreiselModell.test_from_merkmale -v
```
Dependencies (`lib/requirements.txt`): `ezdxf==1.4.1`, `svg.path==7.0`, `pydantic>=2.0.0`, plus `ruff` and `black` (line-length 88, target py312 — see `pyproject.toml`).
## Architecture
### Processing pipeline (`lib/plant2dxf.py`)
1. Load `cfg/shapes.cfg` and `cfg/allgemein.cfg`.
2. Create a new empty DXF document (R2018, millimeters).
3. `arbeiten_mit_csv.get_nachbar_information(csv_path)` does a full pre-pass over the CSV to build a neighbor map (`strecken_nachbarn`): for every Gefällestrecke/VarioFörderer it resolves which Kreisel/Eckrad/angetriebene Kurve/other VarioFörderer are attached via `NachbarIds`, and captures their rotation, height, position and drehrichtung. This neighbor map drives almost all the geometric decisions downstream.
4. Row-by-row over the CSV: parse `TeileArt`/`TeileId`/`Planquadrat`/`Merkmale`, resolve the block library file for that `TeileArt` from `allgemein.cfg` (cached per path so each library is read once), dispatch to a handler function named `handle_<teileart normalized>` (spaces/dots → underscores, lowercased — see `arbeiten_mit_csv.normalize_func_name`), and call it with a `HandlerContext` (`lib/handler_context.py`) bundling `msp`, `teileid`, `merkmale`, `x`, `y`, `doc`, `lib_doc`, `symbols`, `strecken_nachbarn`, `config`, `config_allgemein`.
5. Save the DXF to the output path.
Handler dispatch has special-cased overrides: any `TeileArt` starting with "Omniflo" or "TEF" is routed to `handle_omniflo` regardless of exact name, and "ILS 2.0 Kreisel*" variants all route to `handle_ils_2_0_kreisel`.
### Handler functions (`lib/plant2dxf.py`)
One `handle_*` function per `TeileArt`, e.g. `handle_ils_2_0_kreisel`, `handle_ils_2_0_gefaellestrecke`, `handle_ils_2_0_variofoerderer`, `handle_ils_2_0_eckrad`, `handle_ils_2_0_kurve_angetrieben`, `handle_bt___beladung`/`handle_bt___entladung`, `handle_omniflo`. Each handler:
- builds a Pydantic element object via `<ElementClass>.from_merkmale(...)` (in `lib/Elemente/`),
- inspects `ctx.strecken_nachbarn` to determine how many/which neighbors it's connected to (0, 1, or 2 Kreisel/Eckrad, direct-to-Kreisel vs. via-Stahlband, connected to an angetriebene Kurve needing a Motor-/Umlenkstation),
- builds a deterministic, parameter-encoded block name (e.g. `Ils_2.0_Gefaellestrecke_{laenge}_{hoehe}_{hat_umlenk}_{hat_motor}_{tefkurve}_...`) and only constructs the block geometry via `doc.blocks.new(...)` if that exact name doesn't already exist in `doc.blocks` — this is the project's block-deduplication/caching strategy, and reusing it correctly for any new variant is essential to avoid duplicate block definitions,
- inserts the resulting block (or raw lines for simple cases) into the modelspace with `msp.add_blockref(...)`.
This dispatch/handler structure — and especially the block-name-encodes-geometry-variant caching pattern — is the main thing to understand before adding a new `TeileArt` or modifying connection logic.
### Element classes (`lib/Elemente/`)
Pydantic models with geometry/business logic, one per component type: `Kreisel`, `Gefaellestrecke`, `VarioFoerderer`, `Angetriebene_Kurve`, `Eckrad`, `Bt_element`, `Omniflo`. Each has a `<Name>_tests.py` sibling with unittest cases (discovered by `run_unittests.bat`). `VarioFoerderer.py` and `Gefaellestrecke.py` contain the most complex logic (motor/Umlenkstation placement, AS/ES element insertion, rotation across one/two connected Kreisel) — see [doc/VarioFoerderer_Refactoring.md](doc/VarioFoerderer_Refactoring.md) for the design rationale (constants, enums, dataclasses extracted from what was previously an 820-line method).
### Helper modules (`lib/`)
- `arbeiten_mit_csv.py` — CSV parsing, `Planquadrat` coordinate extraction, `Merkmale` JSON parsing, the neighbor-analysis pass (`get_nachbar_information`), `TeileArt` → handler-name normalization.
- `block_methoden.py` — importing blocks from a library doc into the target doc, block rotation/mirroring (`turn_two_blocks_left`), reading insert color/layer from a library block, attribute helpers.
- `as_es_methoden.py` — AS-/ES-Element placement logic for Gefällestrecke (these depend on CSV height-comparison fields and Kreisel connection topology — flagged in the docs as needing review if RuleDesigner's CSV format changes).
- `handler_context.py` — the `HandlerContext` dataclass-like container passed to every handler, plus a `create_context_from_row` factory and rich `__str__`/`to_dict` for debugging.
- `utils.py``check_environment_var` (reads a required env var or exits) and `setup_logger` (timestamped file + console logger under `PROJECT_LOG`).
### Configuration (`cfg/`)
- `allgemein.cfg` — maps a `TeileArt`'s family (`[ILS]`, `[Omniflo]`, `[BT]`, `[TEF]`) to a block library filename under `data/block_libraries/`; also holds `[LOG]` format settings. Unmapped `TeileArt` falls back to `data/blocks.dxf`.
- `shapes.cfg` — one section per exact `TeileArt` string (must match the CSV column value exactly), listing library block names to place (`items`), their offsets/rotations relative to the computed position, and type-specific parameters (e.g. `asoffset`/`esoffset` for Gefällestrecke, `vario_abstand` for VarioFörderer, `winkel_*` angle constants, Omniflo Sivas-number overrides).
Both are read fresh per run; there's no schema validation beyond what `configparser` and the handler code assume, so a `TeileArt` typo silently falls through to "no handler found" (logged as a warning, row skipped) rather than erroring.
### Known fragility (documented in doc/plant2dxf_dokumentation.md)
The AS/ES-element and height-comparison logic in `as_es_methoden.py`, `lib/plant2dxf.py` (`handle_ils_2_0_gefaellestrecke`, `handle_ils_2_0_variofoerderer`), `lib/Elemente/Gefaellestrecke.py` (`rotation_mit_zwei_verbunden`), and `lib/Elemente/VarioFoerderer.py` (`vario_verbuden_am_kreisel`) all assume the current RuleDesigner CSV column set and semantics. If the CSV export format changes, these are the first places to check.
## Directories not under version control
`data/`, `work/`, `log/` are gitignored (generated/input working data, log files with timestamped names). `testdata/` holds committed CSV+DXF fixture pairs used for manual/reference comparison, separate from the `lib/Elemente/*_tests.py` unittest suite.
+38
View File
@@ -0,0 +1,38 @@
@echo off
CALL setenv.bat
echo ========================================
echo Creating all test DXF files from testdata
echo Output directory: %PROJECT_WORK%
echo ========================================
echo.
REM Track if any errors occurred
set ERRORS_OCCURRED=0
REM Iterate through all CSV files in testdata directory
for %%f in ("%PROJECT_TEST%\*.csv") do (
echo Processing: %%~nxf
CALL plant2dxf.bat -f "%%f" -o "%PROJECT_WORK%\%%~nf.dxf"
if errorlevel 1 (
echo ERROR: Failed to process %%~nxf
echo.
set ERRORS_OCCURRED=1
) else (
echo SUCCESS: Created %%~nf.dxf in work folder
echo.
)
)
echo ========================================
if %ERRORS_OCCURRED%==1 (
echo Processing completed with ERRORS!
echo ========================================
pause
exit /b 1
) else (
echo All test DXF files processed successfully!
echo ========================================
pause
exit /b 0
)
+1 -1
View File
@@ -7,4 +7,4 @@ REM Beispielaufruf mit Parametern:
REM --in: Ordner mit .dxf-Dateien (optional. Standard: work/converted_dxfs)
REM --name: Name der zu erzeugenden Bibliothek
python %PROJECT_LIB%\dxf2lib.py %*
"%VIRTUAL_ENV%\Scripts\python.exe" %PROJECT_LIB%\dxf2lib.py %*
+25 -21
View File
@@ -1,30 +1,34 @@
@echo off
CALL setenv.bat
IF DEFINED NETWORK_INTERPRETER_PATH (
goto %~1_network
IF /I "%1"=="activate" GOTO activate
IF /I "%1"=="deactivate" GOTO deactivate
GOTO :eof
:activate
REM Interpreter wählen
IF DEFINED NETWORK_PYTHON (
SET PYTHON_EXE=%NETWORK_PYTHON%\python.exe
) ELSE (
goto %~1_local
SET PYTHON_EXE=python
)
:activate_interpreter_local
IF NOT EXIST %PROJECT%\.venv CALL %PROJECT_BIN%\install_py.bat
CALL %PROJECT%\.venv\Scripts\activate.bat
goto :eof
REM venv sicherstellen
IF NOT EXIST "%PROJECT%\.venv" (
"%PYTHON_EXE%" -m venv "%PROJECT%\.venv"
)
:deactivate_interpreter_local
deactivate
goto :eof
REM venv aktivieren
CALL "%PROJECT%\.venv\Scripts\activate.bat"
:activate_interpreter_network
SET OLD_PATH=%PATH%
SET PATH=%NETWORK_INTERPRETER_PATH%;%PATH%
CALL %PROJECT%\.venv\Scripts\activate.bat
goto :eof
REM Interpreter festnageln
SET VIRTUAL_ENV_PYTHON=%PYTHON_EXE%
GOTO :eof
:deactivate_interpreter_network
SET PATH=%OLD_PATH%
SET OLD_PATH=
SET NETWORK_INTERPRETER_PATH=
CALL %PROJECT%\.venv\Scripts\deactivate.bat
goto :eof
:deactivate
CALL "%PROJECT%\.venv\Scripts\deactivate.bat"
SET VIRTUAL_ENV_PYTHON=
GOTO :eof
echo off
+17 -3
View File
@@ -1,4 +1,18 @@
@echo off
CALL manage_interpreter.bat activate_interpreter
python %PROJECT_LIB%\plant2dxf.py %*
CALL manage_interpreter.bat deactivate_interpreter
CALL manage_interpreter.bat activate
if errorlevel 1 (
echo ERROR: Failed to activate Python environment
exit /b 1
)
cd /d "%PROJECT%"
if errorlevel 1 (
echo ERROR: Failed to change to project directory: %PROJECT%
exit /b 1
)
"%VIRTUAL_ENV%\Scripts\python.exe" -m lib.plant2dxf %*
set PYTHON_EXIT=%ERRORLEVEL%
CALL manage_interpreter.bat deactivate
exit /b %PYTHON_EXIT%
+36
View File
@@ -0,0 +1,36 @@
@echo off
CALL setenv.bat
echo ========================================
echo running all unittests in lib\Elemente
echo ========================================
echo.
CALL manage_interpreter.bat activate
if %ERRORLEVEL% NEQ 0 (
echo.
echo Failed to activate the Python interpreter!
pause
exit /B 1
)
echo Gefundene Testdateien:
for /f "delims=" %%f in ('dir /b "%PROJECT_LIB%\Elemente\*_tests.py" 2^>nul') do echo %%f
echo.
cd /d "%PROJECT%"
if errorlevel 1 (
echo ERROR: Failed to change to project directory: %PROJECT%
CALL manage_interpreter.bat deactivate
exit /b 1
)
"%VIRTUAL_ENV%\Scripts\python.exe" -m unittest discover -t "%PROJECT%" -s lib\Elemente -p "*_tests.py" -v
if %ERRORLEVEL% NEQ 0 (
echo.
echo Tests failed!
CALL manage_interpreter.bat deactivate
exit /B 1
) else (
echo.
echo All tests passed!
CALL manage_interpreter.bat deactivate
exit /B 0
)
+1
View File
@@ -19,6 +19,7 @@ if not exist %PROJECT%\log mkdir %PROJECT%\log
if not exist %PROJECT%\testdata mkdir %PROJECT%\testdata
set PATH=%PROJECT_BIN%;%PATH%
REM set NETWORK_PYTHON=TO_BE_SET
popd
+3
View File
@@ -25,16 +25,19 @@ esoffset = 479.95
Umlenkstation = 500.0, 0.0, 0.0
Motorstation = 500.0, 0.0, 0.0
vario_abstand = 66.5
[Ils 2.0 core winkel]
winkel_boegen = 3
winkel_motor = 3.0
winkel_umlenk = 3.0
winkel_as = 3.0
winkel_es = 3.0
[Omniflo]
OFgeradesivas = 821106002
Tefgeradesivas = 0_B10030
OFfoerderer = 2
[Scanner_Stoper_namen]
Omniflo_stopper = 827062022
Omniflo_scanner = _DUMMY_SCANNER
+35623 -36239
View File
File diff suppressed because it is too large Load Diff
+15 -15
View File
@@ -10,7 +10,7 @@ Aufgrund möglicher Änderungen im CSV-Dateiformat von RuleDesigner müssen in Z
Die Logik zur Bestimmung und Platzierung von AS- und ES-Elementen bei Gefällestrecken und Vario-Förderern basiert auf dem aktuellen CSV-Format. Bei Änderungen der Datenstruktur oder der verfügbaren Informationen müssen folgende Methoden in `as_es_methoden.py` überprüft und angepasst werden:
- `erstellung_gefaelle_block_verbunenden_am_einen()`: Erstellt AS/ES-Elemente für Gefällestrecken, die mit einem Kreisel verbunden sind
- `erstellung_gefaelle_block_verbunden_an_einen()`: Erstellt AS/ES-Elemente für Gefällestrecken, die mit einem Kreisel verbunden sind
- `gefaellegerade_erstellung()`: Erstellt AS/ES-Elemente für Gefällestrecken, die mit zwei Kreiseln verbunden sind
- `am_kreisel_direct_verbunden()`: Prüft, ob eine Gefällestrecke/Vario-Förderer direkt mit einem Kreisel verbunden ist (ohne Stahlband)
@@ -23,7 +23,7 @@ Die Höhenvergleiche zur Bestimmung der Verbindungsart zwischen Gefällestrecken
- `handle_ils_2_0_gefaellestrecke()`: Haupt-Handler für Gefällestrecken
- `handle_ils_2_0_variofoerderer()`: Haupt-Handler für Vario-Förderer
**Gefällestrecke-Methoden (`lib/Elemente/Gefaehllestrecke.py`):**
**Gefällestrecke-Methoden (`lib/Elemente/Gefaellestrecke.py`):**
- `rotation_mit_zwei_verbunden()`: Berechnet Rotation bei Verbindung mit zwei Kreiseln (verwendet Höhenvergleiche)
@@ -33,7 +33,7 @@ Die Höhenvergleiche zur Bestimmung der Verbindungsart zwischen Gefällestrecken
**Betroffene Dateien:**
- `lib/as_es_methoden.py`: AS/ES-Element-Berechnungen
- `lib/plant2dxf.py`: Handler-Funktionen für Gefällestrecken und Vario-Förderer
- `lib/Elemente/Gefaehllestrecke.py`: Höhenvergleiche und Verbindungslogik
- `lib/Elemente/Gefaellestrecke.py`: Höhenvergleiche und Verbindungslogik
- `lib/Elemente/VarioFoerderer.py`: Höhenvergleiche und Verbindungslogik
## Übersicht
@@ -505,7 +505,7 @@ msp.add_blockref(blockname, (x, y, z), dxfattribs={"rotation": rotation, "layer"
```python
msp.add_blockref("ILS_2.0_Gefaellestrecke_8200_2000_UZS_higher",
(x, y, hoehe_gefaehlle),
(x, y, hoehe_gefaelle),
dxfattribs={"rotation": rotation, "layer": "6-SP"})
```
@@ -536,13 +536,13 @@ if blockname not in doc.blocks:
**Beispiel:**
```python
blockname = f"Ils_2.0_Gefaellestrecke_{laenge}_{hoehe_gefaehlle}_{hat_motor_0}_{hat_umlenk_0}"
blockname = f"Ils_2.0_Gefaellestrecke_{laenge}_{hoehe_gefaelle}_{hat_motor_0}_{hat_umlenk_0}"
if blockname not in doc.blocks:
block = doc.blocks.new(name=blockname, base_point=(0, 0, 0))
# Füge Komponenten zum Block hinzu
block.add_blockref("Vario_Motorstation_500mm", (0, 0, 0))
line = Line.new(dxfattribs={"start": start, "end": ende})
line.translate(-x, -y, -hoehe_gefaehlle)
line.translate(-x, -y, -hoehe_gefaelle)
block.add_entity(line)
```
@@ -733,11 +733,11 @@ Die Methode `hat_motor_umlenk_station()` prüft, ob eine Gefällestrecke eine Mo
**Höhen-basierte Bestimmung:**
- Wenn `upper_hoehe_gefaehlle > lower_hoehe_gefaehlle`:
- Wenn `vario_hoehe_0` oder `vario_hoehe_1 == upper_hoehe_gefaehlle``hat_motor_0 = True`
- Wenn `upper_hoehe_gefaelle > lower_hoehe_gefaelle`:
- Wenn `vario_hoehe_0` oder `vario_hoehe_1 == upper_hoehe_gefaelle``hat_motor_0 = True`
- Sonst → `hat_umlenk_0 = True`
- Wenn `upper_hoehe_gefaehlle < lower_hoehe_gefaehlle`:
- Wenn `vario_hoehe_0` oder `vario_hoehe_1 == lower_hoehe_gefaehlle``hat_motor_0 = True`
- Wenn `upper_hoehe_gefaelle < lower_hoehe_gefaelle`:
- Wenn `vario_hoehe_0` oder `vario_hoehe_1 == lower_hoehe_gefaelle``hat_motor_0 = True`
- Sonst → `hat_umlenk_0 = True`
**Rotations-basierte Bestimmung (bei gleicher Höhe):**
@@ -797,7 +797,7 @@ Die Methode `ein_motor_oder_eine_umlenk()` erstellt die Motor- oder Umlenkstatio
Der Blockname wird dynamisch generiert:
```python
Ils_2.0_Gefaellestrecke_{laenge}_{hoehe_gefaehlle}_{hat_umlenk_0}_{hat_motor_0}_{tefkurve_0}_{umlenk_gerade}_{motor_gerade}
Ils_2.0_Gefaellestrecke_{laenge}_{hoehe_gefaelle}_{hat_umlenk_0}_{hat_motor_0}_{tefkurve_0}_{umlenk_gerade}_{motor_gerade}
```
#### 4. Block-Erstellung
@@ -825,19 +825,19 @@ dy = halbe_laenge * math.cos(0) # dy = halbe_laenge (da cos(0) = 1)
**Rechts-Version:**
```python
Vario_Foerderer_{winkel}_{voerder_richtung}_{laenge}_{hoehe_vario}_rechts_{motor_vorhanden}_{umlenk_vorhanden}_{gefaelle}_{gefahellewinkel}
Vario_Foerderer_{winkel}_{foerder_richtung}_{laenge}_{hoehe_vario}_rechts_{motor_vorhanden}_{umlenk_vorhanden}_{gefaelle}_{gefahellewinkel}
```
**Links-Version:**
```python
Vario_Foerderer_{winkel}_{voerder_richtung}_{laenge}_{hoehe_vario}_links_{motor_vorhanden}_{umlenk_vorhanden}_{gefaelle}_{gefahellewinkel}
Vario_Foerderer_{winkel}_{foerder_richtung}_{laenge}_{hoehe_vario}_links_{motor_vorhanden}_{umlenk_vorhanden}_{gefaelle}_{gefahellewinkel}
```
**Parameter:**
- `winkel`: Winkel des Förderers (z.B. 3, 6, 9)
- `voerder_richtung`: Auf, Ab oder Horizontal
- `foerder_richtung`: Auf, Ab oder Horizontal
- `laenge`: Länge in mm
- `hoehe_vario`: Mittlere Höhe ((h0 + h1) / 2)
- `motor_vorhanden`: True/False
@@ -874,7 +874,7 @@ Vario_Foerderer_{winkel}_{voerder_richtung}_{laenge}_{hoehe_vario}_links_{motor_
foerderer, doc, lib_doc, config,
block, block_name_links,
start, ende,
voerder_richtung,
foerder_richtung,
winkel_VP_offset_vorne, # None, da keine Verbindung
winkel_VP_offset_hinten # None, da keine Verbindung
)
+64 -23
View File
@@ -1,29 +1,71 @@
# -*- coding: utf-8 -*-
"""
Angetriebene_Kurve — Modell für angetriebene Kurven.
Refactoring-Änderungen:
- antrieb-Property Side-Effect entfernt: Normalisierung "Aussen""außen" etc.
wird nun beim Erstellen durch @field_validator erledigt
- antrieb-Property ist jetzt ein reiner read-only Getter
"""
from pydantic import BaseModel, Field, field_validator
class Angetriebene_Kurve(BaseModel):
"""Modell für eine angetriebene Kurve."""
teileid: str
x: float
y:float
drehung: float = Field(default=0.0,description="Rotation der Kurve")
hoehe0: float = Field(default=0.0,description="Hoehe Anfang der Kurve")
hoehe1: float = Field(default=0.0,description="Hoehe Ende der Kurve")
y: float
drehung: float = Field(default=0.0, description="Rotation der Kurve")
hoehe0: float = Field(default=0.0, description="Hoehe Anfang der Kurve")
hoehe1: float = Field(default=0.0, description="Hoehe Ende der Kurve")
kurvenrichtung: str = Field(description="Kurvenrichtung der Kurve")
antriebNebenStrecke: str =Field(description="wo die Angetriebene Strecke ist abhängig von der kurvenrichtung")
antriebNebenStrecke: str = Field(
description="wo die Angetriebene Strecke ist abhängig von der kurvenrichtung"
)
winkel: int = Field(description="Der Winkel der Kurve")
@field_validator("antriebNebenStrecke", mode="before")
@classmethod
def normalize_antrieb(cls, v):
"""
Normalisiert die Antrieb-Bezeichnung beim Erstellen.
Konvertiert englische/gemischte Schreibvarianten in
die konsistente deutsche Kleinbuchstaben-Form.
"Aussen""außen", "Innen""innen"
"""
if isinstance(v, str):
mapping = {"Aussen": "außen", "Innen": "innen"}
return mapping.get(v, v)
return v
@property
def antrieb(self):
if self.antriebNebenStrecke == "Aussen":
self.antriebNebenStrecke = "außen"
elif self.antriebNebenStrecke == "Innen":
self.antriebNebenStrecke = "innen"
def antrieb(self) -> str:
"""Antrieb-Seite (read-only, normalisiert beim Erstellen)."""
return self.antriebNebenStrecke
@property
def hight_zwischen(self):
return ((self.hoehe0 + self.hoehe1) /2)
"""Mittlere Höhe zwischen hoehe0 und hoehe1."""
return (self.hoehe0 + self.hoehe1) / 2
@classmethod
def from_merkmale(cls, teileid: str, x: float, y: float, merkmale: dict) -> 'Angetriebene_Kurve':
def from_merkmale(
cls, teileid: str, x: float, y: float, merkmale: dict
) -> "Angetriebene_Kurve":
"""
Erstellt Angetriebene_Kurve aus einem Merkmale-Dictionary.
Args:
teileid: Teile-Identifikator
x, y: Koordinaten in mm
merkmale: Dictionary mit Eigenschaftswerten
Returns:
Angetriebene_Kurve-Instanz
"""
hoehe0 = float(merkmale.get("Höhe Anfang")) * 1000
hoehe1 = float(merkmale.get("Höhe Ende")) * 1000
winkel = int(merkmale.get("Kurvenwinkel"))
@@ -31,18 +73,17 @@ class Angetriebene_Kurve(BaseModel):
antriebNebenstrecke = merkmale.get("AntriebNebenStrecke")
try:
drehung = float(merkmale.get("Drehung"))
except Exception as e:
except (TypeError, ValueError):
drehung = 0.0
return cls(
teileid = teileid,
x = x,
y = y,
drehung = drehung,
hoehe0 = hoehe0,
hoehe1 = hoehe1,
kurvenrichtung = kurvenrichtung,
antriebNebenStrecke = antriebNebenstrecke,
winkel = winkel
teileid=teileid,
x=x,
y=y,
drehung=drehung,
hoehe0=hoehe0,
hoehe1=hoehe1,
kurvenrichtung=kurvenrichtung,
antriebNebenStrecke=antriebNebenstrecke,
winkel=winkel,
)
+104
View File
@@ -0,0 +1,104 @@
# -*- coding: utf-8 -*-
"""
Unit-Tests für Angetriebene_Kurve (Refactored)
"""
import unittest
from .Angetriebene_Kurve import Angetriebene_Kurve
class TestAngetriebeneKurveModell(unittest.TestCase):
"""Tests für das Angetriebene_Kurve Modell."""
def test_from_merkmale(self):
merkmale = {
"Höhe Anfang": "1.5",
"Höhe Ende": "2.0",
"Kurvenwinkel": "90",
"Kurvenrichtung": "links",
"AntriebNebenStrecke": "außen",
"Drehung": "-270",
}
obj = Angetriebene_Kurve.from_merkmale("AK-001", 100.0, 200.0, merkmale)
self.assertEqual(obj.teileid, "AK-001")
self.assertEqual(obj.hoehe0, 1500.0)
self.assertEqual(obj.hoehe1, 2000.0)
self.assertEqual(obj.winkel, 90)
self.assertEqual(obj.kurvenrichtung, "links")
self.assertEqual(obj.drehung, -270.0)
def test_from_merkmale_drehung_fehlend(self):
merkmale = {
"Höhe Anfang": "1.0",
"Höhe Ende": "1.0",
"Kurvenwinkel": "45",
"Kurvenrichtung": "rechts",
"AntriebNebenStrecke": "innen",
}
obj = Angetriebene_Kurve.from_merkmale("AK-002", 0.0, 0.0, merkmale)
self.assertEqual(obj.drehung, 0.0) # None → default
class TestAntriebNormalisierung(unittest.TestCase):
"""Tests für die field_validator Normalisierung von antriebNebenStrecke."""
def test_aussen_normalisiert(self):
"""'Aussen' wird beim Erstellen zu 'außen' normalisiert."""
obj = Angetriebene_Kurve(
teileid="T", x=0, y=0, kurvenrichtung="links",
antriebNebenStrecke="Aussen", winkel=90,
)
self.assertEqual(obj.antriebNebenStrecke, "außen")
self.assertEqual(obj.antrieb, "außen")
def test_innen_normalisiert(self):
"""'Innen' wird beim Erstellen zu 'innen' normalisiert."""
obj = Angetriebene_Kurve(
teileid="T", x=0, y=0, kurvenrichtung="rechts",
antriebNebenStrecke="Innen", winkel=45,
)
self.assertEqual(obj.antriebNebenStrecke, "innen")
self.assertEqual(obj.antrieb, "innen")
def test_bereits_normalisiert_bleibt(self):
"""Bereits normalisierte Werte bleiben unverändert."""
obj = Angetriebene_Kurve(
teileid="T", x=0, y=0, kurvenrichtung="links",
antriebNebenStrecke="außen", winkel=90,
)
self.assertEqual(obj.antriebNebenStrecke, "außen")
def test_unbekannter_wert_bleibt(self):
"""Unbekannte Werte werden nicht verändert."""
obj = Angetriebene_Kurve(
teileid="T", x=0, y=0, kurvenrichtung="links",
antriebNebenStrecke="custom_wert", winkel=90,
)
self.assertEqual(obj.antriebNebenStrecke, "custom_wert")
def test_antrieb_property_read_only(self):
"""antrieb Property gibt den normierten Wert zurück ohne Side-Effect."""
obj = Angetriebene_Kurve(
teileid="T", x=0, y=0, kurvenrichtung="links",
antriebNebenStrecke="Aussen", winkel=90,
)
# Mehrfacher Zugriff ändert nichts
_ = obj.antrieb
_ = obj.antrieb
self.assertEqual(obj.antrieb, "außen")
class TestHightZwischen(unittest.TestCase):
"""Tests für hight_zwischen Property."""
def test_mittlere_hoehe(self):
obj = Angetriebene_Kurve(
teileid="T", x=0, y=0, kurvenrichtung="links",
antriebNebenStrecke="außen", winkel=90,
hoehe0=1000, hoehe1=3000,
)
self.assertEqual(obj.hight_zwischen, 2000.0)
if __name__ == "__main__":
unittest.main()
+9 -10
View File
@@ -1,14 +1,19 @@
# -*- coding: utf-8 -*-
from pydantic import BaseModel, Field, field_validator
from typing import Optional
class Bt_element(BaseModel):
"""Das sind beide BTMT Elemente"""
teileid: str
drehung: Optional [float] = Field(default=0.0,description="Rotation des Elements")
hoehe: Optional [float] = Field(default=0.0,description="Hoehe des Elements")
drehung: Optional[float] = Field(default=0.0, description="Rotation des Elements")
hoehe: Optional[float] = Field(default=0.0, description="Hoehe des Elements")
@classmethod
def from_merkmale(cls, teileid: str, x: float, y: float, merkmale: dict) -> 'Bt_element':
def from_merkmale(
cls, teileid: str, x: float, y: float, merkmale: dict
) -> "Bt_element":
try:
hoehe = float(merkmale.get("Höhe in Meter"))
except Exception as e:
@@ -18,10 +23,4 @@ class Bt_element(BaseModel):
except Exception as e:
drehung = 0.0
return cls(
teileid = teileid,
x = x,
y = y,
hoehe = hoehe,
drehung = drehung
)
return cls(teileid=teileid, x=x, y=y, hoehe=hoehe, drehung=drehung)
+139 -39
View File
@@ -1,48 +1,148 @@
from pydantic import BaseModel, Field, field_validator
# -*- coding: utf-8 -*-
"""
Eckrad — Modell und Erstellungsfunktionen für Eckrad-Komponenten.
Refactoring-Änderungen:
- Hartkodierte Block-Namen durch Konstanten (BLOCK_AN8, BLOCK_RICHTUNGSPFEIL, etc.) ersetzt
- @staticmethod auf erstellung_eckrad_richtung
- 3x try/except in from_merkmale vereinfacht: drehrichtung braucht kein try/except (dict.get wirft nie)
- drehung wird nun korrekt an den Konstruktor übergeben (war vorher gelesen aber nicht weitergegeben)
- Hinweis: Modell hat keine x/y-Felder, diese werden von Pydantic ignoriert
"""
from pydantic import BaseModel, Field
from typing import Optional
import block_methoden
RADIUS = 400
from lib import block_methoden
# ============================================================================
# KONSTANTEN
# ============================================================================
RADIUS = 400 # Radius der Eckrad-Kreise (mm)
BLOCK_AN8 = "AN8" # Grundform-Block für Eckrad
BLOCK_RICHTUNGSPFEIL = "Richtungspfeil" # Richtungsanzeiger-Block
BLOCK_ECKRAD_UZS = "eckrad_UZS" # Eckrad-Block im Uhrzeigersinn
BLOCK_ECKRAD_GUZS = "eckrad_GUZS" # Eckrad-Block gegen Uhrzeigersinn
RICHTUNGSPFEIL_OFFSET = 200 # Versatz der Richtungspfeile vom Zentrum (mm)
# ============================================================================
# HILFSFUNKTIONEN
# ============================================================================
def _safe_float(value, default=0.0):
"""
Konvertiert einen Wert zu float mit Fallback.
Args:
value: Zu konvertierender Wert (kann None sein)
default: Rückgabewert bei Fehler
Returns:
Float-Wert oder default
"""
try:
if value is None:
return default
return float(value)
except (TypeError, ValueError):
return default
# ============================================================================
# ECKRAD KLASSE
# ============================================================================
class Eckrad(BaseModel):
"""Modell für Eckrad-Komponenten."""
teileid: str
drehung: Optional [float] = Field(default=0.0,description="Rotation des Elements")
hoehe: Optional [float] = Field(default=0.0,description="Hoehe des Elements")
drehrichtung: Optional [str] =Field(default=None,description="Richtung des Eckrads")
drehung: Optional[float] = Field(default=0.0, description="Rotation des Elements")
hoehe: Optional[float] = Field(default=0.0, description="Hoehe des Elements")
drehrichtung: Optional[str] = Field(
default=None, description="Richtung des Eckrads"
)
@classmethod
def from_merkmale(cls, teileid: str, x: float, y: float, merkmale: dict) -> 'Eckrad':
try:
hoehe = float(merkmale.get("Höhe in Meter")) * 1000
except Exception as e:
hoehe = 0.0
try:
drehung = float(merkmale.get("Drehung"))
except Exception as e:
drehung = 0.0
try:
drehrichtung = merkmale.get("Drehrichtung")
except Exception as e:
drehrichtung = None
def from_merkmale(
cls, teileid: str, x: float, y: float, merkmale: dict
) -> "Eckrad":
"""
Erstellt Eckrad aus einem Merkmale-Dictionary.
Args:
teileid: Teile-Identifikator
x, y: Koordinaten (werden vom Modell nicht gespeichert)
merkmale: Dictionary mit Eigenschaftswerten
Returns:
Eckrad-Instanz
"""
hoehe = _safe_float(merkmale.get("Höhe in Meter"), 0.0) * 1000
drehung = _safe_float(merkmale.get("Drehung"), 0.0)
# dict.get() wirft keine Ausnahme — kein try/except nötig
drehrichtung = merkmale.get("Drehrichtung")
return cls(
teileid = teileid,
x = x,
y = y,
hoehe = hoehe,
drehrichtung = drehrichtung
teileid=teileid,
hoehe=hoehe,
drehung=drehung,
drehrichtung=drehrichtung,
)
@staticmethod
def erstellung_eckrad_richtung(merkmale, doc, lib_doc):
block_methoden.import_block("AN8",lib_doc,doc)
block_methoden.import_block("Richtungspfeil",lib_doc,doc)
eckrad_rechts = "eckrad_UZS"
eckrad_links = "eckrad_GUZS"
"""
Erstellt Eckrad-Blöcke mit Richtungspfeilen (UZS und GUZS).
Erstellt bei Bedarf neue Blöcke für beide Drehrichtungen,
jeder mit dem AN8-Grundform-Block und zwei Richtungspfeilen.
Args:
merkmale: Dictionary mit "Höhe in m"
doc: DXF-Dokument
lib_doc: Bibliotheks-Dokument
Returns:
(eckrad_rechts, eckrad_links, hight): Blocknamen und Höhe in mm
"""
block_methoden.import_block(BLOCK_AN8, lib_doc, doc)
block_methoden.import_block(BLOCK_RICHTUNGSPFEIL, lib_doc, doc)
hight = float(merkmale.get("Höhe in m")) * 1000
# Erstellung der Richtungung Blöcke der Eckrads
if eckrad_rechts not in doc.blocks:
block_rechts = doc.blocks.new(name= eckrad_rechts,base_point=(0,0,0))
block_links = doc.blocks.new(name= eckrad_links,base_point=(0,0,0))
block_rechts.add_blockref("AN8",(0,0,0))
block_links.add_blockref("AN8",(0,0,0))
block_rechts.add_blockref("Richtungspfeil",(0+200,0+ RADIUS,0))
block_rechts.add_blockref("Richtungspfeil",(0-200,0- RADIUS,0),dxfattribs={"rotation": 180})
block_links.add_blockref("Richtungspfeil",(0+200,0- RADIUS,0))
block_links.add_blockref("Richtungspfeil",(0-200,0+ RADIUS,0),dxfattribs={"rotation": 180})
return eckrad_rechts,eckrad_links,hight
# Blöcke nur erstellen wenn noch nicht vorhanden
if BLOCK_ECKRAD_UZS not in doc.blocks:
block_rechts = doc.blocks.new(name=BLOCK_ECKRAD_UZS, base_point=(0, 0, 0))
block_links = doc.blocks.new(name=BLOCK_ECKRAD_GUZS, base_point=(0, 0, 0))
# Grundform für beide
block_rechts.add_blockref(BLOCK_AN8, (0, 0, 0))
block_links.add_blockref(BLOCK_AN8, (0, 0, 0))
# UZS: Pfeile bei (+offset, +RADIUS) und (-offset, -RADIUS)
block_rechts.add_blockref(
BLOCK_RICHTUNGSPFEIL,
(RICHTUNGSPFEIL_OFFSET, RADIUS, 0),
)
block_rechts.add_blockref(
BLOCK_RICHTUNGSPFEIL,
(-RICHTUNGSPFEIL_OFFSET, -RADIUS, 0),
dxfattribs={"rotation": 180},
)
# GUZS: Pfeile bei (+offset, -RADIUS) und (-offset, +RADIUS)
block_links.add_blockref(
BLOCK_RICHTUNGSPFEIL,
(RICHTUNGSPFEIL_OFFSET, -RADIUS, 0),
)
block_links.add_blockref(
BLOCK_RICHTUNGSPFEIL,
(-RICHTUNGSPFEIL_OFFSET, RADIUS, 0),
dxfattribs={"rotation": 180},
)
return BLOCK_ECKRAD_UZS, BLOCK_ECKRAD_GUZS, hight
+68
View File
@@ -0,0 +1,68 @@
# -*- coding: utf-8 -*-
"""
Unit-Tests für Eckrad (Refactored)
"""
import unittest
from .Eckrad import Eckrad, _safe_float, BLOCK_AN8, BLOCK_ECKRAD_UZS, BLOCK_ECKRAD_GUZS, RADIUS
class TestSafeFloat(unittest.TestCase):
"""Tests für Eckrad _safe_float."""
def test_normaler_wert(self):
self.assertEqual(_safe_float("2.5"), 2.5)
def test_none_default(self):
self.assertEqual(_safe_float(None, 99.0), 99.0)
def test_ungueltig_default(self):
self.assertEqual(_safe_float("abc", 0.0), 0.0)
class TestEckradModell(unittest.TestCase):
"""Tests für das Eckrad Modell."""
def test_from_merkmale_vollstaendig(self):
merkmale = {
"Höhe in Meter": "3.5",
"Drehung": "45",
"Drehrichtung": "UZS",
}
obj = Eckrad.from_merkmale("E-001", 100.0, 200.0, merkmale)
self.assertEqual(obj.teileid, "E-001")
self.assertEqual(obj.hoehe, 3500.0) # 3.5 * 1000
self.assertEqual(obj.drehung, 45.0)
self.assertEqual(obj.drehrichtung, "UZS")
def test_from_merkmale_fehlende_hoehe(self):
merkmale = {"Drehung": "90", "Drehrichtung": None}
obj = Eckrad.from_merkmale("E-002", 0.0, 0.0, merkmale)
self.assertEqual(obj.hoehe, 0.0) # None → default 0
def test_from_merkmale_fehlende_drehung(self):
merkmale = {"Höhe in Meter": "1.0"}
obj = Eckrad.from_merkmale("E-003", 0.0, 0.0, merkmale)
self.assertEqual(obj.drehung, 0.0) # None → default 0
def test_drehrichtung_ohne_try_except(self):
# drehrichtung = merkmale.get("Drehrichtung") wirft nie
merkmale = {"Höhe in Meter": "1.0", "Drehung": "0"}
obj = Eckrad.from_merkmale("E-004", 0.0, 0.0, merkmale)
self.assertIsNone(obj.drehrichtung)
class TestEckradKonstanten(unittest.TestCase):
"""Tests für Eckrad-Konstanten."""
def test_block_namen(self):
self.assertEqual(BLOCK_AN8, "AN8")
self.assertEqual(BLOCK_ECKRAD_UZS, "eckrad_UZS")
self.assertEqual(BLOCK_ECKRAD_GUZS, "eckrad_GUZS")
def test_radius(self):
self.assertEqual(RADIUS, 400)
if __name__ == "__main__":
unittest.main()
-304
View File
@@ -1,304 +0,0 @@
from pydantic import BaseModel, Field, field_validator
import re
import plant2dxf
import math
from ezdxf.math import Matrix44
import block_methoden
class Gefaellestrecke(BaseModel):
teileid: str
x: float = Field(description="X-Koordinate des Foerder-Zentrums")
y: float = Field(description="Y-Koordinate des Foerder-Zentrums")
laenge:float = Field(description = "Länge des Förderers")
h0: float = Field(description="Höhe Unten in Merkmale")
h1: float = Field(description="Höhe Oben in Merkmale")
drehung: float = Field(default=0.0, description="Drehung an z achse")
anzahl_scanner: int = Field(default=0, description="Anzahl der Scanner")
anzahl_separatoren: int = Field(default=0, description="Anzahl der Separatoren")
@property
def hight_zwischen(self):
return ((self.h0 + self.h1) /2)
@classmethod
def from_merkmale(cls, teileid: str, x: float, y: float, merkmale: dict) -> 'Gefaellestrecke':
h0 = float(merkmale.get("Höhe unten")) * 1000
h1 = float(merkmale.get("Höhe oben")) * 1000
laenge = float(merkmale.get("Länge in Meter")) * 1000
return cls(
teileid = teileid,
laenge = laenge,
x = x,
y = y,
h0 = h0,
h1 =h1,
drehung = float(merkmale.get("Drehung")),
anzahl_scanner = int(merkmale.get("Anzahl der Scanner")),
anzahl_separatoren = int(merkmale.get("Anzahl der Separatoren"))
)
def erstehlung_von_gefalle_ohne_aussnahmen(msp, x, y, upper_hoehe_gefaehlle, lower_hoehe_gefaehlle, halbe_laenge, winkel):
dx = halbe_laenge *math.sin(winkel * -1)
dy = halbe_laenge * math.cos(winkel)
start = x +dx, y + dy,upper_hoehe_gefaehlle
ende = x -dx, y - dy,lower_hoehe_gefaehlle
line =msp.add_line(start,ende)
line.dxf.layer = "6-SP"
def rotation_mit_zwei_verbunden(gefaellestrecke_nachbarn,richtung2, richtung0, am_kreisel, kreisel_verbunden, hight_position):
drehung0 =gefaellestrecke_nachbarn.get("Drehung0")
drehung1 = gefaellestrecke_nachbarn.get("Drehung1")
x0_kreisel = float(gefaellestrecke_nachbarn.get("x0"))
y0_kreisel = float(gefaellestrecke_nachbarn.get("y0"))
x1_kreisel = float(gefaellestrecke_nachbarn.get("x1"))
y1_kreisel = float(gefaellestrecke_nachbarn.get("y1"))
if richtung2 == "DEFAULT":
if richtung0 == "Vertikal":
if x0_kreisel < x1_kreisel:
position = hight_position + "_links"
else:
position = hight_position + "_rechts"
else:
if y0_kreisel > y1_kreisel:
position = hight_position + "_higher"
else:
position = hight_position + "_lower"
if richtung0 == "Vertikal":
if position == "lower_rechts" or position == "higher_links":
gefaelle = "links"
else:
gefaelle = "rechts"
elif richtung0 == "Horizontal":
if position == "lower_lower" or position == "higher_higher":
gefaelle = "oben"
else:
gefaelle = "unten"
# vertausch der drehung und der höhe für die namens gebung des blockes
if (position == "higher_rechts" or position == "lower_rechts" or position=="higher_lower" or position== "lower_lower") and drehung0 != drehung1 and am_kreisel == 0:
drehung_2 = drehung0
drehung0 = drehung1
drehung1= drehung_2
if hight_position == "higher":
hight_position = "lower"
else:
hight_position = "higher"
# austausch der werte damit immer davon ausgehen dass der 1 kreisel in unserer Liste am Kreisel verbuden ist
if kreisel_verbunden == 1 and am_kreisel ==2:
am_kreisel == 1
drehung_2 = drehung0
drehung0 = drehung1
drehung1= drehung_2
if hight_position == "higher":
hight_position = "lower"
else:
hight_position = "higher"
else:
if richtung2 == "Vertikal":
if x0_kreisel < x1_kreisel:
position = hight_position + "_links"
else:
position = hight_position + "_rechts"
else:
if y0_kreisel > y1_kreisel:
position = hight_position + "_higher"
else:
position = hight_position + "_lower"
if richtung2 == "Vertikal":
if position == "lower_rechts" or position == "higher_links":
gefaelle = "links"
else:
gefaelle = "rechts"
elif richtung2 == "Horizontal":
if position == "lower_lower" or position == "higher_higher":
gefaelle = "oben"
else:
gefaelle = "unten"
# austausch der werte damit immer davon ausgehen dass der 1 kreisel in unserer Liste am Kreisel verbuden ist
if am_kreisel == 2:
am_kreisel = 1
drehung_2 = drehung0
drehung0 = drehung1
drehung1= drehung_2
if hight_position == "higher":
hight_position = "lower"
else:
hight_position = "higher"
# Erstellung der Rotation
if gefaelle == "oben":
rotation = 0
elif gefaelle == "unten" :
rotation = 180
elif gefaelle == "links" :
rotation = 90
elif gefaelle == "rechts" :
rotation = 270
return rotation,drehung0,drehung1,hight_position
def ein_motor_oder_eine_umlenk(x, y,start,ende, doc, lib_doc, hoehe_gefaehlle, block_Vario_Umlenkstation_500mm, block_Vario_Motorstation_500mm, blockname_motor_links, blockname_umlenk_links, hat_motor_0, hat_umlenk_0, tefkurve_0, block,umlenk_gerade,motor_gerade):
block_Vario_Bogen_auf = (f"Vario_Bogen_auf_3°")
block_Vario_Bogen_ab = (f"Vario_Bogen_ab_3°")
block_Vario_Bogen_auf_links = (f"Vario_Bogen_auf_3°") + "_links"
block_Vario_Bogen_ab_links = (f"Vario_Bogen_ab_3°") + "_links"
auf_attrib = block_methoden.import_block(block_Vario_Bogen_auf,lib_doc,doc)
ab_attrib = block_methoden.import_block(block_Vario_Bogen_ab,lib_doc,doc)
Vario_Bogen_auf_Delta_SP_0 = list(float(att)for att in re.split(r"[;,]", auf_attrib["DELTA_SP_0"]))
Vario_Bogen_auf_Delta_SP_1 = list(float(att) for att in re.split(r"[;,]", auf_attrib["DELTA_SP_1"]))
Vario_Bogen_ab_Delta_SP_0 = list(float(att) for att in re.split(r"[;,]", ab_attrib["DELTA_SP_0"]))
Vario_Bogen_ab_Delta_SP_1 = list(float(att) for att in re.split(r"[;,]", ab_attrib["DELTA_SP_1"]))
for i, wert in enumerate(Vario_Bogen_auf_Delta_SP_0):
if wert < 0:
Vario_Bogen_auf_Delta_SP_0[i] = abs(wert)
for i, wert in enumerate(Vario_Bogen_auf_Delta_SP_1):
if wert< 0:
Vario_Bogen_auf_Delta_SP_1[i] = abs(wert)
for i, wert in enumerate(Vario_Bogen_ab_Delta_SP_0):
if wert< 0:
Vario_Bogen_ab_Delta_SP_0[i] = abs(wert)
for i, wert in enumerate(Vario_Bogen_ab_Delta_SP_1):
if wert< 0:
Vario_Bogen_ab_Delta_SP_1[i] = abs(wert)
block_methoden.turn_two_blocks_left(doc, block_Vario_Bogen_auf, block_Vario_Bogen_ab, block_Vario_Bogen_ab_links, block_Vario_Bogen_auf_links)
if hat_motor_0 == True:
if tefkurve_0 == "links":
if motor_gerade == False:
block.add_blockref(block_Vario_Bogen_ab_links,(start[0]-x,start[1]-Vario_Bogen_ab_Delta_SP_0[0]-y,start[2]- Vario_Bogen_ab_Delta_SP_0[2]-hoehe_gefaehlle),dxfattribs={"rotation": 270})
start = [start[0],start[1]-Vario_Bogen_ab_Delta_SP_0[0]- Vario_Bogen_ab_Delta_SP_1[0],start[2]-Vario_Bogen_ab_Delta_SP_0[2]- Vario_Bogen_ab_Delta_SP_1[2]]
block.add_blockref(blockname_motor_links, (start[0]-x,start[1] - 250* math.cos(math.radians(3))-y,start[2] - 250* math.sin(math.radians(3))-hoehe_gefaehlle),dxfattribs={"rotation": 270})
start[1]= start[1] - 500* math.cos(math.radians(3))
start[2] = start[2] - 500* math.sin(math.radians(3))
else:
block.add_blockref("Vario_Motorstation_500mm_links", (start[0]-x,start[1] -250 -y,start[2] -hoehe_gefaehlle),dxfattribs={"rotation": 270})
start[1]= start[1] - 500
else:
if motor_gerade == False:
block.add_blockref(block_Vario_Bogen_ab,(start[0]-x,start[1]-Vario_Bogen_ab_Delta_SP_0[0]-y,start[2]- Vario_Bogen_ab_Delta_SP_0[2]-hoehe_gefaehlle),dxfattribs={"rotation": 270})
start = [start[0],start[1]-Vario_Bogen_ab_Delta_SP_0[0]- Vario_Bogen_ab_Delta_SP_1[0],start[2]-Vario_Bogen_ab_Delta_SP_0[2]- Vario_Bogen_ab_Delta_SP_1[2]]
block.add_blockref(block_Vario_Motorstation_500mm, (start[0]-x,start[1] - 250* math.cos(math.radians(3))-y,start[2] - 250* math.sin(math.radians(3))-hoehe_gefaehlle),dxfattribs={"rotation": 270})
start[1]= start[1] - 500* math.cos(math.radians(3))
start[2] = start[2] - 500* math.sin(math.radians(3))
else:
block.add_blockref("Vario_Motorstation_500mm", (start[0]-x,start[1]- 250 -y,start[2] -hoehe_gefaehlle),dxfattribs={"rotation": 270})
start[1]= start[1] - 500
if hat_umlenk_0 == True:
if tefkurve_0 == "links":
if umlenk_gerade == False:
block.add_blockref(block_Vario_Bogen_auf,(ende[0]-x,ende[1]+Vario_Bogen_auf_Delta_SP_0[0]-y,ende[2] + Vario_Bogen_auf_Delta_SP_0[2]-hoehe_gefaehlle),dxfattribs={"rotation": 90})
ende = [ende[0],ende[1]+ Vario_Bogen_auf_Delta_SP_0[0]+ Vario_Bogen_auf_Delta_SP_1[0],ende[2]+Vario_Bogen_auf_Delta_SP_0[2]+ Vario_Bogen_auf_Delta_SP_1[2]]
block.add_blockref(blockname_umlenk_links, (ende[0]-x,ende[1] + 250* math.cos(math.radians(3))-y,ende[2] + 250* math.sin(math.radians(3))-hoehe_gefaehlle),dxfattribs={"rotation": 270})
ende [1]= ende[1] + 500* math.cos(math.radians(3))
ende[2] = ende[2] + 500* math.sin(math.radians(3))
else:
block.add_blockref("Vario_Umlenkstation_500mm_links", (ende[0]-x,ende[1] + 250-y,ende[2] -hoehe_gefaehlle),dxfattribs={"rotation": 270})
ende [1]= ende[1] + 500
else:
if umlenk_gerade == False:
block.add_blockref(block_Vario_Bogen_auf_links,(ende[0]-x,ende[1]+Vario_Bogen_auf_Delta_SP_0[0]-y,ende[2] + Vario_Bogen_auf_Delta_SP_0[2]-hoehe_gefaehlle),dxfattribs={"rotation": 90})
ende = [ende[0],ende[1]+ Vario_Bogen_auf_Delta_SP_0[0]+ Vario_Bogen_auf_Delta_SP_1[0],ende[2]+Vario_Bogen_auf_Delta_SP_0[2]+ Vario_Bogen_auf_Delta_SP_1[2]]
block.add_blockref(block_Vario_Umlenkstation_500mm, (ende[0]-x,ende[1] + 250* math.cos(math.radians(3))-y,ende[2] + 250* math.sin(math.radians(3))-hoehe_gefaehlle),dxfattribs={"rotation": 270})
ende [1]= ende[1] + 500* math.cos(math.radians(3))
ende[2] = ende[2] + 500* math.sin(math.radians(3))
else:
block.add_blockref("Vario_Umlenkstation_500mm", (ende[0]-x,ende[1] + 250-y,ende[2] -hoehe_gefaehlle),dxfattribs={"rotation": 270})
ende [1]= ende[1] + 500
return start,ende
def hat_motor_umlenk_station (gefaelle_objekt, gefaellestrecke_nachbarn):
hat_motor_0 = None
hat_motor_1 = None
hat_umlenk_0 = None
hat_umlenk_1 = None
tefkurve_0 = None
tefkurve_1 = None
umlenk_gerade = False
motor_gerade = False
upper_hoehe_gefaehlle = gefaelle_objekt.h1
lower_hoehe_gefaehlle = gefaelle_objekt.h0
rotation = gefaelle_objekt.drehung
x = gefaelle_objekt.x
y = gefaelle_objekt.y
if "Kurvenrichtung" in gefaellestrecke_nachbarn:
vario_hoehe_0 = float(gefaellestrecke_nachbarn.get("vario_hoehe_0"))
vario_hoehe_1 = float(gefaellestrecke_nachbarn.get("vario_hoehe_1"))
kurvenrichtung = gefaellestrecke_nachbarn.get("Kurvenrichtung")
tefkurve_0 = gefaellestrecke_nachbarn.get("Tefkurve")
x_angetrieben = gefaellestrecke_nachbarn.get("X_angetrieben")
y_angetrieben = gefaellestrecke_nachbarn.get("Y_angetrieben")
x_angetrieben_1 = gefaellestrecke_nachbarn.get("X_angetrieben_1")
y_angetrieben_1 = gefaellestrecke_nachbarn.get("Y_angetrieben_1")
if (kurvenrichtung == "links" and tefkurve_0 == "außen") or kurvenrichtung == "rechts" and tefkurve_0 == "innen":
tefkurve_0 = "rechts"
else:
tefkurve_0 = "links"
if upper_hoehe_gefaehlle > lower_hoehe_gefaehlle:
if vario_hoehe_0 == upper_hoehe_gefaehlle or vario_hoehe_1 == upper_hoehe_gefaehlle:
hat_motor_0 = True
else:
hat_umlenk_0 = True
elif upper_hoehe_gefaehlle < lower_hoehe_gefaehlle:
if vario_hoehe_0 == lower_hoehe_gefaehlle or vario_hoehe_1 == lower_hoehe_gefaehlle:
hat_motor_0 = True
else:
hat_umlenk_0 = True
else:
rotation_zwischen = rotation
if rotation_zwischen == 0.0:
rotation_zwischen = -360.0
if (((-360.0<= rotation_zwischen< -270.0)and y > y_angetrieben) or ((-90.0< rotation< 0.0)and y > y_angetrieben) or
((-270.0< rotation_zwischen< -90.0)and y < y_angetrieben) or
(rotation == -90.0 and x < x_angetrieben) or ((rotation == -270.0)and x<x_angetrieben)):
hat_umlenk_0 = True
umlenk_gerade = True
else:
hat_motor_0 = True
motor_gerade = True
if "Kurvenrichtung_1" in gefaellestrecke_nachbarn:
vario_hoehe_0_1 = float(gefaellestrecke_nachbarn.get("vario_hoehe_0_1"))
vario_hoehe_1_1 = float(gefaellestrecke_nachbarn.get("vario_hoehe_1_1"))
kurvenrichtung_1 = gefaellestrecke_nachbarn.get("Kurvenrichtung_1")
tefkurve_1 = gefaellestrecke_nachbarn.get("Tefkurve_1")
if (kurvenrichtung_1 == "links" and tefkurve_1 == "außen") or kurvenrichtung_1 == "rechts" and tefkurve_1 == "innen":
tefkurve_1 = "rechts"
else:
tefkurve_1 = "links"
if upper_hoehe_gefaehlle > lower_hoehe_gefaehlle:
if vario_hoehe_0_1 == upper_hoehe_gefaehlle or vario_hoehe_1_1 == upper_hoehe_gefaehlle:
hat_motor_1 = True
else:
hat_umlenk_1 = True
elif upper_hoehe_gefaehlle < lower_hoehe_gefaehlle:
if vario_hoehe_0_1 == lower_hoehe_gefaehlle or vario_hoehe_1_1 == lower_hoehe_gefaehlle:
hat_motor_1 = True
else:
hat_umlenk_1 = True
else:
rotation_zwischen = rotation
if rotation_zwischen == 0.0:
rotation_zwischen = -360.0
if (((-360.0<= rotation_zwischen< -270.0)and y > y_angetrieben_1) or ((-90.0< rotation< 0.0)and y > y_angetrieben_1) or
((-270.0< rotation_zwischen< -90.0)and y < y_angetrieben_1) or
(rotation == -90.0 and x < x_angetrieben_1) or ((rotation == -270.0)and x<x_angetrieben_1)):
hat_umlenk_1 = True
umlenk_gerade = True
else:
hat_motor_1 = True
motor_gerade = True
hat_zusatz ={}
hat_zusatz["hat_motor_0"] = hat_motor_0
hat_zusatz["hat_motor_1"] = hat_motor_1
hat_zusatz["hat_umlenk_0"] = hat_umlenk_0
hat_zusatz["hat_umlenk_1"] = hat_umlenk_1
hat_zusatz["tefkurve_0"] = tefkurve_0
hat_zusatz["tefkurve_1"] = tefkurve_1
hat_zusatz["umlenk_gerade"] = umlenk_gerade
hat_zusatz["motor_gerade"] = motor_gerade
return hat_zusatz
+621
View File
@@ -0,0 +1,621 @@
# -*- coding: utf-8 -*-
"""
Gefaellestrecke — Modell und Hilfsfunktionen für Gefällestrecken.
Enthält das Pydantic-Modell und statische Methoden zur Erstellung
von DXF-Geometrie für Gefällestrecken zwischen Fördererkomponenten.
Refactoring-Änderungen:
- @staticmethod auf alle Methoden, die self nicht nutzen
- Bug fix Zeile 112: am_kreisel == 1 → am_kreisel = 1
- Duplizierte Logik (Zeilen 6990 vs 121141) in _bestimme_position/_bestimme_gefaelle extrahiert
- rotation_mit_zwei_verbunden aufgeteilt in Hilfsfunktionen
- hat_motor_umlenk_station aufgeteilt: _pruefe_motor_umlenk_an_kurve, _bestimme_tefkurve
- ein_motor_oder_eine_umlenk aufgeteilt: _add_station_mit_bogen, _add_station_gerade
- Magic Numbers durch Konstanten ersetzt
- Dokumentation hinzugefügt
"""
import math
import re
from pydantic import BaseModel, Field
from lib import block_methoden
# ============================================================================
# KONSTANTEN
# ============================================================================
ROTATION_MAP = {"oben": 0, "unten": 180, "links": 90, "rechts": 270}
"""Zuordnung von Gefälle-Richtung zu Rotationswinkel in Grad."""
LAYER_SP = "6-SP"
"""DXF-Layer für Seitenprofile."""
WINKEL_3_GRAD = 3
"""Standard-Bogenwinkel in Grad."""
STATIONS_LAENGE = 500
"""Länge einer Motor-/Umlenkstation in mm."""
# ============================================================================
# HILFSFUNKTIONEN
# ============================================================================
def _bestimme_position(richtung, x0_kreisel, x1_kreisel, y0_kreisel, y1_kreisel, hight_position):
"""
Bestimmt die relative Position basierend auf Richtung und Kreiselkoordinaten.
Args:
richtung: "Vertikal" oder "Horizontal"
x0_kreisel, y0_kreisel: Koordinaten erstes Kreisel
x1_kreisel, y1_kreisel: Koordinaten zweites Kreisel
hight_position: "higher" oder "lower"
Returns:
Position-String z.B. "higher_links", "lower_rechts"
"""
if richtung == "Vertikal":
suffix = "_links" if x0_kreisel < x1_kreisel else "_rechts"
else:
suffix = "_higher" if y0_kreisel > y1_kreisel else "_lower"
return hight_position + suffix
def _bestimme_gefaelle(richtung, position):
"""
Bestimmt die Gefälle-Richtung basierend auf Richtung und Position.
Args:
richtung: "Vertikal" oder "Horizontal"
position: Position-String (z.B. "lower_rechts")
Returns:
Gefälle-Richtung: "links", "rechts", "oben" oder "unten"
"""
if richtung == "Vertikal":
return "links" if position in ("lower_rechts", "higher_links") else "rechts"
elif richtung == "Horizontal":
return "oben" if position in ("lower_lower", "higher_higher") else "unten"
return "links" # Fallback für unbekannte Richtung
def _tausche_drehungen_und_hoehe(drehung0, drehung1, hight_position):
"""
Tauscht Drehungen und invertiert die Höhe-Position.
Returns:
(neue_drehung0, neue_drehung1, neue_hight_position)
"""
neue_hight = "lower" if hight_position == "higher" else "higher"
return drehung1, drehung0, neue_hight
def _bestimme_tefkurve(kurvenrichtung, tefkurve):
"""
Bestimmt die effektive Tefkurve-Richtung aus Kurvenrichtung und Tefkurve.
Args:
kurvenrichtung: "links" oder "rechts"
tefkurve: "außen" oder "innen"
Returns:
"rechts" oder "links"
"""
if (kurvenrichtung == "links" and tefkurve == "außen") or (
kurvenrichtung == "rechts" and tefkurve == "innen"
):
return "rechts"
return "links"
def _ist_umlenk_position(rotation, x, y, x_angetrieben, y_angetrieben):
"""
Prüft ob die Position eine Umlenkposition ist (gleiche-Höhe-Fall).
Basiert auf Rotationswinkel und relativer Position zur angetriebenen Strecke.
Args:
rotation: Drehung in Grad
x, y: Position des Gefällestreckens
x_angetrieben, y_angetrieben: Position der angetriebenen Strecke
Returns:
True wenn Umlenkposition, sonst False (Motorposition)
"""
rotation_zwischen = rotation if rotation != 0.0 else -360.0
return (
((-360.0 <= rotation_zwischen < -270.0) and y > y_angetrieben)
or ((-90.0 < rotation < 0.0) and y > y_angetrieben)
or ((-270.0 < rotation_zwischen < -90.0) and y < y_angetrieben)
or (rotation == -90.0 and x < x_angetrieben)
or (rotation == -270.0 and x < x_angetrieben)
)
def _normalize_deltas(*delta_lists):
"""Setzt negative Werte in Delta-Listen auf positiv."""
for delta_list in delta_lists:
for i, val in enumerate(delta_list):
if val < 0:
delta_list[i] = abs(val)
def _laedt_bogen_deltas(block_name, lib_doc, doc):
"""
Lädt Bogen-Delta-Attribute und normalisiert negative Werte.
Args:
block_name: Name des Bogen-Blocks
lib_doc: Bibliotheks-Dokument
doc: Ziel-Dokument
Returns:
Dict mit DELTA_SP_0 und DELTA_SP_1 als normalisierten Listen
"""
attrib = block_methoden.import_block(block_name, lib_doc, doc)
deltas = {}
for key in ("DELTA_SP_0", "DELTA_SP_1"):
values = [float(v) for v in re.split(r"[;,]", attrib[key])]
deltas[key] = [abs(v) if v < 0 else v for v in values]
return deltas
def _add_station_mit_bogen(
block, bogen_block, station_block, position, x, y, hoehe,
deltas_0, deltas_1, vorzeichen, rotation_bogen
):
"""
Fügt Station mit Bogen-Übergang hinzu.
Args:
block: DXF-Block zum Hinzufügen
bogen_block: Blockname des Bogens
station_block: Blockname der Station
position: [x, y, z] aktuelle Position
x, y: Offset-Koordinaten
hoehe: Höhe des Gefälles
deltas_0, deltas_1: Delta-Werte des Bogens [x, y, z]
vorzeichen: -1 für Motor (Anfang), +1 für Umlenk (Ende)
rotation_bogen: Rotation des Bogenblocks
Returns:
Neue Position nach Station-Platzierung
"""
v = vorzeichen
# Bogen hinzufügen
block.add_blockref(
bogen_block,
(position[0] - x, position[1] + v * deltas_0[0] - y, position[2] + v * deltas_0[2] - hoehe),
dxfattribs={"rotation": rotation_bogen},
)
# Position nach Bogen aktualisieren
new_pos = [
position[0],
position[1] + v * (deltas_0[0] + deltas_1[0]),
position[2] + v * (deltas_0[2] + deltas_1[2]),
]
# Station hinzufügen
winkel_rad = math.radians(WINKEL_3_GRAD)
block.add_blockref(
station_block,
(
new_pos[0] - x,
new_pos[1] + v * (STATIONS_LAENGE / 2) * math.cos(winkel_rad) - y,
new_pos[2] + v * (STATIONS_LAENGE / 2) * math.sin(winkel_rad) - hoehe,
),
dxfattribs={"rotation": 270},
)
new_pos[1] += v * STATIONS_LAENGE * math.cos(winkel_rad)
new_pos[2] += v * STATIONS_LAENGE * math.sin(winkel_rad)
return new_pos
def _add_station_gerade(block, station_block, position, x, y, hoehe, vorzeichen):
"""
Fügt Station ohne Bogen (gerade) hinzu.
Args:
block: DXF-Block zum Hinzufügen
station_block: Blockname der Station
position: [x, y, z] aktuelle Position
x, y: Offset-Koordinaten
hoehe: Höhe des Gefälles
vorzeichen: -1 für Motor, +1 für Umlenk
Returns:
Neue Position nach Station-Platzierung
"""
v = vorzeichen
block.add_blockref(
station_block,
(position[0] - x, position[1] + v * (STATIONS_LAENGE / 2) - y, position[2] - hoehe),
dxfattribs={"rotation": 270},
)
new_pos = list(position)
new_pos[1] += v * STATIONS_LAENGE
return new_pos
def _pruefe_motor_umlenk_an_kurve(
upper_hoehe, lower_hoehe, vario_hoehe_0, vario_hoehe_1,
rotation, x, y, x_angetrieben, y_angetrieben
):
"""
Prüft ob Motor oder Umlenk an einer Kurve benötigt wird.
Args:
upper_hoehe: Obere Höhe des Gefällestreckens
lower_hoehe: Untere Höhe des Gefällestreckens
vario_hoehe_0, vario_hoehe_1: Höhen der Nachbarn
rotation: Drehung in Grad
x, y: Position des Gefällestreckens
x_angetrieben, y_angetrieben: Position der angetriebenen Strecke
Returns:
(hat_motor, hat_umlenk, ist_gerade)
"""
if upper_hoehe > lower_hoehe:
if vario_hoehe_0 == upper_hoehe or vario_hoehe_1 == upper_hoehe:
return True, False, False
return False, True, False
elif upper_hoehe < lower_hoehe:
if vario_hoehe_0 == lower_hoehe or vario_hoehe_1 == lower_hoehe:
return True, False, False
return False, True, False
else: # Gleiche Höhe → Positionsbasierte Bestimmung
ist_umlenk = _ist_umlenk_position(rotation, x, y, x_angetrieben, y_angetrieben)
if ist_umlenk:
return False, True, True
return True, False, True
# ============================================================================
# GEFAELLESTRECKE KLASSE
# ============================================================================
class Gefaellestrecke(BaseModel):
"""Modell für Gefällestrecken zwischen Fördererkomponenten."""
teileid: str
x: float = Field(description="X-Koordinate des Foerder-Zentrums")
y: float = Field(description="Y-Koordinate des Foerder-Zentrums")
laenge: float = Field(description="Länge des Förderers")
h0: float = Field(description="Höhe Unten in Merkmale")
h1: float = Field(description="Höhe Oben in Merkmale")
drehung: float = Field(default=0.0, description="Drehung an z achse")
anzahl_scanner: int = Field(default=0, description="Anzahl der Scanner")
anzahl_separatoren: int = Field(default=0, description="Anzahl der Separatoren")
@property
def hight_zwischen(self):
"""Mittlere Höhe zwischen h0 und h1."""
return (self.h0 + self.h1) / 2
@classmethod
def from_merkmale(
cls, teileid: str, x: float, y: float, merkmale: dict
) -> "Gefaellestrecke":
"""
Erstellt ein Gefaellestrecke-Objekt aus einem Merkmale-Dictionary.
Args:
teileid: Teile-Identifikator
x, y: Koordinaten in mm
merkmale: Dictionary mit Eigenschaftswerten
Returns:
Gefaellestrecke-Instanz
"""
h0 = float(merkmale.get("Höhe unten")) * 1000
h1 = float(merkmale.get("Höhe oben")) * 1000
laenge = float(merkmale.get("Länge in Meter")) * 1000
return cls(
teileid=teileid,
laenge=laenge,
x=x,
y=y,
h0=h0,
h1=h1,
drehung=float(merkmale.get("Drehung")),
anzahl_scanner=int(merkmale.get("Anzahl der Scanner")),
anzahl_separatoren=int(merkmale.get("Anzahl der Separatoren")),
)
@staticmethod
def erstehlung_von_gefalle_ohne_aussnahmen(
msp, x, y, upper_hoehe_gefaelle, lower_hoehe_gefaelle, halbe_laenge, winkel
):
"""
Zeichnet eine Gefällelinie ohne Motor-/Umlenk-Ausnahmen.
Args:
msp: DXF-Modelspace
x, y: Zentrum der Gefällelinie
upper_hoehe_gefaelle: Höhe am oberen Ende
lower_hoehe_gefaelle: Höhe am unteren Ende
halbe_laenge: Halbe Länge der Strecke
winkel: Rotationswinkel
"""
dx = halbe_laenge * math.sin(winkel * -1)
dy = halbe_laenge * math.cos(winkel)
start = x + dx, y + dy, upper_hoehe_gefaelle
ende = x - dx, y - dy, lower_hoehe_gefaelle
line = msp.add_line(start, ende)
line.dxf.layer = LAYER_SP
@staticmethod
def rotation_mit_zwei_verbunden(
gefaellestrecke_nachbarn,
richtung2,
richtung0,
am_kreisel,
kreisel_verbunden,
hight_position,
):
"""
Berechnet Rotation wenn zwei Kreise verbunden sind.
Bestimmt die Gefälle-Richtung und passt Drehungen an,
damit der 1. Kreisel in der Liste konsistent am Kreisel verbunden ist.
Args:
gefaellestrecke_nachbarn: Dict mit Drehungen und Kreisel-Positionen
richtung2: Richtung des zweiten Kreisels ("DEFAULT", "Vertikal", "Horizontal")
richtung0: Richtung des ersten Kreisels
am_kreisel: Kreisel-Index (0, 1 oder 2)
kreisel_verbunden: Anzahl verbundener Kreisel
hight_position: "higher" oder "lower"
Returns:
(rotation, drehung0, drehung1, hight_position)
"""
drehung0 = gefaellestrecke_nachbarn.get("Drehung0")
drehung1 = gefaellestrecke_nachbarn.get("Drehung1")
x0_kreisel = float(gefaellestrecke_nachbarn.get("x0"))
y0_kreisel = float(gefaellestrecke_nachbarn.get("y0"))
x1_kreisel = float(gefaellestrecke_nachbarn.get("x1"))
y1_kreisel = float(gefaellestrecke_nachbarn.get("y1"))
# DEFAULT → richtung0 verwenden, sonst richtung2
effektive_richtung = richtung0 if richtung2 == "DEFAULT" else richtung2
# Einheitliche Position- und Gefälle-Bestimmung (eliminiert Duplikation)
position = _bestimme_position(
effektive_richtung, x0_kreisel, x1_kreisel, y0_kreisel, y1_kreisel, hight_position
)
gefaelle = _bestimme_gefaelle(effektive_richtung, position)
if richtung2 == "DEFAULT":
# Vertausch wenn Position rechts/lower und Drehungen verschieden
if (
position in ("higher_rechts", "lower_rechts", "higher_lower", "lower_lower")
and drehung0 != drehung1
and am_kreisel == 0
):
drehung0, drehung1, hight_position = _tausche_drehungen_und_hoehe(
drehung0, drehung1, hight_position
)
# Austausch damit der 1. Kreisel in der Liste am Kreisel verbunden ist
if kreisel_verbunden == 1 and am_kreisel == 2:
am_kreisel = 1 # BUG FIX: war "am_kreisel == 1" (Vergleich statt Zuweisung)
drehung0, drehung1, hight_position = _tausche_drehungen_und_hoehe(
drehung0, drehung1, hight_position
)
else:
# Austausch wenn am_kreisel == 2
if am_kreisel == 2:
am_kreisel = 1
drehung0, drehung1, hight_position = _tausche_drehungen_und_hoehe(
drehung0, drehung1, hight_position
)
rotation = ROTATION_MAP[gefaelle]
return rotation, drehung0, drehung1, hight_position
@staticmethod
def ein_motor_oder_eine_umlenk(
x,
y,
start,
ende,
doc,
lib_doc,
hoehe_gefaelle,
block_Vario_Umlenkstation_500mm,
block_Vario_Motorstation_500mm,
blockname_motor_links,
blockname_umlenk_links,
hat_motor_0,
hat_umlenk_0,
tefkurve_0,
block,
umlenk_gerade,
motor_gerade,
):
"""
Platziert Motor- und/oder Umlenkstation mit optionalen Bögen.
Args:
x, y: Zentrum des Förderers
start, ende: Start-/End-Punkt der Gefällelinie
doc, lib_doc: DXF-Dokumente
hoehe_gefaelle: Höhe der Gefällelinie
block_Vario_Umlenkstation_500mm: Block-Name Umlenkstation rechts
block_Vario_Motorstation_500mm: Block-Name Motorstation rechts
blockname_motor_links: Block-Name Motorstation links
blockname_umlenk_links: Block-Name Umlenkstation links
hat_motor_0, hat_umlenk_0: Ob Motor/Umlenk vorhanden
tefkurve_0: "links" oder "rechts"
block: DXF-Block zum Hinzufügen
umlenk_gerade, motor_gerade: Ob Station gerade (keine Bögen)
Returns:
(start, ende) nach Platzierung
"""
# Bogen-Blöcke laden und Deltas extrahieren
block_Vario_Bogen_auf = "Vario_Bogen_auf_3°"
block_Vario_Bogen_ab = "Vario_Bogen_ab_3°"
block_Vario_Bogen_auf_links = block_Vario_Bogen_auf + "_links"
block_Vario_Bogen_ab_links = block_Vario_Bogen_ab + "_links"
ab_deltas = _laedt_bogen_deltas(block_Vario_Bogen_ab, lib_doc, doc)
auf_deltas = _laedt_bogen_deltas(block_Vario_Bogen_auf, lib_doc, doc)
block_methoden.turn_two_blocks_left(
doc,
block_Vario_Bogen_auf,
block_Vario_Bogen_ab,
block_Vario_Bogen_ab_links,
block_Vario_Bogen_auf_links,
)
# --- Motor-Station ---
if hat_motor_0:
ist_links = (tefkurve_0 == "links")
# Motor: tefkurve "links" → bogen_links + station_links
bogen_motor = block_Vario_Bogen_ab_links if ist_links else block_Vario_Bogen_ab
station_motor = blockname_motor_links if ist_links else block_Vario_Motorstation_500mm
station_motor_gerade = (
"Vario_Motorstation_500mm_links" if ist_links else "Vario_Motorstation_500mm"
)
if not motor_gerade:
start = _add_station_mit_bogen(
block, bogen_motor, station_motor, start,
x, y, hoehe_gefaelle,
ab_deltas["DELTA_SP_0"], ab_deltas["DELTA_SP_1"],
vorzeichen=-1, rotation_bogen=270,
)
else:
start = _add_station_gerade(
block, station_motor_gerade, start,
x, y, hoehe_gefaelle, vorzeichen=-1,
)
# --- Umlenk-Station ---
if hat_umlenk_0:
ist_links = (tefkurve_0 == "links")
# Umlenk: Bogen-Blockname ist INVERTIERT relativ zu tefkurve
bogen_umlenk = block_Vario_Bogen_auf if ist_links else block_Vario_Bogen_auf_links
station_umlenk = blockname_umlenk_links if ist_links else block_Vario_Umlenkstation_500mm
station_umlenk_gerade = (
"Vario_Umlenkstation_500mm_links" if ist_links else "Vario_Umlenkstation_500mm"
)
if not umlenk_gerade:
ende = _add_station_mit_bogen(
block, bogen_umlenk, station_umlenk, ende,
x, y, hoehe_gefaelle,
auf_deltas["DELTA_SP_0"], auf_deltas["DELTA_SP_1"],
vorzeichen=+1, rotation_bogen=90,
)
else:
ende = _add_station_gerade(
block, station_umlenk_gerade, ende,
x, y, hoehe_gefaelle, vorzeichen=+1,
)
return start, ende
@staticmethod
def hat_motor_umlenk_station(gefaelle_objekt, gefaellestrecke_nachbarn):
"""
Analysiert Nachbar-Daten und bestimmt Motor-/Umlenkstation-Zuordnung.
Untersucht ob an jedem Ende der Gefällelinie eine Motorstation
oder eine Umlenkstation angeschlossen ist.
Args:
gefaelle_objekt: Gefaellestrecke-Instanz mit h0, h1, drehung, x, y
gefaellestrecke_nachbarn: Dict mit Kurven- und Höhen-Informationen
Returns:
Dict mit hat_motor_0/1, hat_umlenk_0/1, tefkurve_0/1,
umlenk_gerade, motor_gerade
"""
hat_motor_0 = None
hat_motor_1 = None
hat_umlenk_0 = None
hat_umlenk_1 = None
tefkurve_0 = None
tefkurve_1 = None
umlenk_gerade = False
motor_gerade = False
upper_hoehe_gefaelle = gefaelle_objekt.h1
lower_hoehe_gefaelle = gefaelle_objekt.h0
rotation = gefaelle_objekt.drehung
x = gefaelle_objekt.x
y = gefaelle_objekt.y
if "Kurvenrichtung" not in gefaellestrecke_nachbarn:
return {
"hat_motor_0": hat_motor_0,
"hat_motor_1": hat_motor_1,
"hat_umlenk_0": hat_umlenk_0,
"hat_umlenk_1": hat_umlenk_1,
"tefkurve_0": tefkurve_0,
"tefkurve_1": tefkurve_1,
"umlenk_gerade": umlenk_gerade,
"motor_gerade": motor_gerade,
}
# --- Erster Nachbar ---
vario_hoehe_0 = float(gefaellestrecke_nachbarn.get("vario_hoehe_0"))
vario_hoehe_1 = float(gefaellestrecke_nachbarn.get("vario_hoehe_1"))
kurvenrichtung = gefaellestrecke_nachbarn.get("Kurvenrichtung")
tefkurve_0 = gefaellestrecke_nachbarn.get("Tefkurve")
x_angetrieben = gefaellestrecke_nachbarn.get("X_angetrieben")
y_angetrieben = gefaellestrecke_nachbarn.get("Y_angetrieben")
tefkurve_0 = _bestimme_tefkurve(kurvenrichtung, tefkurve_0)
hat_motor_0, hat_umlenk_0, ist_gerade_0 = _pruefe_motor_umlenk_an_kurve(
upper_hoehe_gefaelle, lower_hoehe_gefaelle,
vario_hoehe_0, vario_hoehe_1,
rotation, x, y, x_angetrieben, y_angetrieben,
)
# Gerade-Flag nur setzen im gleiche-Höhe-Fall
if ist_gerade_0:
if hat_umlenk_0:
umlenk_gerade = True
if hat_motor_0:
motor_gerade = True
# --- Zweiter Nachbar (falls vorhanden) ---
if "Kurvenrichtung_1" in gefaellestrecke_nachbarn:
vario_hoehe_0_1 = float(gefaellestrecke_nachbarn.get("vario_hoehe_0_1"))
vario_hoehe_1_1 = float(gefaellestrecke_nachbarn.get("vario_hoehe_1_1"))
kurvenrichtung_1 = gefaellestrecke_nachbarn.get("Kurvenrichtung_1")
tefkurve_1 = gefaellestrecke_nachbarn.get("Tefkurve_1")
x_angetrieben_1 = gefaellestrecke_nachbarn.get("X_angetrieben_1")
y_angetrieben_1 = gefaellestrecke_nachbarn.get("Y_angetrieben_1")
tefkurve_1 = _bestimme_tefkurve(kurvenrichtung_1, tefkurve_1)
hat_motor_1, hat_umlenk_1, ist_gerade_1 = _pruefe_motor_umlenk_an_kurve(
upper_hoehe_gefaelle, lower_hoehe_gefaelle,
vario_hoehe_0_1, vario_hoehe_1_1,
rotation, x, y, x_angetrieben_1, y_angetrieben_1,
)
if ist_gerade_1:
if hat_umlenk_1:
umlenk_gerade = True
if hat_motor_1:
motor_gerade = True
return {
"hat_motor_0": hat_motor_0,
"hat_motor_1": hat_motor_1,
"hat_umlenk_0": hat_umlenk_0,
"hat_umlenk_1": hat_umlenk_1,
"tefkurve_0": tefkurve_0,
"tefkurve_1": tefkurve_1,
"umlenk_gerade": umlenk_gerade,
"motor_gerade": motor_gerade,
}
+238
View File
@@ -0,0 +1,238 @@
# -*- coding: utf-8 -*-
"""
Unit-Tests für Gefaellestrecke (Refactored)
"""
import unittest
import math
from .Gefaellestrecke import (
Gefaellestrecke,
ROTATION_MAP,
_bestimme_position,
_bestimme_gefaelle,
_tausche_drehungen_und_hoehe,
_bestimme_tefkurve,
_ist_umlenk_position,
_pruefe_motor_umlenk_an_kurve,
)
class TestGefaellestreckeModell(unittest.TestCase):
"""Tests für das Gefaellestrecke Pydantic-Modell."""
def test_from_merkmale(self):
merkmale = {
"Höhe unten": "1.5",
"Höhe oben": "2.0",
"Länge in Meter": "5.0",
"Drehung": "-90",
"Anzahl der Scanner": "2",
"Anzahl der Separatoren": "1",
}
obj = Gefaellestrecke.from_merkmale("TEST-001", 100.0, 200.0, merkmale)
self.assertEqual(obj.teileid, "TEST-001")
self.assertEqual(obj.h0, 1500.0)
self.assertEqual(obj.h1, 2000.0)
self.assertEqual(obj.laenge, 5000.0)
self.assertEqual(obj.drehung, -90.0)
self.assertEqual(obj.anzahl_scanner, 2)
self.assertEqual(obj.anzahl_separatoren, 1)
def test_hight_zwischen(self):
obj = Gefaellestrecke(teileid="T", x=0, y=0, laenge=1000, h0=1000, h1=3000)
self.assertEqual(obj.hight_zwischen, 2000.0)
class TestBestimmePosition(unittest.TestCase):
"""Tests für _bestimme_position Hilfsfunktion."""
def test_vertikal_links(self):
# x0 < x1 → "_links"
result = _bestimme_position("Vertikal", 100, 200, 0, 0, "higher")
self.assertEqual(result, "higher_links")
def test_vertikal_rechts(self):
# x0 > x1 → "_rechts"
result = _bestimme_position("Vertikal", 300, 200, 0, 0, "lower")
self.assertEqual(result, "lower_rechts")
def test_horizontal_higher(self):
# y0 > y1 → "_higher"
result = _bestimme_position("Horizontal", 0, 0, 500, 100, "higher")
self.assertEqual(result, "higher_higher")
def test_horizontal_lower(self):
# y0 < y1 → "_lower"
result = _bestimme_position("Horizontal", 0, 0, 50, 500, "lower")
self.assertEqual(result, "lower_lower")
class TestBestimmeGefaelle(unittest.TestCase):
"""Tests für _bestimme_gefaelle Hilfsfunktion."""
def test_vertikal_links(self):
self.assertEqual(_bestimme_gefaelle("Vertikal", "lower_rechts"), "links")
self.assertEqual(_bestimme_gefaelle("Vertikal", "higher_links"), "links")
def test_vertikal_rechts(self):
self.assertEqual(_bestimme_gefaelle("Vertikal", "higher_rechts"), "rechts")
self.assertEqual(_bestimme_gefaelle("Vertikal", "lower_links"), "rechts")
def test_horizontal_oben(self):
self.assertEqual(_bestimme_gefaelle("Horizontal", "lower_lower"), "oben")
self.assertEqual(_bestimme_gefaelle("Horizontal", "higher_higher"), "oben")
def test_horizontal_unten(self):
self.assertEqual(_bestimme_gefaelle("Horizontal", "higher_lower"), "unten")
self.assertEqual(_bestimme_gefaelle("Horizontal", "lower_higher"), "unten")
class TestTauscheDrehungen(unittest.TestCase):
"""Tests für _tausche_drehungen_und_hoehe."""
def test_tausch_drehungen(self):
d0, d1, h = _tausche_drehungen_und_hoehe(10, 20, "higher")
self.assertEqual(d0, 20)
self.assertEqual(d1, 10)
self.assertEqual(h, "lower")
def test_tausch_invert_hoehe(self):
_, _, h = _tausche_drehungen_und_hoehe(0, 0, "lower")
self.assertEqual(h, "higher")
class TestBestimmeeTefkurve(unittest.TestCase):
"""Tests für _bestimme_tefkurve."""
def test_links_aussen_ist_rechts(self):
self.assertEqual(_bestimme_tefkurve("links", "außen"), "rechts")
def test_rechts_innen_ist_rechts(self):
self.assertEqual(_bestimme_tefkurve("rechts", "innen"), "rechts")
def test_links_innen_ist_links(self):
self.assertEqual(_bestimme_tefkurve("links", "innen"), "links")
def test_rechts_aussen_ist_links(self):
self.assertEqual(_bestimme_tefkurve("rechts", "außen"), "links")
class TestIstUmlenkPosition(unittest.TestCase):
"""Tests für _ist_umlenk_position."""
def test_rotation_minus90_x_kleiner(self):
# rotation == -90 und x < x_angetrieben → True (Umlenk)
self.assertTrue(_ist_umlenk_position(-90.0, 50, 100, 200, 100))
def test_rotation_minus90_x_groesser(self):
# rotation == -90 und x >= x_angetrieben → False (Motor)
self.assertFalse(_ist_umlenk_position(-90.0, 300, 100, 200, 100))
def test_rotation_0_wird_minus360(self):
# rotation == 0 → rotation_zwischen = -360
# -360 <= -360 < -270 und y > y_angetrieben → True
self.assertTrue(_ist_umlenk_position(0.0, 100, 500, 100, 100))
class TestPruefeMotorUmlenkAnKurve(unittest.TestCase):
"""Tests für _pruefe_motor_umlenk_an_kurve."""
def test_upper_hoeher_motor_passend(self):
# upper > lower, vario_hoehe_0 == upper → Motor
hat_motor, hat_umlenk, ist_gerade = _pruefe_motor_umlenk_an_kurve(
2000, 1000, 2000, 1500, 0, 0, 0, 0, 0
)
self.assertTrue(hat_motor)
self.assertFalse(hat_umlenk)
self.assertFalse(ist_gerade)
def test_upper_hoeher_umlenk(self):
# upper > lower, keine Höhe passt → Umlenk
hat_motor, hat_umlenk, ist_gerade = _pruefe_motor_umlenk_an_kurve(
2000, 1000, 1500, 1200, 0, 0, 0, 0, 0
)
self.assertFalse(hat_motor)
self.assertTrue(hat_umlenk)
self.assertFalse(ist_gerade)
def test_lower_hoeher_motor(self):
# upper < lower, vario_hoehe_1 == lower → Motor
hat_motor, hat_umlenk, ist_gerade = _pruefe_motor_umlenk_an_kurve(
1000, 2000, 1500, 2000, 0, 0, 0, 0, 0
)
self.assertTrue(hat_motor)
self.assertFalse(hat_umlenk)
def test_gleiche_hoehe_umlenk_position(self):
# gleiche Höhe, Umlenkposition → Umlenk gerade
hat_motor, hat_umlenk, ist_gerade = _pruefe_motor_umlenk_an_kurve(
1000, 1000, 800, 900, -90.0, 50, 100, 200, 100
)
self.assertFalse(hat_motor)
self.assertTrue(hat_umlenk)
self.assertTrue(ist_gerade)
def test_gleiche_hoehe_motor_position(self):
# gleiche Höhe, keine Umlenkposition → Motor gerade
hat_motor, hat_umlenk, ist_gerade = _pruefe_motor_umlenk_an_kurve(
1000, 1000, 800, 900, -90.0, 300, 100, 200, 100
)
self.assertTrue(hat_motor)
self.assertFalse(hat_umlenk)
self.assertTrue(ist_gerade)
class TestRotationMap(unittest.TestCase):
"""Tests für ROTATION_MAP Konstante."""
def test_alle_richtungen(self):
self.assertEqual(ROTATION_MAP["oben"], 0)
self.assertEqual(ROTATION_MAP["unten"], 180)
self.assertEqual(ROTATION_MAP["links"], 90)
self.assertEqual(ROTATION_MAP["rechts"], 270)
class TestRotationMitZweiVerbunden(unittest.TestCase):
"""Tests für rotation_mit_zwei_verbunden (Bug-Fix Zeile 112 getestet)."""
def _nachbarn(self):
return {
"Drehung0": 90, "Drehung1": 180,
"x0": "100", "y0": "200", "x1": "300", "y1": "400",
}
def test_default_richtung_vertikal(self):
# richtung2 == "DEFAULT", richtung0 == "Vertikal"
# x0(100) < x1(300) → "_links", position="higher_links"
# gefaelle="links" → rotation=90
rotation, d0, d1, hp = Gefaellestrecke.rotation_mit_zwei_verbunden(
self._nachbarn(), "DEFAULT", "Vertikal", 0, 0, "higher"
)
self.assertEqual(rotation, 90)
def test_nicht_default_richtung_horizontal(self):
# richtung2 != "DEFAULT", richtung2 == "Horizontal"
# y0(200) < y1(400) → "_lower", position="higher_lower"
# gefaelle="unten" → rotation=180
rotation, _, _, _ = Gefaellestrecke.rotation_mit_zwei_verbunden(
self._nachbarn(), "Horizontal", "Vertikal", 0, 0, "higher"
)
self.assertEqual(rotation, 180)
def test_bug_fix_am_kreisel_zuweisung(self):
# kreisel_verbunden=1, am_kreisel=2 → muss tauschen (Bug fix: war ==)
nachbarn = self._nachbarn()
# Drehungen verschieden damit Tausch sichtbar
nachbarn["Drehung0"] = 45
nachbarn["Drehung1"] = 135
rotation, d0, d1, hp = Gefaellestrecke.rotation_mit_zwei_verbunden(
nachbarn, "DEFAULT", "Vertikal", 2, 1, "higher"
)
# Drehungen sollten getauscht sein
self.assertEqual(d0, 135)
self.assertEqual(d1, 45)
self.assertEqual(hp, "lower")
if __name__ == "__main__":
unittest.main()
+177 -86
View File
@@ -1,29 +1,87 @@
# -*- coding: utf-8 -*-
"""
Kreisel — Modell und Zeichenfunktionen für Kreisel-Komponenten.
Refactoring-Änderungen:
- Pin-Zeichenlogik (4x copy-paste für 0°/90°/180°/270°) durch PIN_OFFSETS-Lookup-Tabelle ersetzt
- @staticmethod auf draw_kreisel_lines und draw_kreisel_drehrichtung_markierung
- Magic Number 50 durch benanntes Constant PIN_OFFSET ersetzt
- Richtungspfeil-Import aus Loop herausgezogen (war 6x idempotent aufgerufen)
- Unused variable bref entfernt
"""
import math
from ezdxf.entities import Line
import math
from pydantic import BaseModel, Field, field_validator
from typing import Optional
import plant2dxf
import block_methoden
from lib import block_methoden
ATTR_TAG = "TeileId" # Attributtag im Block
RADIUS = 400 # Radius der Kreiselkreise (mm)
# ============================================================================
# KONSTANTEN
# ============================================================================
ATTR_TAG = "TeileId" # Attributtag im Block
RADIUS = 400 # Radius der Kreiselkreise (mm)
PIN_OFFSET = 50 # Offset für Pin-Bereichsmarkierung (mm)
# Pin-Bereichs-Offsets pro Drehung: (p1a_dx, p1a_dy, p1b_dx, p1b_dy, p2a_dx, p2a_dy, p2b_dx, p2b_dy)
# Berechnet aus dem manuell kodierten Original für 0°/90°/180°/270°
PIN_OFFSETS = {
0.0: (
-(RADIUS + PIN_OFFSET), +PIN_OFFSET,
-(RADIUS + PIN_OFFSET), -PIN_OFFSET,
+(RADIUS + PIN_OFFSET), +PIN_OFFSET,
+(RADIUS + PIN_OFFSET), -PIN_OFFSET,
),
180.0: (
+(RADIUS + PIN_OFFSET), -PIN_OFFSET,
+(RADIUS + PIN_OFFSET), +PIN_OFFSET,
-(RADIUS + PIN_OFFSET), -PIN_OFFSET,
-(RADIUS + PIN_OFFSET), +PIN_OFFSET,
),
90.0: (
+PIN_OFFSET, +(RADIUS - PIN_OFFSET),
-PIN_OFFSET, +(RADIUS - PIN_OFFSET),
+PIN_OFFSET, -(RADIUS - PIN_OFFSET),
-PIN_OFFSET, -(RADIUS - PIN_OFFSET),
),
270.0: (
-PIN_OFFSET, -(RADIUS - PIN_OFFSET),
+PIN_OFFSET, -(RADIUS - PIN_OFFSET),
-PIN_OFFSET, +(RADIUS - PIN_OFFSET),
+PIN_OFFSET, +(RADIUS - PIN_OFFSET),
),
}
# ============================================================================
# KREISEL KLASSE
# ============================================================================
class Kreisel(BaseModel):
"""Pydantic-Modell für Kreisel-Komponenten."""
teileid: str
x: float = Field(description="X-Koordinate des Kreisel-Zentrums")
y: float = Field(description="Y-Koordinate des Kreisel-Zentrums")
hoehe: float = Field(description="Höhe in mm")
drehung: float = Field(default=0.0, description="Drehung/Winkel in Grad")
drehrichtung: Optional[str] = Field(default=None, description="Drehrichtung: UZS oder GUZS")
abstand: float = Field(default=20000.0, description="Abstand zwischen Kreiselachsen in mm")
kreiselart: Optional[str] = Field(default=None, description="Kreiselart, z.B. 'Pin'")
drehrichtung: Optional[str] = Field(
default=None, description="Drehrichtung: UZS oder GUZS"
)
abstand: float = Field(
default=20000.0, description="Abstand zwischen Kreiselachsen in mm"
)
kreiselart: Optional[str] = Field(
default=None, description="Kreiselart, z.B. 'Pin'"
)
anzahl_scanner: float = Field(default=0.0, description="Anzahl der Scanner")
anzahl_separatoren: float = Field(default=0.0, description="Anzahl der Separatoren")
@field_validator('abstand')
@field_validator("abstand")
@classmethod
def validate_abstand(cls, v):
"""Konvertiert Abstand von Meter zu mm, falls nötig."""
@@ -35,7 +93,7 @@ class Kreisel(BaseModel):
v = 10000.0 # Fallback 10 m
return v
@field_validator('hoehe')
@field_validator("hoehe")
@classmethod
def validate_hoehe(cls, v):
"""Konvertiert Höhe von Meter zu mm, falls nötig."""
@@ -63,7 +121,6 @@ class Kreisel(BaseModel):
@property
def richtung_rad(self) -> float:
"""Richtung in Radianten (für am_kreisel_direct_verbunden)."""
# Wird aus drehung abgeleitet oder separat gesetzt
return math.radians(self.drehung)
@property
@@ -86,7 +143,9 @@ class Kreisel(BaseModel):
return self.hoehe
@classmethod
def from_merkmale(cls, teileid: str, x: float, y: float, merkmale: dict) -> 'Kreisel':
def from_merkmale(
cls, teileid: str, x: float, y: float, merkmale: dict
) -> "Kreisel":
"""Erstellt ein Kreisel-Objekt aus einem merkmale-Dictionary."""
hoehe_m = merkmale.get("Höhe in m", "0").replace(",", ".")
try:
@@ -94,7 +153,9 @@ class Kreisel(BaseModel):
except (ValueError, TypeError):
hoehe = 0.0
abstand_m = merkmale.get("Abstand (Kreiselachse A - Kreiselachse) in Meter", "20").replace(",", ".")
abstand_m = merkmale.get(
"Abstand (Kreiselachse A - Kreiselachse) in Meter", "20"
).replace(",", ".")
try:
abstand = float(abstand_m) * 1000
except (ValueError, TypeError):
@@ -125,14 +186,25 @@ class Kreisel(BaseModel):
abstand=abstand,
kreiselart=merkmale.get("Kreiselart"),
anzahl_scanner=anzahl_scanner,
anzahl_separatoren=anzahl_separatoren
anzahl_separatoren=anzahl_separatoren,
)
@staticmethod
def draw_kreisel_lines(msp, pos1, pos2, kreisel):
"""Zeichnet tangentiale Linien zwischen zwei Kreiselblöcken, unabhängig vom Winkel."""
"""
Zeichnet tangentiale Linien zwischen zwei Kreiselblöcken, unabhängig vom Winkel.
Für Pin-Kreisel werden zusätzlich Pinbereichs-Begrenzungslinien gezeichnet,
die sich basierend auf der Drehung (0°/90°/180°/270°) verschieben.
Args:
msp: DXF-Modelspace
pos1, pos2: Positionen der beiden Kreisel-Blöcke (x, y, z)
kreisel: Kreisel-Instanz
"""
rotation = kreisel.drehung
x1, y1, z1 = pos1
x2, y2, z1 = pos2
x2, y2, _ = pos2 # z identisch für beide Blöcke
# Verbindungsvektor
dx = x2 - x1
dy = y2 - y1
@@ -141,62 +213,57 @@ class Kreisel(BaseModel):
if length == 0:
return # keine Linie bei identischen Punkten
# Normalenvektor (senkrecht, normiert, Länge = RADIUS)
nx = -dy / length * RADIUS
ny = dx / length * RADIUS
# Tangentialpunkte
p1a = (x1 + nx, y1 + ny,z1)
p1b = (x1 - nx, y1 - ny,z1)
p2a = (x2 + nx, y2 + ny,z1)
p2b = (x2 - nx, y2 - ny,z1)
if kreisel.kreiselart == "Pin":
if rotation == 0.0:
p1a2 = p1a[0] - RADIUS - 50, p1a[1] + 50, z1
p1b2 = p1b[0] - RADIUS - 50, p1b[1] - 50, z1
p2a2 = p2a[0] + RADIUS + 50, p2a[1] + 50, z1
p2b2 = p2b[0] + RADIUS + 50, p2b[1] - 50, z1
Line1 = Line.new(dxfattribs={"start": p1a2,"end": p2a2,"layer": "Pinbereich"})
Line2 = Line.new(dxfattribs={"start": p1b2,"end": p2b2,"layer": "Pinbereich"})
msp.add_entity(Line1)
msp.add_entity(Line2)
elif rotation == 180.0:
p1a2 = p1a[0] + RADIUS + 50, p1a[1] - 50, z1
p1b2 = p1b[0] + RADIUS + 50, p1b[1] + 50, z1
p2a2 = p2a[0] - RADIUS - 50, p2a[1] - 50, z1
p2b2 = p2b[0] - RADIUS - 50, p2b[1] + 50, z1
Line1 = Line.new(dxfattribs={"start": p1a2,"end": p2a2,"layer": "Pinbereich"})
Line2 = Line.new(dxfattribs={"start": p1b2,"end": p2b2,"layer": "Pinbereich"})
msp.add_entity(Line1)
msp.add_entity(Line2)
elif rotation == 90.0:
p1a2 = p1a[0] + 50, p1a[1] - 50 + RADIUS , z1
p1b2 = p1b[0] - 50, p1b[1] - 50 + RADIUS, z1
p2a2 = p2a[0] + 50, p2a[1] + 50 - RADIUS, z1
p2b2 = p2b[0] - 50, p2b[1] + 50 - RADIUS, z1
Line1 = Line.new(dxfattribs={"start": p1a2,"end": p2a2,"layer": "Pinbereich"})
Line2 = Line.new(dxfattribs={"start": p1b2,"end": p2b2,"layer": "Pinbereich"})
msp.add_entity(Line1)
msp.add_entity(Line2)
elif rotation == 270.0:
p1a2 = p1a[0] - 50, p1a[1] + 50 - RADIUS , z1
p1b2 = p1b[0] + 50, p1b[1] + 50 - RADIUS, z1
p2a2 = p2a[0] - 50, p2a[1] - 50 + RADIUS, z1
p2b2 = p2b[0] + 50, p2b[1] - 50 + RADIUS, z1
Line1 = Line.new(dxfattribs={"start": p1a2,"end": p2a2,"layer": "Pinbereich"})
Line2 = Line.new(dxfattribs={"start": p1b2,"end": p2b2,"layer": "Pinbereich"})
msp.add_entity(Line1)
msp.add_entity(Line2)
p1a = (x1 + nx, y1 + ny, z1)
p1b = (x1 - nx, y1 - ny, z1)
p2a = (x2 + nx, y2 + ny, z1)
p2b = (x2 - nx, y2 - ny, z1)
# Linien zeichnen
# Pin-Bereichsmarkierung (Lookup-Tabelle statt 4x copy-paste)
if kreisel.kreiselart == "Pin" and rotation in PIN_OFFSETS:
offsets = PIN_OFFSETS[rotation]
points = [p1a, p1b, p2a, p2b]
adjusted = [
(p[0] + offsets[i * 2], p[1] + offsets[i * 2 + 1], z1)
for i, p in enumerate(points)
]
p1a2, p1b2, p2a2, p2b2 = adjusted
msp.add_entity(Line.new(
dxfattribs={"start": p1a2, "end": p2a2, "layer": "Pinbereich"}
))
msp.add_entity(Line.new(
dxfattribs={"start": p1b2, "end": p2b2, "layer": "Pinbereich"}
))
# Hauptlinien zeichnen
msp.add_line(p1a, p2a)
msp.add_line(p1b, p2b)
def draw_kreisel_drehrichtung_markierung(msp, pos1, pos2, kreisel, lib_doc, doc, verbose):
@staticmethod
def draw_kreisel_drehrichtung_markierung(
msp, pos1, pos2, kreisel, lib_doc, doc, verbose
):
"""
Zeichnet Richtungspfeile für die Drehrichtung eines Kreisels.
Platziert 3 Pfeile auf der oberen und 3 auf der unteren Tangentiallinie,
mit invertierter Richtung zwischen oben und unten.
Args:
msp: DXF-Modelspace
pos1, pos2: Positionen der beiden Kreisel-Blöcke
kreisel: Kreisel-Instanz
lib_doc: Bibliotheks-Dokument
doc: DXF-Dokument
verbose: Ob Debug-Ausgabe aktiviert ist
"""
drehrichtung = (kreisel.drehrichtung or "").upper()
if drehrichtung not in ("UZS", "GUZS"):
return
x1, y1,z1= pos1
x2, y2,z2 = pos2
x1, y1, z1 = pos1
x2, y2, _ = pos2
dx = x2 - x1
dy = y2 - y1
length = math.hypot(dx, dy)
@@ -205,35 +272,59 @@ class Kreisel(BaseModel):
# Normalenvektor (senkrecht, normiert, Länge = RADIUS)
nx = -dy / length * RADIUS
ny = dx / length * RADIUS
# Obere Linie
# Obere und untere Tangentiallinien
p1_oben = (x1 + nx, y1 + ny)
p2_oben = (x2 + nx, y2 + ny)
# Untere Linie
p1_unten = (x1 - nx, y1 - ny)
p2_unten = (x2 - nx, y2 - ny)
# S-LP auf oberer Linie (Drehrichtung wie angegeben)
# Richtungspfeil einmal importieren (nicht 6x in der Schleife)
block_methoden.import_block("Richtungspfeil", lib_doc, doc)
blockref_layer, color = block_methoden.get_insert_color_layer(lib_doc, "Richtungspfeil")
# Obere Linie: Drehrichtung wie angegeben
Kreisel._place_richtungspfeile(
msp, p1_oben, p2_oben, z1, drehrichtung,
invert=False, blockref_layer=blockref_layer,
verbose=verbose, label="oben",
)
# Untere Linie: Drehrichtung invertiert
Kreisel._place_richtungspfeile(
msp, p1_unten, p2_unten, z1, drehrichtung,
invert=True, blockref_layer=blockref_layer,
verbose=verbose, label="unten",
)
@staticmethod
def _place_richtungspfeile(msp, p1, p2, z, drehrichtung, invert, blockref_layer, verbose, label):
"""
Platziert 3 Richtungspfeile entlang einer Tangentiallinie.
Args:
msp: DXF-Modelspace
p1, p2: Start-/End-Punkt der Linie (x, y)
z: Z-Koordinate
drehrichtung: "UZS" oder "GUZS"
invert: Ob die Richtung invertiert werden soll
blockref_layer: Layer für den Block-Reference
verbose: Ob Debug-Ausgabe aktiviert ist
label: "oben" oder "unten" für Logging
"""
for i in range(1, 4):
t = i / 4 # 1/4, 2/4, 3/4
px = p1_oben[0] + t * (p2_oben[0] - p1_oben[0])
py = p1_oben[1] + t * (p2_oben[1] - p1_oben[1])
rotation = math.degrees(math.atan2(p2_oben[1] - p1_oben[1], p2_oben[0] - p1_oben[0]))
if drehrichtung == "GUZS":
px = p1[0] + t * (p2[0] - p1[0])
py = p1[1] + t * (p2[1] - p1[1])
rotation = math.degrees(math.atan2(p2[1] - p1[1], p2[0] - p1[0]))
# Obere Linie: GUZS invertiert; Untere Linie: UZS invertiert
should_invert = (drehrichtung == "GUZS" and not invert) or (drehrichtung == "UZS" and invert)
if should_invert:
rotation += 180
block_methoden.import_block("Richtungspfeil", lib_doc, doc)
blockref_layer, color = block_methoden.get_insert_color_layer(lib_doc, "Richtungspfeil")
bref = msp.add_blockref("Richtungspfeil", (px, py,z1), dxfattribs={"rotation": rotation,"layer": blockref_layer})
msp.add_blockref(
"Richtungspfeil",
(px, py, z),
dxfattribs={"rotation": rotation, "layer": blockref_layer},
)
if verbose:
print(f"[INFO] Drehrichtung '{drehrichtung}': Richtungspfeil oben bei ({px:.1f}, {py:.1f}), rot={rotation:.1f}")
# S-LP auf unterer Linie (Drehrichtung invertiert)
for i in range(1, 4):
t = i / 4
px = p1_unten[0] + t * (p2_unten[0] - p1_unten[0])
py = p1_unten[1] + t * (p2_unten[1] - p1_unten[1])
rotation = math.degrees(math.atan2(p2_unten[1] - p1_unten[1], p2_unten[0] - p1_unten[0]))
if drehrichtung == "UZS":
rotation += 180
block_methoden.import_block("Richtungspfeil", lib_doc, doc)
blockref_layer, color = block_methoden.get_insert_color_layer( lib_doc, "Richtungspfeil")
bref = msp.add_blockref("Richtungspfeil", (px, py, z1), dxfattribs={"rotation": rotation , "layer": blockref_layer})
if verbose:
print(f"[INFO] Drehrichtung '{drehrichtung}':Richtungspfeil unten bei ({px:.1f}, {py:.1f}), rot={rotation:.1f}")
print(
f"[INFO] Drehrichtung '{drehrichtung}': Richtungspfeil {label} bei ({px:.1f}, {py:.1f}), rot={rotation:.1f}"
)
+91
View File
@@ -0,0 +1,91 @@
# -*- coding: utf-8 -*-
"""
Unit-Tests für Kreisel (Refactored)
"""
import unittest
import math
from .Kreisel import Kreisel, RADIUS, PIN_OFFSET, PIN_OFFSETS
class TestKreiselModell(unittest.TestCase):
"""Tests für das Kreisel Pydantic-Modell."""
def test_from_merkmale(self):
merkmale = {
"Höhe in m": "2,5", # Komma-Format
"Abstand (Kreiselachse A - Kreiselachse) in Meter": "15",
"Drehung": "90",
"Drehrichtung": "UZS",
"Kreiselart": "Pin",
"Anzahl der Scanner": "1",
"Anzahl der Separatoren": "0",
}
obj = Kreisel.from_merkmale("K-001", 1000.0, 2000.0, merkmale)
self.assertEqual(obj.teileid, "K-001")
self.assertEqual(obj.hoehe, 2500.0) # 2.5 * 1000
self.assertEqual(obj.abstand, 15000.0) # 15 * 1000
self.assertEqual(obj.drehung, 90.0)
self.assertEqual(obj.drehrichtung, "UZS")
self.assertEqual(obj.kreiselart, "Pin")
def test_halbabstand(self):
obj = Kreisel(teileid="K", x=0, y=0, hoehe=0, abstand=10000)
self.assertEqual(obj.halbabstand, 5000.0)
def test_pos1_pos2_bei_90_grad(self):
obj = Kreisel(teileid="K", x=1000, y=1000, hoehe=500, abstand=2000, drehung=90)
# Bei 90° → winkel_rad = radians(90) → cos=0, sin=1
# pos1 = (x - halbabstand*cos, y - halbabstand*sin, z) = (1000-0, 1000-1000, 500)
self.assertAlmostEqual(obj.pos1[0], 1000.0, places=3)
self.assertAlmostEqual(obj.pos1[1], 0.0, places=3)
# pos2 = (x + halbabstand*cos, y + halbabstand*sin, z) = (1000+0, 1000+1000, 500)
self.assertAlmostEqual(obj.pos2[0], 1000.0, places=3)
self.assertAlmostEqual(obj.pos2[1], 2000.0, places=3)
class TestPinOffsets(unittest.TestCase):
"""Tests für die PIN_OFFSETS Lookup-Tabelle."""
def test_alle_rotationen_vorhanden(self):
for rotation in (0.0, 90.0, 180.0, 270.0):
self.assertIn(rotation, PIN_OFFSETS)
def test_offset_laenge(self):
# Jeder Eintrag muss 8 Werte haben (4 Punkte × 2 Koordinaten)
for rotation, offsets in PIN_OFFSETS.items():
self.assertEqual(len(offsets), 8, f"Rotation {rotation}: erwartet 8 Offsets")
def test_rotation_0_p1a_offset(self):
# p1a bei 0°: (-(RADIUS+PIN_OFFSET), +PIN_OFFSET)
offsets = PIN_OFFSETS[0.0]
self.assertEqual(offsets[0], -(RADIUS + PIN_OFFSET))
self.assertEqual(offsets[1], +PIN_OFFSET)
def test_rotation_90_p2b_offset(self):
# p2b bei 90°: (-PIN_OFFSET, -(RADIUS-PIN_OFFSET))
offsets = PIN_OFFSETS[90.0]
self.assertEqual(offsets[6], -PIN_OFFSET)
self.assertEqual(offsets[7], -(RADIUS - PIN_OFFSET))
def test_rotation_180_symmetrie_zu_0(self):
# 180° ist X-Spiegelung von 0° mit Y-Inversion
off_0 = PIN_OFFSETS[0.0]
off_180 = PIN_OFFSETS[180.0]
# p1a bei 0°: (-(R+O), +O) → p1a bei 180°: (+(R+O), -O)
self.assertEqual(off_180[0], -off_0[0])
self.assertEqual(off_180[1], -off_0[1])
class TestKreiselKonstanten(unittest.TestCase):
"""Tests für Kreisel-Konstanten."""
def test_radius_wert(self):
self.assertEqual(RADIUS, 400)
def test_pin_offset_wert(self):
self.assertEqual(PIN_OFFSET, 50)
if __name__ == "__main__":
unittest.main()
+156 -76
View File
@@ -1,94 +1,172 @@
# -*- coding: utf-8 -*-
"""
Omniflo — Modell und Erstellungsfunktionen für Omniflo-Kettenförderer.
Refactoring-Änderungen:
- 8x try/except-Boilerplate durch _safe_float/_safe_int Hilfsfunktionen ersetzt
- @staticmethod auf Omniflo_geraden_erstellung und omniflo_foerdererstellung
- Bug fix: omniflo_foerdererstellung nutzte rotation (Grad) statt winkel_rad in sin/cos
- Duplizierte dx/dy-Berechnung vereinheitlicht
"""
import math
from ezdxf.entities import Line
import math
from pydantic import BaseModel, Field, field_validator
from pydantic import BaseModel, Field
from typing import Optional
import plant2dxf
import block_methoden
from lib import block_methoden
# ============================================================================
# HILFSFUNKTIONEN
# ============================================================================
def _safe_float(value, default=0.0):
"""
Konvertiert einen Wert zu float mit Fallback.
Behandelt automatisch Komma-als-Dezimaltrennzeichen (z.B. "3,5" → 3.5).
Args:
value: Zu konvertierender Wert (str, int, float oder None)
default: Rückgabewert bei Konvertierungsfehler
Returns:
Float-Wert oder default
"""
try:
if value is None:
return default
if isinstance(value, str):
value = value.replace(",", ".")
return float(value)
except (TypeError, ValueError):
return default
def _safe_int(value, default=0):
"""
Konvertiert einen Wert zu int mit Fallback (über float-Zwischenschritt).
Args:
value: Zu konvertierender Wert
default: Rückgabewert bei Konvertierungsfehler
Returns:
Int-Wert oder default
"""
try:
if value is None:
return default
return int(float(value))
except (TypeError, ValueError):
return default
# ============================================================================
# OMNIFLO KLASSE
# ============================================================================
class Omniflo(BaseModel):
teileid:str
x:float
y:float
sivasnummer:str
laenge: Optional [float]
"""Modell für Omniflo-Kettenförderer."""
teileid: str
x: float
y: float
sivasnummer: str
laenge: Optional[float]
drehung: float
hoehe : Optional[float]
h0: Optional[float] = Field(default = 0.0,description="Höhe unten im CSV")
h1: Optional[float] = Field(default = 0.0, description="Höhe Oben im CSV")
anzahl_scanner: Optional [int] = Field(default=0, description="Anzahl der Scanner")
anzahl_stopper: Optional [int] = Field(default=0, description="Anzahl der Separatoren")
hoehe: Optional[float]
h0: Optional[float] = Field(default=0.0, description="Höhe unten im CSV")
h1: Optional[float] = Field(default=0.0, description="Höhe Oben im CSV")
anzahl_scanner: Optional[int] = Field(default=0, description="Anzahl der Scanner")
anzahl_stopper: Optional[int] = Field(
default=0, description="Anzahl der Separatoren"
)
@property
def hight_zwischen(self):
return ((self.h0 + self.h1) /2)
"""Mittlere Höhe zwischen h0 und h1."""
return (self.h0 + self.h1) / 2
@classmethod
def from_merkmale(cls, teileid,x,y, merkmale):
sivasnummer = merkmale.get("SivasNummer")
try:
laenge = float(merkmale.get("Länge in Meter", "0").replace(",", ".")) * 1000 # Meter → mm
except Exception:
laenge = 0
try:
winkel = float(merkmale.get("Drehung"))
except Exception:
winkel = 0.0
try:
hoehe = float(merkmale.get("Höhe"))
except Exception:
hoehe = 0.0
try:
h0 = float(merkmale.get("Höhe unten"))
except Exception:
h0 = 0.0
try:
h1 = float(merkmale.get("Höhe oben"))
except Exception:
h1 = 0.0
try:
anzahl_scanner = float(merkmale.get("Anzahl der Scanner", "0"))
except Exception:
anzahl_scanner = 0
try:
anzahl_separatoren = float(merkmale.get("Anzahl der Separatoren", "0"))
except Exception:
anzahl_separatoren = 0
def from_merkmale(cls, teileid, x, y, merkmale):
"""
Erstellt Omniflo aus einem Merkmale-Dictionary.
Verwendet _safe_float/_safe_int für robuste Konvertierung
statt 8x wiederholtem try/except-Muster.
Args:
teileid: Teile-Identifikator
x, y: Koordinaten in mm
merkmale: Dictionary mit Eigenschaftswerten
Returns:
Omniflo-Instanz
"""
return cls(
teileid= teileid,
teileid=teileid,
x=x,
y=y,
sivasnummer = sivasnummer,
laenge = laenge,
drehung = winkel,
hoehe = hoehe,
h0 = h0,
h1 = h1,
anzahl_scanner = anzahl_scanner,
anzahl_stopper = anzahl_separatoren
sivasnummer=merkmale.get("SivasNummer"),
laenge=_safe_float(merkmale.get("Länge in Meter"), 0.0) * 1000, # Meter → mm
drehung=_safe_float(merkmale.get("Drehung"), 0.0),
hoehe=_safe_float(merkmale.get("Höhe"), 0.0),
h0=_safe_float(merkmale.get("Höhe unten"), 0.0),
h1=_safe_float(merkmale.get("Höhe oben"), 0.0),
anzahl_scanner=_safe_int(merkmale.get("Anzahl der Scanner"), 0),
anzahl_stopper=_safe_int(merkmale.get("Anzahl der Separatoren"), 0),
)
@staticmethod
def Omniflo_geraden_erstellung(msp, doc, tefsivas, omniflo_objekt):
"""Erstellung der Tef gerade und Omniflo gerade"""
"""
Erstellung der Tef gerade und Omniflo gerade.
Zeichnet eine Linie zwischen Start- und Endpunkt basierend auf
Länge und Drehung des Omniflo-Objekts.
Args:
msp: DXF-Modelspace
doc: DXF-Dokument
tefsivas: Sivas-Nummer der Tef-Strecke
omniflo_objekt: Omniflo-Instanz
"""
winkel_rad = math.radians(omniflo_objekt.drehung)
halbe_laenge = omniflo_objekt.laenge / 2
x = omniflo_objekt.x
y = omniflo_objekt.y
# Man muss bei sin -1 machen wegen des links koordinaten system
# Koordinatensystem: sin mit -1 wegen links-Koordinatensystem
dx = halbe_laenge * math.sin(winkel_rad * -1)
dy = halbe_laenge * math.cos(winkel_rad)
start = (x + dx, y + dy, omniflo_objekt.h1 )
ende = (x - dx, y - dy, omniflo_objekt.h0)
start = (x + dx, y + dy, omniflo_objekt.h1)
ende = (x - dx, y - dy, omniflo_objekt.h0)
if "A-2" not in doc.layers:
doc.layers.add(name="A-2", color=2)
if "F-1" not in doc.layers:
doc.layers.add(name="F-1", color =1)
linie=msp.add_line(start, ende)
doc.layers.add(name="F-1", color=1)
linie = msp.add_line(start, ende)
if omniflo_objekt.sivasnummer == tefsivas:
linie.dxf.layer = "F-1"
else:
linie.dxf.layer = "A-2"
@staticmethod
def omniflo_foerdererstellung(msp, doc, lib_doc, omniflo_objekt):
""""Erstellung des Kettenförderers aktuell nur grundriss"""
block_methoden.import_block("bogen1",lib_doc,doc)
block_methoden.import_block("bogen2",lib_doc,doc)
"""
Erstellung des Kettenförderers (aktuell nur Grundriss).
Args:
msp: DXF-Modelspace
doc: DXF-Dokument
lib_doc: Bibliotheks-Dokument
omniflo_objekt: Omniflo-Instanz
"""
block_methoden.import_block("bogen1", lib_doc, doc)
block_methoden.import_block("bogen2", lib_doc, doc)
x = omniflo_objekt.x
y = omniflo_objekt.y
rotation = omniflo_objekt.drehung
@@ -96,21 +174,23 @@ class Omniflo(BaseModel):
h0 = omniflo_objekt.h0
h1 = omniflo_objekt.h1
h_zwischen = omniflo_objekt.hight_zwischen
blockname = (f"OF_Förderer_{laenge}_{h_zwischen}")
blockname = f"OF_Förderer_{laenge}_{h_zwischen}"
winkel_rad = math.radians(rotation)
halbe_laenge = laenge / 2
# Man muss bei sin -1 machen wegen des links koordinaten system
dx = halbe_laenge * math.sin(rotation * -1)
dy = halbe_laenge * math.cos(rotation)
start = (x + dx, y + dy, h1 )
ende = (x - dx, y - dy, h0)
# BUG FIX: war `math.sin(rotation * -1)` — rotation ist in Grad!
# Muss winkel_rad (Radianten) verwenden wie in Omniflo_geraden_erstellung
dx = halbe_laenge * math.sin(winkel_rad * -1)
dy = halbe_laenge * math.cos(winkel_rad)
start = (x + dx, y + dy, h1)
ende = (x - dx, y - dy, h0)
if blockname not in doc.blocks:
block = doc.blocks.new(blockname, base_point=(0,0,0))
block.add_blockref("bogen1",start)
block.add_blockref("bogen2",ende)
line = Line.new(dxfattribs={"start": start,"end":ende})
block = doc.blocks.new(blockname, base_point=(0, 0, 0))
block.add_blockref("bogen1", start)
block.add_blockref("bogen2", ende)
line = Line.new(dxfattribs={"start": start, "end": ende})
copy = line.copy()
copy.translate(-x,-y,-h_zwischen)
copy.translate(-x, -y, -h_zwischen)
block.add_entity(copy)
msp.add_blockref(blockname,(x,y,h_zwischen),dxfattribs={"rotation": rotation})
msp.add_blockref(
blockname, (x, y, h_zwischen), dxfattribs={"rotation": rotation}
)
+94
View File
@@ -0,0 +1,94 @@
# -*- coding: utf-8 -*-
"""
Unit-Tests für Omniflo (Refactored)
"""
import unittest
from .Omniflo import Omniflo, _safe_float, _safe_int
class TestSafeFloat(unittest.TestCase):
"""Tests für _safe_float Hilfsfunktion."""
def test_normaler_string(self):
self.assertEqual(_safe_float("3.14"), 3.14)
def test_komma_als_dezimaltrenner(self):
self.assertEqual(_safe_float("3,14"), 3.14)
def test_none_gibt_default(self):
self.assertEqual(_safe_float(None, 42.0), 42.0)
def test_ungueltiger_string_gibt_default(self):
self.assertEqual(_safe_float("abc", 1.0), 1.0)
def test_int_input(self):
self.assertEqual(_safe_float(5), 5.0)
def test_float_input(self):
self.assertEqual(_safe_float(2.5), 2.5)
class TestSafeInt(unittest.TestCase):
"""Tests für _safe_int Hilfsfunktion."""
def test_normaler_string(self):
self.assertEqual(_safe_int("3"), 3)
def test_float_string_wird_getruncated(self):
self.assertEqual(_safe_int("3.7"), 3)
def test_none_gibt_default(self):
self.assertEqual(_safe_int(None, 7), 7)
def test_ungueltiger_string(self):
self.assertEqual(_safe_int("xyz", 0), 0)
class TestOmnifloModell(unittest.TestCase):
"""Tests für das Omniflo Modell."""
def test_from_merkmale_vollstaendig(self):
merkmale = {
"SivasNummer": "SV-123",
"Länge in Meter": "10,5", # Komma-Format
"Drehung": "-180",
"Höhe": "3.0",
"Höhe unten": "1.0",
"Höhe oben": "2.0",
"Anzahl der Scanner": "2",
"Anzahl der Separatoren": "1",
}
obj = Omniflo.from_merkmale("TEST-001", 50.0, 60.0, merkmale)
self.assertEqual(obj.teileid, "TEST-001")
self.assertEqual(obj.sivasnummer, "SV-123")
self.assertAlmostEqual(obj.laenge, 10500.0) # 10.5 * 1000
self.assertEqual(obj.drehung, -180.0)
self.assertEqual(obj.hoehe, 3.0)
self.assertEqual(obj.h0, 1.0)
self.assertEqual(obj.h1, 2.0)
self.assertEqual(obj.anzahl_scanner, 2)
self.assertEqual(obj.anzahl_stopper, 1)
def test_from_merkmale_fehlende_werte(self):
# Fehlende optionale Felder → defaults
merkmale = {
"SivasNummer": "SV-001",
}
obj = Omniflo.from_merkmale("TEST-002", 0.0, 0.0, merkmale)
self.assertEqual(obj.laenge, 0.0) # None * 1000 → default 0 * 1000
self.assertEqual(obj.drehung, 0.0)
self.assertEqual(obj.hoehe, 0.0)
def test_hight_zwischen(self):
merkmale = {
"SivasNummer": "SV-001",
"Höhe unten": "1.0",
"Höhe oben": "3.0",
}
obj = Omniflo.from_merkmale("TEST-003", 0.0, 0.0, merkmale)
self.assertEqual(obj.hight_zwischen, 2.0)
if __name__ == "__main__":
unittest.main()
File diff suppressed because it is too large Load Diff
+284
View File
@@ -0,0 +1,284 @@
# -*- coding: utf-8 -*-
"""
Unit-Tests für VarioFoerderer (Refactored)
Diese Tests können direkt in der Library-Methode verwendet werden
oder als separate Test-Datei ausgeführt werden.
"""
import unittest
import math
from unittest.mock import Mock, MagicMock
from .VarioFoerderer import (
VarioFoerderer,
Foerderrichtung,
MotorUmlenkConfig,
BogenDeltas,
OffsetResult,
_parse_delta_attribute,
_calculate_offset_with_rotation,
_is_es_as_element,
horizontale_ausrichtung,
)
class TestMotorUmlenkConfig(unittest.TestCase):
"""Tests für MotorUmlenkConfig Dataclass"""
def test_motor_offset_berechnung(self):
"""Test: Motor-Offset-Berechnung"""
config = MotorUmlenkConfig(
winkel_motor=45.0,
winkel_umlenk=30.0,
umlenk_laenge=(100.0, 50.0),
motor_laenge=(80.0, 40.0),
vario_abstand=25.0
)
# Motor-Offsets
expected_motor_x = 100.0 * math.cos(math.radians(45.0))
expected_motor_z = 100.0 * math.sin(math.radians(45.0))
self.assertAlmostEqual(config.motor_offset_x, expected_motor_x, places=5)
self.assertAlmostEqual(config.motor_offset_z, expected_motor_z, places=5)
def test_umlenk_offset_berechnung(self):
"""Test: Umlenk-Offset-Berechnung"""
config = MotorUmlenkConfig(
winkel_motor=45.0,
winkel_umlenk=30.0,
umlenk_laenge=(100.0, 50.0),
motor_laenge=(80.0, 40.0),
vario_abstand=25.0
)
# Umlenk-Offsets
expected_umlenk_x = 80.0 * math.cos(math.radians(30.0))
expected_umlenk_z = 80.0 * math.sin(math.radians(30.0))
self.assertAlmostEqual(config.umlenk_offset_x, expected_umlenk_x, places=5)
self.assertAlmostEqual(config.umlenk_offset_z, expected_umlenk_z, places=5)
class TestBogenDeltas(unittest.TestCase):
"""Tests für BogenDeltas Dataclass"""
def test_normalize_negative_values(self):
"""Test: Normalisierung negativer Werte"""
deltas = BogenDeltas(
SP_0=[-10.0, -20.0, 5.0],
SP_1=[15.0, -25.0, 10.0],
VP_0=[-5.0, 8.0, -3.0],
VP_1=[12.0, -15.0, 7.0]
)
deltas.normalize_negative_values()
# X und Y (Index 0 und 1) sollen positiv sein, Z (Index 2) unverändert
self.assertEqual(deltas.SP_0, [10.0, 20.0, 5.0])
self.assertEqual(deltas.SP_1, [15.0, 25.0, 10.0])
self.assertEqual(deltas.VP_0, [5.0, 8.0, -3.0])
self.assertEqual(deltas.VP_1, [12.0, 15.0, 7.0])
class TestVarioFoerderer(unittest.TestCase):
"""Tests für VarioFoerderer Modell"""
def test_from_merkmale(self):
"""Test: VarioFoerderer aus Merkmale-Dict erstellen"""
merkmale = {
"Höhe Anfang": "1.5",
"Höhe Ende": "2.0",
"Länge in Meter": "5.0",
"Winkel": "6",
"Motorstation_hinten": True,
"Spannstation_vorn": False,
"Drehung": "-90",
"Förderrichtung": "Auf",
"Anzahl der Scanner": "2",
"Anzahl der Separatoren": "3",
}
foerderer = VarioFoerderer.from_merkmale("TEST-001", 100.0, 200.0, merkmale)
self.assertEqual(foerderer.teileid, "TEST-001")
self.assertEqual(foerderer.x, 100.0)
self.assertEqual(foerderer.y, 200.0)
self.assertEqual(foerderer.h0, 1500.0) # 1.5 * 1000
self.assertEqual(foerderer.h1, 2000.0) # 2.0 * 1000
self.assertEqual(foerderer.laenge, 5000.0) # 5.0 * 1000
self.assertEqual(foerderer.winkel, 6.0)
self.assertTrue(foerderer.hat_motor)
self.assertFalse(foerderer.hat_umlenk)
self.assertEqual(foerderer.drehung, -90.0)
self.assertEqual(foerderer.foerderer_richtung, "Auf")
self.assertEqual(foerderer.anzahl_scanner, 2)
self.assertEqual(foerderer.anzahl_separatoren, 3)
def test_hight_zwischen(self):
"""Test: Berechnung der mittleren Höhe"""
merkmale = {
"Höhe Anfang": "1.0",
"Höhe Ende": "3.0",
"Länge in Meter": "1.0",
"Winkel": "3",
"Motorstation_hinten": False,
"Spannstation_vorn": False,
"Drehung": "0",
"Förderrichtung": "Auf",
"Anzahl der Scanner": "0",
"Anzahl der Separatoren": "0",
}
foerderer = VarioFoerderer.from_merkmale("TEST-002", 0, 0, merkmale)
expected_height = (1000.0 + 3000.0) / 2 # (h0 + h1) / 2
self.assertEqual(foerderer.hight_zwischen, expected_height)
class TestHilfsfunktionen(unittest.TestCase):
"""Tests für generische Hilfsfunktionen"""
def test_parse_delta_attribute(self):
"""Test: Delta-Attribut parsen"""
attrib_dict = {
"DELTA_SP_0": "10.5;20.3;-5.7",
"DELTA_VP_1": "15.2,25.8,-8.1"
}
result_sp = _parse_delta_attribute(attrib_dict, "DELTA_SP_0")
result_vp = _parse_delta_attribute(attrib_dict, "DELTA_VP_1")
self.assertEqual(result_sp, [10.5, 20.3, -5.7])
self.assertEqual(result_vp, [15.2, 25.8, -8.1])
def test_calculate_offset_with_rotation(self):
"""Test: Offset-Berechnung mit Rotation"""
SP = [10.0, 5.0, 8.0]
VP = [2.0, 5.0, 3.0]
winkel = 45.0
result = _calculate_offset_with_rotation(SP, VP, winkel)
# Erwartete Werte berechnen
rad = math.radians(45.0)
expected_x = (SP[0] - VP[0]) * math.cos(rad) + (SP[2] - VP[2]) * math.sin(rad)
expected_y = VP[1]
expected_z = -(SP[0] - VP[0]) * math.sin(rad) + (SP[2] - VP[2]) * math.cos(rad)
self.assertAlmostEqual(result[0], expected_x, places=5)
self.assertAlmostEqual(result[1], expected_y, places=5)
self.assertAlmostEqual(result[2], expected_z, places=5)
def test_is_es_as_element_true(self):
"""Test: ES/AS-Element erkennen"""
entity = Mock()
entity.dxftype.return_value = "INSERT"
entity.dxf.name = "200000146_ES-Element_90_rechts_modified"
self.assertTrue(_is_es_as_element(entity))
def test_is_es_as_element_false(self):
"""Test: Kein ES/AS-Element"""
entity = Mock()
entity.dxftype.return_value = "INSERT"
entity.dxf.name = "Vario_Bogen_auf_12°"
self.assertFalse(_is_es_as_element(entity))
def test_is_es_as_element_not_insert(self):
"""Test: Kein INSERT-Entity"""
entity = Mock()
entity.dxftype.return_value = "LINE"
self.assertFalse(_is_es_as_element(entity))
class TestHorizontaleAusrichtung(unittest.TestCase):
"""Tests für horizontale_ausrichtung Funktion"""
def test_horizontal_rotation_0(self):
"""Test: Horizontale Ausrichtung bei Rotation 0°"""
merkmale = {
"Höhe Anfang": "1.0",
"Höhe Ende": "1.0",
"Länge in Meter": "1.0",
"Winkel": "3",
"Motorstation_hinten": False,
"Spannstation_vorn": False,
"Drehung": "0",
"Förderrichtung": "Horizontal",
"Anzahl der Scanner": "0",
"Anzahl der Separatoren": "0",
}
foerderer = VarioFoerderer.from_merkmale("TEST", 100.0, 200.0, merkmale)
# Förderer ist über Kreisel (y > y_kreisel)
result = horizontale_ausrichtung(foerderer, 100.0, 100.0)
self.assertEqual(result, "unten_drehung_0_or_-90")
# Förderer ist unter Kreisel (y < y_kreisel)
result = horizontale_ausrichtung(foerderer, 100.0, 300.0)
self.assertEqual(result, "oben_drehung_0_or_-90")
def test_nicht_horizontal(self):
"""Test: Nicht-horizontale Förderer geben 'None' zurück"""
merkmale = {
"Höhe Anfang": "1.0",
"Höhe Ende": "2.0",
"Länge in Meter": "1.0",
"Winkel": "6",
"Motorstation_hinten": False,
"Spannstation_vorn": False,
"Drehung": "0",
"Förderrichtung": "Auf",
"Anzahl der Scanner": "0",
"Anzahl der Separatoren": "0",
}
foerderer = VarioFoerderer.from_merkmale("TEST", 100.0, 200.0, merkmale)
result = horizontale_ausrichtung(foerderer, 100.0, 100.0)
self.assertEqual(result, "None")
class TestFoerderrichtungEnum(unittest.TestCase):
"""Tests für Foerderrichtung Enum"""
def test_enum_werte(self):
"""Test: Enum-Werte sind korrekt"""
self.assertEqual(Foerderrichtung.AUF.value, "Auf")
self.assertEqual(Foerderrichtung.AB.value, "Ab")
self.assertEqual(Foerderrichtung.HORIZONTAL.value, "Horizontal")
def test_enum_vergleich(self):
"""Test: Enum-Vergleich funktioniert"""
richtung = "Auf"
self.assertEqual(richtung, Foerderrichtung.AUF.value)
# ============================================================================
# Test-Runner
# ============================================================================
def run_tests():
"""Führt alle Unit-Tests aus"""
loader = unittest.TestLoader()
suite = loader.loadTestsFromModule(__import__(__name__))
runner = unittest.TextTestRunner(verbosity=2)
result = runner.run(suite)
print("\n" + "="*70)
print(f"Tests durchgeführt: {result.testsRun}")
print(f"Erfolgreich: {result.testsRun - len(result.failures) - len(result.errors)}")
print(f"Fehlgeschlagen: {len(result.failures)}")
print(f"Errors: {len(result.errors)}")
print("="*70)
return result.wasSuccessful()
if __name__ == "__main__":
success = run_tests()
exit(0 if success else 1)
View File
View File
+3 -2
View File
@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
import os
import sys
import csv
@@ -5,8 +6,8 @@ import json
import re
import configparser
from pathlib import Path
from utils import check_environment_var, setup_logger
from Elemente import Kreisel, VarioFoerderer,Gefaehllestrecke,Angetriebene_Kurve,Bt_element,Omniflo, Eckrad
from lib.utils import check_environment_var, setup_logger
from lib.Elemente import Kreisel, VarioFoerderer,Gefaellestrecke,Angetriebene_Kurve,Bt_element,Omniflo, Eckrad
# --------------------------------------------------------- CFG-Leser für shapes.cfg
def get_shape_cfg(teileart, cfg_path, logger=None):
parser = configparser.ConfigParser()
+715 -306
View File
File diff suppressed because it is too large Load Diff
+87 -43
View File
@@ -1,24 +1,35 @@
# -*- coding: utf-8 -*-
import math
import plant2dxf
from lib import plant2dxf
from ezdxf import units
from ezdxf.entities import Line
from ezdxf.addons import importer
from ezdxf.math import Matrix44, X_AXIS,Y_AXIS,Z_AXIS
def dreh_block(block_name: str, to_doc,lib_doc, winkel) :
"""Nimmt ein schon importierten Block und erstellt einen neuen der an der y_axis oder x_axis gedreht wird
"""
dreh_block_name = block_name +f"_{math.degrees(winkel)}"
layer, color = get_insert_color_layer(lib_doc,block_name)
if (dreh_block_name in to_doc.blocks):
return dreh_block_name
block_zwischen = to_doc.blocks.new(name=block_name + f"zwischenschrit_{winkel}", base_point=(0,0,0))
block = to_doc.blocks.new(name=dreh_block_name, base_point=(0,0,0))
if block_name == "200000146_ES-Element_90_rechts" or block_name =="400102632_ES-Element_90_links" or block_name =="200000241_AS-Element_90_rechts" or block_name =="200000217_AS-Element_90_links":
from ezdxf.math import Matrix44, X_AXIS, Y_AXIS, Z_AXIS
def dreh_block(block_name: str, to_doc, lib_doc, winkel):
"""Nimmt ein schon importierten Block und erstellt einen neuen der an der y_axis oder x_axis gedreht wird"""
dreh_block_name = block_name + f"_{math.degrees(winkel)}"
layer, color = get_insert_color_layer(lib_doc, block_name)
if dreh_block_name in to_doc.blocks:
return dreh_block_name
block_zwischen = to_doc.blocks.new(
name=block_name + f"zwischenschrit_{winkel}", base_point=(0, 0, 0)
)
block = to_doc.blocks.new(name=dreh_block_name, base_point=(0, 0, 0))
if (
block_name == "200000146_ES-Element_90_rechts"
or block_name == "400102632_ES-Element_90_links"
or block_name == "200000241_AS-Element_90_rechts"
or block_name == "200000217_AS-Element_90_links"
):
rotation_matrix = Matrix44.axis_rotate(X_AXIS, winkel)
else:
rotation_matrix = Matrix44.axis_rotate(Y_AXIS, winkel)
block_zwischen.add_blockref(block_name,(0,0,0),dxfattribs={"layer":layer,"color": color})
block_zwischen.add_blockref(
block_name, (0, 0, 0), dxfattribs={"layer": layer, "color": color}
)
for e in block_zwischen:
copy = e.copy()
copy.transform(rotation_matrix)
@@ -26,14 +37,14 @@ def dreh_block(block_name: str, to_doc,lib_doc, winkel) :
return dreh_block_name
def import_block(block_name: str, from_doc, to_doc, winkel = None) -> None:
def import_block(block_name: str, from_doc, to_doc, winkel=None) -> None:
"""Importiert Blockdefinition block_name von from_doc nach to_doc.
- Kopiert alle Entities des Blocks
- Stellt sicher, dass benutzte Layer im Ziel existieren (mit Eigenschaften)
- Übernimmt Basis­punkt und Block-Layer, falls vorhanden
"""
msp2 = from_doc.modelspace()
src = from_doc.blocks[block_name]
att_def = {}
if block_name == "Pinbereich":
@@ -41,16 +52,16 @@ def import_block(block_name: str, from_doc, to_doc, winkel = None) -> None:
# Alle Linientypen importieren
imp.import_table("linetypes")
if ((block_name in to_doc.blocks)):
if block_name in to_doc.blocks:
# speichern der attdef elemente in eine Liste falks diese verhanden sind und gibt diese zurück
for ent in src:
copy = ent.copy()
if ent.dxftype() == "ATTDEF":
att_def[ent.dxf.tag] =ent.dxf.text
att_def[ent.dxf.tag] = ent.dxf.text
if ent.dxftype() == "INSERT":
import_block(ent.dxf.name,from_doc, to_doc,None)
import_block(ent.dxf.name, from_doc, to_doc, None)
if att_def != {}:
return att_def
@@ -92,14 +103,15 @@ def import_block(block_name: str, from_doc, to_doc, winkel = None) -> None:
copy = ent.copy()
if ent.dxftype() == "ATTDEF":
att_def[ent.dxf.tag] =ent.dxf.text
att_def[ent.dxf.tag] = ent.dxf.text
if ent.dxftype() == "INSERT":
import_block(ent.dxf.name,from_doc, to_doc,None)
import_block(ent.dxf.name, from_doc, to_doc, None)
tgt.add_entity(copy)
if att_def != {}:
return att_def
def get_insert_color_layer(lib_doc, blockname):
"""Gibt den Layer und die Color für den Jeweiligen block in der Libary datei zurück"""
msp_lib = lib_doc.modelspace()
@@ -110,34 +122,66 @@ def get_insert_color_layer(lib_doc, blockname):
color = insert.dxf.color
layer = insert.dxf.layer
return layer, color
def rotatate_and_left_motor_umlenk(doc, lib_doc,config):
motor_rotation = float(config.get("Ils 2.0 core winkel","winkel_motor"))
umlenk_rotation = float(config.get("Ils 2.0 core winkel","winkel_umlenk"))
block_Vario_Umlenkstation_500mm ="Vario_Umlenkstation_500mm"
def rotatate_and_left_motor_umlenk(doc, lib_doc, config):
motor_rotation = float(config.get("Ils 2.0 core winkel", "winkel_motor"))
umlenk_rotation = float(config.get("Ils 2.0 core winkel", "winkel_umlenk"))
block_Vario_Umlenkstation_500mm = "Vario_Umlenkstation_500mm"
block_Vario_Motorstation_500mm = "Vario_Motorstation_500mm"
blockname_motor_links = block_Vario_Motorstation_500mm +"_links"
blockname_motor_links = block_Vario_Motorstation_500mm + "_links"
blockname_umlenk_links = block_Vario_Umlenkstation_500mm + "_links"
import_block(block_Vario_Umlenkstation_500mm,lib_doc,doc)
import_block(block_Vario_Motorstation_500mm,lib_doc,doc)
turn_two_blocks_left(doc, block_Vario_Umlenkstation_500mm, block_Vario_Motorstation_500mm, blockname_motor_links, blockname_umlenk_links)
import_block(block_Vario_Umlenkstation_500mm, lib_doc, doc)
import_block(block_Vario_Motorstation_500mm, lib_doc, doc)
turn_two_blocks_left(
doc,
block_Vario_Umlenkstation_500mm,
block_Vario_Motorstation_500mm,
blockname_motor_links,
blockname_umlenk_links,
)
block_Vario_Umlenkstation_500mm =dreh_block(block_Vario_Umlenkstation_500mm,doc,lib_doc,math.radians(umlenk_rotation))
block_Vario_Motorstation_500mm =dreh_block(block_Vario_Motorstation_500mm,doc,lib_doc,math.radians(motor_rotation))
blockname_motor_links =dreh_block(blockname_motor_links,doc,lib_doc,math.radians(umlenk_rotation))
blockname_umlenk_links =dreh_block(blockname_umlenk_links,doc,lib_doc,math.radians(motor_rotation))
return block_Vario_Umlenkstation_500mm,block_Vario_Motorstation_500mm,blockname_motor_links,blockname_umlenk_links
block_Vario_Umlenkstation_500mm = dreh_block(
block_Vario_Umlenkstation_500mm, doc, lib_doc, math.radians(umlenk_rotation)
)
block_Vario_Motorstation_500mm = dreh_block(
block_Vario_Motorstation_500mm, doc, lib_doc, math.radians(motor_rotation)
)
blockname_motor_links = dreh_block(
blockname_motor_links, doc, lib_doc, math.radians(umlenk_rotation)
)
blockname_umlenk_links = dreh_block(
blockname_umlenk_links, doc, lib_doc, math.radians(motor_rotation)
)
return (
block_Vario_Umlenkstation_500mm,
block_Vario_Motorstation_500mm,
blockname_motor_links,
blockname_umlenk_links,
)
def turn_two_blocks_left(doc, block_1_name_zwischen, block_2_name_zwischen, block_2_left_name, block_1_left_name):
def turn_two_blocks_left(
doc,
block_1_name_zwischen,
block_2_name_zwischen,
block_2_left_name,
block_1_left_name,
):
blockname1 = block_1_name_zwischen
blockname2 = block_2_name_zwischen
if block_2_left_name not in doc.blocks:
matrix = Matrix44.scale(1,-1,1)
block1_zwischen = doc.blocks.new(name=block_1_name_zwischen + "zwischenschrit", base_point=(0,0,0))
block2_zwischen = doc.blocks.new(name=block_2_name_zwischen + "zwischenschrit", base_point=(0,0,0))
block_2 = doc.blocks.new(name=block_2_left_name,base_point=(0,0,0))
block_1_left_name = doc.blocks.new(name=block_1_left_name,base_point=(0,0,0))
block1_zwischen.add_blockref(blockname1,(0,0,0))
block2_zwischen.add_blockref(blockname2,(0,0,0))
matrix = Matrix44.scale(1, -1, 1)
block1_zwischen = doc.blocks.new(
name=block_1_name_zwischen + "zwischenschrit", base_point=(0, 0, 0)
)
block2_zwischen = doc.blocks.new(
name=block_2_name_zwischen + "zwischenschrit", base_point=(0, 0, 0)
)
block_2 = doc.blocks.new(name=block_2_left_name, base_point=(0, 0, 0))
block_1_left_name = doc.blocks.new(name=block_1_left_name, base_point=(0, 0, 0))
block1_zwischen.add_blockref(blockname1, (0, 0, 0))
block2_zwischen.add_blockref(blockname2, (0, 0, 0))
for e in block2_zwischen:
copy = e.copy()
copy.transform(matrix)
@@ -148,8 +192,8 @@ def turn_two_blocks_left(doc, block_1_name_zwischen, block_2_name_zwischen, bloc
copy.transform(matrix)
block_1_left_name.add_entity(copy)
def add_attributes_to_block(block, attributes):
def add_attributes_to_block(block, attributes):
"""
Fügt einem bestehenden ezdxf-Block Attribut-Definitionen hinzu.
@@ -160,7 +204,7 @@ def add_attributes_to_block(block, attributes):
for tag, value in attributes.items():
tag = tag
value = value
a =block.add_attrib(
a = block.add_attrib(
tag=tag,
text=value,
)
+179 -104
View File
@@ -1,15 +1,16 @@
# -*- coding: utf-8 -*-
import os
import ezdxf
from ezdxf.math import Matrix44, Vec3, BoundingBox, Vec2
import math
import argparse
import sys
import shutil
import configparser
from utils import check_environment_var, setup_logger
from pathlib import Path
import logging
def create_block_library(input_dir, output_file, config, logger=None):
"""
Erstellt eine DXF-Block-Bibliothek aus einzelnen DXF-Dateien.
@@ -79,22 +80,21 @@ def create_block_library(input_dir, output_file, config, logger=None):
"REGION",
}
filtered_entities = []
att_def= {}
att_def = {}
for insert in src_msp.query("INSERT"):
for attrib in insert.attribs:
att_def[attrib.dxf.tag] = attrib.dxf.text
for e in entities:
if e.dxftype() in allowed_types:
if e.dxftype() in allowed_types:
filtered_entities.append(e)
else:
logger.info(f"{e.dxftype()} is nicht in der erlaubten Liste. Diese befindet sich in {filename}")
logger.info(
f"{e.dxftype()} is nicht in der erlaubten Liste. Diese befindet sich in {filename}"
)
entities = filtered_entities
except Exception as e:
error_msg = f"Fehler beim Lesen von {filename}: {e}"
if logger:
@@ -106,7 +106,9 @@ def create_block_library(input_dir, output_file, config, logger=None):
# Sicherstellen, dass die im Quell-DXF verwendeten Layer im Zieldokument existieren
try:
used_layer_names = {e.dxf.layer for e in entities if hasattr(e.dxf, "layer")}
used_layer_names = {
e.dxf.layer for e in entities if hasattr(e.dxf, "layer")
}
for layer_name in used_layer_names:
if layer_name and layer_name not in doc.layers:
try:
@@ -123,7 +125,9 @@ def create_block_library(input_dir, output_file, config, logger=None):
except Exception:
pass
boundingbox, ausdehnung, center = calculate_block_bounding_box(filtered_entities, doc,src_doc, filename,config)
boundingbox, ausdehnung, center = calculate_block_bounding_box(
filtered_entities, doc, src_doc, filename, config
)
if center is None:
error_msg = f"Keine gültige Geometrie in {filename}"
if logger:
@@ -136,7 +140,7 @@ def create_block_library(input_dir, output_file, config, logger=None):
if name in doc.blocks:
doc.blocks.delete_block(name)
blk = doc.blocks.new(name=name, base_point=(0,0))
blk = doc.blocks.new(name=name, base_point=(0, 0))
for e in entities:
cp = copy_entity(logger, error_files, filename, e, center)
@@ -146,11 +150,10 @@ def create_block_library(input_dir, output_file, config, logger=None):
# Platzierung in Reihen und Spalten
# Attribut-Definition (ATTDEF) hinzufügen
# Blockreferenz-Layer bestimmen
# Blockreferenz-Layer bestimmen
blockref_layer = None
try:
#Konfiguration erlaubt expliziten Layernamen
# Konfiguration erlaubt expliziten Layernamen
cfg_layer = None
try:
cfg_layer = config.get("dxf2lib", "blockref_layer")
@@ -186,40 +189,57 @@ def create_block_library(input_dir, output_file, config, logger=None):
blockref_layer = None
section = "dxf2lib"
text_height = get_cfg_value(section, "text_height", DEFAULTS["text_height"])
extra_block_space_x = get_cfg_value(section, "extra_block_space_x", DEFAULTS["extra_block_space_x"])
blocks_per_row = get_cfg_value(section, "blocks_per_row", DEFAULTS["blocks_per_row"])
extra_text_space_y = get_cfg_value(section, "extra_text_space_y", DEFAULTS["extra_text_space_y"])
extra_block_space_x = get_cfg_value(
section, "extra_block_space_x", DEFAULTS["extra_block_space_x"]
)
blocks_per_row = get_cfg_value(
section, "blocks_per_row", DEFAULTS["blocks_per_row"]
)
extra_text_space_y = get_cfg_value(
section, "extra_text_space_y", DEFAULTS["extra_text_space_y"]
)
ausdehnung_x, ausdehung_y = ausdehnung[0], ausdehnung[1]
block_spacing_y = ausdehung_y + 400
block_spacing_x = ausdehnung_x + extra_block_space_x
max_blockspacing_x = max(max_blockspacing_x, block_spacing_x)
max_blockspacing_y = max(max_blockspacing_y, block_spacing_y)
max_blockspacing_x = max(max_blockspacing_x, block_spacing_x)
max_blockspacing_y = max(max_blockspacing_y, block_spacing_y)
x_offset += max_blockspacing_x
# Blockreferenz mit optionalem Layer einfügen (Entity-Layer bleiben erhalten)
if blockref_layer:
test =msp.add_blockref(name, insert=(x_offset, y_offset), dxfattribs={"layer": blockref_layer})
test = msp.add_blockref(
name, insert=(x_offset, y_offset), dxfattribs={"layer": blockref_layer}
)
else:
test =msp.add_blockref(name, insert=(x_offset, y_offset))
test = msp.add_blockref(name, insert=(x_offset, y_offset))
# Text mit Blocknamen über dem Block
if name == "a" or name == "b":
for tag, value in att_def.items():
tag = tag
value = value
a =test.add_attrib(
a = test.add_attrib(
tag=tag,
text=value,
)
a.is_invisible = True
# Werte aus Config holen (Block: [dxf2lib])
msp.add_text(name, dxfattribs={'height': text_height, 'insert': (x_offset -ausdehnung_x/2 , y_offset + ausdehung_y/2 + extra_text_space_y)})
msp.add_text(
name,
dxfattribs={
"height": text_height,
"insert": (
x_offset - ausdehnung_x / 2,
y_offset + ausdehung_y / 2 + extra_text_space_y,
),
},
)
processed_files.append(filename)
blocks_in_row += 1
# Abstand zwischen Blöcken in einer Reihe
# Abstand zwischen Blöcken in einer Reihe
if blocks_in_row == blocks_per_row:
blocks_in_row = 0
@@ -234,7 +254,9 @@ def create_block_library(input_dir, output_file, config, logger=None):
logger.info(f"Fehlerhafte Dateien: {len(error_files)}")
if error_files:
logger.warning(f"{len(error_files)} Dateien konnten nicht verarbeitet werden:")
logger.warning(
f"{len(error_files)} Dateien konnten nicht verarbeitet werden:"
)
for filename, error_msg in error_files:
logger.error(f"{filename}: {error_msg}")
else:
@@ -245,7 +267,9 @@ def create_block_library(input_dir, output_file, config, logger=None):
print(f"Fehlerhafte Dateien: {len(error_files)}")
if error_files:
print(f"Warnung: {len(error_files)} Dateien konnten nicht verarbeitet werden.")
print(
f"Warnung: {len(error_files)} Dateien konnten nicht verarbeitet werden."
)
output_dir = output_file.parent
if not output_dir.exists():
@@ -267,7 +291,9 @@ def copy_entity(logger, error_files, filename, e, center):
return cp
except Exception as err:
error_msg = f"Fehler beim Verarbeiten von Entity {e.dxftype()} in {filename}: {err}"
error_msg = (
f"Fehler beim Verarbeiten von Entity {e.dxftype()} in {filename}: {err}"
)
if logger:
logger.error(error_msg)
else:
@@ -276,15 +302,15 @@ def copy_entity(logger, error_files, filename, e, center):
return None
# Standardwerte (falls nicht in der Config)
DEFAULTS = {
"text_height": 20, # Schriftgröße des Texts (in DXF-Einheiten)
"blocks_per_row": 20, # Anzahl Blöcke pro Zeile im Raster
"extra_block_space_x" : 50, # Extra Platz damit sich Blöcke nicht überlappen
"extra_text_space_y" : 50 # Abstand der Überschrift über dem Symbol
"text_height": 20, # Schriftgröße des Texts (in DXF-Einheiten)
"blocks_per_row": 20, # Anzahl Blöcke pro Zeile im Raster
"extra_block_space_x": 50, # Extra Platz damit sich Blöcke nicht überlappen
"extra_text_space_y": 50, # Abstand der Überschrift über dem Symbol
}
def get_cfg_value(section, key, fallback):
try:
return int(config.get(section, key))
@@ -292,9 +318,9 @@ def get_cfg_value(section, key, fallback):
return fallback
def convert_dxf_to_block_with_inserts(input_filename, output_filename, block_name="CONVERTED_BLOCK"):
def convert_dxf_to_block_with_inserts(
input_filename, output_filename, block_name="CONVERTED_BLOCK"
):
"""
Konvertiert alle Entities einer DXF-Datei in einen neuen Block
INSERTs werden als Referenzen beibehalten (nicht explodiert)
@@ -305,8 +331,8 @@ def convert_dxf_to_block_with_inserts(input_filename, output_filename, block_nam
print(f"Lade DXF-Datei: {input_filename}")
# Neue Ausgabe-DXF erstellen
output_doc = ezdxf.new('R2010')
output_doc.header['$INSUNITS'] = 4 # Millimeter
output_doc = ezdxf.new("R2010")
output_doc.header["$INSUNITS"] = 4 # Millimeter
# Zuerst alle Block-Definitionen kopieren
copied_blocks = copy_block_definitions(input_doc, output_doc)
@@ -322,7 +348,7 @@ def convert_dxf_to_block_with_inserts(input_filename, output_filename, block_nam
insert_count = 0
for entity in msp:
if entity.dxftype() == 'INSERT':
if entity.dxftype() == "INSERT":
# INSERT direkt kopieren (nicht explodieren)
copy_entity_to_block(entity, new_block)
insert_count += 1
@@ -373,7 +399,7 @@ def copy_block_definitions(source_doc, target_doc):
for block_name in source_doc.blocks:
# Standard-Blöcke (MODEL_SPACE, PAPER_SPACE) überspringen
if block_name.startswith('*'):
if block_name.startswith("*"):
continue
source_block = source_doc.blocks[block_name]
@@ -395,7 +421,7 @@ def copy_block_definitions(source_doc, target_doc):
return copied_blocks
def calculate_block_bounding_box(block, doc, src_doc, filename,config):
def calculate_block_bounding_box(block, doc, src_doc, filename, config):
"""
Berechnet die Bounding Box eines Blocks inklusive aller INSERTs
"""
@@ -403,15 +429,20 @@ def calculate_block_bounding_box(block, doc, src_doc, filename,config):
bbox = BoundingBox()
for entity in block:
entity_bbox = get_entity_bounding_box(entity, doc,src_doc,filename,config)
entity_bbox = get_entity_bounding_box(entity, doc, src_doc, filename, config)
if entity_bbox and entity_bbox.has_data:
bbox.extend(entity_bbox)
return bbox, (bbox.extmax.x - bbox.extmin.x, bbox.extmax.y - bbox.extmin.y), bbox.center
return (
bbox,
(bbox.extmax.x - bbox.extmin.x, bbox.extmax.y - bbox.extmin.y),
bbox.center,
)
def _bbox_from_virtual_entities(entity, doc, src_doc, filename, config, transform_matrix=None):
def _bbox_from_virtual_entities(
entity, doc, src_doc, filename, config, transform_matrix=None
):
"""
Nutzt virtual_entities(), um komplexe Objekte (z.B. SURFACE) in
auswertbare Geometrie zu zerlegen und darauf eine Bounding Box zu
@@ -432,7 +463,7 @@ def _bbox_from_virtual_entities(entity, doc, src_doc, filename, config, transfor
return v_bbox
def get_entity_bounding_box(e, doc,src_doc,filename,config, transform_matrix=None):
def get_entity_bounding_box(e, doc, src_doc, filename, config, transform_matrix=None):
"""
Berechnet die Bounding Box einer einzelnen Entity
Berücksichtigt INSERTs mit ihren Block-Inhalten
@@ -440,7 +471,7 @@ def get_entity_bounding_box(e, doc,src_doc,filename,config, transform_matrix=Non
bbox = BoundingBox()
try:
if e.dxftype() == 'LINE':
if e.dxftype() == "LINE":
start = Vec3(e.dxf.start)
end = Vec3(e.dxf.end)
if transform_matrix:
@@ -449,7 +480,7 @@ def get_entity_bounding_box(e, doc,src_doc,filename,config, transform_matrix=Non
bbox.extend([start])
bbox.extend([end])
elif e.dxftype() == 'CIRCLE':
elif e.dxftype() == "CIRCLE":
# Kreis durch Punktabtastung (robust bei Transformationen)
center = Vec3(e.dxf.center)
radius = float(e.dxf.radius)
@@ -465,7 +496,7 @@ def get_entity_bounding_box(e, doc,src_doc,filename,config, transform_matrix=Non
if points:
bbox.extend(points)
elif e.dxftype() == 'ARC':
elif e.dxftype() == "ARC":
# Bogen durch Punktabtastung zwischen Start- und Endwinkel
center = Vec3(e.dxf.center)
radius = float(e.dxf.radius)
@@ -496,7 +527,7 @@ def get_entity_bounding_box(e, doc,src_doc,filename,config, transform_matrix=Non
if points:
bbox.extend(points)
elif e.dxftype() == 'LWPOLYLINE':
elif e.dxftype() == "LWPOLYLINE":
# Nutze virtuelle Entities (Linien/Bögen), inkl. Bulge-Unterstützung
ve_bbox = _bbox_from_virtual_entities(
e, doc, src_doc, filename, config, transform_matrix
@@ -504,7 +535,7 @@ def get_entity_bounding_box(e, doc,src_doc,filename,config, transform_matrix=Non
if ve_bbox.has_data:
bbox.extend(ve_bbox)
elif e.dxftype() == 'POLYLINE':
elif e.dxftype() == "POLYLINE":
# 2D/3D Polylines ebenfalls über virtuelle Entities
ve_bbox = _bbox_from_virtual_entities(
e, doc, src_doc, filename, config, transform_matrix
@@ -512,7 +543,7 @@ def get_entity_bounding_box(e, doc,src_doc,filename,config, transform_matrix=Non
if ve_bbox.has_data:
bbox.extend(ve_bbox)
elif e.dxftype() == '3DFACE':
elif e.dxftype() == "3DFACE":
# 3DFace: direkte Eckpunkte verwenden
pts = []
try:
@@ -527,7 +558,7 @@ def get_entity_bounding_box(e, doc,src_doc,filename,config, transform_matrix=Non
if pts:
bbox.extend(pts)
elif e.dxftype() in {'MESH', 'POLYFACE', 'POLYFACEMESH'}:
elif e.dxftype() in {"MESH", "POLYFACE", "POLYFACEMESH"}:
# Mesh/Polyface über virtuelle Geometrie auswerten
ve_bbox = _bbox_from_virtual_entities(
e, doc, src_doc, filename, config, transform_matrix
@@ -535,7 +566,7 @@ def get_entity_bounding_box(e, doc,src_doc,filename,config, transform_matrix=Non
if ve_bbox.has_data:
bbox.extend(ve_bbox)
elif e.dxftype() == 'SPLINE':
elif e.dxftype() == "SPLINE":
# Approximation der Spline-Kurve
try:
pts = list(e.approximate(60))
@@ -557,18 +588,20 @@ def get_entity_bounding_box(e, doc,src_doc,filename,config, transform_matrix=Non
if sampled:
bbox.extend(sampled)
elif e.dxftype() == 'ATTDEF':
elif e.dxftype() == "ATTDEF":
insert_point = Vec3(e.dxf.insert)
if transform_matrix:
insert_point = transform_matrix.transform(insert_point)
elif e.dxftype() == 'TEXT':
elif e.dxftype() == "TEXT":
insert_point = Vec3(e.dxf.insert)
height = float(getattr(e.dxf, "height", 1.0))
width_factor = float(getattr(e.dxf, "width", 1.0))
rotation = math.radians(getattr(e.dxf, "rotation", 0.0))
text_content = getattr(e.dxf, "text", "") or ""
est_width = max(len(text_content) * height * 0.6 * width_factor, height * 0.5)
est_width = max(
len(text_content) * height * 0.6 * width_factor, height * 0.5
)
corners = [
insert_point,
insert_point + Vec3(est_width, 0, 0),
@@ -590,7 +623,7 @@ def get_entity_bounding_box(e, doc,src_doc,filename,config, transform_matrix=Non
pt = transform_matrix.transform(pt)
bbox.extend([pt])
elif e.dxftype() == 'MTEXT':
elif e.dxftype() == "MTEXT":
insert_point = Vec3(e.dxf.insert)
char_height = float(getattr(e.dxf, "char_height", 1.0))
width = float(getattr(e.dxf, "width", 0.0))
@@ -610,7 +643,7 @@ def get_entity_bounding_box(e, doc,src_doc,filename,config, transform_matrix=Non
pt = transform_matrix.transform(pt)
bbox.extend([pt])
elif e.dxftype() == 'REGION':
elif e.dxftype() == "REGION":
# Region: Begrenzungsgeometrie über virtual_entities()
ve_bbox = _bbox_from_virtual_entities(
e, doc, src_doc, filename, config, transform_matrix
@@ -618,7 +651,7 @@ def get_entity_bounding_box(e, doc,src_doc,filename,config, transform_matrix=Non
if ve_bbox.has_data:
bbox.extend(ve_bbox)
elif e.dxftype().endswith('SURFACE'):
elif e.dxftype().endswith("SURFACE"):
# Viele Surface-Typen liefern ihre Proxy-Geometrie über virtual_entities()
ve_bbox = _bbox_from_virtual_entities(
e, doc, src_doc, filename, config, transform_matrix
@@ -626,30 +659,42 @@ def get_entity_bounding_box(e, doc,src_doc,filename,config, transform_matrix=Non
if ve_bbox.has_data:
bbox.extend(ve_bbox)
elif e.dxftype() == 'INSERT':
elif e.dxftype() == "INSERT":
# INSERT: Block-Inhalt mit Transformation berücksichtigen
insert_bbox = calculate_insert_bounding_box(e, doc,src_doc,filename,config, transform_matrix)
if insert_bbox and insert_bbox.has_data and e.dxf.layer not in config.get("dxf2lib","automation_layer"):
insert_bbox = calculate_insert_bounding_box(
e, doc, src_doc, filename, config, transform_matrix
)
if (
insert_bbox
and insert_bbox.has_data
and e.dxf.layer not in config.get("dxf2lib", "automation_layer")
):
bbox.extend(insert_bbox)
except Exception as e:
print(f"Fehler bei Bounding Box Berechnung für {e.dxftype()}: {e}")
return bbox
def extract_scaling(matrix):
sx = math.sqrt(matrix[0, 0]**2 + matrix[0, 1]**2 + matrix[0, 2]**2)
sy = math.sqrt(matrix[1, 0]**2 + matrix[1, 1]**2 + matrix[1, 2]**2)
return sx, sy,
def calculate_insert_bounding_box(insert_entity, doc,src_doc,filename,config,parent_transform=None):
def extract_scaling(matrix):
sx = math.sqrt(matrix[0, 0] ** 2 + matrix[0, 1] ** 2 + matrix[0, 2] ** 2)
sy = math.sqrt(matrix[1, 0] ** 2 + matrix[1, 1] ** 2 + matrix[1, 2] ** 2)
return (
sx,
sy,
)
def calculate_insert_bounding_box(
insert_entity, doc, src_doc, filename, config, parent_transform=None
):
"""
Berechnet die Bounding Box eines INSERTs inklusive Block-Inhalt
"""
try:
# Block-Definition finden
# Block-Definition finden
block_name = insert_entity.dxf.name
src_blk = src_doc.blocks[block_name]
@@ -667,7 +712,6 @@ def calculate_insert_bounding_box(insert_entity, doc,src_doc,filename,config,par
block_def = src_blk
# Transformation der INSERT-Entity berechnen
insert_transform = get_insert_transform_matrix(insert_entity)
@@ -682,11 +726,13 @@ def calculate_insert_bounding_box(insert_entity, doc,src_doc,filename,config,par
for block_entity in block_def:
entity_bbox= get_entity_bounding_box(block_entity, doc,src_doc,filename, config,combined_transform)
if entity_bbox and entity_bbox.has_data:
if new_insert:
dst_blk.add_entity(block_entity.copy())
block_bbox.extend(entity_bbox)
entity_bbox = get_entity_bounding_box(
block_entity, doc, src_doc, filename, config, combined_transform
)
if entity_bbox and entity_bbox.has_data:
if new_insert:
dst_blk.add_entity(block_entity.copy())
block_bbox.extend(entity_bbox)
return block_bbox
@@ -703,18 +749,18 @@ def get_insert_transform_matrix(insert_entity):
insert_point = Vec3(insert_entity.dxf.insert)
# Skalierung
xscale = getattr(insert_entity.dxf, 'xscale', 1.0)
yscale = getattr(insert_entity.dxf, 'yscale', 1.0)
zscale = getattr(insert_entity.dxf, 'zscale', 1.0)
xscale = getattr(insert_entity.dxf, "xscale", 1.0)
yscale = getattr(insert_entity.dxf, "yscale", 1.0)
zscale = getattr(insert_entity.dxf, "zscale", 1.0)
# Rotation (in Radiant umwandeln)
rotation = math.radians(getattr(insert_entity.dxf, 'rotation', 0.0))
rotation = math.radians(getattr(insert_entity.dxf, "rotation", 0.0))
# Transformationsmatrix erstellen
matrix = Matrix44.chain(
Matrix44.scale(xscale, yscale, zscale),
Matrix44.z_rotate(rotation),
Matrix44.translate(insert_point.x, insert_point.y, insert_point.z)
Matrix44.translate(insert_point.x, insert_point.y, insert_point.z),
)
return matrix
@@ -759,11 +805,14 @@ def add_bounding_box_to_modelspace(msp, bbox, centered=False):
(right, bottom),
(right, top),
(left, top),
(left, bottom)
(left, bottom),
]
# Roter Punkt in der Mitte
msp.add_circle(center=(0.0, 0.0), radius=max(0.5, min(width, height) * 0.01),
dxfattribs={"layer": "BOUNDING_BOX", "color": 1})
msp.add_circle(
center=(0.0, 0.0),
radius=max(0.5, min(width, height) * 0.01),
dxfattribs={"layer": "BOUNDING_BOX", "color": 1},
)
else:
# Ursprüngliche, nicht-zentrierte Bounding Box
bbox_points = [
@@ -771,7 +820,7 @@ def add_bounding_box_to_modelspace(msp, bbox, centered=False):
(max_pt.x, min_pt.y),
(max_pt.x, max_pt.y),
(min_pt.x, max_pt.y),
(min_pt.x, min_pt.y)
(min_pt.x, min_pt.y),
]
bbox_poly = msp.add_lwpolyline(bbox_points)
@@ -779,13 +828,23 @@ def add_bounding_box_to_modelspace(msp, bbox, centered=False):
bbox_poly.dxf.color = 1 # Rot
# Text mit Abmessungen
text_pos = Vec3(bbox_points[0][0], (bbox_points[2][1] if centered else max_pt.y) + 5, 0)
msp.add_text(f"Breite: {width:.2f} mm", height=3,
dxfattribs={'insert': text_pos, 'layer': "BOUNDING_BOX", 'color': 1})
text_pos = Vec3(
bbox_points[0][0], (bbox_points[2][1] if centered else max_pt.y) + 5, 0
)
msp.add_text(
f"Breite: {width:.2f} mm",
height=3,
dxfattribs={"insert": text_pos, "layer": "BOUNDING_BOX", "color": 1},
)
text_pos2 = Vec3(bbox_points[0][0], (bbox_points[2][1] if centered else max_pt.y) + 10, 0)
msp.add_text(f"Höhe: {height:.2f} mm", height=3,
dxfattribs={'insert': text_pos2, 'layer': "BOUNDING_BOX", 'color': 1})
text_pos2 = Vec3(
bbox_points[0][0], (bbox_points[2][1] if centered else max_pt.y) + 10, 0
)
msp.add_text(
f"Höhe: {height:.2f} mm",
height=3,
dxfattribs={"insert": text_pos2, "layer": "BOUNDING_BOX", "color": 1},
)
def format_bounding_box(bbox):
@@ -801,9 +860,11 @@ def format_bounding_box(bbox):
height = max_pt.y - min_pt.y
depth = max_pt.z - min_pt.z
return (f"Min: ({min_pt.x:.2f}, {min_pt.y:.2f}, {min_pt.z:.2f}) "
f"Max: ({max_pt.x:.2f}, {max_pt.y:.2f}, {max_pt.z:.2f}) "
f"Größe: {width:.2f} × {height:.2f} × {depth:.2f} mm")
return (
f"Min: ({min_pt.x:.2f}, {min_pt.y:.2f}, {min_pt.z:.2f}) "
f"Max: ({max_pt.x:.2f}, {max_pt.y:.2f}, {max_pt.z:.2f}) "
f"Größe: {width:.2f} × {height:.2f} × {depth:.2f} mm"
)
def analyze_source_dxf_with_blocks(filename):
@@ -824,16 +885,16 @@ def analyze_source_dxf_with_blocks(filename):
entity_type = entity.dxftype()
entity_types[entity_type] = entity_types.get(entity_type, 0) + 1
layer = getattr(entity.dxf, 'layer', '0')
layer = getattr(entity.dxf, "layer", "0")
layer_count[layer] = layer_count.get(layer, 0) + 1
if entity_type == 'INSERT':
if entity_type == "INSERT":
block_name = entity.dxf.name
insert_blocks[block_name] = insert_blocks.get(block_name, 0) + 1
# Block-Definitionen analysieren
for block_name in doc.blocks:
if not block_name.startswith('*'): # Keine Standard-Blöcke
if not block_name.startswith("*"): # Keine Standard-Blöcke
block_def = doc.blocks[block_name]
entity_count = len(list(block_def))
block_definitions[block_name] = entity_count
@@ -864,11 +925,21 @@ def analyze_source_dxf_with_blocks(filename):
print(f"Fehler bei der Analyse: {e}")
return {}, {}, {}, {}
if __name__ == "__main__":
# Argumentparser für Kommandozeilenoptionen
parser = argparse.ArgumentParser(description="SVG/XML zu DXF Konverter")
parser.add_argument('-i', '--input', type=str, help='Input-Verzeichnis mit SVG/XML-Dateien')
parser.add_argument('-n', '--name', required=False, type=str, help='Name der zu erzeugenden Bibliothek (optional, wird sonst abgefragt)', default="test")
parser.add_argument(
"-i", "--input", type=str, help="Input-Verzeichnis mit SVG/XML-Dateien"
)
parser.add_argument(
"-n",
"--name",
required=False,
type=str,
help="Name der zu erzeugenden Bibliothek (optional, wird sonst abgefragt)",
default="test",
)
if len(sys.argv) == 2 and sys.argv[1] in ("-h", "--help"):
parser.print_help()
@@ -877,12 +948,14 @@ if __name__ == "__main__":
args = parser.parse_args()
if not args.name:
args.name = input("Bitte Namen der zu erzeugenden Bibliothek eingeben: ").strip()
args.name = input(
"Bitte Namen der zu erzeugenden Bibliothek eingeben: "
).strip()
if not args.name:
print("Fehler: Kein Name angegeben. Beende.")
sys.exit(1)
# Verzeichnisse über Umgebungsvariablen oder Fallback
# Verzeichnisse über Umgebungsvariablen oder Fallback
if args.input:
INPUT_DIR = Path(args.input)
print(f"Verwende Input-Verzeichnis: {INPUT_DIR} \n")
@@ -890,7 +963,9 @@ if __name__ == "__main__":
INPUT_DIR = check_environment_var("PROJECT_DATA") / "omniflo"
print(f"Kein Input-Verzeichnis angegeben, verwende Standard: {INPUT_DIR} \n")
OUTPUT_FILE = check_environment_var("PROJECT_DATA") / "block_libraries" / f"{args.name}.dxf"
OUTPUT_FILE = (
check_environment_var("PROJECT_DATA") / "block_libraries" / f"{args.name}.dxf"
)
# Prüfe und erstelle log-Verzeichnis falls nötig
log_dir = check_environment_var("PROJECT_LOG")
@@ -899,9 +974,9 @@ if __name__ == "__main__":
print(f"Log-Verzeichnis erstellt: {log_dir}")
# Logger Setup
log_file = Path(os.environ['PROJECT_LOG']) / 'dxf2lib.log'
file_handler = logging.FileHandler(str(log_file), 'a', 'utf-8')
logger = setup_logger(log_dir, name='dxf2lib')
log_file = Path(os.environ["PROJECT_LOG"]) / "dxf2lib.log"
file_handler = logging.FileHandler(str(log_file), "a", "utf-8")
logger = setup_logger(log_dir, name="dxf2lib")
logger.info("=== DXF2LIB Verarbeitung gestartet ===")
logger.info(f"Input-Verzeichnis: {INPUT_DIR}")
logger.info(f"Output-Datei: {OUTPUT_FILE}")
+215
View File
@@ -0,0 +1,215 @@
# -*- coding: utf-8 -*-
"""
Handler Context für DXF-Elementgenerierung.
Vereinfacht die Aufrufe der handle-Funktionen und bietet Debug-Unterstützung.
"""
class HandlerContext:
"""
Kontextobjekt für Handler-Funktionsaufrufe.
Speichert alle Parameter für handle-Funktionen und bietet:
- Vereinfachte Aufrufe
- Schöne String-Repräsentation für Debugging
- Einfachen Zugriff auf alle Parameter
"""
def __init__(
self,
msp,
teileid,
merkmale,
x,
y,
doc,
lib_doc,
verbose=False,
symbols=None,
strecken_nachbarn=None,
config=None,
config_allgemein=None,
):
self.msp = msp
self.teileid = teileid
self.merkmale = merkmale if merkmale is not None else {}
self.x = x
self.y = y
self.doc = doc
self.lib_doc = lib_doc
self.verbose = verbose
self.symbols = symbols if symbols is not None else []
self.strecken_nachbarn = (
strecken_nachbarn if strecken_nachbarn is not None else []
)
self.config = config
self.config_allgemein = config_allgemein
def call(self, handler_func):
"""
Ruft eine Handler-Funktion mit allen gespeicherten Parametern auf.
Args:
handler_func: Die Handler-Funktion (z.B. handle_ils_2_0_kreisel)
Returns:
Rückgabewert der Handler-Funktion
"""
return handler_func(
self.msp,
self.teileid,
self.merkmale,
self.x,
self.y,
self.doc,
self.lib_doc,
self.verbose,
self.symbols,
self.strecken_nachbarn,
self.config,
self.config_allgemein,
)
def __str__(self):
"""String-Repräsentation für Debugging."""
lines = [
"=" * 80,
"HandlerContext:",
"=" * 80,
f"TeileId: {self.teileid}",
f"Position: x={self.x:.2f}, y={self.y:.2f}",
f"Verbose: {self.verbose}",
"-" * 80,
"Merkmale:",
]
if self.merkmale:
for key, value in self.merkmale.items():
lines.append(f" {key:20s}: {value}")
else:
lines.append(" (keine)")
lines.append("-" * 80)
lines.append(f"Symbols: {len(self.symbols)} Einträge")
if self.symbols:
for i, sym in enumerate(self.symbols, 1):
lines.append(f" [{i}] {sym}")
lines.append("-" * 80)
lines.append(f"Nachbarn: {len(self.strecken_nachbarn)} Einträge")
lines.append("-" * 80)
lines.append(f"doc: {type(self.doc).__name__}")
lines.append(
f"lib_doc: {type(self.lib_doc).__name__ if self.lib_doc else 'None'}"
)
lines.append(f"msp: {type(self.msp).__name__}")
lines.append(f"config: {'vorhanden' if self.config else 'None'}")
lines.append(f"config_allg: {'vorhanden' if self.config_allgemein else 'None'}")
lines.append("=" * 80)
return "\n".join(lines)
def __repr__(self):
"""Kurze Repräsentation."""
return (
f"HandlerContext(teileid={self.teileid!r}, "
f"x={self.x:.2f}, y={self.y:.2f}, "
f"merkmale={len(self.merkmale)} items)"
)
def to_dict(self):
"""
Gibt alle Parameter als Dictionary zurück (ohne Objekt-Referenzen).
Nützlich für Logging oder Serialisierung.
"""
return {
"teileid": self.teileid,
"x": self.x,
"y": self.y,
"verbose": self.verbose,
"merkmale": self.merkmale.copy(),
"symbols_count": len(self.symbols),
"symbols": self.symbols,
"nachbarn_count": len(self.strecken_nachbarn),
"has_config": self.config is not None,
"has_config_allgemein": self.config_allgemein is not None,
"has_lib_doc": self.lib_doc is not None,
}
@property
def position(self):
"""Gibt Position als Tuple zurück."""
return (self.x, self.y)
@property
def has_library(self):
"""Prüft, ob eine Bibliothek verfügbar ist."""
return self.lib_doc is not None
def get_merkmal(self, key, default=None):
"""
Holt ein Merkmal mit Fallback-Wert.
Args:
key: Merkmal-Schlüssel
default: Standardwert, falls Merkmal nicht existiert
Returns:
Merkmalwert oder default
"""
return self.merkmale.get(key, default)
def create_context_from_row(
row,
msp,
doc,
lib_doc,
strecken_nachbarn,
config,
config_allgemein,
symbols=None,
verbose=False,
):
"""
Factory-Funktion: Erstellt HandlerContext aus einer CSV-Zeile.
Args:
row: Dictionary mit CSV-Daten
msp: Modelspace
doc: DXF-Dokument
lib_doc: Bibliotheks-Dokument
strecken_nachbarn: Liste der Nachbarn
config: Config-Parser
config_allgemein: Allgemeine Config
symbols: Symbol-Liste (optional)
verbose: Debug-Modus (optional)
Returns:
HandlerContext-Instanz
"""
from arbeiten_mit_csv import parse_merkmale, extract_coords
bezeichner = row["Bezeichnung"].strip()
teileid = row["TeileId"].strip()
planquadrat = row["Planquadrat"]
merkmale = parse_merkmale(row.get("Merkmale", ""))
merkmale["bezeichner"] = bezeichner
x, y = extract_coords(planquadrat)
return HandlerContext(
msp=msp,
teileid=teileid,
merkmale=merkmale,
x=x,
y=y,
doc=doc,
lib_doc=lib_doc,
verbose=verbose,
symbols=symbols if symbols is not None else [],
strecken_nachbarn=strecken_nachbarn,
config=config,
config_allgemein=config_allgemein,
)
+2 -1
View File
@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
import ezdxf
from pathlib import Path
@@ -7,7 +8,7 @@ def inspect_blocks(dxf_path):
print(f"Datei: {dxf_path}")
print("Gefundene Blöcke und Attribute:\n")
for block in doc.blocks:
if block.name.startswith('*'): # Überspringe anonyme/Standardblöcke
if block.name.startswith("*"): # Überspringe anonyme/Standardblöcke
continue
print(f"Block: {block.name}")
attribs = [e for e in block if e.dxftype() == "ATTDEF"]
+1147 -412
View File
File diff suppressed because it is too large Load Diff
+3 -2
View File
@@ -1,3 +1,4 @@
ruff==0.14.14
black==26.1.0
pydantic==2.12.5
ezdxf==1.4.1
svg.path==7.0
pydantic>=2.0.0
+213 -67
View File
@@ -1,9 +1,11 @@
# -*- coding: utf-8 -*-
import unittest
from pathlib import Path
import difflib
from utils import check_environment_var, setup_logger
import plant2dxf
class TestDXFGeometry(unittest.TestCase):
testordner_path = Path(check_environment_var("PROJECT_TEST"))
lib_path = Path(check_environment_var("PROJECT_DATA"))
@@ -13,7 +15,25 @@ class TestDXFGeometry(unittest.TestCase):
cfg_path = config_dir / "shapes.cfg"
allgemein_cfg_path = config_dir / "allgemein.cfg"
GEOM_CODES = {"0", "8", "10", "20", "30", "11", "21", "31", "41", "42", "43", "70", "71", "72", "73", "74", "75"}
GEOM_CODES = {
"0",
"8",
"10",
"20",
"30",
"11",
"21",
"31",
"41",
"42",
"43",
"70",
"71",
"72",
"73",
"74",
"75",
}
def extract_geometry_lines(self, file_path):
"""
@@ -39,125 +59,251 @@ class TestDXFGeometry(unittest.TestCase):
geom1 = self.extract_geometry_lines(file1)
geom2 = self.extract_geometry_lines(file2)
if geom1 != geom2:
diff = "".join(difflib.unified_diff(geom1, geom2, fromfile=str(file1), tofile=str(file2)))
diff = "".join(
difflib.unified_diff(
geom1, geom2, fromfile=str(file1), tofile=str(file2)
)
)
raise AssertionError(f"Geometrische Daten unterscheiden sich:\n{diff}")
def test_omniflobogen_dxf_file(self):
omniflo_bogen = "omniflo_bogen"
omniflo_bogen_csv = self.testordner_path / f"{omniflo_bogen}.csv"
omniflo_bogen_csv = self.testordner_path / f"{omniflo_bogen}.csv"
omniflo_bogen_dxf = f"{omniflo_bogen}.dxf"
omniflo_bogen_jason = self.work_dir / f"{omniflo_bogen}.jason"
omniflo_bogen_json = self.work_dir / f"{omniflo_bogen}.json"
omniflo_bogen_output = self.work_dir / omniflo_bogen_dxf
omniflo_bogen_reference = self.testordner_path / omniflo_bogen_dxf
plant2dxf.main(omniflo_bogen_csv , self.default_lib_path, self.cfg_path, self.allgemein_cfg_path, omniflo_bogen_output,omniflo_bogen_jason)
plant2dxf.main(
omniflo_bogen_csv,
self.default_lib_path,
self.cfg_path,
self.allgemein_cfg_path,
omniflo_bogen_output,
omniflo_bogen_json,
)
self.assert_dxf_geometry_equal(omniflo_bogen_output, omniflo_bogen_reference)
def test_weiche_90_dxf_file(self):
weiche_90= "weiche_90"
weiche_90_csv = self.testordner_path / f"{weiche_90}.csv"
weiche_90 = "weiche_90"
weiche_90_csv = self.testordner_path / f"{weiche_90}.csv"
weiche_90_dxf = f"{weiche_90}.dxf"
weiche_90_jason = self.work_dir /f"{weiche_90}.jason"
weiche_90_json = self.work_dir / f"{weiche_90}.json"
weiche_90_output = self.work_dir / weiche_90_dxf
weiche_90_reference = self.testordner_path / weiche_90_dxf
plant2dxf.main(weiche_90_csv , self.default_lib_path, self.cfg_path, self.allgemein_cfg_path, weiche_90_output,weiche_90_jason)
plant2dxf.main(
weiche_90_csv,
self.default_lib_path,
self.cfg_path,
self.allgemein_cfg_path,
weiche_90_output,
weiche_90_json,
)
self.assert_dxf_geometry_equal(weiche_90_output, weiche_90_reference)
def test_weiche_45_simple_dxf_file(self):
weiche_45_simple= "weiche_45_simple"
weiche_45_simple_csv = self.testordner_path / f"{weiche_45_simple}.csv"
weiche_45_simple = "weiche_45_simple"
weiche_45_simple_csv = self.testordner_path / f"{weiche_45_simple}.csv"
weiche_45_simple_dxf = f"{weiche_45_simple}.dxf"
weiche_45_simple_jason = self.work_dir/ f"{weiche_45_simple}.jason"
weiche_45_simple_json = self.work_dir / f"{weiche_45_simple}.json"
weiche_45_simple_output = self.work_dir / weiche_45_simple_dxf
weiche_45_simple_reference = self.testordner_path / weiche_45_simple_dxf
plant2dxf.main(weiche_45_simple_csv , self.default_lib_path, self.cfg_path, self.allgemein_cfg_path, weiche_45_simple_output,weiche_45_simple_jason)
self.assert_dxf_geometry_equal(weiche_45_simple_output, weiche_45_simple_reference)
plant2dxf.main(
weiche_45_simple_csv,
self.default_lib_path,
self.cfg_path,
self.allgemein_cfg_path,
weiche_45_simple_output,
weiche_45_simple_json,
)
self.assert_dxf_geometry_equal(
weiche_45_simple_output, weiche_45_simple_reference
)
def test_weiche_45_doppel_dxf_file(self):
weiche_45_doppel= "weiche_45_doppel"
weiche_45_doppel_csv = self.testordner_path / f"{weiche_45_doppel}.csv"
weiche_45_doppel = "weiche_45_doppel"
weiche_45_doppel_csv = self.testordner_path / f"{weiche_45_doppel}.csv"
weiche_45_doppel_dxf = f"{weiche_45_doppel}.dxf"
weiche_45_doppel_jason = self.work_dir /f"{weiche_45_doppel}.jason"
weiche_45_doppel_json = self.work_dir / f"{weiche_45_doppel}.json"
weiche_45_doppel_output = self.work_dir / weiche_45_doppel_dxf
weiche_45_doppel_reference = self.testordner_path / weiche_45_doppel_dxf
plant2dxf.main(weiche_45_doppel_csv , self.default_lib_path, self.cfg_path, self.allgemein_cfg_path, weiche_45_doppel_output,weiche_45_doppel_jason)
self.assert_dxf_geometry_equal(weiche_45_doppel_output, weiche_45_doppel_reference)
plant2dxf.main(
weiche_45_doppel_csv,
self.default_lib_path,
self.cfg_path,
self.allgemein_cfg_path,
weiche_45_doppel_output,
weiche_45_doppel_json,
)
self.assert_dxf_geometry_equal(
weiche_45_doppel_output, weiche_45_doppel_reference
)
def test_weiche_45_dreiwege_dxf_file(self):
weiche_45_dreiwege= "weiche_45_dreiwege"
weiche_45_dreiwege_csv = self.testordner_path / f"{weiche_45_dreiwege}.csv"
weiche_45_dreiwege = "weiche_45_dreiwege"
weiche_45_dreiwege_csv = self.testordner_path / f"{weiche_45_dreiwege}.csv"
weiche_45_dreiwege_dxf = f"{weiche_45_dreiwege}.dxf"
weiche_45_dreiwege_jason = self.work_dir /f"{weiche_45_dreiwege}.jason"
weiche_45_dreiwege_json = self.work_dir / f"{weiche_45_dreiwege}.json"
weiche_45_dreiwege_output = self.work_dir / weiche_45_dreiwege_dxf
weiche_45_dreiwege_reference = self.testordner_path / weiche_45_dreiwege_dxf
plant2dxf.main(weiche_45_dreiwege_csv , self.default_lib_path, self.cfg_path, self.allgemein_cfg_path, weiche_45_dreiwege_output,weiche_45_dreiwege_jason)
self.assert_dxf_geometry_equal(weiche_45_dreiwege_output, weiche_45_dreiwege_reference)
plant2dxf.main(
weiche_45_dreiwege_csv,
self.default_lib_path,
self.cfg_path,
self.allgemein_cfg_path,
weiche_45_dreiwege_output,
weiche_45_dreiwege_json,
)
self.assert_dxf_geometry_equal(
weiche_45_dreiwege_output, weiche_45_dreiwege_reference
)
def test_weichenkoerper_dxf_file(self):
weichenkoerper= "weichenkoerper"
weichenkoerper_csv = self.testordner_path / f"{weichenkoerper}.csv"
weichenkoerper = "weichenkoerper"
weichenkoerper_csv = self.testordner_path / f"{weichenkoerper}.csv"
weichenkoerper_dxf = f"{weichenkoerper}.dxf"
weichenkoerper_jason = self.work_dir /f"{weichenkoerper}.jason"
weichenkoerper_json = self.work_dir / f"{weichenkoerper}.json"
weichenkoerper_output = self.work_dir / weichenkoerper_dxf
weichenkoerper_reference = self.testordner_path / weichenkoerper_dxf
plant2dxf.main(weichenkoerper_csv , self.default_lib_path, self.cfg_path, self.allgemein_cfg_path, weichenkoerper_output,weichenkoerper_jason)
plant2dxf.main(
weichenkoerper_csv,
self.default_lib_path,
self.cfg_path,
self.allgemein_cfg_path,
weichenkoerper_output,
weichenkoerper_json,
)
self.assert_dxf_geometry_equal(weichenkoerper_output, weichenkoerper_reference)
def test_weichenkombination_dxf_file(self):
weichenkombination= "weichenkombination"
weichenkombination_csv = self.testordner_path / f"{weichenkombination}.csv"
weichenkombination = "weichenkombination"
weichenkombination_csv = self.testordner_path / f"{weichenkombination}.csv"
weichenkombination_dxf = f"{weichenkombination}.dxf"
weichenkombination_jason = self.work_dir /f"{weichenkombination}.jason"
weichenkombination_json = self.work_dir / f"{weichenkombination}.json"
weichenkombination_output = self.work_dir / weichenkombination_dxf
weichenkombination_reference = self.testordner_path / weichenkombination_dxf
plant2dxf.main(weichenkombination_csv , self.default_lib_path, self.cfg_path, self.allgemein_cfg_path, weichenkombination_output,weichenkombination_jason)
self.assert_dxf_geometry_equal(weichenkombination_output, weichenkombination_reference)
plant2dxf.main(
weichenkombination_csv,
self.default_lib_path,
self.cfg_path,
self.allgemein_cfg_path,
weichenkombination_output,
weichenkombination_json,
)
self.assert_dxf_geometry_equal(
weichenkombination_output, weichenkombination_reference
)
def test_gefaelle_dxf_file(self):
gefaelle= "gefaelle"
gefaelle_csv = self.testordner_path / f"{gefaelle}.csv"
gefaelle = "gefaelle"
gefaelle_csv = self.testordner_path / f"{gefaelle}.csv"
gefaelle_dxf = f"{gefaelle}.dxf"
gefaelle_jason = self.work_dir /f"{gefaelle}.jason"
gefaelle_json = self.work_dir / f"{gefaelle}.json"
weichenkombination_output = self.work_dir / gefaelle_dxf
weichenkombination_reference = self.testordner_path / gefaelle_dxf
plant2dxf.main(gefaelle_csv , self.default_lib_path, self.cfg_path, self.allgemein_cfg_path, weichenkombination_output,gefaelle_jason)
self.assert_dxf_geometry_equal(weichenkombination_output, weichenkombination_reference)
plant2dxf.main(
gefaelle_csv,
self.default_lib_path,
self.cfg_path,
self.allgemein_cfg_path,
weichenkombination_output,
gefaelle_json,
)
self.assert_dxf_geometry_equal(
weichenkombination_output, weichenkombination_reference
)
def test_gefaelle_ausnahme_gleiche_orientierung_dxf_file(self):
gefaelle_ausnahme_gleiche_orientierung= "gefaelle_ausnahme_gleiche_orientierung"
gefaelle_ausnahme_gleiche_orientierung_csv = self.testordner_path / f"{gefaelle_ausnahme_gleiche_orientierung}.csv"
gefaelle_ausnahme_gleiche_orientierung_dxf = f"{gefaelle_ausnahme_gleiche_orientierung}.dxf"
gefaelle_ausnahme_gleiche_orientierung_jason = self.work_dir /f"{gefaelle_ausnahme_gleiche_orientierung}.jason"
gefaelle_ausnahme_gleiche_orientierung_output = self.work_dir / gefaelle_ausnahme_gleiche_orientierung_dxf
gefaelle_ausnahme_gleiche_orientierung_reference = self.testordner_path / gefaelle_ausnahme_gleiche_orientierung_dxf
plant2dxf.main(gefaelle_ausnahme_gleiche_orientierung_csv , self.default_lib_path, self.cfg_path, self.allgemein_cfg_path, gefaelle_ausnahme_gleiche_orientierung_output,gefaelle_ausnahme_gleiche_orientierung_jason)
self.assert_dxf_geometry_equal(gefaelle_ausnahme_gleiche_orientierung_output, gefaelle_ausnahme_gleiche_orientierung_reference)
gefaelle_ausnahme_gleiche_orientierung = (
"gefaelle_ausnahme_gleiche_orientierung"
)
gefaelle_ausnahme_gleiche_orientierung_csv = (
self.testordner_path / f"{gefaelle_ausnahme_gleiche_orientierung}.csv"
)
gefaelle_ausnahme_gleiche_orientierung_dxf = (
f"{gefaelle_ausnahme_gleiche_orientierung}.dxf"
)
gefaelle_ausnahme_gleiche_orientierung_json = (
self.work_dir / f"{gefaelle_ausnahme_gleiche_orientierung}.json"
)
gefaelle_ausnahme_gleiche_orientierung_output = (
self.work_dir / gefaelle_ausnahme_gleiche_orientierung_dxf
)
gefaelle_ausnahme_gleiche_orientierung_reference = (
self.testordner_path / gefaelle_ausnahme_gleiche_orientierung_dxf
)
plant2dxf.main(
gefaelle_ausnahme_gleiche_orientierung_csv,
self.default_lib_path,
self.cfg_path,
self.allgemein_cfg_path,
gefaelle_ausnahme_gleiche_orientierung_output,
gefaelle_ausnahme_gleiche_orientierung_json,
)
self.assert_dxf_geometry_equal(
gefaelle_ausnahme_gleiche_orientierung_output,
gefaelle_ausnahme_gleiche_orientierung_reference,
)
def test_gefaelle_ausnahme_unterschiedlich_orientierung_dxf_file(self):
gefaelle_ausnahme_unterschiedlich_orientierung= "gefaelle_ausnahme_unterschiedlich_orientierung"
gefaelle_ausnahme_unterschiedlich_orientierung_csv = self.testordner_path / f"{gefaelle_ausnahme_unterschiedlich_orientierung}.csv"
gefaelle_ausnahme_unterschiedlich_orientierung_dxf = f"{gefaelle_ausnahme_unterschiedlich_orientierung}.dxf"
gefaelle_ausnahme_unterschiedlich_orientierung_jason = self.work_dir /f"{gefaelle_ausnahme_unterschiedlich_orientierung}.jason"
gefaelle_ausnahme_unterschiedlich_orientierung_output = self.work_dir / gefaelle_ausnahme_unterschiedlich_orientierung_dxf
gefaelle_ausnahme_unterschiedlich_orientierung_reference = self.testordner_path / gefaelle_ausnahme_unterschiedlich_orientierung_dxf
plant2dxf.main(gefaelle_ausnahme_unterschiedlich_orientierung_csv , self.default_lib_path, self.cfg_path, self.allgemein_cfg_path, gefaelle_ausnahme_unterschiedlich_orientierung_output,gefaelle_ausnahme_unterschiedlich_orientierung_jason)
self.assert_dxf_geometry_equal(gefaelle_ausnahme_unterschiedlich_orientierung_output, gefaelle_ausnahme_unterschiedlich_orientierung_reference)
gefaelle_ausnahme_unterschiedlich_orientierung = (
"gefaelle_ausnahme_unterschiedlich_orientierung"
)
gefaelle_ausnahme_unterschiedlich_orientierung_csv = (
self.testordner_path
/ f"{gefaelle_ausnahme_unterschiedlich_orientierung}.csv"
)
gefaelle_ausnahme_unterschiedlich_orientierung_dxf = (
f"{gefaelle_ausnahme_unterschiedlich_orientierung}.dxf"
)
gefaelle_ausnahme_unterschiedlich_orientierung_json = (
self.work_dir / f"{gefaelle_ausnahme_unterschiedlich_orientierung}.json"
)
gefaelle_ausnahme_unterschiedlich_orientierung_output = (
self.work_dir / gefaelle_ausnahme_unterschiedlich_orientierung_dxf
)
gefaelle_ausnahme_unterschiedlich_orientierung_reference = (
self.testordner_path / gefaelle_ausnahme_unterschiedlich_orientierung_dxf
)
plant2dxf.main(
gefaelle_ausnahme_unterschiedlich_orientierung_csv,
self.default_lib_path,
self.cfg_path,
self.allgemein_cfg_path,
gefaelle_ausnahme_unterschiedlich_orientierung_output,
gefaelle_ausnahme_unterschiedlich_orientierung_json,
)
self.assert_dxf_geometry_equal(
gefaelle_ausnahme_unterschiedlich_orientierung_output,
gefaelle_ausnahme_unterschiedlich_orientierung_reference,
)
def test_gefaelle_einzeln_verbunden_dxf_file(self):
gefaelle_einzeln_verbunden= "gefaelle_einzeln_verbunden"
gefaelle_einzeln_verbunden_csv = self.testordner_path / f"{gefaelle_einzeln_verbunden}.csv"
gefaelle_einzeln_verbunden = "gefaelle_einzeln_verbunden"
gefaelle_einzeln_verbunden_csv = (
self.testordner_path / f"{gefaelle_einzeln_verbunden}.csv"
)
gefaelle_einzeln_verbunden_dxf = f"{gefaelle_einzeln_verbunden}.dxf"
gefaelle_einzeln_verbunden_jason = self.work_dir /f"{gefaelle_einzeln_verbunden}.jason"
gefaelle_einzeln_verbunden_output = self.work_dir / gefaelle_einzeln_verbunden_dxf
gefaelle_einzeln_verbunden_reference = self.testordner_path / gefaelle_einzeln_verbunden_dxf
plant2dxf.main(gefaelle_einzeln_verbunden_csv , self.default_lib_path, self.cfg_path, self.allgemein_cfg_path, gefaelle_einzeln_verbunden_output,gefaelle_einzeln_verbunden_jason)
self.assert_dxf_geometry_equal(gefaelle_einzeln_verbunden_output, gefaelle_einzeln_verbunden_reference)
gefaelle_einzeln_verbunden_json = (
self.work_dir / f"{gefaelle_einzeln_verbunden}.json"
)
gefaelle_einzeln_verbunden_output = (
self.work_dir / gefaelle_einzeln_verbunden_dxf
)
gefaelle_einzeln_verbunden_reference = (
self.testordner_path / gefaelle_einzeln_verbunden_dxf
)
plant2dxf.main(
gefaelle_einzeln_verbunden_csv,
self.default_lib_path,
self.cfg_path,
self.allgemein_cfg_path,
gefaelle_einzeln_verbunden_output,
gefaelle_einzeln_verbunden_json,
)
self.assert_dxf_geometry_equal(
gefaelle_einzeln_verbunden_output, gefaelle_einzeln_verbunden_reference
)
if __name__ == "__main__":
unittest.main()
+5 -3
View File
@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
import os
import sys
import logging
@@ -14,7 +15,7 @@ def check_environment_var(env_str: str) -> Path:
sys.exit(1)
def setup_logger(log_dir: Path, name: str = 'app') -> logging.Logger:
def setup_logger(log_dir: Path, name: str = "app") -> logging.Logger:
"""
Erstellt und konfiguriert einen Logger, der sowohl in eine Datei als auch auf die Konsole schreibt.
Die Logdatei erhält einen Zeitstempel im Namen und ist UTF-8 kodiert.
@@ -26,6 +27,7 @@ def setup_logger(log_dir: Path, name: str = 'app') -> logging.Logger:
logging.Logger: Konfigurierter Logger
"""
from datetime import datetime
logger = logging.getLogger(name)
logger.setLevel(logging.INFO)
@@ -38,7 +40,7 @@ def setup_logger(log_dir: Path, name: str = 'app') -> logging.Logger:
# Logdatei mit Zeitstempel im Namen erzeugen
log_file = log_dir / f"{name}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
file_handler = logging.FileHandler(log_file, encoding='utf-8')
file_handler = logging.FileHandler(log_file, encoding="utf-8")
file_handler.setLevel(logging.INFO)
# Handler für die Konsole
@@ -46,7 +48,7 @@ def setup_logger(log_dir: Path, name: str = 'app') -> logging.Logger:
console_handler.setLevel(logging.INFO)
# Einheitliches Log-Format definieren
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)
+8
View File
@@ -0,0 +1,8 @@
[tool.black]
line-length = 88
target-version = ["py312"]
[tool.ruff]
line-length = 88
select = ["E", "F", "I"]
+10
View File
@@ -0,0 +1,10 @@
{
"[python]": {
"editor.defaultFormatter": "ms-python.black-formatter",
"editor.formatOnSave": true
},
"ruff.enable": true,
"ruff.fixOnSave": true,
"ruff.organizeImports": true
}
+6854 -1132
View File
File diff suppressed because it is too large Load Diff
+7
View File
@@ -0,0 +1,7 @@
Fehlen:
ILS Vario Förderer Test.
- Benötigt alle Test wie bei Gevälleststrecke + unterschedlche Winkel Berechnung mit Gefällestrecke und Berechnung ohne Motor und Umlenk vor allem bei winkel 0 und winkel 3 bei einem ab förderer + Verbindung mit Kreisel
Ils angetriebene Kurve(Vario kurve)
verbinding mit Gefällestrecke
Ils Kurve(Gefällekurve)
- Dimensionen und dxf Blöcke sind noch nicht richtig weshalb es hier noch keine Test gibt's
+5854 -132
View File
File diff suppressed because it is too large Load Diff
+10470 -4748
View File
File diff suppressed because it is too large Load Diff
+5878 -156
View File
File diff suppressed because it is too large Load Diff
+12414 -6692
View File
File diff suppressed because it is too large Load Diff
+162 -162
View File
@@ -1,163 +1,163 @@
Elementnummer;TeileArt;TeileId;NachbarIds;Bezeichnung;Planquadrat;rotation;Merkmale
0;"Omniflo Gerade";"shape_c28c3e87-a202-999b-8307-86423e59d723";;"OFGerade :1";"X:2987.01 Y:21597.21";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":-157.5,"SivasNummer":"821106002"}
1;"Omniflo Weiche";"shape_ed65ab41-03ff-5643-5e6a-b65d4d741f66";;"OFWeiche :1";"X:3437.5 Y:20513.33";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":0.0,"Antrieb Kurve":false,"SivasNummer":"834342011"}
2;"Omniflo Weiche";"shape_8a64f204-2809-7cdc-c381-f3774a1a2801";;"OFWeiche :5";"X:3640.11 Y:17135.33";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":0.0,"Antrieb Kurve":false,"SivasNummer":"834342011"}
3;"Omniflo Weiche";"shape_6ab32e55-7cfd-d321-35cb-e94716d19b24";;"OFWeiche :6";"X:3472.29 Y:17448.73";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-337.5,"Antrieb Kurve":false,"SivasNummer":"834342011"}
4;"Omniflo Weiche";"shape_68202e3c-df01-0890-ddc8-a26e2039a735";;"OFWeiche :7";"X:3197.32 Y:17674.05";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-315.0,"Antrieb Kurve":false,"SivasNummer":"834342011"}
5;"Omniflo Weiche";"shape_f84638af-d14a-8d9f-048b-4ba27581c686";;"OFWeiche :8";"X:2857.05 Y:17777";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-292.5,"Antrieb Kurve":false,"SivasNummer":"834342011"}
6;"Omniflo Weiche";"shape_641956f7-eb1f-b8a9-2c29-4c755321c289";;"OFWeiche :9";"X:2503.28 Y:17741.89";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-270.0,"Antrieb Kurve":false,"SivasNummer":"834342011"}
7;"Omniflo Weiche";"shape_24ff2071-a404-2ccf-350a-cbf7e82ddf38";;"OFWeiche :10";"X:2189.88 Y:17574.08";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-247.5,"Antrieb Kurve":false,"SivasNummer":"834342011"}
8;"Omniflo Weiche";"shape_9e5458eb-22ac-aaaa-cead-b39a75afd5aa";;"OFWeiche :11";"X:1964.56 Y:17299.1";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-225.0,"Antrieb Kurve":false,"SivasNummer":"834342011"}
9;"Omniflo Weiche";"shape_66d309e1-b951-c72f-5f17-7fd7eac29c7f";;"OFWeiche :12";"X:1861.61 Y:16958.83";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-202.5,"Antrieb Kurve":false,"SivasNummer":"834342011"}
10;"Omniflo Weiche";"shape_321f24e1-b44a-b4ba-7b24-ee5e2fec7cac";;"OFWeiche :13";"X:1896.72 Y:16605.06";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-180.0,"Antrieb Kurve":false,"SivasNummer":"834342011"}
11;"Omniflo Weiche";"shape_800bfe85-d8b8-f22b-7c84-5005548b013a";;"OFWeiche :14";"X:2064.54 Y:16291.66";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-157.5,"Antrieb Kurve":false,"SivasNummer":"834342011"}
12;"Omniflo Weiche";"shape_7fd2a6f8-8cad-24ef-4e1f-900e920d4009";;"OFWeiche :15";"X:2339.52 Y:16066.33";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-135.0,"Antrieb Kurve":false,"SivasNummer":"834342011"}
13;"Omniflo Weiche";"shape_6ab94c71-a18f-11d7-317e-aec53523f7ad";;"OFWeiche :16";"X:2679.78 Y:15963.38";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-112.5,"Antrieb Kurve":false,"SivasNummer":"834342011"}
14;"Omniflo Weiche";"shape_d9ea62df-a10b-7e40-df49-bd8948d96b09";;"OFWeiche :17";"X:3033.55 Y:15998.49";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-90.0,"Antrieb Kurve":false,"SivasNummer":"834342011"}
15;"Omniflo Weiche";"shape_d663149a-e3d0-972e-4ec6-2283b336c89b";;"OFWeiche :18";"X:3346.94 Y:16166.3";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-67.5,"Antrieb Kurve":false,"SivasNummer":"834342011"}
16;"Omniflo Weiche";"shape_0617228e-8d0c-9d0d-3e08-a8e7eeaf7b4b";;"OFWeiche :19";"X:3572.28 Y:16441.28";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-45.0,"Antrieb Kurve":false,"SivasNummer":"834342011"}
17;"Omniflo Weiche";"shape_514f8c32-aa28-c580-51fc-74e610204553";;"OFWeiche :20";"X:3675.23 Y:16781.55";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-22.5,"Antrieb Kurve":false,"SivasNummer":"834342011"}
18;"Omniflo Weiche";"shape_6aefdf65-10a6-01ad-a53c-46d0ad8a2c4b";;"OFWeiche :2";"X:4842.57 Y:20630.01";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":0.0,"Antrieb Kurve":false,"SivasNummer":"834342001"}
19;"Omniflo Weiche";"shape_47adcba4-6c6a-36ba-1017-90230aba79d3";;"OFWeiche :21";"X:4743.48 Y:17368.23";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":0.0,"Antrieb Kurve":false,"SivasNummer":"834342001"}
20;"Omniflo Weiche";"shape_76485f6c-9477-b689-9e32-f0f13d027b37";;"OFWeiche :22";"X:4911.3 Y:17681.63";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-22.5,"Antrieb Kurve":false,"SivasNummer":"834342001"}
21;"Omniflo Weiche";"shape_90232079-87f3-a60e-6600-f91137a6975a";;"OFWeiche :23";"X:5186.27 Y:17906.95";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-45.0,"Antrieb Kurve":false,"SivasNummer":"834342001"}
22;"Omniflo Weiche";"shape_ab3559af-3ec7-f922-8c0f-146a26c99beb";;"OFWeiche :24";"X:5526.54 Y:18009.9";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-67.5,"Antrieb Kurve":false,"SivasNummer":"834342001"}
23;"Omniflo Weiche";"shape_db149b8f-ea68-27c3-8544-2f41512ba242";;"OFWeiche :25";"X:5880.31 Y:17974.8";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-90.0,"Antrieb Kurve":false,"SivasNummer":"834342001"}
24;"Omniflo Weiche";"shape_17a47f48-97fe-bbb3-43f8-a0da81b0eb33";;"OFWeiche :26";"X:6193.71 Y:17806.99";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-112.5,"Antrieb Kurve":false,"SivasNummer":"834342001"}
25;"Omniflo Weiche";"shape_6b47bb12-4a38-6b67-76c1-81ce8d91ff9c";;"OFWeiche :27";"X:6419.04 Y:17532.01";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-135.0,"Antrieb Kurve":false,"SivasNummer":"834342001"}
26;"Omniflo Weiche";"shape_172842c7-8f23-d4fb-f6ff-e0cfb8723cd7";;"OFWeiche :28";"X:6521.99 Y:17191.74";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-157.5,"Antrieb Kurve":false,"SivasNummer":"834342001"}
27;"Omniflo Weiche";"shape_15bf22f8-8ebb-3421-f7a5-b29616eb8818";;"OFWeiche :29";"X:6486.88 Y:16837.97";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-180.0,"Antrieb Kurve":false,"SivasNummer":"834342001"}
28;"Omniflo Weiche";"shape_10e7f649-f966-2fb5-9b28-ce3ed35f5860";;"OFWeiche :30";"X:6319.07 Y:16524.57";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-202.5,"Antrieb Kurve":false,"SivasNummer":"834342001"}
29;"Omniflo Weiche";"shape_ded388e2-f306-1e64-5ec9-812844f585e3";;"OFWeiche :31";"X:6044.08 Y:16299.24";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-225.0,"Antrieb Kurve":false,"SivasNummer":"834342001"}
30;"Omniflo Weiche";"shape_0a6ba6f0-5b96-81d6-eba5-6e9a6d95131f";;"OFWeiche :32";"X:5703.82 Y:16196.29";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-247.5,"Antrieb Kurve":false,"SivasNummer":"834342001"}
31;"Omniflo Weiche";"shape_a032f781-9a32-70a6-2d3d-042a6b2a4621";;"OFWeiche :33";"X:5350.06 Y:16231.4";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-270.0,"Antrieb Kurve":false,"SivasNummer":"834342001"}
32;"Omniflo Weiche";"shape_11221d63-9db5-ea80-db6d-e63e47eb9331";;"OFWeiche :34";"X:5036.66 Y:16399.21";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-292.5,"Antrieb Kurve":false,"SivasNummer":"834342001"}
33;"Omniflo Weiche";"shape_536410df-9504-815f-2ca7-1aa3243fb019";;"OFWeiche :35";"X:4811.32 Y:16674.19";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-315.0,"Antrieb Kurve":false,"SivasNummer":"834342001"}
34;"Omniflo Weiche";"shape_a1a55130-29a3-00b2-d4f9-53bf1fa4dffb";;"OFWeiche :36";"X:4708.37 Y:17014.46";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-337.5,"Antrieb Kurve":false,"SivasNummer":"834342001"}
35;"Omniflo Weiche";"shape_2b17c199-bc06-49c9-fbbd-5cf1d911213c";;"OFWeiche :4";"X:11001.35 Y:20013.22";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":0.0,"Antrieb Kurve":false,"SivasNummer":"834342002"}
36;"Omniflo Weiche";"shape_37c38ff5-0008-28d6-ddaa-fb755559c355";;"OFWeiche :53";"X:10750.11 Y:16616.61";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":0.0,"Antrieb Kurve":false,"SivasNummer":"834342002"}
37;"Omniflo Weiche";"shape_faf356db-64e7-149d-2ad5-0d9a4657b293";;"OFWeiche :54";"X:10917.92 Y:16930.02";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-22.5,"Antrieb Kurve":false,"SivasNummer":"834342002"}
38;"Omniflo Weiche";"shape_f734e32b-7940-dc13-07e9-4773a8faf92e";;"OFWeiche :55";"X:11192.9 Y:17155.34";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-45.0,"Antrieb Kurve":false,"SivasNummer":"834342002"}
39;"Omniflo Weiche";"shape_594e019f-1ef4-620d-23b4-9e0a727f1727";;"OFWeiche :56";"X:11533.16 Y:17258.29";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-67.5,"Antrieb Kurve":false,"SivasNummer":"834342002"}
40;"Omniflo Weiche";"shape_af898037-336c-9986-1e58-58de76ac9d76";;"OFWeiche :57";"X:11886.93 Y:17223.19";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-90.0,"Antrieb Kurve":false,"SivasNummer":"834342002"}
41;"Omniflo Weiche";"shape_247bcf6a-42f9-3399-b905-d02c22b8dff3";;"OFWeiche :58";"X:12200.33 Y:17055.37";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-112.5,"Antrieb Kurve":false,"SivasNummer":"834342002"}
42;"Omniflo Weiche";"shape_80bb2e1a-beac-bb76-8550-32400a0f8bde";;"OFWeiche :59";"X:12425.67 Y:16780.39";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-135.0,"Antrieb Kurve":false,"SivasNummer":"834342002"}
43;"Omniflo Weiche";"shape_1a33b82d-385e-87ff-a8dc-aa4c497b680f";;"OFWeiche :60";"X:12528.62 Y:16440.13";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-157.5,"Antrieb Kurve":false,"SivasNummer":"834342002"}
44;"Omniflo Weiche";"shape_76bf365f-0837-14e1-ff51-bc9724f8ecb0";;"OFWeiche :61";"X:12493.5 Y:16086.36";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-180.0,"Antrieb Kurve":false,"SivasNummer":"834342002"}
45;"Omniflo Weiche";"shape_fc3e9554-56c0-e51e-3e0d-e9d994c15c72";;"OFWeiche :62";"X:12325.69 Y:15772.96";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-202.5,"Antrieb Kurve":false,"SivasNummer":"834342002"}
46;"Omniflo Weiche";"shape_63642dd0-a688-5709-2311-519f6e7f6ed1";;"OFWeiche :63";"X:12050.71 Y:15547.63";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-225.0,"Antrieb Kurve":false,"SivasNummer":"834342002"}
47;"Omniflo Weiche";"shape_e8be0ce0-321d-8733-ab85-2807eb2767d3";;"OFWeiche :64";"X:11710.44 Y:15444.68";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-247.5,"Antrieb Kurve":false,"SivasNummer":"834342002"}
48;"Omniflo Weiche";"shape_cacd4b95-1823-2281-50be-63587b85426f";;"OFWeiche :65";"X:11356.68 Y:15479.79";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-270.0,"Antrieb Kurve":false,"SivasNummer":"834342002"}
49;"Omniflo Weiche";"shape_13d5f9cb-2405-02b7-ffe8-2ff787d855b0";;"OFWeiche :66";"X:11043.28 Y:15647.6";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-292.5,"Antrieb Kurve":false,"SivasNummer":"834342002"}
50;"Omniflo Weiche";"shape_3a07e029-9233-277d-7a1c-2c06d2d8ab9c";;"OFWeiche :67";"X:10817.95 Y:15922.58";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-315.0,"Antrieb Kurve":false,"SivasNummer":"834342002"}
51;"Omniflo Weiche";"shape_dcacbab1-2a2b-1c8c-2305-c439c5211f6c";;"OFWeiche :68";"X:10715 Y:16262.86";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-337.5,"Antrieb Kurve":false,"SivasNummer":"834342002"}
52;"Omniflo Weiche";"shape_b5f9e1d6-ec82-58f7-df38-543c23300aa6";;"OFWeiche :3";"X:9733.32 Y:20197.59";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":0.0,"Antrieb Kurve":false,"SivasNummer":"834342012"}
53;"Omniflo Weiche";"shape_6bdc6141-69cc-29ca-233c-74dacbfafa6d";;"OFWeiche :37";"X:9397.83 Y:17436.71";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-315.0,"Antrieb Kurve":false,"SivasNummer":"834342012"}
54;"Omniflo Weiche";"shape_3a03e7ce-dc23-8040-9233-8bbc9bfe2d4b";;"OFWeiche :38";"X:9057.56 Y:17539.66";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-292.5,"Antrieb Kurve":false,"SivasNummer":"834342012"}
55;"Omniflo Weiche";"shape_9b299674-8a04-f186-4e7a-7374cc793877";;"OFWeiche :39";"X:9840.62 Y:16897.98";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":0.0,"Antrieb Kurve":false,"SivasNummer":"834342012"}
56;"Omniflo Weiche";"shape_6f6f6895-f27c-2a62-e395-9c78737d25a4";;"OFWeiche :40";"X:9672.8 Y:17211.39";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-337.5,"Antrieb Kurve":false,"SivasNummer":"834342012"}
57;"Omniflo Weiche";"shape_47dc4ae0-d662-247d-c3cf-100f6db40c6c";;"OFWeiche :41";"X:8165.06 Y:17061.76";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-225.0,"Antrieb Kurve":false,"SivasNummer":"834342012"}
58;"Omniflo Weiche";"shape_39206f42-bd37-22d8-4447-04e807e2f1f2";;"OFWeiche :42";"X:8062.12 Y:16721.5";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-202.5,"Antrieb Kurve":false,"SivasNummer":"834342012"}
59;"Omniflo Weiche";"shape_11572cf2-bdfa-cec6-5c56-9e8605d8af9c";;"OFWeiche :43";"X:8703.79 Y:17504.56";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-270.0,"Antrieb Kurve":false,"SivasNummer":"834342012"}
60;"Omniflo Weiche";"shape_359b95f3-7f89-b46f-fa71-787aa33f0e3a";;"OFWeiche :44";"X:8390.39 Y:17336.75";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-247.5,"Antrieb Kurve":false,"SivasNummer":"834342012"}
61;"Omniflo Weiche";"shape_2240036f-3c1b-964c-1b40-22e3652547db";;"OFWeiche :45";"X:8540.03 Y:15829";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-135.0,"Antrieb Kurve":false,"SivasNummer":"834342012"}
62;"Omniflo Weiche";"shape_3ea7a8a1-ba61-807d-3315-9f9641be145a";;"OFWeiche :46";"X:8880.3 Y:15726.05";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-112.5,"Antrieb Kurve":false,"SivasNummer":"834342012"}
63;"Omniflo Weiche";"shape_3501691e-1994-b951-ab1a-f3076dc78855";;"OFWeiche :47";"X:8097.23 Y:16367.73";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-180.0,"Antrieb Kurve":false,"SivasNummer":"834342012"}
64;"Omniflo Weiche";"shape_735e598d-5ead-894f-0347-c20c1cdf0fd3";;"OFWeiche :48";"X:8265.05 Y:16054.33";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-157.5,"Antrieb Kurve":false,"SivasNummer":"834342012"}
65;"Omniflo Weiche";"shape_b547b91e-9eb3-0067-b874-ac0bed6373cd";;"OFWeiche :49";"X:9772.79 Y:16203.95";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-45.0,"Antrieb Kurve":false,"SivasNummer":"834342012"}
66;"Omniflo Weiche";"shape_8fe43748-94cb-8b2d-6fc7-19c1e850b324";;"OFWeiche :50";"X:9875.74 Y:16544.22";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-22.5,"Antrieb Kurve":false,"SivasNummer":"834342012"}
67;"Omniflo Weiche";"shape_647a333a-4725-149d-1877-934ce43f35b3";;"OFWeiche :51";"X:9234.06 Y:15761.16";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-90.0,"Antrieb Kurve":false,"SivasNummer":"834342012"}
68;"Omniflo Weiche";"shape_783f151b-e363-45ae-ee5d-85a7b77dae86";;"OFWeiche :52";"X:9547.46 Y:15928.97";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-67.5,"Antrieb Kurve":false,"SivasNummer":"834342012"}
69;"Omniflo Gerade";"shape_e15a9ee3-d18a-82cb-1c7c-83cc7673151f";;"OFGerade :2";"X:3471.2 Y:21693.33";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":-180.0,"SivasNummer":"821106002"}
70;"Omniflo Gerade";"shape_f0812e4f-c0db-4dc2-04f0-6763ce79043f";;"OFGerade :4";"X:5293.06 Y:21713.89";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":-202.5,"SivasNummer":"821106002"}
71;"Omniflo Gerade";"shape_9febb504-a812-b945-c2f2-accdf67e38b6";;"OFGerade :3";"X:3471.2 Y:19333.33";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":0.0,"SivasNummer":"821106002"}
72;"Omniflo Gerade";"shape_6543d35f-9494-4a26-7cb4-00558d776cd0";;"OFGerade :6";"X:4808.87 Y:19450.01";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":0.0,"SivasNummer":"821106002"}
73;"Omniflo Gerade";"shape_85e377fa-67fc-1ebf-6407-62681289c348";;"OFGerade :7";"X:9282.84 Y:21281.47";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":-157.5,"SivasNummer":"821106002"}
74;"Omniflo Gerade";"shape_4947a12e-d9b5-c6c5-b138-258cc3916e3f";;"OFGerade :9";"X:9767.02 Y:21377.59";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":-180.0,"SivasNummer":"821106002"}
75;"Omniflo Gerade";"shape_a97214ae-a19e-16dc-d8fc-ea80ae19144d";;"OFGerade :5";"X:4808.87 Y:21810.01";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":-180.0,"SivasNummer":"821106002"}
76;"Omniflo Gerade";"shape_39ad72bc-c463-c6b4-0257-ed6c273439b1";;"OFGerade :11";"X:10967.66 Y:21193.22";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":-180.0,"SivasNummer":"821106002"}
77;"Omniflo Gerade";"shape_a0957a75-33fa-039c-9cc2-84f52c6ed573";;"OFGerade :8";"X:9767.02 Y:19017.59";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":0.0,"SivasNummer":"821106002"}
78;"Omniflo Gerade";"shape_dcc568a8-8282-fa20-1508-f802a848608b";;"OFGerade :10";"X:11451.84 Y:21097.1";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":-202.5,"SivasNummer":"821106002"}
79;"Omniflo Weiche";"shape_2bb8e674-6c3a-e151-705d-3bf302336766";;"OFWeiche :69";"X:4062.5 Y:13013.33";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":0.0,"Antrieb Kurve":false,"SivasNummer":"834342100"}
80;"Omniflo Weiche";"shape_041da0cf-8d6a-6135-6510-17600d97ba98";;"OFWeiche :71";"X:3259.37 Y:9633.58";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":0.0,"Antrieb Kurve":false,"SivasNummer":"834342100"}
81;"Omniflo Weiche";"shape_223d2db7-ee46-ada9-fac7-b71c26c51522";;"OFWeiche :72";"X:3427.47 Y:9948.38";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-22.5,"Antrieb Kurve":false,"SivasNummer":"834342100"}
82;"Omniflo Weiche";"shape_f181294b-a019-5568-8913-a2109d542904";;"OFWeiche :73";"X:3703.25 Y:10174.89";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-45.0,"Antrieb Kurve":false,"SivasNummer":"834342100"}
83;"Omniflo Weiche";"shape_3ff6b943-0035-6acb-a172-65b1f737799a";;"OFWeiche :74";"X:4044.72 Y:10278.63";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-67.5,"Antrieb Kurve":false,"SivasNummer":"834342100"}
84;"Omniflo Weiche";"shape_3470fe79-dd44-a35d-56aa-81665a607de9";;"OFWeiche :75";"X:4399.88 Y:10243.79";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-90.0,"Antrieb Kurve":false,"SivasNummer":"834342100"}
85;"Omniflo Weiche";"shape_cf530110-bef9-505e-5883-59e28cf4c6d3";;"OFWeiche :76";"X:4714.68 Y:10075.69";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-112.5,"Antrieb Kurve":false,"SivasNummer":"834342100"}
86;"Omniflo Weiche";"shape_b1d9d70a-7a39-470b-24ac-07964df31cbf";;"OFWeiche :77";"X:4941.19 Y:9799.87";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-135.0,"Antrieb Kurve":false,"SivasNummer":"834342100"}
87;"Omniflo Weiche";"shape_08895cb0-ce26-ec52-0985-648709fdc694";;"OFWeiche :78";"X:5044.92 Y:9458.43";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-157.5,"Antrieb Kurve":false,"SivasNummer":"834342100"}
88;"Omniflo Weiche";"shape_37a6a791-9a10-bd8a-8e40-a5f040b46833";;"OFWeiche :79";"X:5010.08 Y:9103.26";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-180.0,"Antrieb Kurve":false,"SivasNummer":"834342100"}
89;"Omniflo Weiche";"shape_db0aa45b-4b38-5d5d-252f-dade9af29e6d";;"OFWeiche :80";"X:4841.98 Y:8788.4";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-202.5,"Antrieb Kurve":false,"SivasNummer":"834342100"}
90;"Omniflo Weiche";"shape_72ddf224-2ed6-4f93-6f24-92b141b4c211";;"OFWeiche :81";"X:4566.2 Y:8561.92";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-225.0,"Antrieb Kurve":false,"SivasNummer":"834342100"}
91;"Omniflo Weiche";"shape_5fdd1642-ab15-5331-ecd1-a75e5db56281";;"OFWeiche :82";"X:4224.75 Y:8458.2";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-247.5,"Antrieb Kurve":false,"SivasNummer":"834342100"}
92;"Omniflo Weiche";"shape_c39d9426-56c4-7b65-2653-4769e023f0cc";;"OFWeiche :83";"X:3869.58 Y:8493";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-270.0,"Antrieb Kurve":false,"SivasNummer":"834342100"}
93;"Omniflo Weiche";"shape_11cc4e7f-f71c-a2a8-10a2-fe81289efa9e";;"OFWeiche :84";"X:3554.78 Y:8661.14";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-292.5,"Antrieb Kurve":false,"SivasNummer":"834342100"}
94;"Omniflo Weiche";"shape_053f58a2-b45a-8dfd-e9f4-2e7e85c0d5d7";;"OFWeiche :85";"X:3328.27 Y:8936.97";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-315.0,"Antrieb Kurve":false,"SivasNummer":"834342100"}
95;"Omniflo Weiche";"shape_bd2946c0-6ea0-fe80-a7e1-935b9903e6fe";;"OFWeiche :86";"X:3224.54 Y:9278.41";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-337.5,"Antrieb Kurve":false,"SivasNummer":"834342100"}
96;"Omniflo Gerade";"shape_b9ca0ac6-31bd-fd64-64c9-17565cd003db";;"OFGerade :12";"X:10967.66 Y:18833.22";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":0.0,"SivasNummer":"821106002"}
97;"Omniflo Weiche";"shape_4d230566-6551-8300-484a-5d967872be13";;"OFWeiche :70";"X:6620.95 Y:12901.79";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":0.0,"Antrieb Kurve":false,"SivasNummer":"834342101"}
98;"Omniflo Weiche";"shape_174f1f2f-adbb-b033-97cd-9369cf3febb8";;"OFWeiche :87";"X:6559.22 Y:9659.4";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":0.0,"Antrieb Kurve":false,"SivasNummer":"834342101"}
99;"Omniflo Weiche";"shape_b3afef71-ff30-42ea-c61a-d9bd679979d6";;"OFWeiche :88";"X:6727.32 Y:9974.18";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-22.5,"Antrieb Kurve":false,"SivasNummer":"834342101"}
100;"Omniflo Weiche";"shape_19c2db56-5250-5c3c-e7a6-dd1cce35ea5b";;"OFWeiche :89";"X:7003.09 Y:10200.69";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-45.0,"Antrieb Kurve":false,"SivasNummer":"834342101"}
101;"Omniflo Weiche";"shape_1af9ff99-b7eb-a0c0-a3e0-9936cdbb3be3";;"OFWeiche :90";"X:7344.55 Y:10304.42";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-67.5,"Antrieb Kurve":false,"SivasNummer":"834342101"}
102;"Omniflo Weiche";"shape_1a295c40-7a81-8304-ccbd-3ece740aee78";;"OFWeiche :91";"X:7699.72 Y:10269.59";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-90.0,"Antrieb Kurve":false,"SivasNummer":"834342101"}
103;"Omniflo Weiche";"shape_e4a46e39-577a-5845-b979-938320c1ffc2";;"OFWeiche :92";"X:8014.52 Y:10101.48";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-112.5,"Antrieb Kurve":false,"SivasNummer":"834342101"}
104;"Omniflo Weiche";"shape_ac35c116-8540-674a-cac9-267bbd770cc9";;"OFWeiche :93";"X:8241.03 Y:9825.67";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-135.0,"Antrieb Kurve":false,"SivasNummer":"834342101"}
105;"Omniflo Weiche";"shape_87099f46-591e-d319-520f-a0980f88f5cc";;"OFWeiche :94";"X:8344.76 Y:9484.22";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-157.5,"Antrieb Kurve":false,"SivasNummer":"834342101"}
106;"Omniflo Weiche";"shape_c53d6df4-a77a-8459-83dd-25114f50775d";;"OFWeiche :95";"X:8309.93 Y:9129.06";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-180.0,"Antrieb Kurve":false,"SivasNummer":"834342101"}
107;"Omniflo Weiche";"shape_d9fb25d0-79d4-0e2b-443f-394caf2770b2";;"OFWeiche :96";"X:8141.83 Y:8814.22";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-202.5,"Antrieb Kurve":false,"SivasNummer":"834342101"}
108;"Omniflo Weiche";"shape_c3f2dbb3-37bc-b8c9-c8d0-dd7502710824";;"OFWeiche :97";"X:7866.05 Y:8587.73";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-225.0,"Antrieb Kurve":false,"SivasNummer":"834342101"}
109;"Omniflo Weiche";"shape_0d28b368-c7a6-61b6-e1a7-46786b3953a1";;"OFWeiche :98";"X:7524.6 Y:8484.02";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-247.5,"Antrieb Kurve":false,"SivasNummer":"834342101"}
110;"Omniflo Weiche";"shape_676d9674-b761-f4cb-7fe1-37c2d92bda3b";;"OFWeiche :99";"X:7169.43 Y:8518.81";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-270.0,"Antrieb Kurve":false,"SivasNummer":"834342101"}
111;"Omniflo Weiche";"shape_1592f4d7-9ba5-800d-123b-f4942fdce17c";;"OFWeiche :100";"X:6854.63 Y:8686.95";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-292.5,"Antrieb Kurve":false,"SivasNummer":"834342101"}
112;"Omniflo Weiche";"shape_4ec87ee5-a085-4f4a-a0bd-9d2d68d6ddd6";;"OFWeiche :101";"X:6628.12 Y:8962.78";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-315.0,"Antrieb Kurve":false,"SivasNummer":"834342101"}
113;"Omniflo Weiche";"shape_165b661e-abfc-ccc0-69c9-b47490175774";;"OFWeiche :102";"X:6524.39 Y:9304.23";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-337.5,"Antrieb Kurve":false,"SivasNummer":"834342101"}
114;"Omniflo Gerade";"shape_a40d0370-30cc-d614-22cf-47eac751f50f";;"OFGerade :14";"X:3578.32 Y:14091.23";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":-337.5,"SivasNummer":"821106002"}
115;"Omniflo Gerade";"shape_fcda7b25-3893-fcf6-8a34-7df969801ef7";;"OFGerade :13";"X:4546.69 Y:14091.23";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":-22.5,"SivasNummer":"821106002"}
116;"Omniflo Gerade";"shape_b5a80eb7-fd23-c393-5734-608fbcbe2cdf";;"OFGerade :15";"X:4062.5 Y:11839.3";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":0.0,"SivasNummer":"821106002"}
117;"Omniflo Gerade";"shape_b00aa523-ec5a-5d56-3687-423d792566ae";;"OFGerade :16";"X:7105.13 Y:13979.7";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":-22.5,"SivasNummer":"821106002"}
118;"Omniflo Gerade";"shape_723b8795-a978-6e10-5bc1-8dcced33311f";;"OFGerade :18";"X:6620.95 Y:11727.77";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":0.0,"SivasNummer":"821106002"}
119;"Omniflo Gerade";"shape_ffa39de9-9974-68d7-2de8-dd93ac6f5a6e";;"OFGerade :17";"X:6136.76 Y:13979.7";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":-337.5,"SivasNummer":"821106002"}
120;"Omniflo Weiche";"shape_4344e827-9d08-15f7-2c02-ca4dfd8038ac";;"OFWeiche :104";"X:7172.7 Y:5434.56";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":0.0,"Antrieb Kurve":false,"SivasNummer":"834342201"}
121;"Omniflo Weiche";"shape_4b863468-09aa-5f6f-1606-cd1f86bb83fd";;"OFWeiche :121";"X:8462.7 Y:1203.19";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":0.0,"Antrieb Kurve":false,"SivasNummer":"834342201"}
122;"Omniflo Weiche";"shape_810db42d-d195-4163-ec4e-d011a35d71bf";;"OFWeiche :122";"X:8292.31 Y:1529.44";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-337.5,"Antrieb Kurve":false,"SivasNummer":"834342201"}
123;"Omniflo Weiche";"shape_71a110cc-efef-45e1-4c54-122d40278b60";;"OFWeiche :123";"X:8010.04 Y:1765.76";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-315.0,"Antrieb Kurve":false,"SivasNummer":"834342201"}
124;"Omniflo Weiche";"shape_33d4c2f8-cf49-8bb8-ae5a-9790b77261f0";;"OFWeiche :124";"X:7658.83 Y:1875.96";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-292.5,"Antrieb Kurve":false,"SivasNummer":"834342201"}
125;"Omniflo Weiche";"shape_c46dd27d-3645-0f6c-604c-5c7d106498ce";;"OFWeiche :125";"X:7292.16 Y:1843.43";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-270.0,"Antrieb Kurve":false,"SivasNummer":"834342201"}
126;"Omniflo Weiche";"shape_8ba2d344-68b1-3900-b7f1-c1be5652e801";;"OFWeiche :126";"X:6965.87 Y:1673.04";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-247.5,"Antrieb Kurve":false,"SivasNummer":"834342201"}
127;"Omniflo Weiche";"shape_62dbb0d4-3dcd-1bde-1a19-3f4e99a7a356";;"OFWeiche :127";"X:6729.61 Y:1390.73";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-225.0,"Antrieb Kurve":false,"SivasNummer":"834342201"}
128;"Omniflo Weiche";"shape_a4c7811f-4930-4867-57e3-82abcfc8541d";;"OFWeiche :128";"X:6619.35 Y:1039.49";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-202.5,"Antrieb Kurve":false,"SivasNummer":"834342201"}
129;"Omniflo Weiche";"shape_8d38188c-9831-bee4-9487-d155c106d54a";;"OFWeiche :129";"X:6651.9 Y:672.83";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-180.0,"Antrieb Kurve":false,"SivasNummer":"834342201"}
130;"Omniflo Weiche";"shape_0bd18242-96e1-723d-46cc-2c23a47b6140";;"OFWeiche :130";"X:6822.28 Y:346.47";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-157.5,"Antrieb Kurve":false,"SivasNummer":"834342201"}
131;"Omniflo Weiche";"shape_826ae9a2-0dfb-a646-fa74-a1933ebc99c0";;"OFWeiche :131";"X:7104.57 Y:110.19";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-135.0,"Antrieb Kurve":false,"SivasNummer":"834342201"}
132;"Omniflo Weiche";"shape_56be85a5-dc95-6cd6-3753-42c70bad3008";;"OFWeiche :132";"X:7455.77 Y:0";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-112.5,"Antrieb Kurve":false,"SivasNummer":"834342201"}
133;"Omniflo Weiche";"shape_be174689-5cc1-4dae-2799-bc31b8126476";;"OFWeiche :133";"X:7822.43 Y:32.54";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-90.0,"Antrieb Kurve":false,"SivasNummer":"834342201"}
134;"Omniflo Weiche";"shape_ca5cc998-c43e-8716-54f0-e955f3f4b7f5";;"OFWeiche :134";"X:8148.73 Y:202.93";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-67.5,"Antrieb Kurve":false,"SivasNummer":"834342201"}
135;"Omniflo Weiche";"shape_eaebcfbb-d751-3c4c-b8fd-519fb211bd75";;"OFWeiche :135";"X:8384.99 Y:485.24";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-45.0,"Antrieb Kurve":false,"SivasNummer":"834342201"}
136;"Omniflo Weiche";"shape_7a84a28f-169d-ad90-3140-fd37cdd36243";;"OFWeiche :136";"X:8495.24 Y:836.48";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-22.5,"Antrieb Kurve":false,"SivasNummer":"834342201"}
137;"Omniflo Gerade";"shape_4252db0f-3348-279d-aabb-93cde93c59df";;"OFGerade :19";"X:5266 Y:6640.47";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":-202.5,"SivasNummer":"821106002"}
138;"Omniflo Weiche";"shape_ce3c4c74-c20d-bca6-5a2f-1a7e7b1da764";;"OFWeiche :103";"X:4781.82 Y:5556.56";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":0.0,"Antrieb Kurve":false,"SivasNummer":"834342200"}
139;"Omniflo Weiche";"shape_5536580b-4002-75ac-4846-5fe15ea55d70";;"OFWeiche :105";"X:5122.18 Y:1531.93";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":0.0,"Antrieb Kurve":false,"SivasNummer":"834342200"}
140;"Omniflo Weiche";"shape_2cd6c02b-6826-8c7c-5234-ed652d2b26b0";;"OFWeiche :106";"X:4951.79 Y:1858.18";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-337.5,"Antrieb Kurve":false,"SivasNummer":"834342200"}
141;"Omniflo Weiche";"shape_369568c3-474e-5151-6a84-35fc8c70ff07";;"OFWeiche :107";"X:4669.51 Y:2094.37";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-315.0,"Antrieb Kurve":false,"SivasNummer":"834342200"}
142;"Omniflo Weiche";"shape_75689a76-6a9d-1059-7f50-323600d5d7ff";;"OFWeiche :108";"X:4318.3 Y:2204.57";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-292.5,"Antrieb Kurve":false,"SivasNummer":"834342200"}
143;"Omniflo Weiche";"shape_568d24dd-2b1f-9752-9f5f-f3b602fc420e";;"OFWeiche :109";"X:3951.64 Y:2172.04";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-270.0,"Antrieb Kurve":false,"SivasNummer":"834342200"}
144;"Omniflo Weiche";"shape_494ac0b1-d31b-7d40-ae95-8cb58e714498";;"OFWeiche :110";"X:3625.34 Y:2001.65";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-247.5,"Antrieb Kurve":false,"SivasNummer":"834342200"}
145;"Omniflo Weiche";"shape_79b821ff-018f-c963-e88b-f31e10919bef";;"OFWeiche :111";"X:3389.08 Y:1719.34";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-225.0,"Antrieb Kurve":false,"SivasNummer":"834342200"}
146;"Omniflo Weiche";"shape_aab836b4-70dd-8130-d65b-d0396c75ecc6";;"OFWeiche :112";"X:3278.83 Y:1368.11";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-202.5,"Antrieb Kurve":false,"SivasNummer":"834342200"}
147;"Omniflo Weiche";"shape_ba2e453e-6419-a083-32bc-2b2693d97566";;"OFWeiche :113";"X:3311.38 Y:1001.45";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-180.0,"Antrieb Kurve":false,"SivasNummer":"834342200"}
148;"Omniflo Weiche";"shape_fea58350-d17e-5ecc-92e9-2ddcd2c5da90";;"OFWeiche :114";"X:3481.76 Y:675.08";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-157.5,"Antrieb Kurve":false,"SivasNummer":"834342200"}
149;"Omniflo Weiche";"shape_4b33e5b8-893f-eede-5eb7-77e5af88d445";;"OFWeiche :115";"X:3764.04 Y:438.81";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-135.0,"Antrieb Kurve":false,"SivasNummer":"834342200"}
150;"Omniflo Weiche";"shape_c5cbaa0a-a0bc-048c-9150-65b9384e3964";;"OFWeiche :116";"X:4115.25 Y:328.61";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-112.5,"Antrieb Kurve":false,"SivasNummer":"834342200"}
151;"Omniflo Weiche";"shape_ce25d10b-3650-e938-8165-4ba6dc2d42e0";;"OFWeiche :117";"X:4481.91 Y:361.15";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-90.0,"Antrieb Kurve":false,"SivasNummer":"834342200"}
152;"Omniflo Weiche";"shape_8baaf80d-5ead-d573-aa3a-928c7d161b8e";;"OFWeiche :118";"X:4808.21 Y:531.54";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-67.5,"Antrieb Kurve":false,"SivasNummer":"834342200"}
153;"Omniflo Weiche";"shape_168a80e6-b4d4-6639-eda0-4a9d71221712";;"OFWeiche :119";"X:5044.47 Y:813.86";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-45.0,"Antrieb Kurve":false,"SivasNummer":"834342200"}
154;"Omniflo Weiche";"shape_50b307ef-e4d2-2bf0-846c-b96fe7ca895e";;"OFWeiche :120";"X:5154.72 Y:1165.09";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-22.5,"Antrieb Kurve":false,"SivasNummer":"834342200"}
155;"Omniflo Gerade";"shape_f67d332d-5006-6fcf-e44d-88aed25907da";;"OFGerade :21";"X:4297.63 Y:6640.47";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":-337.5,"SivasNummer":"821106002"}
156;"Omniflo Gerade";"shape_db8b6322-7432-f341-9c9f-209cd0a2dfc1";;"OFGerade :22";"X:4781.81 Y:4376.6";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":0.0,"SivasNummer":"821106002"}
157;"Omniflo Gerade";"shape_dddb805a-3853-efdd-a465-e01097ef0ffd";;"OFGerade :20";"X:4781.81 Y:6736.6";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":-180.0,"SivasNummer":"821106002"}
158;"Omniflo Gerade";"shape_1f22d496-db96-5462-ab9e-6005a04eca48";;"OFGerade :23";"X:7656.88 Y:6518.5";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":-202.5,"SivasNummer":"821106002"}
159;"Omniflo Gerade";"shape_774b109d-cc64-45c6-023b-9ba6ae24a7dc";;"OFGerade :24";"X:7172.69 Y:6614.62";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":-180.0,"SivasNummer":"821106002"}
160;"Omniflo Gerade";"shape_328e63f2-8cc3-c094-af79-3a2b6cf12ce4";;"OFGerade :25";"X:6688.51 Y:6518.5";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":-337.5,"SivasNummer":"821106002"}
161;"Omniflo Gerade";"shape_a48da57f-decc-9fa1-a787-c02ac0cdc194";;"OFGerade :26";"X:7172.69 Y:4254.62";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":0.0,"SivasNummer":"821106002"}
0;"Omniflo Gerade";"shape_9b26e364-d9f3-5f32-ee6c-0616b44e1798";;"OFGerade :1";"X:3962.26 Y:24338.11";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":0.0,"SivasNummer":"821106002"}
1;"Omniflo Weiche";"shape_49fb15fd-aacc-abe3-bb93-f1df4858fdd0";;"OFWeiche :1";"X:3928.57 Y:25518.11";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":0.0,"Antrieb Kurve":false,"SivasNummer":"834342011"}
2;"Omniflo Weiche";"shape_64347abe-fbf9-7873-2934-e14fc46d1a48";;"OFWeiche :5";"X:3988.09 Y:20504.27";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":0.0,"Antrieb Kurve":false,"SivasNummer":"834342011"}
3;"Omniflo Weiche";"shape_318f47b2-7631-0ce8-13c2-316a7006c369";;"OFWeiche :6";"X:3820.28 Y:20817.67";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-337.5,"Antrieb Kurve":false,"SivasNummer":"834342011"}
4;"Omniflo Weiche";"shape_6895c12f-32b6-317e-5e4c-8724b0b4b362";;"OFWeiche :7";"X:3545.3 Y:21043";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-315.0,"Antrieb Kurve":false,"SivasNummer":"834342011"}
5;"Omniflo Weiche";"shape_28f608db-52db-7a27-daae-14692b7776b5";;"OFWeiche :8";"X:3205.03 Y:21145.95";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-292.5,"Antrieb Kurve":false,"SivasNummer":"834342011"}
6;"Omniflo Weiche";"shape_802dcf32-0209-913d-6633-45a0c7167cf2";;"OFWeiche :9";"X:2851.27 Y:21110.84";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-270.0,"Antrieb Kurve":false,"SivasNummer":"834342011"}
7;"Omniflo Weiche";"shape_fd30ae3c-7a2e-1305-7fec-ca78b36b0275";;"OFWeiche :10";"X:2537.87 Y:20943.03";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-247.5,"Antrieb Kurve":false,"SivasNummer":"834342011"}
8;"Omniflo Weiche";"shape_3ce2b136-c43c-700b-a6e6-bef196cbe98b";;"OFWeiche :11";"X:2312.54 Y:20668.04";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-225.0,"Antrieb Kurve":false,"SivasNummer":"834342011"}
9;"Omniflo Weiche";"shape_323bc6de-6a93-979b-1e13-f729fb008136";;"OFWeiche :12";"X:2209.59 Y:20327.76";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-202.5,"Antrieb Kurve":false,"SivasNummer":"834342011"}
10;"Omniflo Weiche";"shape_d5333793-3140-9866-de6d-c53e3153a727";;"OFWeiche :13";"X:2244.7 Y:19974";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-180.0,"Antrieb Kurve":false,"SivasNummer":"834342011"}
11;"Omniflo Weiche";"shape_905bb376-c7f2-5635-2005-4b0049f6fb7c";;"OFWeiche :14";"X:2412.51 Y:19660.6";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-157.5,"Antrieb Kurve":false,"SivasNummer":"834342011"}
12;"Omniflo Weiche";"shape_62644b1d-946d-6db8-ae47-9b9c86cddee9";;"OFWeiche :15";"X:2687.49 Y:19435.27";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-135.0,"Antrieb Kurve":false,"SivasNummer":"834342011"}
13;"Omniflo Weiche";"shape_39c4b344-2124-657c-76f0-abc72a0b05d5";;"OFWeiche :16";"X:3027.76 Y:19332.32";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-112.5,"Antrieb Kurve":false,"SivasNummer":"834342011"}
14;"Omniflo Weiche";"shape_1e047631-77f4-3b77-3580-fe218df549c7";;"OFWeiche :17";"X:3381.52 Y:19367.43";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-90.0,"Antrieb Kurve":false,"SivasNummer":"834342011"}
15;"Omniflo Weiche";"shape_cd5256ea-fd47-df0a-c071-5bea7ece9e42";;"OFWeiche :18";"X:3694.92 Y:19535.24";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-67.5,"Antrieb Kurve":false,"SivasNummer":"834342011"}
16;"Omniflo Weiche";"shape_30d81c27-ad8d-cdaf-a93a-03ce72089773";;"OFWeiche :19";"X:3920.24 Y:19810.22";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-45.0,"Antrieb Kurve":false,"SivasNummer":"834342011"}
17;"Omniflo Weiche";"shape_879ddf67-143b-a25d-0d71-44f105ec918c";;"OFWeiche :20";"X:4023.19 Y:20150.49";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-22.5,"Antrieb Kurve":false,"SivasNummer":"834342011"}
18;"Omniflo Weiche";"shape_b73972a6-d892-0833-40cc-86ba33983800";;"OFWeiche :3";"X:9386.3 Y:25634.03";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":0.0,"Antrieb Kurve":false,"SivasNummer":"834342012"}
19;"Omniflo Weiche";"shape_0269fea0-01a1-9fdc-9f3e-4174a29d61de";;"OFWeiche :37";"X:9623.05 Y:20582.39";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":0.0,"Antrieb Kurve":false,"SivasNummer":"834342012"}
20;"Omniflo Weiche";"shape_2dc1496a-d8bf-92a6-b9c2-559b5cb2eab0";;"OFWeiche :38";"X:9455.23 Y:20895.79";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-337.5,"Antrieb Kurve":false,"SivasNummer":"834342012"}
21;"Omniflo Weiche";"shape_693b4421-0efc-6e90-7dee-e8deebb11455";;"OFWeiche :39";"X:9180.25 Y:21121.13";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-315.0,"Antrieb Kurve":false,"SivasNummer":"834342012"}
22;"Omniflo Weiche";"shape_c8392a6e-e378-a6f2-dcc4-35b0e3206e3e";;"OFWeiche :40";"X:8839.97 Y:21224.06";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-292.5,"Antrieb Kurve":false,"SivasNummer":"834342012"}
23;"Omniflo Weiche";"shape_9bdfded6-4ad5-13c1-1fb6-71832b762d16";;"OFWeiche :41";"X:8486.21 Y:21188.95";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-270.0,"Antrieb Kurve":false,"SivasNummer":"834342012"}
24;"Omniflo Weiche";"shape_ef1bf5c2-3f70-fe07-ad6d-1c381c52e0b6";;"OFWeiche :42";"X:8172.81 Y:21021.14";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-247.5,"Antrieb Kurve":false,"SivasNummer":"834342012"}
25;"Omniflo Weiche";"shape_6e64a28b-f8c6-2a4f-e28b-a0bfdc0c5af7";;"OFWeiche :43";"X:7947.48 Y:20746.16";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-225.0,"Antrieb Kurve":false,"SivasNummer":"834342012"}
26;"Omniflo Weiche";"shape_f051c29b-523f-c051-6a30-7c09feeab862";;"OFWeiche :44";"X:7844.53 Y:20405.89";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-202.5,"Antrieb Kurve":false,"SivasNummer":"834342012"}
27;"Omniflo Weiche";"shape_8451c719-af1a-7cfa-e424-a0c00c5f94ad";;"OFWeiche :45";"X:7879.64 Y:20052.13";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-180.0,"Antrieb Kurve":false,"SivasNummer":"834342012"}
28;"Omniflo Weiche";"shape_2acb8acb-d33c-457c-6027-b1cdb1fdf50b";;"OFWeiche :46";"X:8047.45 Y:19738.73";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-157.5,"Antrieb Kurve":false,"SivasNummer":"834342012"}
29;"Omniflo Weiche";"shape_0a2d18ae-af55-5bdb-39fa-24d8977d20c2";;"OFWeiche :47";"X:8322.43 Y:19513.4";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-135.0,"Antrieb Kurve":false,"SivasNummer":"834342012"}
30;"Omniflo Weiche";"shape_9ad86f6d-4041-8fac-847c-3a103e0dea05";;"OFWeiche :48";"X:8662.7 Y:19410.45";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-112.5,"Antrieb Kurve":false,"SivasNummer":"834342012"}
31;"Omniflo Weiche";"shape_977e338e-2582-f208-94e1-b319e5f2401d";;"OFWeiche :49";"X:9016.46 Y:19445.56";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-90.0,"Antrieb Kurve":false,"SivasNummer":"834342012"}
32;"Omniflo Weiche";"shape_d59a1df2-ecf3-211b-d43b-68accbdaf162";;"OFWeiche :50";"X:9329.86 Y:19613.37";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-67.5,"Antrieb Kurve":false,"SivasNummer":"834342012"}
33;"Omniflo Weiche";"shape_c2c101a6-4706-61c5-889d-6f3474eac8f8";;"OFWeiche :51";"X:9555.19 Y:19888.35";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-45.0,"Antrieb Kurve":false,"SivasNummer":"834342012"}
34;"Omniflo Weiche";"shape_57de5bab-1811-7594-20ab-8fe95b8a6633";;"OFWeiche :52";"X:9658.14 Y:20228.62";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-22.5,"Antrieb Kurve":false,"SivasNummer":"834342012"}
35;"Omniflo Weiche";"shape_a16752d8-71cf-10d3-b9e3-00501f5b3521";;"OFWeiche :4";"X:11065.78 Y:25644.75";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":0.0,"Antrieb Kurve":false,"SivasNummer":"834342002"}
36;"Omniflo Weiche";"shape_099ac37a-41eb-f24e-ea33-ea1e37bc9d3f";;"OFWeiche :53";"X:10983.05 Y:20355.77";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":0.0,"Antrieb Kurve":false,"SivasNummer":"834342002"}
37;"Omniflo Weiche";"shape_ff3ccbec-db41-62c6-be11-ecd93202d54c";;"OFWeiche :54";"X:11150.87 Y:20669.17";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-22.5,"Antrieb Kurve":false,"SivasNummer":"834342002"}
38;"Omniflo Weiche";"shape_834f6365-cad2-d8c1-f6ea-8033ae65a49b";;"OFWeiche :55";"X:11425.85 Y:20894.5";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-45.0,"Antrieb Kurve":false,"SivasNummer":"834342002"}
39;"Omniflo Weiche";"shape_63066836-bb31-2a67-1c81-37c92095b7ae";;"OFWeiche :56";"X:11766.13 Y:20997.45";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-67.5,"Antrieb Kurve":false,"SivasNummer":"834342002"}
40;"Omniflo Weiche";"shape_576b313c-3d93-965b-4ece-a845af325d7b";;"OFWeiche :57";"X:12119.89 Y:20962.34";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-90.0,"Antrieb Kurve":false,"SivasNummer":"834342002"}
41;"Omniflo Weiche";"shape_7300ff5e-43a5-2cb3-3aa8-74c4b9fb3253";;"OFWeiche :58";"X:12433.29 Y:20794.53";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-112.5,"Antrieb Kurve":false,"SivasNummer":"834342002"}
42;"Omniflo Weiche";"shape_493f4700-ec32-6dc2-da18-1d28dbaede7c";;"OFWeiche :59";"X:12658.62 Y:20519.54";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-135.0,"Antrieb Kurve":false,"SivasNummer":"834342002"}
43;"Omniflo Weiche";"shape_853a95e2-2d47-4678-904e-15acfdff35be";;"OFWeiche :60";"X:12761.57 Y:20179.28";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-157.5,"Antrieb Kurve":false,"SivasNummer":"834342002"}
44;"Omniflo Weiche";"shape_97c9b74c-e6a9-94bb-ab13-90e5b8fe240c";;"OFWeiche :61";"X:12726.46 Y:19825.51";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-180.0,"Antrieb Kurve":false,"SivasNummer":"834342002"}
45;"Omniflo Weiche";"shape_deda337a-179c-b16c-a80b-3cfd5a53e8d4";;"OFWeiche :62";"X:12558.65 Y:19512.12";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-202.5,"Antrieb Kurve":false,"SivasNummer":"834342002"}
46;"Omniflo Weiche";"shape_bdfc688d-aea9-6b26-8065-109f450df824";;"OFWeiche :63";"X:12283.67 Y:19286.78";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-225.0,"Antrieb Kurve":false,"SivasNummer":"834342002"}
47;"Omniflo Weiche";"shape_73e9d1d1-0ef4-ea4f-6fc9-2f4a7f8cdd69";;"OFWeiche :64";"X:11943.4 Y:19183.83";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-247.5,"Antrieb Kurve":false,"SivasNummer":"834342002"}
48;"Omniflo Weiche";"shape_76928ec7-08b1-c086-c3a5-52a15ac2e4df";;"OFWeiche :65";"X:11589.64 Y:19218.95";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-270.0,"Antrieb Kurve":false,"SivasNummer":"834342002"}
49;"Omniflo Weiche";"shape_7b218610-e98a-ef08-fb32-c817bf29988b";;"OFWeiche :66";"X:11276.24 Y:19386.76";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-292.5,"Antrieb Kurve":false,"SivasNummer":"834342002"}
50;"Omniflo Weiche";"shape_5095147a-d039-c5a7-9fb5-15c60275e5b9";;"OFWeiche :67";"X:11050.91 Y:19661.74";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-315.0,"Antrieb Kurve":false,"SivasNummer":"834342002"}
51;"Omniflo Weiche";"shape_6b3300bb-09d5-7c61-7934-7a60b32ab146";;"OFWeiche :68";"X:10947.96 Y:20002.01";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-337.5,"Antrieb Kurve":false,"SivasNummer":"834342002"}
52;"Omniflo Weiche";"shape_c2bea835-6174-d0c3-6246-0096706bb081";;"OFWeiche :2";"X:5669.64 Y:25566.62";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":0.0,"Antrieb Kurve":false,"SivasNummer":"834342001"}
53;"Omniflo Weiche";"shape_244e39c0-0f2e-c08b-968c-436612d7390c";;"OFWeiche :21";"X:5372.34 Y:20691.85";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":0.0,"Antrieb Kurve":false,"SivasNummer":"834342001"}
54;"Omniflo Weiche";"shape_17d96b64-d9c7-14f1-86d8-f32ea52c72c3";;"OFWeiche :22";"X:5540.15 Y:21005.25";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-22.5,"Antrieb Kurve":false,"SivasNummer":"834342001"}
55;"Omniflo Weiche";"shape_db5e1581-1cb3-70ad-a648-2c5f02058c7b";;"OFWeiche :23";"X:5815.13 Y:21230.58";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-45.0,"Antrieb Kurve":false,"SivasNummer":"834342001"}
56;"Omniflo Weiche";"shape_172f58d9-a614-6de7-e8e8-f0591d9e73f4";;"OFWeiche :24";"X:6155.4 Y:21333.53";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-67.5,"Antrieb Kurve":false,"SivasNummer":"834342001"}
57;"Omniflo Weiche";"shape_344aaffa-6909-2f8e-535f-0b895456eb8e";;"OFWeiche :25";"X:6509.16 Y:21298.42";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-90.0,"Antrieb Kurve":false,"SivasNummer":"834342001"}
58;"Omniflo Weiche";"shape_12484e71-d465-1909-0f20-a069ea8b7bf1";;"OFWeiche :26";"X:6822.57 Y:21130.61";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-112.5,"Antrieb Kurve":false,"SivasNummer":"834342001"}
59;"Omniflo Weiche";"shape_5bc9cf29-7017-dae1-4aa1-6a315dfab1a7";;"OFWeiche :27";"X:7047.89 Y:20855.62";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-135.0,"Antrieb Kurve":false,"SivasNummer":"834342001"}
60;"Omniflo Weiche";"shape_1fef2a0b-0219-3152-c896-0ee89435d305";;"OFWeiche :28";"X:7150.85 Y:20515.36";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-157.5,"Antrieb Kurve":false,"SivasNummer":"834342001"}
61;"Omniflo Weiche";"shape_376ade13-eb6b-1a0a-100f-f377b9bf1287";;"OFWeiche :29";"X:7115.74 Y:20161.6";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-180.0,"Antrieb Kurve":false,"SivasNummer":"834342001"}
62;"Omniflo Weiche";"shape_85c25caa-b5a5-7c1a-9e14-6199c3541d04";;"OFWeiche :30";"X:6947.93 Y:19848.2";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-202.5,"Antrieb Kurve":false,"SivasNummer":"834342001"}
63;"Omniflo Weiche";"shape_64c8ca46-4d4e-9cf8-df9c-3dea60be12c5";;"OFWeiche :31";"X:6672.95 Y:19622.86";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-225.0,"Antrieb Kurve":false,"SivasNummer":"834342001"}
64;"Omniflo Weiche";"shape_58c38947-efe7-db1b-d1d9-2eda14480410";;"OFWeiche :32";"X:6332.68 Y:19519.92";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-247.5,"Antrieb Kurve":false,"SivasNummer":"834342001"}
65;"Omniflo Weiche";"shape_b9a22166-28d2-0240-60e0-2133e22ed61f";;"OFWeiche :33";"X:5978.92 Y:19555.03";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-270.0,"Antrieb Kurve":false,"SivasNummer":"834342001"}
66;"Omniflo Weiche";"shape_ea4a1758-1579-cd29-f0ac-c529c7bc3c36";;"OFWeiche :34";"X:5665.52 Y:19722.84";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-292.5,"Antrieb Kurve":false,"SivasNummer":"834342001"}
67;"Omniflo Weiche";"shape_ff8f5cf3-a77f-efc7-7c25-5ba17354864f";;"OFWeiche :35";"X:5440.19 Y:19997.82";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-315.0,"Antrieb Kurve":false,"SivasNummer":"834342001"}
68;"Omniflo Weiche";"shape_9fba3e21-9afa-3a31-4c72-f4ec3d83b74a";;"OFWeiche :36";"X:5337.24 Y:20338.1";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Einzelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-337.5,"Antrieb Kurve":false,"SivasNummer":"834342001"}
69;"Omniflo Gerade";"shape_0390732f-452a-7fe8-2456-2a38898e458f";;"OFGerade :2";"X:3478.08 Y:26601.99";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":-337.5,"SivasNummer":"821106002"}
70;"Omniflo Gerade";"shape_c9813720-d1ad-e7a0-2392-4d6c85120306";;"OFGerade :4";"X:5635.95 Y:24386.62";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":0.0,"SivasNummer":"821106002"}
71;"Omniflo Gerade";"shape_8408812e-a914-b15a-ec28-516fa0f6d8da";;"OFGerade :5";"X:6120.13 Y:26650.5";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":-22.5,"SivasNummer":"821106002"}
72;"Omniflo Gerade";"shape_d015912b-6243-fa63-1db0-a3a21a17dc67";;"OFGerade :3";"X:3962.26 Y:26698.11";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":-180.0,"SivasNummer":"821106002"}
73;"Omniflo Gerade";"shape_768dca95-2c3b-88b6-d5ee-93cbe27a3254";;"OFGerade :6";"X:5635.95 Y:26746.62";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":-180.0,"SivasNummer":"821106002"}
74;"Omniflo Gerade";"shape_0a652d48-0f3a-d4b2-a49f-4b410faacd59";;"OFGerade :7";"X:9420 Y:24454.03";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":0.0,"SivasNummer":"821106002"}
75;"Omniflo Gerade";"shape_e56e363d-d451-8e3e-fa25-9f32a06f9182";;"OFGerade :9";"X:9420 Y:26814.03";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":-180.0,"SivasNummer":"821106002"}
76;"Omniflo Gerade";"shape_207cd422-fbe4-1dc7-6592-40b3dff17700";;"OFGerade :8";"X:8935.81 Y:26717.91";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":-337.5,"SivasNummer":"821106002"}
77;"Omniflo Gerade";"shape_b0467877-85d8-37d8-be13-1c09708bdf49";;"OFGerade :10";"X:11032.08 Y:24464.75";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":0.0,"SivasNummer":"821106002"}
78;"Omniflo Gerade";"shape_db25308a-2ab1-a616-ffa6-5f7bfb14a7e8";;"OFGerade :12";"X:11032.09 Y:26824.75";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":-180.0,"SivasNummer":"821106002"}
79;"Omniflo Weiche";"shape_d978be2e-b4d1-b666-2a89-4f262878caad";;"OFWeiche :70";"X:8976.91 Y:14076.17";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":0.0,"Antrieb Kurve":false,"SivasNummer":"834342101"}
80;"Omniflo Weiche";"shape_f3db1d4a-f915-f6d5-e23f-b26f3045a8a3";;"OFWeiche :87";"X:8936.89 Y:9956.45";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":0.0,"Antrieb Kurve":false,"SivasNummer":"834342101"}
81;"Omniflo Weiche";"shape_58a80dd5-4032-a696-66c1-c3c783fd4557";;"OFWeiche :88";"X:9104.99 Y:10283.23";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-22.5,"Antrieb Kurve":false,"SivasNummer":"834342101"}
82;"Omniflo Weiche";"shape_ddaf90f0-cd0c-8b21-70ac-0b41f794022b";;"OFWeiche :89";"X:9385.34 Y:10520.78";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-45.0,"Antrieb Kurve":false,"SivasNummer":"834342101"}
83;"Omniflo Weiche";"shape_110a18a6-6f7c-9ec9-62bd-e3953f5f6be5";;"OFWeiche :90";"X:9735.25 Y:10632.97";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-67.5,"Antrieb Kurve":false,"SivasNummer":"834342101"}
84;"Omniflo Weiche";"shape_1d65180a-4876-c5a2-727d-0540eb277d37";;"OFWeiche :91";"X:10101.46 Y:10602.75";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-90.0,"Antrieb Kurve":false,"SivasNummer":"834342101"}
85;"Omniflo Weiche";"shape_41674a89-4242-b0f4-e151-e70ca3ddf437";;"OFWeiche :92";"X:10428.2 Y:10434.61";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-112.5,"Antrieb Kurve":false,"SivasNummer":"834342101"}
86;"Omniflo Weiche";"shape_6ccaf0ce-c3a1-2960-a586-77b8e69a82ba";;"OFWeiche :93";"X:10665.76 Y:10154.29";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-135.0,"Antrieb Kurve":false,"SivasNummer":"834342101"}
87;"Omniflo Weiche";"shape_b5b3f052-60c7-941c-f222-64ded798378a";;"OFWeiche :94";"X:10777.94 Y:9804.37";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-157.5,"Antrieb Kurve":false,"SivasNummer":"834342101"}
88;"Omniflo Weiche";"shape_9033812d-2cba-ca47-28b5-861bbd5892f1";;"OFWeiche :95";"X:10747.69 Y:9438.13";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-180.0,"Antrieb Kurve":false,"SivasNummer":"834342101"}
89;"Omniflo Weiche";"shape_e9045f58-16f8-02fc-2564-d709eb0115bd";;"OFWeiche :96";"X:10579.58 Y:9111.37";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-202.5,"Antrieb Kurve":false,"SivasNummer":"834342101"}
90;"Omniflo Weiche";"shape_5899b317-e5fa-0b92-56f3-ad386c6ae3ba";;"OFWeiche :97";"X:10299.24 Y:8873.78";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-225.0,"Antrieb Kurve":false,"SivasNummer":"834342101"}
91;"Omniflo Weiche";"shape_6de0d681-54f1-3d93-1f47-fc500ce615c5";;"OFWeiche :98";"X:9949.33 Y:8761.59";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-247.5,"Antrieb Kurve":false,"SivasNummer":"834342101"}
92;"Omniflo Weiche";"shape_e032685f-bed8-e7a6-2e71-f0543d16b681";;"OFWeiche :99";"X:9583.12 Y:8791.89";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-270.0,"Antrieb Kurve":false,"SivasNummer":"834342101"}
93;"Omniflo Weiche";"shape_2bb19ced-a1b9-566e-92e2-0313f86bc6b1";;"OFWeiche :100";"X:9256.38 Y:8960.03";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-292.5,"Antrieb Kurve":false,"SivasNummer":"834342101"}
94;"Omniflo Weiche";"shape_1c3b4a6a-43bc-e775-9137-0d15c8df7b88";;"OFWeiche :101";"X:9018.82 Y:9240.36";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-315.0,"Antrieb Kurve":false,"SivasNummer":"834342101"}
95;"Omniflo Weiche";"shape_e7622454-4ad8-2b60-89fa-1169d69ee9ff";;"OFWeiche :102";"X:8906.64 Y:9590.27";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-337.5,"Antrieb Kurve":false,"SivasNummer":"834342101"}
96;"Omniflo Weiche";"shape_92eff04c-78ae-b2c7-bf6a-aa49252bdf00";;"OFWeiche :69";"X:4285.69 Y:14089.53";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":0.0,"Antrieb Kurve":false,"SivasNummer":"834342100"}
97;"Omniflo Weiche";"shape_116a62a7-dbe6-67f1-d822-3ed061d21b2b";;"OFWeiche :71";"X:4120.94 Y:10094.08";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":0.0,"Antrieb Kurve":false,"SivasNummer":"834342100"}
98;"Omniflo Weiche";"shape_bb22417f-52fe-4335-c32b-755156444634";;"OFWeiche :72";"X:4289.04 Y:10420.84";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-22.5,"Antrieb Kurve":false,"SivasNummer":"834342100"}
99;"Omniflo Weiche";"shape_28c9930d-9ad9-61a7-6053-af1cf8f7ea09";;"OFWeiche :73";"X:4569.38 Y:10658.43";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-45.0,"Antrieb Kurve":false,"SivasNummer":"834342100"}
100;"Omniflo Weiche";"shape_66cb58c6-7cd0-401f-238f-50649487858f";;"OFWeiche :74";"X:4919.29 Y:10770.62";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-67.5,"Antrieb Kurve":false,"SivasNummer":"834342100"}
101;"Omniflo Weiche";"shape_374f8fd7-4135-219a-c11e-f051048ac70b";;"OFWeiche :75";"X:5285.5 Y:10740.32";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-90.0,"Antrieb Kurve":false,"SivasNummer":"834342100"}
102;"Omniflo Weiche";"shape_d61ddd1c-bef7-f4f1-c34d-18b7b935d0ac";;"OFWeiche :76";"X:5612.25 Y:10572.18";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-112.5,"Antrieb Kurve":false,"SivasNummer":"834342100"}
103;"Omniflo Weiche";"shape_aada6a91-14ec-6e46-2ae8-ada04c0090d9";;"OFWeiche :77";"X:5849.81 Y:10291.85";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-135.0,"Antrieb Kurve":false,"SivasNummer":"834342100"}
104;"Omniflo Weiche";"shape_c18829f4-d44b-5c3e-a9a9-90bda78740e7";;"OFWeiche :78";"X:5961.99 Y:9941.94";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-157.5,"Antrieb Kurve":false,"SivasNummer":"834342100"}
105;"Omniflo Weiche";"shape_ee94d807-c63e-a6a4-3b0e-d90896c0ef77";;"OFWeiche :79";"X:5931.74 Y:9575.76";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-180.0,"Antrieb Kurve":false,"SivasNummer":"834342100"}
106;"Omniflo Weiche";"shape_85887d52-5af0-1e70-101f-541ade7b31c4";;"OFWeiche :80";"X:5763.63 Y:9249";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-202.5,"Antrieb Kurve":false,"SivasNummer":"834342100"}
107;"Omniflo Weiche";"shape_9d834cc5-421e-8f21-6ac5-8f5d9167dab8";;"OFWeiche :81";"X:5483.29 Y:9011.4";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-225.0,"Antrieb Kurve":false,"SivasNummer":"834342100"}
108;"Omniflo Weiche";"shape_85b1c977-aa45-d562-7912-245ababf808c";;"OFWeiche :82";"X:5133.38 Y:8899.22";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-247.5,"Antrieb Kurve":false,"SivasNummer":"834342100"}
109;"Omniflo Weiche";"shape_aaf28fe8-497d-ca6c-0e7a-58c97bdc3ba2";;"OFWeiche :83";"X:4767.17 Y:8929.51";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-270.0,"Antrieb Kurve":false,"SivasNummer":"834342100"}
110;"Omniflo Weiche";"shape_b481d2fc-6d94-4bdd-3666-a292e864878c";;"OFWeiche :84";"X:4440.42 Y:9097.66";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-292.5,"Antrieb Kurve":false,"SivasNummer":"834342100"}
111;"Omniflo Weiche";"shape_0c5ca6e2-6995-7730-4513-3bb2a673f590";;"OFWeiche :85";"X:4202.87 Y:9377.98";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-315.0,"Antrieb Kurve":false,"SivasNummer":"834342100"}
112;"Omniflo Weiche";"shape_00a867fc-f5e3-528f-5b5e-8d84ae04462b";;"OFWeiche :86";"X:4090.69 Y:9727.89";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Doppelweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-337.5,"Antrieb Kurve":false,"SivasNummer":"834342100"}
113;"Omniflo Gerade";"shape_57344488-bf95-695f-cf55-007ed7116d54";;"OFGerade :11";"X:11516.27 Y:26728.63";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":-22.5,"SivasNummer":"821106002"}
114;"Omniflo Gerade";"shape_91aa3827-a26f-59d9-8f90-1391635d635a";;"OFGerade :15";"X:3801.51 Y:15179.38";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":-157.5,"SivasNummer":"821106002"}
115;"Omniflo Gerade";"shape_e5ca5ce3-ad67-940e-a3c9-5f9f06ef02a8";;"OFGerade :13";"X:4285.69 Y:12915.51";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":0.0,"SivasNummer":"821106002"}
116;"Omniflo Gerade";"shape_7724af93-f739-9009-3de0-f493f705608e";;"OFGerade :14";"X:4769.88 Y:15179.38";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":-22.5,"SivasNummer":"821106002"}
117;"Omniflo Gerade";"shape_6561cab9-1005-a5b1-c409-8c5959eff47a";;"OFGerade :17";"X:8492.73 Y:15166.02";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":-157.5,"SivasNummer":"821106002"}
118;"Omniflo Gerade";"shape_49ece4f4-a0ab-b831-96ba-109c70a66bc2";;"OFGerade :18";"X:9461.1 Y:15166.02";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":-22.5,"SivasNummer":"821106002"}
119;"Omniflo Gerade";"shape_ee2e6acc-6cd0-cdc2-5586-69d29e7d9647";;"OFGerade :16";"X:8976.91 Y:12902.14";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":0.0,"SivasNummer":"821106002"}
120;"Omniflo Weiche";"shape_781c209e-8b7e-e5ec-77dc-d84a5b60ad7b";;"OFWeiche :104";"X:9370.95 Y:5088.12";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":0.0,"Antrieb Kurve":false,"SivasNummer":"834342201"}
121;"Omniflo Weiche";"shape_913b1db5-dbeb-2ac3-630d-6682a1a76e51";;"OFWeiche :121";"X:9267.53 Y:1363.66";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":0.0,"Antrieb Kurve":false,"SivasNummer":"834342201"}
122;"Omniflo Weiche";"shape_7295a06c-ecf3-1628-a0c1-63800467259f";;"OFWeiche :122";"X:9097.15 Y:1689.89";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-337.5,"Antrieb Kurve":false,"SivasNummer":"834342201"}
123;"Omniflo Weiche";"shape_820f03af-681c-fe0d-fa81-9b07d70868fe";;"OFWeiche :123";"X:8814.87 Y:1926.16";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-315.0,"Antrieb Kurve":false,"SivasNummer":"834342201"}
124;"Omniflo Weiche";"shape_23c956a1-b1fa-fb7c-56ee-2e06dad73c25";;"OFWeiche :124";"X:8463.66 Y:2036.36";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-292.5,"Antrieb Kurve":false,"SivasNummer":"834342201"}
125;"Omniflo Weiche";"shape_18fa149d-4042-3f38-03f0-45c220474ef7";;"OFWeiche :125";"X:8097 Y:2003.82";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-270.0,"Antrieb Kurve":false,"SivasNummer":"834342201"}
126;"Omniflo Weiche";"shape_8c598517-d272-d675-1b85-53feda99712e";;"OFWeiche :126";"X:7770.7 Y:1833.43";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-247.5,"Antrieb Kurve":false,"SivasNummer":"834342201"}
127;"Omniflo Weiche";"shape_28f30b76-2f04-ef3c-a156-6c88dab9dcca";;"OFWeiche :127";"X:7534.44 Y:1551.12";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-225.0,"Antrieb Kurve":false,"SivasNummer":"834342201"}
128;"Omniflo Weiche";"shape_184058af-e1c1-ebfc-78ca-327525010958";;"OFWeiche :128";"X:7424.19 Y:1199.88";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-202.5,"Antrieb Kurve":false,"SivasNummer":"834342201"}
129;"Omniflo Weiche";"shape_47c7907b-8226-8492-eb60-b52c3a7e2bfa";;"OFWeiche :129";"X:7456.74 Y:833.17";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-180.0,"Antrieb Kurve":false,"SivasNummer":"834342201"}
130;"Omniflo Weiche";"shape_efa0072d-2799-a460-7116-71fcdb5b654b";;"OFWeiche :130";"X:7627.11 Y:506.94";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-157.5,"Antrieb Kurve":false,"SivasNummer":"834342201"}
131;"Omniflo Weiche";"shape_104be4d1-995a-b894-acc4-ebcbde86c53e";;"OFWeiche :131";"X:7909.4 Y:270.67";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-135.0,"Antrieb Kurve":false,"SivasNummer":"834342201"}
132;"Omniflo Weiche";"shape_164b6d34-2c7d-7648-f051-09b61d1715b2";;"OFWeiche :132";"X:8260.61 Y:160.47";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-112.5,"Antrieb Kurve":false,"SivasNummer":"834342201"}
133;"Omniflo Weiche";"shape_f3e05b51-9b4c-5abe-2042-8af24b658b44";;"OFWeiche :133";"X:8627.27 Y:193.01";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-90.0,"Antrieb Kurve":false,"SivasNummer":"834342201"}
134;"Omniflo Weiche";"shape_2850ef7e-1070-846e-0f94-5b0fd653ccbc";;"OFWeiche :134";"X:8953.56 Y:363.4";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-67.5,"Antrieb Kurve":false,"SivasNummer":"834342201"}
135;"Omniflo Weiche";"shape_9582eeae-0df5-55d3-2b62-f295002f7972";;"OFWeiche :135";"X:9189.82 Y:645.71";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-45.0,"Antrieb Kurve":false,"SivasNummer":"834342201"}
136;"Omniflo Weiche";"shape_f0b0220c-62ca-9304-0494-56f3ea5ea72e";;"OFWeiche :136";"X:9300.07 Y:996.95";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-22.5,"Antrieb Kurve":false,"SivasNummer":"834342201"}
137;"Omniflo Weiche";"shape_b0427595-b41b-6d6e-4ef1-26b465fa4ba0";;"OFWeiche :103";"X:3949.86 Y:5269.36";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":0.0,"Antrieb Kurve":false,"SivasNummer":"834342200"}
138;"Omniflo Weiche";"shape_53caa992-f6c0-d5cd-30af-53bf475dbc6a";;"OFWeiche :105";"X:4021.44 Y:1203.19";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":0.0,"Antrieb Kurve":false,"SivasNummer":"834342200"}
139;"Omniflo Weiche";"shape_771a064f-06c0-af1a-abb5-ef0c38f77fb3";;"OFWeiche :106";"X:3851.06 Y:1529.55";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-337.5,"Antrieb Kurve":false,"SivasNummer":"834342200"}
140;"Omniflo Weiche";"shape_4bc2a761-d244-6fe5-1729-a2ee58b483fe";;"OFWeiche :107";"X:3568.78 Y:1765.83";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-315.0,"Antrieb Kurve":false,"SivasNummer":"834342200"}
141;"Omniflo Weiche";"shape_0cdabc89-149d-2c93-c379-dd694eee0502";;"OFWeiche :108";"X:3217.57 Y:1876.03";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-292.5,"Antrieb Kurve":false,"SivasNummer":"834342200"}
142;"Omniflo Weiche";"shape_c078c7e2-f18c-de88-e41e-a7ef73a6b82e";;"OFWeiche :109";"X:2850.91 Y:1843.48";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-270.0,"Antrieb Kurve":false,"SivasNummer":"834342200"}
143;"Omniflo Weiche";"shape_a33afa4c-a30f-a157-ffdd-26c9874efb24";;"OFWeiche :110";"X:2524.61 Y:1673.09";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-247.5,"Antrieb Kurve":false,"SivasNummer":"834342200"}
144;"Omniflo Weiche";"shape_34fa2575-9379-c92f-a49f-f7d099c0bbff";;"OFWeiche :111";"X:2288.35 Y:1390.78";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-225.0,"Antrieb Kurve":false,"SivasNummer":"834342200"}
145;"Omniflo Weiche";"shape_fba8c563-2897-c288-1e77-8aa1129bf3fe";;"OFWeiche :112";"X:2178.1 Y:1039.55";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-202.5,"Antrieb Kurve":false,"SivasNummer":"834342200"}
146;"Omniflo Weiche";"shape_92471692-978e-0736-563f-16c4c4b8f3b6";;"OFWeiche :113";"X:2210.64 Y:672.83";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-180.0,"Antrieb Kurve":false,"SivasNummer":"834342200"}
147;"Omniflo Weiche";"shape_a6ecfb53-1532-d4c1-aab3-789c0c5ad2a9";;"OFWeiche :114";"X:2381.02 Y:346.47";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-157.5,"Antrieb Kurve":false,"SivasNummer":"834342200"}
148;"Omniflo Weiche";"shape_3c228aad-e823-a5d6-2709-5934a729e256";;"OFWeiche :115";"X:2663.31 Y:110.2";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-135.0,"Antrieb Kurve":false,"SivasNummer":"834342200"}
149;"Omniflo Weiche";"shape_be7c1147-1967-3765-a0a0-e5799ed2fe8a";;"OFWeiche :116";"X:3014.52 Y:0";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-112.5,"Antrieb Kurve":false,"SivasNummer":"834342200"}
150;"Omniflo Weiche";"shape_70c5f2c2-4c19-4dac-168b-e3795c70b305";;"OFWeiche :117";"X:3381.18 Y:32.54";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-90.0,"Antrieb Kurve":false,"SivasNummer":"834342200"}
151;"Omniflo Weiche";"shape_b68fb393-45de-f460-7698-a0684a64c1e1";;"OFWeiche :118";"X:3707.47 Y:202.93";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-67.5,"Antrieb Kurve":false,"SivasNummer":"834342200"}
152;"Omniflo Weiche";"shape_1152d6c3-efcd-25ca-3958-290018a4f6ef";;"OFWeiche :119";"X:3943.73 Y:485.25";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-45.0,"Antrieb Kurve":false,"SivasNummer":"834342200"}
153;"Omniflo Weiche";"shape_d6566d43-6b80-290e-b94c-35a85e1b27f5";;"OFWeiche :120";"X:4053.98 Y:836.48";;{"Länge in Meter links":null,"Länge in Meter rechts":null,"Länge in Meter gerade":null,"Weichentyp":"Dreiwegeweiche","Richtung":"False","Weichenwinkel":22.5,"Höhe":"2000","Drehung":-22.5,"Antrieb Kurve":false,"SivasNummer":"834342200"}
154;"Omniflo Gerade";"shape_c01f26c6-2634-af0a-f6f3-9aeea765b99e";;"OFGerade :19";"X:3949.86 Y:6449.36";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":0.0,"SivasNummer":"821106002"}
155;"Omniflo Gerade";"shape_303d7b75-2381-aa00-a3bf-e227214592d9";;"OFGerade :22";"X:4434.04 Y:6353.24";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":-22.5,"SivasNummer":"821106002"}
156;"Omniflo Gerade";"shape_7b0313cb-77cb-f016-289f-c8b97c5d26a1";;"OFGerade :20";"X:3465.67 Y:6353.24";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":-157.5,"SivasNummer":"821106002"}
157;"Omniflo Gerade";"shape_4d567ce6-ddc3-2f11-bda9-843f0b203c5b";;"OFGerade :21";"X:3949.86 Y:4089.36";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":-180.0,"SivasNummer":"821106002"}
158;"Omniflo Gerade";"shape_bb26f4ec-c56d-36ee-68a7-ad920bc03cc9";;"OFGerade :25";"X:9855.13 Y:6172";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":-22.5,"SivasNummer":"821106002"}
159;"Omniflo Gerade";"shape_550bcbf3-e307-4ce1-4ba1-4fbf8174cb04";;"OFGerade :23";"X:9370.95 Y:6268.12";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":0.0,"SivasNummer":"821106002"}
160;"Omniflo Gerade";"shape_dd0bb3b6-1aaf-7193-6749-82fd301a71d9";;"OFGerade :24";"X:8886.76 Y:6172";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":-157.5,"SivasNummer":"821106002"}
161;"Omniflo Gerade";"shape_854fa51f-c098-cebf-742f-5b09cf4d74a8";;"OFGerade :26";"X:9370.95 Y:3908.12";;{"Anzahl der Separatoren":"0","Länge in Meter":"2","Winkel":"0","Anzahl der Scanner":0,"Höhe oben":"2000","Höhe unten":"2000","Drehung":-180.0,"SivasNummer":"821106002"}
Can't render this file because it contains an unexpected character in line 2 and column 105.
+8931 -3185
View File
File diff suppressed because it is too large Load Diff
+6569 -847
View File
File diff suppressed because it is too large Load Diff