Introduce double_circle and segments-based patterns on the Pico, refactor the Presets engine to expose a logical ring over all strips, and migrate presets/test code from the old point pattern to segments while switching to a top-level presets.json. Made-with: Cursor
137 lines
5.2 KiB
Python
137 lines
5.2 KiB
Python
import utime
|
||
|
||
|
||
class SegmentsTransition:
|
||
def __init__(self, driver):
|
||
self.driver = driver
|
||
|
||
def run(self, preset):
|
||
"""
|
||
SegmentsTransition: fade from whatever is currently on the strips
|
||
to a new static Segments layout defined by n1–n8 and c[0..3].
|
||
|
||
- Uses the existing strip buffers as the starting state.
|
||
- Target state matches the Segments pattern: up to 4 colored bands
|
||
along the logical reference strip, mapped to all physical strips.
|
||
- Transition duration is taken from preset.d (ms), minimum 50ms.
|
||
"""
|
||
strips = self.driver.strips
|
||
if not strips:
|
||
while True:
|
||
yield
|
||
|
||
# Snapshot starting GRB buffers (already scaled by per-strip brightness)
|
||
start_bufs = [bytes(strip.ar) for strip in strips]
|
||
|
||
# Prepare target buffers (same length as each strip's ar)
|
||
target_bufs = [bytearray(len(strip.ar)) for strip in strips]
|
||
|
||
# Base colors (up to 4), missing ones default to black
|
||
colors = list(preset.c) if getattr(preset, "c", None) else []
|
||
while len(colors) < 4:
|
||
colors.append((0, 0, 0))
|
||
|
||
# Apply preset/global brightness once per color
|
||
bright_colors = [
|
||
self.driver.apply_brightness(colors[0], preset.b),
|
||
self.driver.apply_brightness(colors[1], preset.b),
|
||
self.driver.apply_brightness(colors[2], preset.b),
|
||
self.driver.apply_brightness(colors[3], preset.b),
|
||
]
|
||
|
||
# Logical reference length for all strips (from scale_map[0])
|
||
ref_len = len(self.driver.scale_map[0]) if self.driver.scale_map else 0
|
||
if ref_len <= 0:
|
||
# Fallback: nothing to do, just hold current state
|
||
while True:
|
||
yield
|
||
|
||
# Helper to clamp and normalize a logical range [a, b] (inclusive) over ref_len.
|
||
# Returns (start, end_exclusive) suitable for range(start, end_exclusive).
|
||
def norm_range(a, b):
|
||
a = int(a)
|
||
b = int(b)
|
||
if a > b:
|
||
a, b = b, a
|
||
if b < 0 or a >= ref_len:
|
||
return None
|
||
a = max(0, a)
|
||
b = min(ref_len - 1, b)
|
||
if a > b:
|
||
return None
|
||
return a, b + 1
|
||
|
||
raw_ranges = [
|
||
(getattr(preset, "n1", 0), getattr(preset, "n2", -1), bright_colors[0]),
|
||
(getattr(preset, "n3", 0), getattr(preset, "n4", -1), bright_colors[1]),
|
||
(getattr(preset, "n5", 0), getattr(preset, "n6", -1), bright_colors[2]),
|
||
(getattr(preset, "n7", 0), getattr(preset, "n8", -1), bright_colors[3]),
|
||
]
|
||
|
||
# Build target buffers using the same logical indexing idea as Segments
|
||
for strip_idx, strip in enumerate(strips):
|
||
bright = strip.brightness
|
||
scale_map = self.driver.scale_map[strip_idx]
|
||
buf = target_bufs[strip_idx]
|
||
n_leds = strip.num_leds
|
||
|
||
# Start from black everywhere
|
||
for i in range(len(buf)):
|
||
buf[i] = 0
|
||
|
||
# Apply each logical range to this strip
|
||
for a, b, color in raw_ranges:
|
||
rng = norm_range(a, b)
|
||
if not rng:
|
||
continue
|
||
start, end = rng
|
||
r, g, bl = color
|
||
for logical_idx in range(start, end):
|
||
if logical_idx < 0 or logical_idx >= len(scale_map):
|
||
continue
|
||
phys_idx = scale_map[logical_idx]
|
||
if phys_idx < 0 or phys_idx >= n_leds:
|
||
continue
|
||
base = phys_idx * 3
|
||
if base + 2 >= len(buf):
|
||
continue
|
||
buf[base] = int(g * bright)
|
||
buf[base + 1] = int(r * bright)
|
||
buf[base + 2] = int(bl * bright)
|
||
|
||
# Duration in ms for the whole transition (slower by default)
|
||
# If preset.d is provided, use it; otherwise default to a slow 3000ms fade.
|
||
raw_d = int(getattr(preset, "d", 3000) or 3000)
|
||
duration = max(1000, raw_d) # enforce at least 1s for a clearly visible transition
|
||
start_time = utime.ticks_ms()
|
||
|
||
while True:
|
||
now = utime.ticks_ms()
|
||
elapsed = utime.ticks_diff(now, start_time)
|
||
|
||
if elapsed >= duration:
|
||
# Final frame: commit target buffers and hold, then update all strips together
|
||
for strip, target in zip(strips, target_bufs):
|
||
ar = strip.ar
|
||
for i in range(len(ar)):
|
||
ar[i] = target[i]
|
||
self.driver.show_all()
|
||
while True:
|
||
yield
|
||
|
||
# Interpolation factor in [0,1]
|
||
factor = elapsed / duration
|
||
inv = 1.0 - factor
|
||
|
||
# Blend from start to target in GRB space per byte
|
||
for idx, strip in enumerate(strips):
|
||
start_buf = start_bufs[idx]
|
||
target_buf = target_bufs[idx]
|
||
ar = strip.ar
|
||
for i in range(len(ar)):
|
||
ar[i] = int(start_buf[i] * inv + target_buf[i] * factor)
|
||
self.driver.show_all()
|
||
|
||
yield
|
||
|