# -*- coding: utf-8 -*- """ LED Animations-Framework. Definiert die IAnimation-Basisklasse und die AnimationRegistry fuer die Verwaltung verfuegbarer LED-Animationen. """ from __future__ import annotations from abc import ABC, abstractmethod from collections.abc import Iterator from trixy_core.led.layouts import LEDLayout from trixy_core.utils.debug import pdebug, pwarn # RGB Tupel Type-Alias RGB = tuple[int, int, int] class IAnimation(ABC): """Basis-Klasse fuer alle LED-Animationen. Animationen erzeugen eine Folge von Frames als Generator. Jeder Frame ist eine Liste von RGB-Tupeln (ein Tupel pro LED). Klassen-Attribute: NAME: Registry-Name der Animation (z.B. "solid", "breathe"). CONTINUOUS: True fuer endlose Animationen, False fuer einmalige. """ NAME: str = "" CONTINUOUS: bool = False @abstractmethod def frames( self, led_count: int, color: RGB, speed: int, layout: LEDLayout, **opts: object, ) -> Iterator[list[RGB]]: """Generiert die Animation als Folge von Frames. Jeder Frame ist eine komplette Liste von RGB-Werten fuer alle LEDs. Der Aufrufer steuert das Timing zwischen den Frames anhand des speed-Parameters. Args: led_count: Anzahl der LEDs im Strip/Ring. color: Hauptfarbe als (R, G, B) Tupel. speed: Geschwindigkeit in Millisekunden pro Frame-Schritt. layout: LED-Layout mit ring_indices und center_index. **opts: Zusaetzliche Parameter je nach Animation (z.B. center_color, tail, direction). Yields: Frame als Liste von RGB-Tupeln, ein Tupel pro LED. Bei CONTINUOUS=True: endloser Generator (laeuft bis Abbruch). Bei CONTINUOUS=False: endliche Sequenz. """ ... class AnimationRegistry: """Registry fuer verfuegbare LED-Animationen. Verwaltet eine Zuordnung von Animations-Namen zu ihren Klassen. Beim Start werden die eingebauten Animationen automatisch registriert, zusaetzliche koennen zur Laufzeit hinzugefuegt werden. """ def __init__(self) -> None: self._animations: dict[str, type[IAnimation]] = {} def register(self, cls: type[IAnimation]) -> None: """Registriert eine Animations-Klasse. Verwendet den NAME-Klassenattribut als Schluessel. Warnt bei Ueberschreibung eines bereits registrierten Namens. Args: cls: Animations-Klasse (muss NAME gesetzt haben). """ name = cls.NAME if not name: pwarn( f"Animation {cls.__name__} hat keinen NAME — " f"wird nicht registriert" ) return if name in self._animations: pdebug( f"Animation '{name}' wird ueberschrieben: " f"{self._animations[name].__name__} → {cls.__name__}" ) self._animations[name] = cls pdebug(f"Animation registriert: '{name}' ({cls.__name__})") def get(self, name: str) -> type[IAnimation] | None: """Gibt die Animations-Klasse fuer den angegebenen Namen zurueck. Args: name: Registry-Name der Animation. Returns: Animations-Klasse oder None wenn nicht gefunden. """ return self._animations.get(name) def list_names(self) -> list[str]: """Gibt alle registrierten Animations-Namen zurueck. Returns: Sortierte Liste der verfuegbaren Animations-Namen. """ return sorted(self._animations.keys()) def register_builtins(self) -> None: """Registriert alle eingebauten Animationen. Importiert die Animations-Module und registriert alle darin definierten IAnimation-Unterklassen. Fehlende Module werden mit einer Warnung uebersprungen. """ builtin_modules = [ ".static", ".continuous", ".circular", ".effects", ] for module_name in builtin_modules: try: import importlib full_name = f"trixy_core.led.animations{module_name}" module = importlib.import_module(full_name) # Alle IAnimation-Unterklassen im Modul registrieren for attr_name in dir(module): attr = getattr(module, attr_name) if ( isinstance(attr, type) and issubclass(attr, IAnimation) and attr is not IAnimation and attr.NAME ): self.register(attr) except ImportError: pdebug( f"Animations-Modul '{module_name}' nicht gefunden — " f"uebersprungen" ) except Exception as e: pwarn( f"Fehler beim Laden von Animations-Modul " f"'{module_name}': {e}" ) __all__ = [ "RGB", "IAnimation", "AnimationRegistry", ]