Files
led-hoop/pico/src/patterns/segments_transition.py
jimmy 3e58f4e97e Add segments and double_circle patterns with shared presets
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
2026-03-05 23:41:13 +13:00

137 lines
5.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 n1n8 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