Compare commits

...

19 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
46 changed files with 63292 additions and 19423 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
+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
)
+67 -26
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
)
return cls(
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()
+10 -11
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:
@@ -17,11 +22,5 @@ class Bt_element(BaseModel):
drehung = float(merkmale.get("Drehung"))
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()
+193 -102
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."""
@@ -34,8 +92,8 @@ class Kreisel(BaseModel):
except ValueError:
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."""
@@ -46,12 +104,12 @@ class Kreisel(BaseModel):
except ValueError:
v = 0.0
return v
@property
def halbabstand(self) -> float:
"""Halbabstand zwischen den beiden Blöcken."""
return self.abstand / 2
@property
def winkel_rad(self) -> float:
"""Winkel in Radianten für Berechnungen."""
@@ -59,62 +117,65 @@ class Kreisel(BaseModel):
return math.radians(self.drehung)
else:
return math.radians(self.drehung - 180)
@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
def pos1(self) -> tuple[float, float, float]:
"""Position des ersten Blocks (x, y, z)."""
dx = self.halbabstand * math.cos(self.winkel_rad)
dy = self.halbabstand * math.sin(self.winkel_rad)
return (self.x - dx, self.y - dy, self.hoehe)
@property
def pos2(self) -> tuple[float, float, float]:
"""Position des zweiten Blocks (x, y, z)."""
dx = self.halbabstand * math.cos(self.winkel_rad)
dy = self.halbabstand * math.sin(self.winkel_rad)
return (self.x + dx, self.y + dy, self.hoehe)
@property
def z(self) -> float:
"""Z-Koordinate (gleich der Höhe)."""
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:
hoehe = float(hoehe_m) * 1000
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):
abstand = 10000.0
try:
drehung = float(merkmale.get("Drehung", "0"))
except (ValueError, TypeError):
drehung = 0.0
try:
anzahl_scanner = float(merkmale.get("Anzahl der Scanner", "0"))
except (ValueError, TypeError):
anzahl_scanner = 0.0
try:
anzahl_separatoren = float(merkmale.get("Anzahl der Separatoren", "0"))
except (ValueError, TypeError):
anzahl_separatoren = 0.0
return cls(
teileid=teileid,
x=x,
@@ -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
+4 -3
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()
@@ -218,4 +219,4 @@ def normalize_func_name(name):
.replace('.', '_')
.replace('-', '_')
.lower()
)
)
+718 -309
View File
File diff suppressed because it is too large Load Diff
+94 -50
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:
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
@@ -87,21 +98,22 @@ def import_block(block_name: str, from_doc, to_doc, winkel = None) -> None:
except Exception:
pass
# kopiert die elemente von dem element aus dem block und speichert diese in den block für dass Modelspace auf und # 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)
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"""
"""Gibt den Layer und die Color für den Jeweiligen block in der Libary datei zurück"""
msp_lib = lib_doc.modelspace()
color = 0
layer = 0
@@ -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,11 +192,11 @@ 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):
"""
Fügt einem bestehenden ezdxf-Block Attribut-Definitionen hinzu.
Parameter:
block ein ezdxf.blocks.BlockLayout Objekt
attributes Liste von Tupeln [(tag, value), ...]
@@ -160,8 +204,8 @@ 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,
)
a.is_invisible = True
a.is_invisible = True
+262 -187
View File
@@ -1,30 +1,31 @@
# -*- 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.
Diese Funktion liest alle DXF-Dateien aus einem Verzeichnis und erstellt
daraus eine Bibliothek mit Blöcken. Die Blöcke werden in einem Raster
angeordnet und mit Beschriftungen versehen. Fehlerhafte Dateien werden
protokolliert.
Args:
input_dir (str): Verzeichnis mit den zu verarbeitenden DXF-Dateien
output_file (str): Pfad zur zu erstellenden Bibliotheks-DXF-Datei
config: ConfigParser-Objekt mit den Konfigurationswerten
logger: Optionaler Logger für Logging-Ausgaben
Note:
- Unterstützte Entity-Typen: LINE, LWPOLYLINE, POLYLINE, SPLINE, CIRCLE, ARC, INSERT, TEXT, MTEXT, REGION,
- Blöcke werden zentriert und in einem 20x20 Raster angeordnet
@@ -37,12 +38,12 @@ def create_block_library(input_dir, output_file, config, logger=None):
x_offset = 0
y_offset = 0
blocks_in_row = 0
error_files = []
processed_files = []
max_blockspacing_x = 0
max_blockspacing_y = 0
for filename in os.listdir(input_dir):
if not filename.lower().endswith(".dxf"):
@@ -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:
@@ -122,8 +124,10 @@ def create_block_library(input_dir, output_file, config, logger=None):
doc.layers.add(name=layer_name)
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,42 +189,59 @@ 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)
x_offset += max_blockspacing_x
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
x_offset = 0
y_offset -= max_blockspacing_y # Neue Zeile, nach unten versetzt
@@ -232,9 +252,11 @@ def create_block_library(input_dir, output_file, config, logger=None):
logger.info(f"Bibliotheks-DXF gespeichert: {output_file}")
logger.info(f"Verarbeitete Dateien: {len(processed_files)}")
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:
@@ -243,9 +265,11 @@ def create_block_library(input_dir, output_file, config, logger=None):
print(f"Bibliotheks-DXF gespeichert: {output_file}")
print(f"Verarbeitete Dateien: {len(processed_files)}")
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,24 +291,26 @@ 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:
print(error_msg)
error_files.append((filename, error_msg))
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)
@@ -303,57 +329,57 @@ def convert_dxf_to_block_with_inserts(input_filename, output_filename, block_nam
# Eingabe-DXF laden
input_doc = ezdxf.readfile(input_filename)
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)
print(f"Block-Definitionen kopiert: {len(copied_blocks)}")
# Neuen Hauptblock erstellen
new_block = output_doc.blocks.new(name=block_name)
print(f"Erstelle neuen Block: {block_name}")
# Alle Entities aus dem Modelspace kopieren
msp = input_doc.modelspace()
entity_count = 0
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
else:
# Normale Entity kopieren
copy_entity_to_block(entity, new_block)
entity_count += 1
# Bounding Box berechnen
bbox = calculate_block_bounding_box(new_block, output_doc)
# Block im Modelspace der neuen Datei platzieren
output_msp = output_doc.modelspace()
output_msp.add_blockref(block_name, insert=(0, 0))
# Bounding Box als Hilfslinien hinzufügen (optional)
if bbox.has_data:
add_bounding_box_to_modelspace(output_msp, bbox)
# Speichern
output_doc.saveas(output_filename)
print(f"Konvertierung abgeschlossen:")
print(f" - {entity_count} Entities übertragen")
print(f" - {insert_count} INSERTs als Referenzen beibehalten")
print(f" - Bounding Box: {format_bounding_box(bbox)}")
print(f" - Ausgabe: {output_filename}")
return bbox
except FileNotFoundError:
print(f"Fehler: Datei {input_filename} nicht gefunden")
return None
@@ -370,48 +396,53 @@ def copy_block_definitions(source_doc, target_doc):
Kopiert alle Block-Definitionen vom Quell- zum Ziel-Dokument
"""
copied_blocks = []
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]
# Prüfen ob Block bereits existiert
if block_name in target_doc.blocks:
print(f"Warnung: Block '{block_name}' existiert bereits, wird übersprungen")
continue
# Neuen Block in Ziel-Dokument erstellen
target_block = target_doc.blocks.new(name=block_name)
# Alle Entities des Quell-Blocks kopieren
for entity in source_block:
copy_entity_to_block(entity, target_block)
copied_blocks.append(block_name)
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
"""
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,15 +463,15 @@ 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
"""
bbox = BoundingBox()
bbox = BoundingBox()
try:
if e.dxftype() == 'LINE':
if e.dxftype() == "LINE":
start = Vec3(e.dxf.start)
end = Vec3(e.dxf.end)
if transform_matrix:
@@ -448,8 +479,8 @@ def get_entity_bounding_box(e, doc,src_doc,filename,config, transform_matrix=Non
end = transform_matrix.transform(end)
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)
@@ -464,8 +495,8 @@ def get_entity_bounding_box(e, doc,src_doc,filename,config, transform_matrix=Non
points.append(p)
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)
@@ -495,16 +526,16 @@ def get_entity_bounding_box(e, doc,src_doc,filename,config, transform_matrix=Non
points.append(p)
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
)
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,15 +558,15 @@ 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
)
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))
@@ -556,19 +587,21 @@ def get_entity_bounding_box(e, doc,src_doc,filename,config, transform_matrix=Non
sampled.append(v)
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,86 +643,99 @@ 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
)
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
)
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]
if block_name in doc.blocks:
dst_blk = doc.blocks[block_name]
new_insert = False
else:
dst_blk = doc.blocks.new(name=block_name)
new_insert = True
if block_name not in src_doc.blocks:
print(f"Warnung: Block '{block_name}' ")
return BoundingBox()
block_def = src_blk
# Transformation der INSERT-Entity berechnen
insert_transform = get_insert_transform_matrix(insert_entity)
# Mit übergeordneter Transformation kombinieren
if parent_transform:
combined_transform = parent_transform * insert_transform
else:
combined_transform = insert_transform
# Bounding Box aller Entities im Block berechnen
block_bbox = BoundingBox()
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
except Exception as e:
print(f"Fehler bei INSERT Bounding Box: {e},{filename}")
return BoundingBox()
@@ -701,22 +747,22 @@ def get_insert_transform_matrix(insert_entity):
"""
# Position
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
@@ -728,7 +774,7 @@ def copy_entity_to_block(entity, target_block):
# Entity kopieren und zum Block hinzufügen
entity_copy = entity.copy()
target_block.add_entity(entity_copy)
except Exception as e:
print(f"Fehler beim Kopieren von {entity.dxftype()}: {e}")
@@ -741,13 +787,13 @@ def add_bounding_box_to_modelspace(msp, bbox, centered=False):
"""
if not bbox.has_data:
return
# Bounding Box Rechteck zeichnen
min_pt = bbox.extmin
max_pt = bbox.extmax
width = max_pt.x - min_pt.x
height = max_pt.y - min_pt.y
if centered:
# Rechteck um (0,0) zentriert
left = -width / 2.0
@@ -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,21 +820,31 @@ 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)
bbox_poly.dxf.layer = "BOUNDING_BOX"
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_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_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},
)
def format_bounding_box(bbox):
@@ -794,16 +853,18 @@ def format_bounding_box(bbox):
"""
if not bbox.has_data:
return "Keine gültigen Geometriedaten gefunden"
min_pt = bbox.extmin
max_pt = bbox.extmax
width = max_pt.x - min_pt.x
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):
@@ -813,76 +874,88 @@ def analyze_source_dxf_with_blocks(filename):
try:
doc = ezdxf.readfile(filename)
msp = doc.modelspace()
entity_types = {}
layer_count = {}
insert_blocks = {}
block_definitions = {}
# Modelspace analysieren
for entity in msp:
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
print(f"\nAnalyse von {filename}:")
print("=" * 50)
print("Entity-Typen im Modelspace:")
for etype, count in sorted(entity_types.items()):
print(f" {etype}: {count}")
print(f"\nLayer ({len(layer_count)}):")
for layer, count in sorted(layer_count.items()):
print(f" {layer}: {count} entities")
if insert_blocks:
print(f"\nINSERT-Verwendungen ({sum(insert_blocks.values())} total):")
for block, count in sorted(insert_blocks.items()):
print(f" {block}: {count}× verwendet")
if block_definitions:
print(f"\nBlock-Definitionen ({len(block_definitions)}):")
for block, count in sorted(block_definitions.items()):
print(f" {block}: {count} entities")
return entity_types, layer_count, insert_blocks, block_definitions
except Exception as e:
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()
sys.exit(0)
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,
)
+3 -2
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"]
@@ -22,4 +23,4 @@ def inspect_blocks(dxf_path):
if __name__ == "__main__":
dxf_file = "data/blocks.dxf"
inspect_blocks(dxf_file)
inspect_blocks(dxf_file)
+1172 -437
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
+218 -72
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"))
@@ -12,8 +14,26 @@ class TestDXFGeometry(unittest.TestCase):
default_lib_path = lib_path / "blocks.dxf"
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_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)
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_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()
+7 -5
View File
@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
import os
import sys
import logging
@@ -14,11 +15,11 @@ 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.
Args:
log_dir (Path): Verzeichnis für die Log-Dateien
name (str): Name des Loggers (z.B. 'plant2dxf', 'dxf2lib')
@@ -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,11 +48,11 @@ 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)
# Handler dem Logger hinzufügen
logger.addHandler(file_handler)
logger.addHandler(console_handler)
return logger
return logger
+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