From a200d79ab182876b2b1a504eac94f1f0146274fd Mon Sep 17 00:00:00 2001
From: Paul Wolok
Date: Tue, 2 Dec 2025 16:18:08 +0100
Subject: [PATCH] Refectoring vom Kreisel
---
lib/plant2dxf.py | 203 +++++++++++++++++++++++++++++++++----------
lib/requirements.txt | 3 +-
2 files changed, 158 insertions(+), 48 deletions(-)
diff --git a/lib/plant2dxf.py b/lib/plant2dxf.py
index 60f9623..467e4f9 100644
--- a/lib/plant2dxf.py
+++ b/lib/plant2dxf.py
@@ -19,6 +19,8 @@ from ezdxf import bbox
from pathlib import Path
import math
import re
+from pydantic import BaseModel, Field, field_validator
+from typing import Optional
from utils import check_environment_var, setup_logger
# global_zaehler = 0
@@ -67,6 +69,126 @@ def get_shape_cfg(teileart, cfg_path, logger=None):
ATTR_TAG = "TeileId" # Attributtag im Block
RADIUS = 400 # Radius der Kreiselkreise (mm)
+# --------------------------------------------------------- Pydantic Modelle
+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'")
+ 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')
+ @classmethod
+ def validate_abstand(cls, v):
+ """Konvertiert Abstand von Meter zu mm, falls nötig."""
+ if isinstance(v, str):
+ v = v.replace(",", ".")
+ try:
+ v = float(v) * 1000 # Meter → mm
+ except ValueError:
+ v = 10000.0 # Fallback 10 m
+ return v
+
+ @field_validator('hoehe')
+ @classmethod
+ def validate_hoehe(cls, v):
+ """Konvertiert Höhe von Meter zu mm, falls nötig."""
+ if isinstance(v, str):
+ v = v.replace(",", ".")
+ try:
+ v = float(v) * 1000 # Meter → mm
+ 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."""
+ if self.drehung == 270 or self.drehung == 90:
+ 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)
+ 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':
+ """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(",", ".")
+ 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,
+ y=y,
+ hoehe=hoehe,
+ drehung=drehung,
+ drehrichtung=merkmale.get("Drehrichtung"),
+ abstand=abstand,
+ kreiselart=merkmale.get("Kreiselart"),
+ anzahl_scanner=anzahl_scanner,
+ anzahl_separatoren=anzahl_separatoren
+ )
+
# --------------------------------------------------------- Hilfsfunktionen
def extract_coords(planquadrat: str) -> tuple[float, float]:
"""Extrahiert X/Y Koordinaten aus PlanquadratString."""
@@ -213,57 +335,39 @@ def handle_ils_2_0_kreisel_mit_pin(msp, teileid, merkmale, x, y, doc, lib_doc, v
handle_ils_2_0_kreisel(msp, teileid, merkmale, x, y, doc, lib_doc, verbose, symbols,strecken_nachbarn,config,config_allgemein)
def handle_ils_2_0_kreisel(msp, teileid, merkmale, x, y, doc, lib_doc, verbose, symbols,strecken_nachbarn,config,config_allgemein):
- scanner = float(merkmale.get("Anzahl der Scanner"))
- separatoren = float(merkmale.get("Anzahl der Separatoren"))
+ # Erstelle Kreisel-Objekt aus merkmale
+ kreisel = Kreisel.from_merkmale(teileid, x, y, merkmale)
+
block_scanner = "SCAN"
block_separatoren = "S-LP"
- scanner_x = x
- scanner_y = y
- separatoren_x = x
- separatoren_y = y + 160
+ scanner_x = kreisel.x
+ scanner_y = kreisel.y
+ separatoren_x = kreisel.x
+ separatoren_y = kreisel.y + 160
import_block(block_scanner,lib_doc,doc)
import_block(block_separatoren,lib_doc,doc)
i = 0
- while i < scanner:
- msp.add_blockref(block_scanner, (scanner_x,scanner_y,float(merkmale.get("Höhe in m"))* 1000))
+ while i < kreisel.anzahl_scanner:
+ msp.add_blockref(block_scanner, (scanner_x,scanner_y, kreisel.hoehe))
scanner_x = scanner_x + 300
i = i+1
i = 0
- while i < separatoren:
- msp.add_blockref(block_separatoren, (separatoren_x,separatoren_y,float(merkmale.get("Höhe in m"))* 1000))
+ while i < kreisel.anzahl_separatoren:
+ msp.add_blockref(block_separatoren, (separatoren_x,separatoren_y, kreisel.hoehe))
separatoren_x = separatoren_x + 300
i = i +1
- abstand_m = merkmale.get(
- "Abstand (Kreiselachse A - Kreiselachse) in Meter", "20"
- ).replace(",", ".")
- try:
- abstand = float(abstand_m) * 1000 # Meter → mm
- except ValueError:
- abstand = 10000 # Fallback 10 m
-
- # Drehung (Winkel in Grad, Standard 0) aus Merkmale
- try:
- winkel = float(merkmale.get("Drehung"))
- except (ValueError, TypeError):
- winkel = 0.0
- if winkel== 270 or winkel == 90:
- winkel_rad = math.radians(winkel)
- else:
- winkel_rad = math.radians(winkel -180)
+
# Die Koordinaten (x, y) sind die Mitte zwischen den beiden Blöcken (bereits transformiert)
- halbabstand = abstand / 2
- dx = halbabstand * math.cos(winkel_rad)
- dy = halbabstand * math.sin(winkel_rad)
- pos1 = (x - dx, y - dy,float(merkmale.get("Höhe in m"))* 1000)
- pos2 = (x + dx, y + dy, float(merkmale.get("Höhe in m"))* 1000)
+ pos1 = kreisel.pos1
+ pos2 = kreisel.pos2
positions = [pos1, pos2]
for i, sym in enumerate(symbols):
blockname = sym["name"]
offset = sym["offset"]
rotation = sym["rotation"]
if i < len(positions):
- pos = (positions[i][0] + offset[0], positions[i][1] + offset[1],float(merkmale.get("Höhe in m"))*1000)
+ pos = (positions[i][0] + offset[0], positions[i][1] + offset[1], kreisel.hoehe)
import_block(blockname, lib_doc, doc)
blockref_layer = get_layer(doc, lib_doc, blockname)
bref = msp.add_blockref(blockname, pos, dxfattribs={"layer" : blockref_layer})
@@ -273,12 +377,12 @@ def handle_ils_2_0_kreisel(msp, teileid, merkmale, x, y, doc, lib_doc, verbose,
f"({pos[0]:.1f}, {pos[1]:.1f}), rot={rotation}")
# Linien zeichnen
import_block("Pinbereich",lib_doc,doc)
- draw_kreisel_lines(msp, pos1, pos2,merkmale)
- draw_kreisel_drehrichtung_markierung(msp, pos1, pos2, merkmale, lib_doc, doc, verbose)
+ draw_kreisel_lines(msp, pos1, pos2, kreisel)
+ draw_kreisel_drehrichtung_markierung(msp, pos1, pos2, kreisel, lib_doc, doc, verbose)
-def draw_kreisel_lines(msp, pos1, pos2,merkmale):
+def draw_kreisel_lines(msp, pos1, pos2, kreisel: Kreisel):
"""Zeichnet tangentiale Linien zwischen zwei Kreiselblöcken, unabhängig vom Winkel."""
- rotation = float(merkmale.get("Drehung"))
+ rotation = kreisel.drehung
x1, y1, z1 = pos1
x2, y2, z1 = pos2
# Verbindungsvektor
@@ -297,7 +401,7 @@ def draw_kreisel_lines(msp, pos1, pos2,merkmale):
p1b = (x1 - nx, y1 - ny,z1)
p2a = (x2 + nx, y2 + ny,z1)
p2b = (x2 - nx, y2 - ny,z1)
- if merkmale.get("Kreiselart") == "Pin":
+ 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
@@ -339,8 +443,8 @@ def draw_kreisel_lines(msp, pos1, pos2,merkmale):
msp.add_line(p1a, p2a)
msp.add_line(p1b, p2b)
-def draw_kreisel_drehrichtung_markierung(msp, pos1, pos2, merkmale, lib_doc, doc, verbose):
- drehrichtung = merkmale.get("Drehrichtung", "").upper()
+def draw_kreisel_drehrichtung_markierung(msp, pos1, pos2, kreisel: Kreisel, lib_doc, doc, verbose):
+ drehrichtung = (kreisel.drehrichtung or "").upper()
if drehrichtung not in ("UZS", "GUZS"):
return
x1, y1,z1= pos1
@@ -2585,13 +2689,18 @@ def get_rotations_of_strecken(csv_path:Path) -> dict:
planquadrat = row["Planquadrat"]
x, y = extract_coords(planquadrat)
merkmale = parse_merkmale(row.get("Merkmale", ""))
- drehung = merkmale.get("Drehrichtung")
- rotation = merkmale.get("Drehung")
- hight = float(merkmale.get("Höhe in m")) *1000
- abstand_m = merkmale.get(
- "Abstand (Kreiselachse A - Kreiselachse) in Meter", "20"
- ).replace(",", ".")
- kreisel.append({"Id":Id, "drehung":drehung, "höhe":hight,"x": x, "y": y, "rotation": rotation,"abstand":abstand_m})
+ # Erstelle Kreisel-Objekt
+ kreisel_obj = Kreisel.from_merkmale(Id, x, y, merkmale)
+ # Für Kompatibilität auch als Dict speichern (für bestehende Code-Stellen)
+ kreisel.append({
+ "Id": Id,
+ "drehung": kreisel_obj.drehrichtung,
+ "höhe": kreisel_obj.hoehe,
+ "x": kreisel_obj.x,
+ "y": kreisel_obj.y,
+ "rotation": kreisel_obj.drehung,
+ "abstand": str(kreisel_obj.abstand / 1000) # Zurück in Meter als String
+ })
if bezeichner =="ILS 2.0 VarioFoerderer":
Id = row["TeileId"].strip()
NachbarIds = row["NachbarIds"].strip()
diff --git a/lib/requirements.txt b/lib/requirements.txt
index 2902cfd..4160a68 100644
--- a/lib/requirements.txt
+++ b/lib/requirements.txt
@@ -1,2 +1,3 @@
ezdxf==1.4.1
-svg.path==7.0
\ No newline at end of file
+svg.path==7.0
+pydantic>=2.0.0
\ No newline at end of file