Add 5 procedural LED effects, gradient presets, auto-crop min aspect ratio, static source polling optimization
New features: - Procedural effect source type with fire, meteor, plasma, noise, and aurora algorithms using palette LUT system and 1D value noise generator - 12 predefined gradient presets (rainbow, sunset, ocean, forest, fire, lava, aurora, ice, warm, cool, neon, pastel) selectable from a dropdown in the gradient editor - Auto-crop filter: min aspect ratio parameter to prevent false-positive cropping in dark scenes on ultrawide displays Optimization: - Static/gradient sources without animation: stream thread sleeps 0.25s instead of frame_time; processor repolls at frame_time instead of 5ms (~40x fewer iterations) - Inverted isinstance checks in routes to test for PictureColorStripSource only Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -57,6 +57,11 @@ class ColorStripSource:
|
||||
"animation": None,
|
||||
"colors": None,
|
||||
"cycle_speed": None,
|
||||
"effect_type": None,
|
||||
"palette": None,
|
||||
"intensity": None,
|
||||
"scale": None,
|
||||
"mirror": None,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
@@ -125,6 +130,25 @@ class ColorStripSource:
|
||||
led_count=data.get("led_count") or 0,
|
||||
)
|
||||
|
||||
if source_type == "effect":
|
||||
raw_color = data.get("color")
|
||||
color = (
|
||||
raw_color if isinstance(raw_color, list) and len(raw_color) == 3
|
||||
else [255, 80, 0]
|
||||
)
|
||||
return EffectColorStripSource(
|
||||
id=sid, name=name, source_type="effect",
|
||||
created_at=created_at, updated_at=updated_at, description=description,
|
||||
effect_type=data.get("effect_type") or "fire",
|
||||
speed=float(data.get("speed") or 1.0),
|
||||
led_count=data.get("led_count") or 0,
|
||||
palette=data.get("palette") or "fire",
|
||||
color=color,
|
||||
intensity=float(data.get("intensity") or 1.0),
|
||||
scale=float(data.get("scale") or 1.0),
|
||||
mirror=bool(data.get("mirror", False)),
|
||||
)
|
||||
|
||||
# Default: "picture" type
|
||||
return PictureColorStripSource(
|
||||
id=sid, name=name, source_type=source_type,
|
||||
@@ -248,3 +272,34 @@ class ColorCycleColorStripSource(ColorStripSource):
|
||||
d["cycle_speed"] = self.cycle_speed
|
||||
d["led_count"] = self.led_count
|
||||
return d
|
||||
|
||||
|
||||
@dataclass
|
||||
class EffectColorStripSource(ColorStripSource):
|
||||
"""Color strip source that runs a procedural LED effect.
|
||||
|
||||
The effect_type field selects which algorithm to use:
|
||||
fire, meteor, plasma, noise, aurora.
|
||||
LED count auto-sizes from the connected device when led_count == 0.
|
||||
"""
|
||||
|
||||
effect_type: str = "fire" # fire | meteor | plasma | noise | aurora
|
||||
speed: float = 1.0 # animation speed multiplier (0.1–10.0)
|
||||
led_count: int = 0 # 0 = use device LED count
|
||||
palette: str = "fire" # named color palette
|
||||
color: list = field(default_factory=lambda: [255, 80, 0]) # [R,G,B] for meteor head
|
||||
intensity: float = 1.0 # effect-specific intensity (0.1–2.0)
|
||||
scale: float = 1.0 # spatial scale / zoom (0.5–5.0)
|
||||
mirror: bool = False # bounce mode (meteor)
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
d = super().to_dict()
|
||||
d["effect_type"] = self.effect_type
|
||||
d["speed"] = self.speed
|
||||
d["led_count"] = self.led_count
|
||||
d["palette"] = self.palette
|
||||
d["color"] = list(self.color)
|
||||
d["intensity"] = self.intensity
|
||||
d["scale"] = self.scale
|
||||
d["mirror"] = self.mirror
|
||||
return d
|
||||
|
||||
@@ -10,6 +10,7 @@ from wled_controller.core.capture.calibration import CalibrationConfig, calibrat
|
||||
from wled_controller.storage.color_strip_source import (
|
||||
ColorCycleColorStripSource,
|
||||
ColorStripSource,
|
||||
EffectColorStripSource,
|
||||
GradientColorStripSource,
|
||||
PictureColorStripSource,
|
||||
StaticColorStripSource,
|
||||
@@ -109,6 +110,12 @@ class ColorStripStore:
|
||||
animation: Optional[dict] = None,
|
||||
colors: Optional[list] = None,
|
||||
cycle_speed: float = 1.0,
|
||||
effect_type: str = "fire",
|
||||
speed: float = 1.0,
|
||||
palette: str = "fire",
|
||||
intensity: float = 1.0,
|
||||
scale: float = 1.0,
|
||||
mirror: bool = False,
|
||||
) -> ColorStripSource:
|
||||
"""Create a new color strip source.
|
||||
|
||||
@@ -169,6 +176,24 @@ class ColorStripStore:
|
||||
cycle_speed=float(cycle_speed) if cycle_speed else 1.0,
|
||||
led_count=led_count,
|
||||
)
|
||||
elif source_type == "effect":
|
||||
rgb = color if isinstance(color, list) and len(color) == 3 else [255, 80, 0]
|
||||
source = EffectColorStripSource(
|
||||
id=source_id,
|
||||
name=name,
|
||||
source_type="effect",
|
||||
created_at=now,
|
||||
updated_at=now,
|
||||
description=description,
|
||||
effect_type=effect_type or "fire",
|
||||
speed=float(speed) if speed else 1.0,
|
||||
led_count=led_count,
|
||||
palette=palette or "fire",
|
||||
color=rgb,
|
||||
intensity=float(intensity) if intensity else 1.0,
|
||||
scale=float(scale) if scale else 1.0,
|
||||
mirror=bool(mirror),
|
||||
)
|
||||
else:
|
||||
if calibration is None:
|
||||
calibration = CalibrationConfig(layout="clockwise", start_position="bottom_left")
|
||||
@@ -217,6 +242,12 @@ class ColorStripStore:
|
||||
animation: Optional[dict] = None,
|
||||
colors: Optional[list] = None,
|
||||
cycle_speed: Optional[float] = None,
|
||||
effect_type: Optional[str] = None,
|
||||
speed: Optional[float] = None,
|
||||
palette: Optional[str] = None,
|
||||
intensity: Optional[float] = None,
|
||||
scale: Optional[float] = None,
|
||||
mirror: Optional[bool] = None,
|
||||
) -> ColorStripSource:
|
||||
"""Update an existing color strip source.
|
||||
|
||||
@@ -280,6 +311,23 @@ class ColorStripStore:
|
||||
source.cycle_speed = float(cycle_speed)
|
||||
if led_count is not None:
|
||||
source.led_count = led_count
|
||||
elif isinstance(source, EffectColorStripSource):
|
||||
if effect_type is not None:
|
||||
source.effect_type = effect_type
|
||||
if speed is not None:
|
||||
source.speed = float(speed)
|
||||
if led_count is not None:
|
||||
source.led_count = led_count
|
||||
if palette is not None:
|
||||
source.palette = palette
|
||||
if color is not None and isinstance(color, list) and len(color) == 3:
|
||||
source.color = color
|
||||
if intensity is not None:
|
||||
source.intensity = float(intensity)
|
||||
if scale is not None:
|
||||
source.scale = float(scale)
|
||||
if mirror is not None:
|
||||
source.mirror = bool(mirror)
|
||||
|
||||
source.updated_at = datetime.utcnow()
|
||||
self._save()
|
||||
|
||||
Reference in New Issue
Block a user