Files
plant2dxf/lib/Elemente/VarioFoerderer.py
T

1232 lines
39 KiB
Python

# -*- 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
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)