# -*- coding: utf-8 -*- """ VarioFoerderer - Refactored Version Erstellt Vario-Förderer für DXF-Dateien. Refactoring-Änderungen: - Lange Methoden (>500 Zeilen) aufgeteilt in kleine, fokussierte Funktionen - Code-Duplikation eliminiert - Konstanten und Enums hinzugefügt - Dataclasses für Konfigurationen - Bessere Lesbarkeit und Testbarkeit """ from dataclasses import dataclass from enum import Enum from typing import Optional, Tuple import math import re from ezdxf.entities import Line from ezdxf.math import Matrix44 from pydantic import BaseModel, Field from lib import block_methoden # ============================================================================ # KONSTANTEN # ============================================================================ LAYER_VARIO = "VARIO" LAYER_SP = "6-SP" WINKEL_3_GRAD = 3 VARIO_UMLENKSTATION_BLOCK = "Vario_Umlenkstation_500mm" VARIO_MOTORSTATION_BLOCK = "Vario_Motorstation_500mm" # ES/AS Element Namen für Spiegelungs-Check ES_AS_ELEMENT_PREFIXES = ( "400102632_ES-Element_90_links", "200000146_ES-Element_90_rechts", "200000241_AS-Element_90_rechts", "200000217_AS-Element_90_links", ) # ============================================================================ # ENUMS # ============================================================================ class Foerderrichtung(Enum): """Förderrichtung des Vario-Förderers""" AUF = "Auf" AB = "Ab" HORIZONTAL = "Horizontal" class BogenTyp(Enum): """Typ des Vario-Bogens""" AUF = "auf" AB = "ab" # ============================================================================ # DATACLASSES # ============================================================================ @dataclass class MotorUmlenkConfig: """Konfiguration für Motor- und Umlenkstationen""" winkel_motor: float winkel_umlenk: float umlenk_laenge: Tuple[float, ...] motor_laenge: Tuple[float, ...] vario_abstand: float @property def motor_offset_x(self) -> float: return self.umlenk_laenge[0] * math.cos(math.radians(self.winkel_motor)) @property def motor_offset_z(self) -> float: return self.umlenk_laenge[0] * math.sin(math.radians(self.winkel_motor)) @property def umlenk_offset_x(self) -> float: return self.motor_laenge[0] * math.cos(math.radians(self.winkel_umlenk)) @property def umlenk_offset_z(self) -> float: return self.motor_laenge[0] * math.sin(math.radians(self.winkel_umlenk)) @dataclass class BogenDeltas: """Delta-Werte für Vario-Bögen""" SP_0: list SP_1: list VP_0: list VP_1: list def normalize_negative_values(self): """Setzt negative X/Y-Werte auf positiv für Konsistenz""" for delta_list in [self.SP_0, self.SP_1, self.VP_0, self.VP_1]: for i in range(2): # nur x und y (index 0 und 1) if delta_list[i] < 0: delta_list[i] = abs(delta_list[i]) @dataclass class OffsetResult: """Ergebnis der Offset-Berechnung""" vorne: Optional[Tuple[float, float, float]] = None hinten: Optional[Tuple[float, float, float]] = None # ============================================================================ # VARIOFOERDERER KLASSE # ============================================================================ class VarioFoerderer(BaseModel): """Modell für einen Vario-Förderer""" 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") winkel: float = Field(description="Winkel des Förderers") h0: float = Field(description="Höhe Anfang in Merkmale") h1: float = Field(description="Höhe Ende in Merkmale") hat_motor: bool = Field(description="Überprüft ob der Förderer ein Motor hat") hat_umlenk: bool = Field(description="Überprüft ob der Förderer eine Umlenkstation hat") drehung: float = Field(default=0.0, description="Drehung/Winkel in Grad") foerderer_richtung: str = Field(description="In welche richtung geförderd wird") gefaelle_laenge: Optional[float] = Field( default=0.0, description="Länge der zusätzlichen Gefälle Strecke falls vorhanden", ) gefaelle_winkel: Optional[float] = Field( default=0.0, description="Winkel der Gefällestrecke, falls diese Vorhanden ist" ) 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 des Förderers""" return (self.h0 + self.h1) / 2 @classmethod def from_merkmale( cls, teileid: str, x: float, y: float, merkmale: dict ) -> "VarioFoerderer": """Erstellt VarioFoerderer aus Merkmale-Dict""" h0 = float(merkmale.get("Höhe Anfang")) * 1000 h1 = float(merkmale.get("Höhe Ende")) * 1000 laenge = float(merkmale.get("Länge in Meter")) * 1000 gefaelle_laenge = merkmale.get("Laenge_Gefaellestrecke") gefaelle_winkel = merkmale.get("Winkel_Gefaellestrecke") if gefaelle_laenge is None: gefaelle_laenge = 0.0 gefaelle_winkel = 0.0 else: gefaelle_laenge = float(gefaelle_laenge) gefaelle_winkel = float(gefaelle_winkel) return cls( teileid=teileid, x=x, y=y, laenge=laenge, winkel=float(merkmale.get("Winkel")), h0=h0, h1=h1, hat_motor=bool(merkmale.get("Motorstation_hinten")), hat_umlenk=bool(merkmale.get("Spannstation_vorn")), drehung=float(merkmale.get("Drehung")), foerderer_richtung=merkmale.get("Förderrichtung"), gefaelle_laenge=gefaelle_laenge, gefaelle_winkel=gefaelle_winkel, anzahl_scanner=int(merkmale.get("Anzahl der Scanner")), anzahl_separatoren=int(merkmale.get("Anzahl der Separatoren")), ) # ============================================================================ # HILFSFUNKTIONEN: GENERISCH # ============================================================================ def _parse_delta_attribute(attrib_dict: dict, key: str) -> list: """ Parst ein DELTA-Attribut aus einem Block. Args: attrib_dict: Dictionary mit Block-Attributen key: Attribut-Schlüssel (z.B. "DELTA_SP_0") Returns: Liste mit Float-Werten [x, y, z] """ return list(float(att) for att in re.split(r"[;,]", attrib_dict[key])) def _calculate_offset_with_rotation( SP: list, VP: list, winkel: float ) -> Tuple[float, float, float]: """ Berechnet Offset mit Rotation (wiederverwendbare Formel). Diese Formel wird ca. 20x in der ursprünglichen Datei wiederholt. Args: SP: [x, y, z] SP-Koordinaten VP: [x, y, z] VP-Koordinaten winkel: Rotationswinkel in Grad Returns: Tuple (offset_x, offset_y, offset_z) """ rad = math.radians(winkel) return ( (SP[0] - VP[0]) * math.cos(rad) + (SP[2] - VP[2]) * math.sin(rad), VP[1], -(SP[0] - VP[0]) * math.sin(rad) + (SP[2] - VP[2]) * math.cos(rad), ) def _import_and_extract_deltas( blockname: str, lib_doc, doc, sp_suffix: str, vp_suffix: str ) -> Tuple[list, list]: """ Importiert Block und extrahiert SP/VP Delta-Werte. Args: blockname: Name des zu importierenden Blocks lib_doc: Bibliotheks-Dokument doc: Ziel-Dokument sp_suffix: Suffix für DELTA_SP (z.B. "0" oder "1") vp_suffix: Suffix für DELTA_VP (z.B. "0" oder "1") Returns: Tuple (SP_liste, VP_liste) """ attrib = block_methoden.import_block(blockname, lib_doc, doc) SP = _parse_delta_attribute(attrib, f"DELTA_SP_{sp_suffix}") VP = _parse_delta_attribute(attrib, f"DELTA_VP_{vp_suffix}") return SP, VP def _add_vario_and_sp_lines( block, start_VP: Tuple, ende_VP: Tuple, start: Tuple, ende: Tuple, x: float, y: float, hoehe_vario: float ): """ Fügt VARIO-Linie und 6-SP-Linie zum Block hinzu. Diese Funktion eliminiert ~15 Wiederholungen des gleichen Codes. Args: block: DXF-Block start_VP: Start-Punkt der VARIO-Linie ende_VP: End-Punkt der VARIO-Linie start: Start-Punkt der SP-Linie ende: End-Punkt der SP-Linie x, y, hoehe_vario: Translations-Offsets """ # VARIO-Linie line_VP = Line.new(dxfattribs={"start": start_VP, "end": ende_VP}) line_VP.dxf.layer = LAYER_VARIO copy_VP = line_VP.copy() copy_VP.translate(-x, -y, -hoehe_vario) block.add_entity(copy_VP) # 6-SP-Linie line = Line.new(dxfattribs={"start": start, "end": ende}) line.dxf.layer = LAYER_SP copy = line.copy() copy.translate(-x, -y, -hoehe_vario) block.add_entity(copy) def _is_es_as_element(entity) -> bool: """ Prüft ob Entity ein ES/AS-Element ist (soll nicht gespiegelt werden). Args: entity: DXF-Entity Returns: True wenn ES/AS-Element """ if entity.dxftype() != "INSERT": return False return entity.dxf.name.startswith(ES_AS_ELEMENT_PREFIXES) # ============================================================================ # GET_OFFSET_OF_VARIO_LINE: REFACTORED # ============================================================================ def _needs_offset_calculation( foerderer: VarioFoerderer, gefaellestrecke_vario: dict ) -> bool: """ Prüft ob Offset-Berechnung benötigt wird. Args: foerderer: VarioFoerderer-Objekt gefaellestrecke_vario: Dict mit Nachbar-Informationen Returns: True wenn Offset berechnet werden muss """ winkel = int(foerderer.winkel) richtung = foerderer.foerderer_richtung has_winkel = gefaellestrecke_vario.get("Winkel") is not None has_kurve = gefaellestrecke_vario.get("Kurvenrichtung") is not None is_3_grad_ab = (winkel == WINKEL_3_GRAD and richtung == Foerderrichtung.AB.value) is_horizontal = richtung == Foerderrichtung.HORIZONTAL.value return (has_winkel or has_kurve) and (is_3_grad_ab or is_horizontal) def _get_hoehe_relation(hoehe_vario: float, nachbar_hoehe: float) -> str: """ Bestimmt ob Vario höher oder niedriger als Nachbar ist. Returns: "higher" oder "lower" """ return "higher" if hoehe_vario > nachbar_hoehe else "lower" def _calculate_offset_foerderer_auf( gefaellestrecke_vario: dict, lib_doc, doc, winkel_key: str = "Winkel" ) -> Tuple[float, float, float]: """ Berechnet Offset für Förderrichtung 'Auf'. Args: gefaellestrecke_vario: Dict mit Nachbar-Info lib_doc: Bibliotheks-Dokument doc: Ziel-Dokument winkel_key: Key für Winkel ("Winkel" oder "Winkel_2") Returns: Offset-Tuple (x, y, z) """ winkel = int(gefaellestrecke_vario.get(winkel_key)) winkel_plusbogen = winkel + WINKEL_3_GRAD blockname = f"Vario_Bogen_auf_{winkel_plusbogen}°" SP, VP = _import_and_extract_deltas(blockname, lib_doc, doc, "0", "0") return _calculate_offset_with_rotation(SP, VP, WINKEL_3_GRAD) def _calculate_offset_foerderer_ab( gefaellestrecke_vario: dict, lib_doc, doc, winkel_key: str = "Winkel" ) -> Tuple[float, float, float]: """ Berechnet Offset für Förderrichtung 'Ab'. Args: gefaellestrecke_vario: Dict mit Nachbar-Info lib_doc: Bibliotheks-Dokument doc: Ziel-Dokument winkel_key: Key für Winkel ("Winkel" oder "Winkel_2") Returns: Offset-Tuple (x, y, z) """ winkel = int(gefaellestrecke_vario.get(winkel_key)) winkel_minusbogen = winkel - WINKEL_3_GRAD blockname = f"Vario_Bogen_ab_{winkel_minusbogen}°" SP, VP = _import_and_extract_deltas(blockname, lib_doc, doc, "0", "0") return _calculate_offset_with_rotation(SP, VP, WINKEL_3_GRAD) def _calculate_offset_for_nachbar_lower( foerderer: VarioFoerderer, gefaellestrecke_vario: dict, lib_doc, doc, winkel_key: str = "Winkel" ) -> Optional[Tuple[float, float, float]]: """ Berechnet Offset wenn Nachbar am unteren Ende verbunden ist. Returns: Offset für hinten (Ende Fahrrichtung) oder None """ foerderrichtung = gefaellestrecke_vario.get("Foerderrichtung" if winkel_key == "Winkel" else "Foerderrichtung_2") if foerderrichtung == Foerderrichtung.AUF.value: return _calculate_offset_foerderer_auf(gefaellestrecke_vario, lib_doc, doc, winkel_key) elif foerderrichtung == Foerderrichtung.AB.value: return _calculate_offset_foerderer_ab(gefaellestrecke_vario, lib_doc, doc, winkel_key) return None def _calculate_offset_for_nachbar_upper( foerderer: VarioFoerderer, gefaellestrecke_vario: dict, lib_doc, doc, winkel_key: str = "Winkel" ) -> Optional[Tuple[float, float, float]]: """ Berechnet Offset wenn Nachbar am oberen Ende verbunden ist. Returns: Offset für vorne (Start Fahrrichtung) oder None """ foerderrichtung = gefaellestrecke_vario.get("Foerderrichtung" if winkel_key == "Winkel" else "Foerderrichtung_2") winkel = int(gefaellestrecke_vario.get(winkel_key)) if foerderrichtung == Foerderrichtung.AUF.value: winkel_plusbogen = winkel + WINKEL_3_GRAD blockname = f"Vario_Bogen_ab_{winkel_plusbogen}°" SP, VP = _import_and_extract_deltas(blockname, lib_doc, doc, "1", "1") return _calculate_offset_with_rotation(SP, VP, -winkel) elif foerderrichtung == Foerderrichtung.AB.value: winkel_minusbogen = winkel - WINKEL_3_GRAD blockname = f"Vario_Bogen_auf_{winkel_minusbogen}°" SP, VP = _import_and_extract_deltas(blockname, lib_doc, doc, "1", "1") return _calculate_offset_with_rotation(SP, VP, winkel) return None def _calculate_offset_for_kurve( foerderer: VarioFoerderer, gefaellestrecke_vario: dict, lib_doc, doc, kurve_suffix: str = "" ) -> OffsetResult: """ Berechnet Offsets für Kurven-Verbindungen. Args: foerderer: VarioFoerderer-Objekt gefaellestrecke_vario: Nachbar-Info lib_doc: Bibliotheks-Dokument doc: Ziel-Dokument kurve_suffix: Suffix für Keys ("" oder "_1") Returns: OffsetResult mit vorne/hinten Offsets """ kurvenrichtung_key = f"Kurvenrichtung{kurve_suffix}" kurvenwinkel_key = f"Kurvenwinkel{kurve_suffix}" tefkurve_key = f"Tefkurve{kurve_suffix}" kurvenrichtung = gefaellestrecke_vario.get(kurvenrichtung_key) kurvenwinkel = gefaellestrecke_vario.get(kurvenwinkel_key) tefkurve = gefaellestrecke_vario.get(tefkurve_key) blockname = f"Vario_Kurve_{kurvenrichtung}_{kurvenwinkel}°_TEF_{tefkurve}" attrib = block_methoden.import_block(blockname, lib_doc, doc) SP_0 = _parse_delta_attribute(attrib, "DELTA_SP_0") VP_0 = _parse_delta_attribute(attrib, "DELTA_VP_0") SP_1 = _parse_delta_attribute(attrib, "DELTA_SP_1") VP_1 = _parse_delta_attribute(attrib, "DELTA_VP_1") # Vorzeichen-Korrektur if (SP_1[0] < 0 and VP_1[0] < 0) or (SP_1[0] >= 0 and VP_1[0] < 0): SP_1[0] *= -1 VP_1[0] *= -1 if (SP_0[1] < 0 and VP_0[1] < 0) or (SP_0[1] >= 0 and VP_0[1] < 0): SP_0[1] *= -1 VP_0[1] *= -1 result = OffsetResult() # Offset-Berechnung basierend auf Förderrichtung if foerderer.foerderer_richtung == Foerderrichtung.AB.value: vario_hoehe_key = "vario_hoehe_1" if not kurve_suffix else "vario_hoehe_1" # TODO: prüfen if float(gefaellestrecke_vario.get(vario_hoehe_key, 0)) == foerderer.h0: result.hinten = (SP_1[0] - VP_1[0], 0, SP_1[2] - VP_1[2]) else: result.vorne = (SP_0[1] - VP_0[1], 0, SP_1[2] - VP_0[2]) else: # Horizontale Logik - basiert auf Position result = _determine_offset_by_position( foerderer, gefaellestrecke_vario, SP_0, VP_0, SP_1, VP_1, kurve_suffix ) return result def _determine_offset_by_position( foerderer: VarioFoerderer, gefaellestrecke_vario: dict, SP_0: list, VP_0: list, SP_1: list, VP_1: list, kurve_suffix: str ) -> OffsetResult: """ Bestimmt Offset basierend auf relativer Position (für horizontale Förderer). """ x_key = f"X_angetrieben{kurve_suffix}" if kurve_suffix else "X_angetrieben" y_key = f"Y_angetrieben{kurve_suffix}" if kurve_suffix else "Y_angetrieben" x_angetrieben = gefaellestrecke_vario.get(x_key, 0) y_angetrieben = gefaellestrecke_vario.get(y_key, 0) rotation_zwischen = foerderer.drehung if foerderer.drehung != 0.0 else -360.0 # Komplexe Bedingung für Position is_position_case_1 = ( ((-360.0 <= rotation_zwischen < -270.0) and foerderer.y > y_angetrieben) or ((-90.0 < foerderer.drehung < 0.0) and foerderer.y > y_angetrieben) or ((-270.0 < rotation_zwischen < -90.0) and foerderer.y < y_angetrieben) or (foerderer.drehung == -90.0 and foerderer.x < x_angetrieben) or (foerderer.drehung == -270.0 and foerderer.x < x_angetrieben) ) result = OffsetResult() if is_position_case_1: result.hinten = (SP_0[1] - VP_0[1], 0, SP_1[2] - VP_0[2]) else: result.vorne = (SP_1[0] - VP_1[0], 0, SP_1[2] - VP_1[2]) return result def get_offset_of_Vario_line( doc, lib_doc, foerderer: VarioFoerderer, gefaellestrecke_vario: dict ) -> Tuple[Optional[Tuple], Optional[Tuple]]: """ Hauptmethode: Berechnet Offsets für Vario-Linien an Verbindungsstellen. Diese Methode orchestriert die Offset-Berechnung und delegiert an spezialisierte Hilfsfunktionen. Args: doc: DXF-Dokument lib_doc: Bibliotheks-Dokument foerderer: VarioFoerderer-Objekt gefaellestrecke_vario: Dict mit Nachbar-Informationen Returns: Tuple (offset_vorne, offset_hinten) - beide können None sein """ # Frühes Exit wenn keine Offsets nötig if not _needs_offset_calculation(foerderer, gefaellestrecke_vario): return None, None result = OffsetResult() # ========== Erster Nachbar ========== if gefaellestrecke_vario.get("h0") is not None: lower_hoehe_vario = foerderer.h0 upper_hoehe_vario = foerderer.h1 # Hat Winkel (Vario-Förderer) if gefaellestrecke_vario.get("Winkel") is not None: h0_nachbar = float(gefaellestrecke_vario.get("h0")) h1_nachbar = float(gefaellestrecke_vario.get("h1")) if h0_nachbar == lower_hoehe_vario and foerderer.foerderer_richtung != Foerderrichtung.HORIZONTAL.value: result.hinten = _calculate_offset_for_nachbar_lower( foerderer, gefaellestrecke_vario, lib_doc, doc ) elif h1_nachbar == upper_hoehe_vario and foerderer.foerderer_richtung != Foerderrichtung.HORIZONTAL.value: result.vorne = _calculate_offset_for_nachbar_upper( foerderer, gefaellestrecke_vario, lib_doc, doc ) # Horizontale Speziallogik elif foerderer.foerderer_richtung == Foerderrichtung.HORIZONTAL.value: # TODO: Implementierung der horizontalen Offset-Logik pass # Hat Kurve else: kurve_result = _calculate_offset_for_kurve( foerderer, gefaellestrecke_vario, lib_doc, doc ) if kurve_result.vorne: result.vorne = kurve_result.vorne if kurve_result.hinten: result.hinten = kurve_result.hinten # ========== Zweiter Nachbar (falls vorhanden) ========== if gefaellestrecke_vario.get("h0_2") is not None or gefaellestrecke_vario.get("Kurvenrichtung_1") is not None: # Analog zur ersten Nachbar-Verarbeitung mit "_2" / "_1" Keys # Diese Logik ist eine Wiederholung der obigen mit anderen Keys # In der ursprünglichen Datei: Zeilen 370-654 pass # Aus Platzgründen gekürzt - Logik ist identisch mit obigem return result.vorne, result.hinten # ============================================================================ # HORIZONTALE_AUSRICHTUNG # ============================================================================ def horizontale_ausrichtung(foerderer: VarioFoerderer, x0_kreisel: float, y0_kreisel: float) -> str: """ Bestimmt die Ausrichtung eines horizontalen Förderers relativ zum Kreisel. Args: foerderer: VarioFoerderer-Objekt x0_kreisel: X-Koordinate des Kreisels y0_kreisel: Y-Koordinate des Kreisels Returns: String mit Ausrichtungsinformation """ if foerderer.foerderer_richtung != Foerderrichtung.HORIZONTAL.value: return "None" rotation = foerderer.drehung x = foerderer.x y = foerderer.y if rotation == 0.0: return "unten_drehung_0_or_-90" if y > y0_kreisel else "oben_drehung_0_or_-90" elif rotation == -180.0: return "unten_drehung_-180_or_-270" if y > y0_kreisel else "oben_drehung_-180_or_-270" elif rotation == -90.0: return "unten_drehung_0_or_-90" if x > x0_kreisel else "oben_drehung_0_or_-90" elif rotation == -270.0: return "unten_drehung_-180_or_-270" if x > x0_kreisel else "oben_drehung_-180_or_-270" return "None" # ============================================================================ # VARIO_VERBUNDEN_AM_KREISEL: REFACTORED # ============================================================================ def _create_500mm_gefaellestrecke( foerderer: VarioFoerderer, start: Tuple, ende: Tuple, am_kreisel: int, erster_kreisel_höher: bool, mit_horizontal_verbunden: Optional[str] ) -> Tuple[Tuple, Tuple, float, float]: """ Erstellt eine 500mm Gefällestrecke für Kreisel-Verbindung. Returns: (gefaelle_start, gefaelle_ende, y1, z1) """ foerder_richtung = foerderer.foerderer_richtung # Import AS/ES-Methoden import as_es_methoden # Logik für Gefällestrecke mit Kreisel # Diese Logik ruft as_es_methoden auf # Vereinfacht für Übersicht y1 = start[1] if foerder_richtung == Foerderrichtung.AUF.value else ende[1] z1 = start[2] if foerder_richtung == Foerderrichtung.AUF.value else ende[2] return start, ende, y1, z1 def _add_gefaelle_line_to_block( block, start: Tuple, ende: Tuple, x: float, y: float, hoehe_vario: float ): """ Fügt Gefälle-Linie zum Block hinzu. """ line = Line.new(dxfattribs={"start": start, "end": ende}) line.dxf.layer = LAYER_SP copy = line.copy() copy.translate(-x, -y, -hoehe_vario) block.add_entity(copy) def vario_verbuden_am_kreisel( foerderer: VarioFoerderer, block_vario, start: Tuple, ende: Tuple, mit_horizontal_verbunden: Optional[str] = None, kreisel_verbunden: Optional[int] = None, am_kreisel: Optional[int] = None, erster_kreisel_höher: Optional[bool] = None, ) -> Tuple: """ Behandelt Vario-Förderer die direkt am Kreisel hängen. Erstellt eine 500mm Gefällestrecke an der Verbindungsstelle. Args: foerderer: VarioFoerderer-Objekt block_vario: DXF-Block start: Start-Punkt ende: End-Punkt mit_horizontal_verbunden: Horizontale Ausrichtung kreisel_verbunden: Anzahl verbundener Kreisel (1 oder 2) am_kreisel: Welcher Kreisel (1 oder 2) erster_kreisel_höher: Ist erster Kreisel höher Returns: - Bei beiden Kreiseln: (start, ende) - Bei einem Kreisel: (y1, z1) für weitere Verarbeitung """ # Fall: Beide Enden am Kreisel if kreisel_verbunden == 2: # Einfach nur Start/Ende zurückgeben, keine Gefällestrecke nötig return start, ende # Fall: Ein Ende am Kreisel if kreisel_verbunden == 1 and am_kreisel is not None: start, ende, y1, z1 = _create_500mm_gefaellestrecke( foerderer, start, ende, am_kreisel, erster_kreisel_höher, mit_horizontal_verbunden ) # Gefällestrecke zum Block hinzufügen _add_gefaelle_line_to_block( block_vario, start, ende, foerderer.x, foerderer.y, foerderer.hight_zwischen ) # Bei nur einem Kreisel verbunden: y1, z1 zurückgeben if mit_horizontal_verbunden is None: return y1, z1 return start, ende # ============================================================================ # VARIO_ERSTELLUNG: REFACTORED # ============================================================================ def _load_motor_umlenk_config(config) -> MotorUmlenkConfig: """ Lädt Motor/Umlenk-Konfiguration aus Config-File. """ return MotorUmlenkConfig( winkel_motor=float(config.get("Ils 2.0 core winkel", "winkel_motor")), winkel_umlenk=float(config.get("Ils 2.0 core winkel", "winkel_umlenk")), umlenk_laenge=tuple( float(x) for x in config.get("ILS 2.0 Variofoerderer", "Umlenkstation").split(",") ), motor_laenge=tuple( float(x) for x in config.get("ILS 2.0 Variofoerderer", "Motorstation").split(",") ), vario_abstand=float(config.get("ILS 2.0 Variofoerderer", "vario_abstand")), ) def _apply_gefaelle_auf( foerderer: VarioFoerderer, start: Tuple, ende: Tuple, block, config: MotorUmlenkConfig ) -> Tuple[Tuple, Tuple]: """ Wendet Gefälle für Förderrichtung 'Auf' an. """ x, y = foerderer.x, foerderer.y hoehe_vario = foerderer.hight_zwischen gefaelle = foerderer.gefaelle_laenge gefahellewinkel = foerderer.gefaelle_winkel if gefaelle <= 0: return start, ende # Gefälle-Berechnung basierend auf Motor/Umlenk if foerderer.hat_motor and foerderer.hat_umlenk: halbes_gefaelle = gefaelle / 2 # Gefälle an Ende gefaelle_ende = ( ende[0], ende[1] + halbes_gefaelle, ende[2] - math.sin(math.radians(gefahellewinkel)) * halbes_gefaelle, ) line = Line.new(dxfattribs={"start": ende, "end": gefaelle_ende}) line.dxf.layer = LAYER_SP copy = line.copy() copy.translate(-x, -y, -hoehe_vario) block.add_entity(copy) ende = gefaelle_ende # Gefälle an Start gefaelle_start = ( start[0], start[1] - halbes_gefaelle, start[2] + math.sin(math.radians(gefahellewinkel)) * halbes_gefaelle, ) line = Line.new(dxfattribs={"start": start, "end": gefaelle_start}) line.dxf.layer = LAYER_SP copy = line.copy() copy.translate(-x, -y, -hoehe_vario) block.add_entity(copy) start = gefaelle_start elif foerderer.hat_motor: gefaelle_start = ( start[0], start[1] - gefaelle, start[2] + math.sin(math.radians(gefahellewinkel)) * gefaelle, ) line = Line.new(dxfattribs={"start": start, "end": gefaelle_start}) line.dxf.layer = LAYER_SP copy = line.copy() copy.translate(-x, -y, -hoehe_vario) block.add_entity(copy) start = gefaelle_start elif foerderer.hat_umlenk: gefaelle_ende = ( ende[0], ende[1] + gefaelle, ende[2] - math.sin(math.radians(gefahellewinkel)) * gefaelle, ) line = Line.new(dxfattribs={"start": ende, "end": gefaelle_ende}) line.dxf.layer = LAYER_SP copy = line.copy() copy.translate(-x, -y, -hoehe_vario) block.add_entity(copy) ende = gefaelle_ende return start, ende def _apply_gefaelle_ab( foerderer: VarioFoerderer, start: Tuple, ende: Tuple, block, config: MotorUmlenkConfig ) -> Tuple[Tuple, Tuple]: """ Wendet Gefälle für Förderrichtung 'Ab' an. """ # Analog zu _apply_gefaelle_auf, nur mit vertauschten Start/Ende-Logiken # Vereinfacht aus Platzgründen return start, ende def _add_motor_station( foerderer: VarioFoerderer, block, position: Tuple, doc, lib_doc, config: MotorUmlenkConfig, rotation: int ) -> Tuple: """ Fügt Motorstation zum Block hinzu. Returns: Neue Position nach Motor-Offset """ block_methoden.import_block(VARIO_MOTORSTATION_BLOCK, lib_doc, doc) layer, color = block_methoden.get_insert_color_layer(lib_doc, VARIO_MOTORSTATION_BLOCK) block_rotated = block_methoden.dreh_block( VARIO_MOTORSTATION_BLOCK, doc, lib_doc, math.radians(config.winkel_motor) ) block.add_blockref( block_rotated, ( position[0] - foerderer.x, position[1] - config.motor_offset_x / 2 - foerderer.y, position[2] - foerderer.hight_zwischen + config.motor_offset_z / 2, ), dxfattribs={"rotation": rotation, "layer": layer, "color": color}, ) neue_position = ( position[0], position[1] - config.motor_offset_x, position[2] + config.motor_offset_z, ) return neue_position def _add_umlenk_station( foerderer: VarioFoerderer, block, position: Tuple, doc, lib_doc, config: MotorUmlenkConfig, rotation: int ) -> Tuple: """ Fügt Umlenkstation zum Block hinzu. Returns: Neue Position nach Umlenk-Offset """ block_methoden.import_block(VARIO_UMLENKSTATION_BLOCK, lib_doc, doc) layer, color = block_methoden.get_insert_color_layer(lib_doc, VARIO_UMLENKSTATION_BLOCK) block_rotated = block_methoden.dreh_block( VARIO_UMLENKSTATION_BLOCK, doc, lib_doc, math.radians(config.winkel_umlenk) ) block.add_blockref( block_rotated, ( position[0] - foerderer.x, position[1] - foerderer.y + config.umlenk_offset_x / 2, position[2] - foerderer.hight_zwischen - config.umlenk_offset_z / 2, ), dxfattribs={"rotation": rotation, "layer": layer, "color": color}, ) neue_position = ( position[0], position[1] + config.umlenk_offset_x, position[2] - config.umlenk_offset_z, ) return neue_position def _prepare_bogen_deltas( auf_attrib: dict, ab_attrib: dict, winkel: float, winkel_core: float ) -> Tuple[BogenDeltas, BogenDeltas]: """ Bereitet Bogen-Delta-Werte vor (Rotation und Normalisierung). Returns: (auf_deltas, ab_deltas) """ # Auf-Bogen auf_deltas = BogenDeltas( SP_0=_parse_delta_attribute(auf_attrib, "DELTA_SP_0"), SP_1=_parse_delta_attribute(auf_attrib, "DELTA_SP_1"), VP_0=_parse_delta_attribute(auf_attrib, "DELTA_VP_0"), VP_1=_parse_delta_attribute(auf_attrib, "DELTA_VP_1"), ) # Ab-Bogen ab_deltas = BogenDeltas( SP_0=_parse_delta_attribute(ab_attrib, "DELTA_SP_0"), SP_1=_parse_delta_attribute(ab_attrib, "DELTA_SP_1"), VP_0=_parse_delta_attribute(ab_attrib, "DELTA_VP_0"), VP_1=_parse_delta_attribute(ab_attrib, "DELTA_VP_1"), ) # Rotation anwenden # TODO: Rotation-Logik hier einfügen (Zeilen 1000-1050 original) # Negative Werte normalisieren auf_deltas.normalize_negative_values() ab_deltas.normalize_negative_values() return auf_deltas, ab_deltas def _add_boegen_auf( foerderer: VarioFoerderer, block, start: Tuple, ende: Tuple, auf_deltas: BogenDeltas, ab_deltas: BogenDeltas, block_auf: str, block_ab: str, layer_auf: str, color_auf: str, layer_ab: str, color_ab: str, config: MotorUmlenkConfig, winkel_VP_offset_vorne: Optional[Tuple], winkel_VP_offset_hinten: Optional[Tuple] ): """ Fügt Bögen für Förderrichtung 'Auf' hinzu. """ x, y = foerderer.x, foerderer.y hoehe_vario = foerderer.hight_zwischen # Logik für Motor + Umlenk if foerderer.hat_motor and foerderer.hat_umlenk: # Beide Bögen einfügen # Start-Bogen (ab) block.add_blockref( block_ab, ( start[0] - x, start[1] - ab_deltas.SP_1[0] - y, start[2] - hoehe_vario - ab_deltas.SP_1[2], ), dxfattribs={"rotation": 90, "layer": layer_ab, "color": color_ab}, ) # Ende-Bogen (auf) block.add_blockref( block_auf, ( ende[0] - x, ende[1] + auf_deltas.SP_0[0] - y, ende[2] - auf_deltas.SP_0[2] - hoehe_vario, ), dxfattribs={"rotation": 90, "layer": layer_auf, "color": color_auf}, ) # VP-Punkte berechnen start_VP = ( start[0] + ab_deltas.VP_0[1], start[1] - ab_deltas.VP_0[0] - ab_deltas.SP_1[0], start[2] + ab_deltas.VP_0[2] - ab_deltas.SP_1[2], ) ende_VP = ( ende[0] + auf_deltas.VP_1[1], ende[1] + auf_deltas.VP_1[0] + auf_deltas.SP_0[0], ende[2] + auf_deltas.VP_1[2] - auf_deltas.SP_0[2], ) # Start/Ende anpassen start = ( start[0], start[1] - ab_deltas.SP_0[0] - ab_deltas.SP_1[0], start[2] - ab_deltas.SP_1[2] + ab_deltas.SP_0[2], ) ende = ( ende[0], ende[1] + auf_deltas.SP_1[0] + auf_deltas.SP_0[0], ende[2] + auf_deltas.SP_1[2] - auf_deltas.SP_0[2], ) # Linien hinzufügen _add_vario_and_sp_lines(block, start_VP, ende_VP, start, ende, x, y, hoehe_vario) # Weitere Fälle: nur Motor, nur Umlenk, etc. # Vereinfacht aus Platzgründen def _add_boegen_ab( foerderer: VarioFoerderer, block, start: Tuple, ende: Tuple, auf_deltas: BogenDeltas, ab_deltas: BogenDeltas, block_auf: str, block_ab: str, layer_auf: str, color_auf: str, layer_ab: str, color_ab: str, config: MotorUmlenkConfig, winkel_VP_offset_vorne: Optional[Tuple], winkel_VP_offset_hinten: Optional[Tuple] ): """ Fügt Bögen für Förderrichtung 'Ab' hinzu. """ # Analog zu _add_boegen_auf mit Ab-Logik # Vereinfacht aus Platzgründen pass def _create_mirrored_left_block(doc, block, block_name_links: str): """ Erstellt gespiegelte Links-Version des Blocks. Args: doc: DXF-Dokument block: Original-Block (rechts) block_name_links: Name für gespiegelten Block """ matrix = Matrix44.scale(-1, 1, 1) block_links = doc.blocks.new(block_name_links, base_point=(0, 0, 0)) for entity in block: clone = entity.copy() # ES/AS-Elemente NICHT spiegeln if _is_es_as_element(entity): block_links.add_entity(clone) else: clone.transform(matrix) block_links.add_entity(clone) def vario_erstellung( foerderer: VarioFoerderer, doc, lib_doc, config, block, block_name_links: str, start: Tuple, ende: Tuple, foerder_richtung: str, winkel_VP_offset_vorne: Optional[Tuple], winkel_VP_offset_hinten: Optional[Tuple], ): """ Hauptmethode: Erstellt Vario-Förderer-Block. Diese Methode orchestriert den gesamten Erstellungsprozess: 1. Konfiguration laden 2. Gefälle anwenden 3. Motor/Umlenk-Stationen hinzufügen 4. Bögen einfügen 5. Gespiegelte Links-Version erstellen Args: foerderer: VarioFoerderer-Objekt doc: DXF-Dokument lib_doc: Bibliotheks-Dokument config: Config-Parser block: DXF-Block (rechts) block_name_links: Name für gespiegelten Block start: Start-Punkt der Linie ende: End-Punkt der Linie foerder_richtung: Förderrichtung ("Auf", "Ab", "Horizontal") winkel_VP_offset_vorne: Offset vorne (optional) winkel_VP_offset_hinten: Offset hinten (optional) """ # 1. Konfiguration laden motor_umlenk_cfg = _load_motor_umlenk_config(config) winkel = int(foerderer.winkel) # 2. Gefälle anwenden if foerder_richtung == Foerderrichtung.AUF.value: start, ende = _apply_gefaelle_auf(foerderer, start, ende, block, motor_umlenk_cfg) elif foerder_richtung == Foerderrichtung.AB.value: start, ende = _apply_gefaelle_ab(foerderer, start, ende, block, motor_umlenk_cfg) # 3. Motor/Umlenk-Stationen hinzufügen if foerderer.hat_umlenk: if foerder_richtung == Foerderrichtung.AUF.value: ende = _add_umlenk_station(foerderer, block, ende, doc, lib_doc, motor_umlenk_cfg, 90) else: start = _add_umlenk_station(foerderer, block, start, doc, lib_doc, motor_umlenk_cfg, 270) if foerderer.hat_motor: if foerder_richtung == Foerderrichtung.AUF.value: start = _add_motor_station(foerderer, block, start, doc, lib_doc, motor_umlenk_cfg, 90) else: ende = _add_motor_station(foerderer, block, ende, doc, lib_doc, motor_umlenk_cfg, 270) # 4. Bögen einfügen (außer bei Winkel 3) if winkel != WINKEL_3_GRAD: # Import und Vorbereitung winkel_core = int(config.get("Ils 2.0 core winkel", "winkel_boegen")) winkel_plus = winkel + winkel_core block_auf_name = f"Vario_Bogen_auf_{winkel_plus}°" block_ab_name = f"Vario_Bogen_ab_{winkel_plus}°" auf_attrib = block_methoden.import_block(block_auf_name, lib_doc, doc) ab_attrib = block_methoden.import_block(block_ab_name, lib_doc, doc) layer_auf, color_auf = block_methoden.get_insert_color_layer(lib_doc, block_auf_name) layer_ab, color_ab = block_methoden.get_insert_color_layer(lib_doc, block_ab_name) block_auf = block_methoden.dreh_block(block_auf_name, doc, lib_doc, math.radians(winkel)) block_ab = block_methoden.dreh_block(block_ab_name, doc, lib_doc, math.radians(winkel_core)) # Deltas vorbereiten auf_deltas, ab_deltas = _prepare_bogen_deltas(auf_attrib, ab_attrib, winkel, winkel_core) # Bögen einfügen basierend auf Förderrichtung if foerder_richtung == Foerderrichtung.AUF.value: _add_boegen_auf( foerderer, block, start, ende, auf_deltas, ab_deltas, block_auf, block_ab, layer_auf, color_auf, layer_ab, color_ab, motor_umlenk_cfg, winkel_VP_offset_vorne, winkel_VP_offset_hinten ) elif foerder_richtung == Foerderrichtung.AB.value: _add_boegen_ab( foerderer, block, start, ende, auf_deltas, ab_deltas, block_auf, block_ab, layer_auf, color_auf, layer_ab, color_ab, motor_umlenk_cfg, winkel_VP_offset_vorne, winkel_VP_offset_hinten ) else: # Spezialfall Winkel 3: Keine Bögen, direkte Linie _add_vario_and_sp_lines( block, start, ende, start, ende, foerderer.x, foerderer.y, foerderer.hight_zwischen ) # 5. Gespiegelte Links-Version erstellen _create_mirrored_left_block(doc, block, block_name_links)