# -*- 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 from pydantic import BaseModel, Field from typing import Optional 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): """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" ) @property def hight_zwischen(self): """Mittlere Höhe zwischen h0 und h1.""" return (self.h0 + self.h1) / 2 @classmethod 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, x=x, y=y, 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. 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 # 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) 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) 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). 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 laenge = omniflo_objekt.laenge h0 = omniflo_objekt.h0 h1 = omniflo_objekt.h1 h_zwischen = omniflo_objekt.hight_zwischen blockname = f"OF_Förderer_{laenge}_{h_zwischen}" winkel_rad = math.radians(rotation) halbe_laenge = laenge / 2 # 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}) copy = line.copy() copy.translate(-x, -y, -h_zwischen) block.add_entity(copy) msp.add_blockref( blockname, (x, y, h_zwischen), dxfattribs={"rotation": rotation} )