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:
@@ -79,6 +79,7 @@ def _css_to_response(source, overlay_active: bool = False) -> ColorStripSourceRe
|
||||
description=source.description,
|
||||
frame_interpolation=getattr(source, "frame_interpolation", None),
|
||||
animation=getattr(source, "animation", None),
|
||||
layers=getattr(source, "layers", None),
|
||||
overlay_active=overlay_active,
|
||||
created_at=source.created_at,
|
||||
updated_at=source.updated_at,
|
||||
@@ -128,6 +129,8 @@ async def create_color_strip_source(
|
||||
|
||||
stops = [s.model_dump() for s in data.stops] if data.stops is not None else None
|
||||
|
||||
layers = [l.model_dump() for l in data.layers] if data.layers is not None else None
|
||||
|
||||
source = store.create_source(
|
||||
name=data.name,
|
||||
source_type=data.source_type,
|
||||
@@ -152,6 +155,7 @@ async def create_color_strip_source(
|
||||
intensity=data.intensity,
|
||||
scale=data.scale,
|
||||
mirror=data.mirror,
|
||||
layers=layers,
|
||||
)
|
||||
return _css_to_response(source)
|
||||
|
||||
@@ -193,6 +197,8 @@ async def update_color_strip_source(
|
||||
|
||||
stops = [s.model_dump() for s in data.stops] if data.stops is not None else None
|
||||
|
||||
layers = [l.model_dump() for l in data.layers] if data.layers is not None else None
|
||||
|
||||
source = store.update_source(
|
||||
source_id=source_id,
|
||||
name=data.name,
|
||||
@@ -217,6 +223,7 @@ async def update_color_strip_source(
|
||||
intensity=data.intensity,
|
||||
scale=data.scale,
|
||||
mirror=data.mirror,
|
||||
layers=layers,
|
||||
)
|
||||
|
||||
# Hot-reload running stream (no restart needed for in-place param changes)
|
||||
@@ -249,6 +256,12 @@ async def delete_color_strip_source(
|
||||
detail="Color strip source is referenced by one or more LED targets. "
|
||||
"Delete or reassign the targets first.",
|
||||
)
|
||||
if store.is_referenced_by_composite(source_id):
|
||||
raise HTTPException(
|
||||
status_code=409,
|
||||
detail="Color strip source is used as a layer in a composite source. "
|
||||
"Remove it from the composite first.",
|
||||
)
|
||||
store.delete_source(source_id)
|
||||
except HTTPException:
|
||||
raise
|
||||
|
||||
Reference in New Issue
Block a user