Add composite color strip source type with layer blending
Composite sources stack multiple existing color strip sources as layers with configurable blend modes (Normal, Add, Multiply, Screen) and per-layer opacity. Includes full CRUD, hot-reload, delete protection for referenced layers, and pre-allocated integer blend math at 30 FPS. Also eliminates per-frame numpy allocations in color_strip_stream, effect_stream, and wled_target_processor (buffer pre-allocation). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -27,11 +27,20 @@ class ColorStop(BaseModel):
|
||||
)
|
||||
|
||||
|
||||
class CompositeLayer(BaseModel):
|
||||
"""A single layer in a composite color strip source."""
|
||||
|
||||
source_id: str = Field(description="ID of the layer's color strip source")
|
||||
blend_mode: str = Field(default="normal", description="Blend mode: normal|add|multiply|screen")
|
||||
opacity: float = Field(default=1.0, ge=0.0, le=1.0, description="Layer opacity 0.0-1.0")
|
||||
enabled: bool = Field(default=True, description="Whether this layer is active")
|
||||
|
||||
|
||||
class ColorStripSourceCreate(BaseModel):
|
||||
"""Request to create a color strip source."""
|
||||
|
||||
name: str = Field(description="Source name", min_length=1, max_length=100)
|
||||
source_type: Literal["picture", "static", "gradient", "color_cycle", "effect"] = Field(default="picture", description="Source type")
|
||||
source_type: Literal["picture", "static", "gradient", "color_cycle", "effect", "composite"] = Field(default="picture", description="Source type")
|
||||
# picture-type fields
|
||||
picture_source_id: str = Field(default="", description="Picture source ID (for picture type)")
|
||||
brightness: float = Field(default=1.0, description="Brightness multiplier (0.0-2.0)", ge=0.0, le=2.0)
|
||||
@@ -54,6 +63,8 @@ class ColorStripSourceCreate(BaseModel):
|
||||
intensity: Optional[float] = Field(None, description="Effect intensity 0.1-2.0", ge=0.1, le=2.0)
|
||||
scale: Optional[float] = Field(None, description="Spatial scale 0.5-5.0", ge=0.5, le=5.0)
|
||||
mirror: Optional[bool] = Field(None, description="Mirror/bounce mode (meteor)")
|
||||
# composite-type fields
|
||||
layers: Optional[List[CompositeLayer]] = Field(None, description="Layers for composite type")
|
||||
# shared
|
||||
led_count: int = Field(default=0, description="Total LED count (0 = auto from calibration / device)", ge=0)
|
||||
description: Optional[str] = Field(None, description="Optional description", max_length=500)
|
||||
@@ -87,6 +98,8 @@ class ColorStripSourceUpdate(BaseModel):
|
||||
intensity: Optional[float] = Field(None, description="Effect intensity 0.1-2.0", ge=0.1, le=2.0)
|
||||
scale: Optional[float] = Field(None, description="Spatial scale 0.5-5.0", ge=0.5, le=5.0)
|
||||
mirror: Optional[bool] = Field(None, description="Mirror/bounce mode")
|
||||
# composite-type fields
|
||||
layers: Optional[List[CompositeLayer]] = Field(None, description="Layers for composite type")
|
||||
# shared
|
||||
led_count: Optional[int] = Field(None, description="Total LED count (0 = auto from calibration / device)", ge=0)
|
||||
description: Optional[str] = Field(None, description="Optional description", max_length=500)
|
||||
@@ -122,6 +135,8 @@ class ColorStripSourceResponse(BaseModel):
|
||||
intensity: Optional[float] = Field(None, description="Effect intensity")
|
||||
scale: Optional[float] = Field(None, description="Spatial scale")
|
||||
mirror: Optional[bool] = Field(None, description="Mirror/bounce mode")
|
||||
# composite-type fields
|
||||
layers: Optional[List[dict]] = Field(None, description="Layers for composite type")
|
||||
# shared
|
||||
led_count: int = Field(0, description="Total LED count (0 = auto from calibration / device)")
|
||||
description: Optional[str] = Field(None, description="Description")
|
||||
|
||||
Reference in New Issue
Block a user