Berry animation DSL: computed parameters and simplified generated code (#23828)
This commit is contained in:
parent
517eae733b
commit
72ddde049e
@ -57,22 +57,16 @@ var aurora_colors_ = bytes("00000022" "40004400" "8000AA44" "C044AA88" "FF88FFAA
|
||||
var aurora_purple_ = bytes("00220022" "40440044" "808800AA" "C0AA44CC" "FFCCAAFF")
|
||||
# Base aurora animation with slow flowing colors
|
||||
var aurora_base_ = animation.rich_palette_animation(engine)
|
||||
aurora_base_.palette = animation.global('aurora_colors_', 'aurora_colors') # palette
|
||||
aurora_base_.palette = aurora_colors_ # palette
|
||||
aurora_base_.cycle_period = 10000 # cycle period
|
||||
aurora_base_.transition_type = animation.global('SINE_', 'SINE') # transition type (explicit for clarity)
|
||||
aurora_base_.transition_type = animation.SINE # transition type (explicit for clarity)
|
||||
aurora_base_.brightness = 180 # brightness (dimmed for aurora effect)
|
||||
def sequence_demo()
|
||||
var demo_ = (def (engine)
|
||||
var steps = []
|
||||
steps.push(animation.create_play_step(animation.global('aurora_base_'), 0)) # infinite duration (no 'for' clause)
|
||||
var seq_manager = animation.SequenceManager(engine)
|
||||
seq_manager.start_sequence(steps)
|
||||
return seq_manager
|
||||
end
|
||||
# Start all animations/sequences
|
||||
if global.contains('sequence_demo')
|
||||
var seq_manager = global.sequence_demo()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('demo_'))
|
||||
end
|
||||
end)(engine)
|
||||
engine.add_sequence_manager(demo_)
|
||||
engine.start()
|
||||
|
||||
@ -66,11 +66,11 @@ var breathe_orange_ = 0xFFFF8000
|
||||
var breathe_palette_ = bytes("00FF0000" "33FF8000" "66FFFF00" "9900FF00" "CC0000FF" "FF800080")
|
||||
# Create a rich palette color provider
|
||||
var palette_pattern_ = animation.rich_palette(engine)
|
||||
palette_pattern_.palette = animation.global('breathe_palette_', 'breathe_palette') # palette
|
||||
palette_pattern_.palette = breathe_palette_ # palette
|
||||
palette_pattern_.cycle_period = 15000 # cycle period (defaults: smooth transition, 255 brightness)
|
||||
# Create breathing animation using the palette
|
||||
var breathing_ = animation.breathe_animation(engine)
|
||||
breathing_.color = animation.global('palette_pattern_', 'palette_pattern') # base animation
|
||||
breathing_.color = palette_pattern_ # base animation
|
||||
breathing_.min_brightness = 100 # min brightness
|
||||
breathing_.max_brightness = 255 # max brightness
|
||||
breathing_.period = 4000 # breathing period (4 seconds)
|
||||
@ -79,13 +79,7 @@ var temp_smooth_152 = animation.smooth(engine)
|
||||
temp_smooth_152.min_value = 100
|
||||
temp_smooth_152.max_value = 255
|
||||
temp_smooth_152.duration = 4000
|
||||
animation.global('breathing_').opacity = temp_smooth_152
|
||||
breathing_.opacity = temp_smooth_152
|
||||
# Start the animation
|
||||
# Start all animations/sequences
|
||||
if global.contains('sequence_breathing')
|
||||
var seq_manager = global.sequence_breathing()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('breathing_'))
|
||||
end
|
||||
engine.add_animation(breathing_)
|
||||
engine.start()
|
||||
|
||||
@ -67,52 +67,52 @@ var candy_white_ = 0xFFFFFFFF
|
||||
# Create alternating red and white animation
|
||||
# Using multiple pulse positions to create stripes
|
||||
var stripe1_ = animation.beacon_animation(engine)
|
||||
stripe1_.color = animation.global('candy_red_', 'candy_red')
|
||||
stripe1_.color = candy_red_
|
||||
stripe1_.pos = 3
|
||||
stripe1_.beacon_size = 4
|
||||
stripe1_.slew_size = 1
|
||||
var stripe2_ = animation.beacon_animation(engine)
|
||||
stripe2_.color = animation.global('candy_white_', 'candy_white')
|
||||
stripe2_.color = candy_white_
|
||||
stripe2_.pos = 9
|
||||
stripe2_.beacon_size = 4
|
||||
stripe2_.slew_size = 1
|
||||
var stripe3_ = animation.beacon_animation(engine)
|
||||
stripe3_.color = animation.global('candy_red_', 'candy_red')
|
||||
stripe3_.color = candy_red_
|
||||
stripe3_.pos = 15
|
||||
stripe3_.beacon_size = 4
|
||||
stripe3_.slew_size = 1
|
||||
var stripe4_ = animation.beacon_animation(engine)
|
||||
stripe4_.color = animation.global('candy_white_', 'candy_white')
|
||||
stripe4_.color = candy_white_
|
||||
stripe4_.pos = 21
|
||||
stripe4_.beacon_size = 4
|
||||
stripe4_.slew_size = 1
|
||||
var stripe5_ = animation.beacon_animation(engine)
|
||||
stripe5_.color = animation.global('candy_red_', 'candy_red')
|
||||
stripe5_.color = candy_red_
|
||||
stripe5_.pos = 27
|
||||
stripe5_.beacon_size = 4
|
||||
stripe5_.slew_size = 1
|
||||
var stripe6_ = animation.beacon_animation(engine)
|
||||
stripe6_.color = animation.global('candy_white_', 'candy_white')
|
||||
stripe6_.color = candy_white_
|
||||
stripe6_.pos = 33
|
||||
stripe6_.beacon_size = 4
|
||||
stripe6_.slew_size = 1
|
||||
var stripe7_ = animation.beacon_animation(engine)
|
||||
stripe7_.color = animation.global('candy_red_', 'candy_red')
|
||||
stripe7_.color = candy_red_
|
||||
stripe7_.pos = 39
|
||||
stripe7_.beacon_size = 4
|
||||
stripe7_.slew_size = 1
|
||||
var stripe8_ = animation.beacon_animation(engine)
|
||||
stripe8_.color = animation.global('candy_white_', 'candy_white')
|
||||
stripe8_.color = candy_white_
|
||||
stripe8_.pos = 45
|
||||
stripe8_.beacon_size = 4
|
||||
stripe8_.slew_size = 1
|
||||
var stripe9_ = animation.beacon_animation(engine)
|
||||
stripe9_.color = animation.global('candy_red_', 'candy_red')
|
||||
stripe9_.color = candy_red_
|
||||
stripe9_.pos = 51
|
||||
stripe9_.beacon_size = 4
|
||||
stripe9_.slew_size = 1
|
||||
var stripe10_ = animation.beacon_animation(engine)
|
||||
stripe10_.color = animation.global('candy_white_', 'candy_white')
|
||||
stripe10_.color = candy_white_
|
||||
stripe10_.pos = 57
|
||||
stripe10_.beacon_size = 4
|
||||
stripe10_.slew_size = 1
|
||||
@ -121,113 +121,62 @@ var move_speed_ = 8000
|
||||
var temp_sawtooth_258 = animation.sawtooth(engine)
|
||||
temp_sawtooth_258.min_value = 3
|
||||
temp_sawtooth_258.max_value = 63
|
||||
temp_sawtooth_258.duration = animation.global('move_speed_', 'move_speed')
|
||||
animation.global('stripe1_').pos = temp_sawtooth_258
|
||||
temp_sawtooth_258.duration = move_speed_
|
||||
stripe1_.pos = temp_sawtooth_258
|
||||
var temp_sawtooth_277 = animation.sawtooth(engine)
|
||||
temp_sawtooth_277.min_value = 9
|
||||
temp_sawtooth_277.max_value = 69
|
||||
temp_sawtooth_277.duration = animation.global('move_speed_', 'move_speed')
|
||||
animation.global('stripe2_').pos = temp_sawtooth_277
|
||||
temp_sawtooth_277.duration = move_speed_
|
||||
stripe2_.pos = temp_sawtooth_277
|
||||
var temp_sawtooth_296 = animation.sawtooth(engine)
|
||||
temp_sawtooth_296.min_value = 15
|
||||
temp_sawtooth_296.max_value = 75
|
||||
temp_sawtooth_296.duration = animation.global('move_speed_', 'move_speed')
|
||||
animation.global('stripe3_').pos = temp_sawtooth_296
|
||||
temp_sawtooth_296.duration = move_speed_
|
||||
stripe3_.pos = temp_sawtooth_296
|
||||
var temp_sawtooth_315 = animation.sawtooth(engine)
|
||||
temp_sawtooth_315.min_value = 21
|
||||
temp_sawtooth_315.max_value = 81
|
||||
temp_sawtooth_315.duration = animation.global('move_speed_', 'move_speed')
|
||||
animation.global('stripe4_').pos = temp_sawtooth_315
|
||||
temp_sawtooth_315.duration = move_speed_
|
||||
stripe4_.pos = temp_sawtooth_315
|
||||
var temp_sawtooth_334 = animation.sawtooth(engine)
|
||||
temp_sawtooth_334.min_value = 27
|
||||
temp_sawtooth_334.max_value = 87
|
||||
temp_sawtooth_334.duration = animation.global('move_speed_', 'move_speed')
|
||||
animation.global('stripe5_').pos = temp_sawtooth_334
|
||||
temp_sawtooth_334.duration = move_speed_
|
||||
stripe5_.pos = temp_sawtooth_334
|
||||
var temp_sawtooth_353 = animation.sawtooth(engine)
|
||||
temp_sawtooth_353.min_value = 33
|
||||
temp_sawtooth_353.max_value = 93
|
||||
temp_sawtooth_353.duration = animation.global('move_speed_', 'move_speed')
|
||||
animation.global('stripe6_').pos = temp_sawtooth_353
|
||||
temp_sawtooth_353.duration = move_speed_
|
||||
stripe6_.pos = temp_sawtooth_353
|
||||
var temp_sawtooth_372 = animation.sawtooth(engine)
|
||||
temp_sawtooth_372.min_value = 39
|
||||
temp_sawtooth_372.max_value = 99
|
||||
temp_sawtooth_372.duration = animation.global('move_speed_', 'move_speed')
|
||||
animation.global('stripe7_').pos = temp_sawtooth_372
|
||||
temp_sawtooth_372.duration = move_speed_
|
||||
stripe7_.pos = temp_sawtooth_372
|
||||
var temp_sawtooth_391 = animation.sawtooth(engine)
|
||||
temp_sawtooth_391.min_value = 45
|
||||
temp_sawtooth_391.max_value = 105
|
||||
temp_sawtooth_391.duration = animation.global('move_speed_', 'move_speed')
|
||||
animation.global('stripe8_').pos = temp_sawtooth_391
|
||||
temp_sawtooth_391.duration = move_speed_
|
||||
stripe8_.pos = temp_sawtooth_391
|
||||
var temp_sawtooth_410 = animation.sawtooth(engine)
|
||||
temp_sawtooth_410.min_value = 51
|
||||
temp_sawtooth_410.max_value = 111
|
||||
temp_sawtooth_410.duration = animation.global('move_speed_', 'move_speed')
|
||||
animation.global('stripe9_').pos = temp_sawtooth_410
|
||||
temp_sawtooth_410.duration = move_speed_
|
||||
stripe9_.pos = temp_sawtooth_410
|
||||
var temp_sawtooth_429 = animation.sawtooth(engine)
|
||||
temp_sawtooth_429.min_value = 57
|
||||
temp_sawtooth_429.max_value = 117
|
||||
temp_sawtooth_429.duration = animation.global('move_speed_', 'move_speed')
|
||||
animation.global('stripe10_').pos = temp_sawtooth_429
|
||||
temp_sawtooth_429.duration = move_speed_
|
||||
stripe10_.pos = temp_sawtooth_429
|
||||
# Start all stripes
|
||||
# Start all animations/sequences
|
||||
if global.contains('sequence_stripe1')
|
||||
var seq_manager = global.sequence_stripe1()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('stripe1_'))
|
||||
end
|
||||
if global.contains('sequence_stripe2')
|
||||
var seq_manager = global.sequence_stripe2()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('stripe2_'))
|
||||
end
|
||||
if global.contains('sequence_stripe3')
|
||||
var seq_manager = global.sequence_stripe3()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('stripe3_'))
|
||||
end
|
||||
if global.contains('sequence_stripe4')
|
||||
var seq_manager = global.sequence_stripe4()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('stripe4_'))
|
||||
end
|
||||
if global.contains('sequence_stripe5')
|
||||
var seq_manager = global.sequence_stripe5()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('stripe5_'))
|
||||
end
|
||||
if global.contains('sequence_stripe6')
|
||||
var seq_manager = global.sequence_stripe6()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('stripe6_'))
|
||||
end
|
||||
if global.contains('sequence_stripe7')
|
||||
var seq_manager = global.sequence_stripe7()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('stripe7_'))
|
||||
end
|
||||
if global.contains('sequence_stripe8')
|
||||
var seq_manager = global.sequence_stripe8()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('stripe8_'))
|
||||
end
|
||||
if global.contains('sequence_stripe9')
|
||||
var seq_manager = global.sequence_stripe9()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('stripe9_'))
|
||||
end
|
||||
if global.contains('sequence_stripe10')
|
||||
var seq_manager = global.sequence_stripe10()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('stripe10_'))
|
||||
end
|
||||
engine.add_animation(stripe1_)
|
||||
engine.add_animation(stripe2_)
|
||||
engine.add_animation(stripe3_)
|
||||
engine.add_animation(stripe4_)
|
||||
engine.add_animation(stripe5_)
|
||||
engine.add_animation(stripe6_)
|
||||
engine.add_animation(stripe7_)
|
||||
engine.add_animation(stripe8_)
|
||||
engine.add_animation(stripe9_)
|
||||
engine.add_animation(stripe10_)
|
||||
engine.start()
|
||||
|
||||
@ -78,79 +78,53 @@ var engine = animation.init_strip()
|
||||
|
||||
var tree_green_ = 0xFF006600
|
||||
var tree_base_ = animation.solid(engine)
|
||||
tree_base_.color = animation.global('tree_green_', 'tree_green')
|
||||
tree_base_.color = tree_green_
|
||||
# Define ornament colors
|
||||
var ornament_colors_ = bytes("00FF0000" "40FFD700" "800000FF" "C0FFFFFF" "FFFF00FF")
|
||||
# Colorful ornaments as twinkling lights
|
||||
var ornament_pattern_ = animation.rich_palette(engine)
|
||||
ornament_pattern_.palette = animation.global('ornament_colors_', 'ornament_colors')
|
||||
ornament_pattern_.palette = ornament_colors_
|
||||
ornament_pattern_.cycle_period = 3000
|
||||
ornament_pattern_.transition_type = animation.global('LINEAR_', 'LINEAR')
|
||||
ornament_pattern_.transition_type = animation.LINEAR
|
||||
ornament_pattern_.brightness = 255
|
||||
var ornaments_ = animation.twinkle_animation(engine)
|
||||
ornaments_.color = animation.global('ornament_pattern_', 'ornament_pattern') # color source
|
||||
ornaments_.color = ornament_pattern_ # color source
|
||||
ornaments_.density = 15 # density (many ornaments)
|
||||
ornaments_.twinkle_speed = 800 # twinkle speed (slow twinkle)
|
||||
animation.global('ornaments_').priority = 10
|
||||
ornaments_.priority = 10
|
||||
# Star on top (bright yellow pulse)
|
||||
var tree_star_ = animation.beacon_animation(engine)
|
||||
tree_star_.color = 0xFFFFFF00 # Bright yellow
|
||||
tree_star_.pos = 58 # position (near the top)
|
||||
tree_star_.beacon_size = 3 # star size
|
||||
tree_star_.slew_size = 1 # sharp edges
|
||||
animation.global('tree_star_').priority = 20
|
||||
tree_star_.priority = 20
|
||||
var temp_smooth_170 = animation.smooth(engine)
|
||||
temp_smooth_170.min_value = 200
|
||||
temp_smooth_170.max_value = 255
|
||||
temp_smooth_170.duration = 2000
|
||||
animation.global('tree_star_').opacity = temp_smooth_170 # Gentle pulsing
|
||||
tree_star_.opacity = temp_smooth_170 # Gentle pulsing
|
||||
# Add some white sparkles for snow/magic
|
||||
var snow_sparkles_ = animation.twinkle_animation(engine)
|
||||
snow_sparkles_.color = 0xFFFFFFFF # White snow
|
||||
snow_sparkles_.density = 8 # density (sparkle count)
|
||||
snow_sparkles_.twinkle_speed = 400 # twinkle speed (quick sparkles)
|
||||
animation.global('snow_sparkles_').priority = 15
|
||||
snow_sparkles_.priority = 15
|
||||
# Garland effect - moving colored lights
|
||||
var garland_pattern_ = animation.rich_palette(engine)
|
||||
garland_pattern_.palette = animation.global('ornament_colors_', 'ornament_colors')
|
||||
garland_pattern_.palette = ornament_colors_
|
||||
garland_pattern_.cycle_period = 2000
|
||||
garland_pattern_.transition_type = animation.global('LINEAR_', 'LINEAR')
|
||||
garland_pattern_.transition_type = animation.LINEAR
|
||||
garland_pattern_.brightness = 200
|
||||
var garland_ = animation.comet_animation(engine)
|
||||
garland_.color = animation.global('garland_pattern_', 'garland_pattern') # color source
|
||||
garland_.color = garland_pattern_ # color source
|
||||
garland_.tail_length = 6 # garland length (tail length)
|
||||
garland_.speed = 4000 # slow movement (speed)
|
||||
animation.global('garland_').priority = 5
|
||||
garland_.priority = 5
|
||||
# Start all animations
|
||||
# Start all animations/sequences
|
||||
if global.contains('sequence_tree_base')
|
||||
var seq_manager = global.sequence_tree_base()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('tree_base_'))
|
||||
end
|
||||
if global.contains('sequence_ornaments')
|
||||
var seq_manager = global.sequence_ornaments()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('ornaments_'))
|
||||
end
|
||||
if global.contains('sequence_tree_star')
|
||||
var seq_manager = global.sequence_tree_star()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('tree_star_'))
|
||||
end
|
||||
if global.contains('sequence_snow_sparkles')
|
||||
var seq_manager = global.sequence_snow_sparkles()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('snow_sparkles_'))
|
||||
end
|
||||
if global.contains('sequence_garland')
|
||||
var seq_manager = global.sequence_garland()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('garland_'))
|
||||
end
|
||||
engine.add_animation(tree_base_)
|
||||
engine.add_animation(ornaments_)
|
||||
engine.add_animation(tree_star_)
|
||||
engine.add_animation(snow_sparkles_)
|
||||
engine.add_animation(garland_)
|
||||
engine.start()
|
||||
|
||||
@ -57,50 +57,29 @@ var engine = animation.init_strip()
|
||||
|
||||
var space_blue_ = 0xFF000066 # Note: opaque 0xFF alpha channel is implicitly added
|
||||
var background_ = animation.solid(engine)
|
||||
background_.color = animation.global('space_blue_', 'space_blue')
|
||||
background_.color = space_blue_
|
||||
# Main comet with bright white head
|
||||
var comet_main_ = animation.comet_animation(engine)
|
||||
comet_main_.color = 0xFFFFFFFF # White head
|
||||
comet_main_.tail_length = 10 # tail length
|
||||
comet_main_.speed = 2000 # speed
|
||||
animation.global('comet_main_').priority = 7
|
||||
comet_main_.priority = 7
|
||||
# Secondary comet in different color, opposite direction
|
||||
var comet_secondary_ = animation.comet_animation(engine)
|
||||
comet_secondary_.color = 0xFFFF4500 # Orange head
|
||||
comet_secondary_.tail_length = 8 # shorter tail
|
||||
comet_secondary_.speed = 3000 # slower speed
|
||||
comet_secondary_.direction = -1 # other direction
|
||||
animation.global('comet_secondary_').priority = 5
|
||||
comet_secondary_.direction = (-1) # other direction
|
||||
comet_secondary_.priority = 5
|
||||
# Add sparkle trail behind comets but on top of blue background
|
||||
var comet_sparkles_ = animation.twinkle_animation(engine)
|
||||
comet_sparkles_.color = 0xFFAAAAFF # Light blue sparkles
|
||||
comet_sparkles_.density = 8 # density (moderate sparkles)
|
||||
comet_sparkles_.twinkle_speed = 400 # twinkle speed (quick sparkle)
|
||||
animation.global('comet_sparkles_').priority = 8
|
||||
comet_sparkles_.priority = 8
|
||||
# Start all animations
|
||||
# Start all animations/sequences
|
||||
if global.contains('sequence_background')
|
||||
var seq_manager = global.sequence_background()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('background_'))
|
||||
end
|
||||
if global.contains('sequence_comet_main')
|
||||
var seq_manager = global.sequence_comet_main()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('comet_main_'))
|
||||
end
|
||||
if global.contains('sequence_comet_secondary')
|
||||
var seq_manager = global.sequence_comet_secondary()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('comet_secondary_'))
|
||||
end
|
||||
if global.contains('sequence_comet_sparkles')
|
||||
var seq_manager = global.sequence_comet_sparkles()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('comet_sparkles_'))
|
||||
end
|
||||
engine.add_animation(background_)
|
||||
engine.add_animation(comet_main_)
|
||||
engine.add_animation(comet_secondary_)
|
||||
engine.add_animation(comet_sparkles_)
|
||||
engine.start()
|
||||
|
||||
@ -0,0 +1,71 @@
|
||||
# Generated Berry code from Animation DSL
|
||||
# Source: computed_values_demo.anim
|
||||
# Generated automatically
|
||||
#
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
# Original DSL source:
|
||||
# # Computed Values Demo - Example from the original request
|
||||
# # Shows how to use computed values from value providers
|
||||
#
|
||||
# # Get the current strip length
|
||||
# set strip_len = strip_length()
|
||||
#
|
||||
# # Create animation with computed values
|
||||
# animation stream1 = comet_animation(
|
||||
# color=red
|
||||
# tail_length=abs(strip_len / 4) # computed value
|
||||
# speed=1.5
|
||||
# priority=10
|
||||
# )
|
||||
#
|
||||
# # More complex computed values
|
||||
# set base_speed = 2.0
|
||||
# animation stream2 = comet_animation(
|
||||
# color=blue
|
||||
# tail_length=strip_len / 8 + 2 # computed with addition
|
||||
# speed=base_speed * 1.5 # computed with multiplication
|
||||
# direction=-1
|
||||
# priority=5
|
||||
# )
|
||||
#
|
||||
# # Property assignment with computed values
|
||||
# stream1.tail_length = strip_len / 5
|
||||
# stream2.opacity = strip_len * 4
|
||||
#
|
||||
# # Run both animations
|
||||
# run stream1
|
||||
# run stream2
|
||||
|
||||
import animation
|
||||
|
||||
# Computed Values Demo - Example from the original request
|
||||
# Shows how to use computed values from value providers
|
||||
# Get the current strip length
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
var temp_strip_length_11 = animation.strip_length(engine)
|
||||
var strip_len_ = temp_strip_length_11
|
||||
# Create animation with computed values
|
||||
var stream1_ = animation.comet_animation(engine)
|
||||
stream1_.color = 0xFFFF0000
|
||||
stream1_.tail_length = animation.create_closure_value(engine, def (self, param_name, time_ms) return (self.abs(self.resolve(self.resolve(strip_len_, param_name, time_ms), param_name, time_ms) / 4)) end) # computed value
|
||||
stream1_.speed = 1.5
|
||||
stream1_.priority = 10
|
||||
# More complex computed values
|
||||
var base_speed_ = 2.0
|
||||
var stream2_ = animation.comet_animation(engine)
|
||||
stream2_.color = 0xFF0000FF
|
||||
stream2_.tail_length = animation.create_closure_value(engine, def (self, param_name, time_ms) return (self.resolve(strip_len_, param_name, time_ms) / 8 + 2) end) # computed with addition
|
||||
stream2_.speed = animation.create_closure_value(engine, def (self, param_name, time_ms) return (self.resolve(base_speed_, param_name, time_ms) * 1.5) end) # computed with multiplication
|
||||
stream2_.direction = (-1)
|
||||
stream2_.priority = 5
|
||||
# Property assignment with computed values
|
||||
stream1_.tail_length = animation.create_closure_value(engine, def (self, param_name, time_ms) return (self.resolve(strip_len_, param_name, time_ms) / 5) end)
|
||||
stream2_.opacity = animation.create_closure_value(engine, def (self, param_name, time_ms) return (self.resolve(strip_len_, param_name, time_ms) * 4) end)
|
||||
# Run both animations
|
||||
engine.add_animation(stream1_)
|
||||
engine.add_animation(stream2_)
|
||||
engine.start()
|
||||
@ -71,9 +71,9 @@ var engine = animation.init_strip()
|
||||
var disco_colors_ = bytes("00FF0000" "2AFF8000" "55FFFF00" "8000FF00" "AA0000FF" "D58000FF" "FFFF00FF")
|
||||
# Fast color cycling base
|
||||
var disco_base_ = animation.rich_palette_animation(engine)
|
||||
disco_base_.palette = animation.global('disco_colors_', 'disco_colors')
|
||||
disco_base_.palette = disco_colors_
|
||||
disco_base_.cycle_period = 1000
|
||||
disco_base_.transition_type = animation.global('LINEAR_', 'LINEAR')
|
||||
disco_base_.transition_type = animation.LINEAR
|
||||
disco_base_.brightness = 255
|
||||
# Add strobe effect
|
||||
var temp_square_105 = animation.square(engine)
|
||||
@ -81,7 +81,7 @@ temp_square_105.min_value = 0
|
||||
temp_square_105.max_value = 255
|
||||
temp_square_105.duration = 100
|
||||
temp_square_105.duty_cycle = 30
|
||||
animation.global('disco_base_').opacity = temp_square_105 # Fast strobe
|
||||
disco_base_.opacity = temp_square_105 # Fast strobe
|
||||
# Add white flash overlay
|
||||
var white_flash_ = animation.solid(engine)
|
||||
white_flash_.color = 0xFFFFFFFF
|
||||
@ -90,60 +90,39 @@ temp_square_142.min_value = 0
|
||||
temp_square_142.max_value = 255
|
||||
temp_square_142.duration = 50
|
||||
temp_square_142.duty_cycle = 10
|
||||
animation.global('white_flash_').opacity = temp_square_142 # Quick white flashes
|
||||
animation.global('white_flash_').priority = 20
|
||||
white_flash_.opacity = temp_square_142 # Quick white flashes
|
||||
white_flash_.priority = 20
|
||||
# Add colored sparkles
|
||||
var sparkle_pattern_ = animation.rich_palette(engine)
|
||||
sparkle_pattern_.palette = animation.global('disco_colors_', 'disco_colors')
|
||||
sparkle_pattern_.palette = disco_colors_
|
||||
sparkle_pattern_.cycle_period = 500
|
||||
sparkle_pattern_.transition_type = animation.global('LINEAR_', 'LINEAR')
|
||||
sparkle_pattern_.transition_type = animation.LINEAR
|
||||
sparkle_pattern_.brightness = 255
|
||||
var disco_sparkles_ = animation.twinkle_animation(engine)
|
||||
disco_sparkles_.color = animation.global('sparkle_pattern_', 'sparkle_pattern') # color source
|
||||
disco_sparkles_.color = sparkle_pattern_ # color source
|
||||
disco_sparkles_.density = 12 # density (many sparkles)
|
||||
disco_sparkles_.twinkle_speed = 80 # twinkle speed (very quick)
|
||||
animation.global('disco_sparkles_').priority = 15
|
||||
disco_sparkles_.priority = 15
|
||||
# Add moving pulse for extra effect
|
||||
var pulse_pattern_ = animation.rich_palette(engine)
|
||||
pulse_pattern_.palette = animation.global('disco_colors_', 'disco_colors')
|
||||
pulse_pattern_.palette = disco_colors_
|
||||
pulse_pattern_.cycle_period = 800
|
||||
pulse_pattern_.transition_type = animation.global('LINEAR_', 'LINEAR')
|
||||
pulse_pattern_.transition_type = animation.LINEAR
|
||||
pulse_pattern_.brightness = 255
|
||||
var disco_pulse_ = animation.beacon_animation(engine)
|
||||
disco_pulse_.color = animation.global('pulse_pattern_', 'pulse_pattern') # color source
|
||||
disco_pulse_.color = pulse_pattern_ # color source
|
||||
disco_pulse_.pos = 4 # initial position
|
||||
disco_pulse_.beacon_size = 8 # pulse width
|
||||
disco_pulse_.slew_size = 2 # sharp edges (slew size)
|
||||
animation.global('disco_pulse_').priority = 10
|
||||
disco_pulse_.priority = 10
|
||||
var temp_sawtooth_285 = animation.sawtooth(engine)
|
||||
temp_sawtooth_285.min_value = 4
|
||||
temp_sawtooth_285.max_value = 56
|
||||
temp_sawtooth_285.duration = 2000
|
||||
animation.global('disco_pulse_').pos = temp_sawtooth_285 # Fast movement
|
||||
disco_pulse_.pos = temp_sawtooth_285 # Fast movement
|
||||
# Start all animations
|
||||
# Start all animations/sequences
|
||||
if global.contains('sequence_disco_base')
|
||||
var seq_manager = global.sequence_disco_base()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('disco_base_'))
|
||||
end
|
||||
if global.contains('sequence_white_flash')
|
||||
var seq_manager = global.sequence_white_flash()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('white_flash_'))
|
||||
end
|
||||
if global.contains('sequence_disco_sparkles')
|
||||
var seq_manager = global.sequence_disco_sparkles()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('disco_sparkles_'))
|
||||
end
|
||||
if global.contains('sequence_disco_pulse')
|
||||
var seq_manager = global.sequence_disco_pulse()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('disco_pulse_'))
|
||||
end
|
||||
engine.add_animation(disco_base_)
|
||||
engine.add_animation(white_flash_)
|
||||
engine.add_animation(disco_sparkles_)
|
||||
engine.add_animation(disco_pulse_)
|
||||
engine.start()
|
||||
|
||||
@ -51,39 +51,28 @@ var engine = animation.init_strip()
|
||||
var fire_colors_ = bytes("00000000" "40800000" "80FF0000" "C0FF4500" "FFFFFF00")
|
||||
# Create base fire animation with palette
|
||||
var fire_base_ = animation.rich_palette_animation(engine)
|
||||
fire_base_.palette = animation.global('fire_colors_', 'fire_colors')
|
||||
fire_base_.palette = fire_colors_
|
||||
fire_base_.cycle_period = 3000
|
||||
fire_base_.transition_type = animation.global('LINEAR_', 'LINEAR')
|
||||
fire_base_.transition_type = animation.LINEAR
|
||||
fire_base_.brightness = 255
|
||||
# Add flickering effect with random intensity changes
|
||||
var temp_smooth_89 = animation.smooth(engine)
|
||||
temp_smooth_89.min_value = 180
|
||||
temp_smooth_89.max_value = 255
|
||||
temp_smooth_89.duration = 800
|
||||
animation.global('fire_base_').opacity = temp_smooth_89
|
||||
fire_base_.opacity = temp_smooth_89
|
||||
# Add subtle position variation for more realism
|
||||
var flicker_pattern_ = animation.rich_palette(engine)
|
||||
flicker_pattern_.palette = animation.global('fire_colors_', 'fire_colors')
|
||||
flicker_pattern_.palette = fire_colors_
|
||||
flicker_pattern_.cycle_period = 2000
|
||||
flicker_pattern_.transition_type = animation.global('LINEAR_', 'LINEAR')
|
||||
flicker_pattern_.transition_type = animation.LINEAR
|
||||
flicker_pattern_.brightness = 255
|
||||
var fire_flicker_ = animation.twinkle_animation(engine)
|
||||
fire_flicker_.color = animation.global('flicker_pattern_', 'flicker_pattern') # color source
|
||||
fire_flicker_.color = flicker_pattern_ # color source
|
||||
fire_flicker_.density = 12 # density (number of flickers)
|
||||
fire_flicker_.twinkle_speed = 200 # twinkle speed (flicker duration)
|
||||
animation.global('fire_flicker_').priority = 10
|
||||
fire_flicker_.priority = 10
|
||||
# Start both animations
|
||||
# Start all animations/sequences
|
||||
if global.contains('sequence_fire_base')
|
||||
var seq_manager = global.sequence_fire_base()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('fire_base_'))
|
||||
end
|
||||
if global.contains('sequence_fire_flicker')
|
||||
var seq_manager = global.sequence_fire_flicker()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('fire_flicker_'))
|
||||
end
|
||||
engine.add_animation(fire_base_)
|
||||
engine.add_animation(fire_flicker_)
|
||||
engine.start()
|
||||
|
||||
@ -60,7 +60,7 @@ var engine = animation.init_strip()
|
||||
|
||||
var heart_bg_ = 0xFF110000
|
||||
var background_ = animation.solid(engine)
|
||||
background_.color = animation.global('heart_bg_', 'heart_bg')
|
||||
background_.color = heart_bg_
|
||||
# Define heartbeat timing - double pulse animation
|
||||
# First pulse (stronger)
|
||||
var heartbeat1_ = animation.solid(engine)
|
||||
@ -71,8 +71,8 @@ temp_square_46.min_value = 0
|
||||
temp_square_46.max_value = 255
|
||||
temp_square_46.duration = 150
|
||||
temp_square_46.duty_cycle = 20
|
||||
animation.global('heartbeat1_').opacity = temp_square_46 # Quick strong pulse
|
||||
animation.global('heartbeat1_').priority = 10
|
||||
heartbeat1_.opacity = temp_square_46 # Quick strong pulse
|
||||
heartbeat1_.priority = 10
|
||||
# Second pulse (weaker, slightly delayed)
|
||||
var heartbeat2_ = animation.solid(engine)
|
||||
heartbeat2_.color = 0xFFCC0000
|
||||
@ -83,8 +83,8 @@ temp_square_92.min_value = 0
|
||||
temp_square_92.max_value = 180
|
||||
temp_square_92.duration = 150
|
||||
temp_square_92.duty_cycle = 15
|
||||
animation.global('heartbeat2_').opacity = temp_square_92 # Weaker pulse
|
||||
animation.global('heartbeat2_').priority = 8
|
||||
heartbeat2_.opacity = temp_square_92 # Weaker pulse
|
||||
heartbeat2_.priority = 8
|
||||
# Add subtle glow effect
|
||||
var heart_glow_ = animation.solid(engine)
|
||||
heart_glow_.color = 0xFF660000
|
||||
@ -93,51 +93,25 @@ var temp_smooth_136 = animation.smooth(engine)
|
||||
temp_smooth_136.min_value = 30
|
||||
temp_smooth_136.max_value = 100
|
||||
temp_smooth_136.duration = 1000
|
||||
animation.global('heart_glow_').opacity = temp_smooth_136 # Gentle breathing glow
|
||||
animation.global('heart_glow_').priority = 5
|
||||
heart_glow_.opacity = temp_smooth_136 # Gentle breathing glow
|
||||
heart_glow_.priority = 5
|
||||
# Add center pulse for emphasis
|
||||
var center_pulse_ = animation.beacon_animation(engine)
|
||||
center_pulse_.color = 0xFFFFFFFF # White center
|
||||
center_pulse_.pos = 30 # center of strip
|
||||
center_pulse_.beacon_size = 4 # small center
|
||||
center_pulse_.slew_size = 2 # soft edges
|
||||
animation.global('center_pulse_').priority = 20
|
||||
center_pulse_.priority = 20
|
||||
var temp_square_199 = animation.square(engine)
|
||||
temp_square_199.min_value = 0
|
||||
temp_square_199.max_value = 200
|
||||
temp_square_199.duration = 100
|
||||
temp_square_199.duty_cycle = 10
|
||||
animation.global('center_pulse_').opacity = temp_square_199 # Quick white flash
|
||||
center_pulse_.opacity = temp_square_199 # Quick white flash
|
||||
# Start all animations
|
||||
# Start all animations/sequences
|
||||
if global.contains('sequence_background')
|
||||
var seq_manager = global.sequence_background()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('background_'))
|
||||
end
|
||||
if global.contains('sequence_heart_glow')
|
||||
var seq_manager = global.sequence_heart_glow()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('heart_glow_'))
|
||||
end
|
||||
if global.contains('sequence_heartbeat1')
|
||||
var seq_manager = global.sequence_heartbeat1()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('heartbeat1_'))
|
||||
end
|
||||
if global.contains('sequence_heartbeat2')
|
||||
var seq_manager = global.sequence_heartbeat2()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('heartbeat2_'))
|
||||
end
|
||||
if global.contains('sequence_center_pulse')
|
||||
var seq_manager = global.sequence_center_pulse()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('center_pulse_'))
|
||||
end
|
||||
engine.add_animation(background_)
|
||||
engine.add_animation(heart_glow_)
|
||||
engine.add_animation(heartbeat1_)
|
||||
engine.add_animation(heartbeat2_)
|
||||
engine.add_animation(center_pulse_)
|
||||
engine.start()
|
||||
|
||||
@ -82,100 +82,74 @@ var engine = animation.init_strip()
|
||||
var lava_colors_ = bytes("00330000" "40660000" "80CC3300" "C0FF6600" "FFFFAA00")
|
||||
# Base lava animation - very slow color changes
|
||||
var lava_base_ = animation.rich_palette_animation(engine)
|
||||
lava_base_.palette = animation.global('lava_colors_', 'lava_colors')
|
||||
lava_base_.palette = lava_colors_
|
||||
lava_base_.cycle_period = 15000
|
||||
lava_base_.transition_type = animation.global('SINE_', 'SINE')
|
||||
lava_base_.transition_type = animation.SINE
|
||||
lava_base_.brightness = 180
|
||||
# Add slow-moving lava blobs
|
||||
var blob1_pattern_ = animation.rich_palette(engine)
|
||||
blob1_pattern_.palette = animation.global('lava_colors_', 'lava_colors')
|
||||
blob1_pattern_.palette = lava_colors_
|
||||
blob1_pattern_.cycle_period = 12000
|
||||
blob1_pattern_.transition_type = animation.global('SINE_', 'SINE')
|
||||
blob1_pattern_.transition_type = animation.SINE
|
||||
blob1_pattern_.brightness = 255
|
||||
var lava_blob1_ = animation.beacon_animation(engine)
|
||||
lava_blob1_.color = animation.global('blob1_pattern_', 'blob1_pattern') # color source
|
||||
lava_blob1_.color = blob1_pattern_ # color source
|
||||
lava_blob1_.pos = 9 # initial position
|
||||
lava_blob1_.beacon_size = 18 # large blob
|
||||
lava_blob1_.slew_size = 12 # very soft edges
|
||||
animation.global('lava_blob1_').priority = 10
|
||||
lava_blob1_.priority = 10
|
||||
var temp_smooth_145 = animation.smooth(engine)
|
||||
temp_smooth_145.min_value = 9
|
||||
temp_smooth_145.max_value = 51
|
||||
temp_smooth_145.duration = 20000
|
||||
animation.global('lava_blob1_').pos = temp_smooth_145 # Very slow movement
|
||||
lava_blob1_.pos = temp_smooth_145 # Very slow movement
|
||||
var blob2_pattern_ = animation.rich_palette(engine)
|
||||
blob2_pattern_.palette = animation.global('lava_colors_', 'lava_colors')
|
||||
blob2_pattern_.palette = lava_colors_
|
||||
blob2_pattern_.cycle_period = 10000
|
||||
blob2_pattern_.transition_type = animation.global('SINE_', 'SINE')
|
||||
blob2_pattern_.transition_type = animation.SINE
|
||||
blob2_pattern_.brightness = 220
|
||||
var lava_blob2_ = animation.beacon_animation(engine)
|
||||
lava_blob2_.color = animation.global('blob2_pattern_', 'blob2_pattern') # color source
|
||||
lava_blob2_.color = blob2_pattern_ # color source
|
||||
lava_blob2_.pos = 46 # initial position
|
||||
lava_blob2_.beacon_size = 14 # medium blob
|
||||
lava_blob2_.slew_size = 10 # soft edges
|
||||
animation.global('lava_blob2_').priority = 8
|
||||
lava_blob2_.priority = 8
|
||||
var temp_smooth_222 = animation.smooth(engine)
|
||||
temp_smooth_222.min_value = 46
|
||||
temp_smooth_222.max_value = 14
|
||||
temp_smooth_222.duration = 25000
|
||||
animation.global('lava_blob2_').pos = temp_smooth_222 # Opposite direction, slower
|
||||
lava_blob2_.pos = temp_smooth_222 # Opposite direction, slower
|
||||
var blob3_pattern_ = animation.rich_palette(engine)
|
||||
blob3_pattern_.palette = animation.global('lava_colors_', 'lava_colors')
|
||||
blob3_pattern_.palette = lava_colors_
|
||||
blob3_pattern_.cycle_period = 8000
|
||||
blob3_pattern_.transition_type = animation.global('SINE_', 'SINE')
|
||||
blob3_pattern_.transition_type = animation.SINE
|
||||
blob3_pattern_.brightness = 200
|
||||
var lava_blob3_ = animation.beacon_animation(engine)
|
||||
lava_blob3_.color = animation.global('blob3_pattern_', 'blob3_pattern') # color source
|
||||
lava_blob3_.color = blob3_pattern_ # color source
|
||||
lava_blob3_.pos = 25 # initial position
|
||||
lava_blob3_.beacon_size = 10 # smaller blob
|
||||
lava_blob3_.slew_size = 8 # soft edges
|
||||
animation.global('lava_blob3_').priority = 6
|
||||
lava_blob3_.priority = 6
|
||||
var temp_smooth_299 = animation.smooth(engine)
|
||||
temp_smooth_299.min_value = 25
|
||||
temp_smooth_299.max_value = 35
|
||||
temp_smooth_299.duration = 18000
|
||||
animation.global('lava_blob3_').pos = temp_smooth_299 # Small movement range
|
||||
lava_blob3_.pos = temp_smooth_299 # Small movement range
|
||||
# Add subtle heat shimmer effect
|
||||
var shimmer_pattern_ = animation.rich_palette(engine)
|
||||
shimmer_pattern_.palette = animation.global('lava_colors_', 'lava_colors')
|
||||
shimmer_pattern_.palette = lava_colors_
|
||||
shimmer_pattern_.cycle_period = 6000
|
||||
shimmer_pattern_.transition_type = animation.global('SINE_', 'SINE')
|
||||
shimmer_pattern_.transition_type = animation.SINE
|
||||
shimmer_pattern_.brightness = 255
|
||||
var heat_shimmer_ = animation.twinkle_animation(engine)
|
||||
heat_shimmer_.color = animation.global('shimmer_pattern_', 'shimmer_pattern') # color source
|
||||
heat_shimmer_.color = shimmer_pattern_ # color source
|
||||
heat_shimmer_.density = 6 # density (shimmer points)
|
||||
heat_shimmer_.twinkle_speed = 1500 # twinkle speed (slow shimmer)
|
||||
animation.global('heat_shimmer_').priority = 15
|
||||
heat_shimmer_.priority = 15
|
||||
# Start all animations
|
||||
# Start all animations/sequences
|
||||
if global.contains('sequence_lava_base')
|
||||
var seq_manager = global.sequence_lava_base()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('lava_base_'))
|
||||
end
|
||||
if global.contains('sequence_lava_blob1')
|
||||
var seq_manager = global.sequence_lava_blob1()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('lava_blob1_'))
|
||||
end
|
||||
if global.contains('sequence_lava_blob2')
|
||||
var seq_manager = global.sequence_lava_blob2()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('lava_blob2_'))
|
||||
end
|
||||
if global.contains('sequence_lava_blob3')
|
||||
var seq_manager = global.sequence_lava_blob3()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('lava_blob3_'))
|
||||
end
|
||||
if global.contains('sequence_heat_shimmer')
|
||||
var seq_manager = global.sequence_heat_shimmer()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('heat_shimmer_'))
|
||||
end
|
||||
engine.add_animation(lava_base_)
|
||||
engine.add_animation(lava_blob1_)
|
||||
engine.add_animation(lava_blob2_)
|
||||
engine.add_animation(lava_blob3_)
|
||||
engine.add_animation(heat_shimmer_)
|
||||
engine.start()
|
||||
|
||||
@ -66,9 +66,9 @@ var engine = animation.init_strip()
|
||||
|
||||
var storm_colors_ = bytes("00000011" "80110022" "FF220033")
|
||||
var storm_bg_ = animation.rich_palette_animation(engine)
|
||||
storm_bg_.palette = animation.global('storm_colors_', 'storm_colors')
|
||||
storm_bg_.palette = storm_colors_
|
||||
storm_bg_.cycle_period = 12000
|
||||
storm_bg_.transition_type = animation.global('SINE_', 'SINE')
|
||||
storm_bg_.transition_type = animation.SINE
|
||||
storm_bg_.brightness = 100
|
||||
# Random lightning flashes - full strip
|
||||
var lightning_main_ = animation.solid(engine)
|
||||
@ -79,21 +79,21 @@ temp_square_82.min_value = 0
|
||||
temp_square_82.max_value = 255
|
||||
temp_square_82.duration = 80
|
||||
temp_square_82.duty_cycle = 3
|
||||
animation.global('lightning_main_').opacity = temp_square_82 # Quick bright flashes
|
||||
animation.global('lightning_main_').priority = 20
|
||||
lightning_main_.opacity = temp_square_82 # Quick bright flashes
|
||||
lightning_main_.priority = 20
|
||||
# Secondary lightning - partial strip
|
||||
var lightning_partial_ = animation.beacon_animation(engine)
|
||||
lightning_partial_.color = 0xFFFFFFAA # Slightly yellow white
|
||||
lightning_partial_.pos = 30 # center position
|
||||
lightning_partial_.beacon_size = 20 # covers part of strip
|
||||
lightning_partial_.slew_size = 5 # soft edges
|
||||
animation.global('lightning_partial_').priority = 15
|
||||
lightning_partial_.priority = 15
|
||||
var temp_square_149 = animation.square(engine)
|
||||
temp_square_149.min_value = 0
|
||||
temp_square_149.max_value = 200
|
||||
temp_square_149.duration = 120
|
||||
temp_square_149.duty_cycle = 4
|
||||
animation.global('lightning_partial_').opacity = temp_square_149 # Different timing
|
||||
lightning_partial_.opacity = temp_square_149 # Different timing
|
||||
# Add blue afterglow
|
||||
var afterglow_ = animation.solid(engine)
|
||||
afterglow_.color = 0xFF4444FF
|
||||
@ -103,44 +103,18 @@ temp_square_187.min_value = 0
|
||||
temp_square_187.max_value = 80
|
||||
temp_square_187.duration = 200
|
||||
temp_square_187.duty_cycle = 8
|
||||
animation.global('afterglow_').opacity = temp_square_187 # Longer, dimmer glow
|
||||
animation.global('afterglow_').priority = 10
|
||||
afterglow_.opacity = temp_square_187 # Longer, dimmer glow
|
||||
afterglow_.priority = 10
|
||||
# Distant thunder (dim flashes)
|
||||
var distant_flash_ = animation.twinkle_animation(engine)
|
||||
distant_flash_.color = 0xFF666699 # Dim blue-white
|
||||
distant_flash_.density = 4 # density (few flashes)
|
||||
distant_flash_.twinkle_speed = 300 # twinkle speed (medium duration)
|
||||
animation.global('distant_flash_').priority = 5
|
||||
distant_flash_.priority = 5
|
||||
# Start all animations
|
||||
# Start all animations/sequences
|
||||
if global.contains('sequence_storm_bg')
|
||||
var seq_manager = global.sequence_storm_bg()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('storm_bg_'))
|
||||
end
|
||||
if global.contains('sequence_lightning_main')
|
||||
var seq_manager = global.sequence_lightning_main()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('lightning_main_'))
|
||||
end
|
||||
if global.contains('sequence_lightning_partial')
|
||||
var seq_manager = global.sequence_lightning_partial()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('lightning_partial_'))
|
||||
end
|
||||
if global.contains('sequence_afterglow')
|
||||
var seq_manager = global.sequence_afterglow()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('afterglow_'))
|
||||
end
|
||||
if global.contains('sequence_distant_flash')
|
||||
var seq_manager = global.sequence_distant_flash()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('distant_flash_'))
|
||||
end
|
||||
engine.add_animation(storm_bg_)
|
||||
engine.add_animation(lightning_main_)
|
||||
engine.add_animation(lightning_partial_)
|
||||
engine.add_animation(afterglow_)
|
||||
engine.add_animation(distant_flash_)
|
||||
engine.start()
|
||||
|
||||
@ -76,38 +76,38 @@ var engine = animation.init_strip()
|
||||
|
||||
var matrix_bg_ = 0xFF000000
|
||||
var background_ = animation.solid(engine)
|
||||
background_.color = animation.global('matrix_bg_', 'matrix_bg')
|
||||
background_.color = matrix_bg_
|
||||
background_.priority = 50
|
||||
# Define matrix green palette
|
||||
var matrix_greens_ = bytes("00000000" "40003300" "80006600" "C000AA00" "FF00FF00")
|
||||
# Create multiple cascading streams
|
||||
var stream1_pattern_ = animation.rich_palette(engine)
|
||||
stream1_pattern_.palette = animation.global('matrix_greens_', 'matrix_greens')
|
||||
stream1_pattern_.palette = matrix_greens_
|
||||
stream1_pattern_.cycle_period = 2000
|
||||
stream1_pattern_.transition_type = animation.global('LINEAR_', 'LINEAR')
|
||||
stream1_pattern_.transition_type = animation.LINEAR
|
||||
stream1_pattern_.brightness = 255
|
||||
var stream1_ = animation.comet_animation(engine)
|
||||
stream1_.color = animation.global('stream1_pattern_', 'stream1_pattern') # color source
|
||||
stream1_.color = stream1_pattern_ # color source
|
||||
stream1_.tail_length = 15 # long tail
|
||||
stream1_.speed = 1500 # speed
|
||||
stream1_.priority = 10
|
||||
var stream2_pattern_ = animation.rich_palette(engine)
|
||||
stream2_pattern_.palette = animation.global('matrix_greens_', 'matrix_greens')
|
||||
stream2_pattern_.palette = matrix_greens_
|
||||
stream2_pattern_.cycle_period = 1800
|
||||
stream2_pattern_.transition_type = animation.global('LINEAR_', 'LINEAR')
|
||||
stream2_pattern_.transition_type = animation.LINEAR
|
||||
stream2_pattern_.brightness = 200
|
||||
var stream2_ = animation.comet_animation(engine)
|
||||
stream2_.color = animation.global('stream2_pattern_', 'stream2_pattern') # color source
|
||||
stream2_.color = stream2_pattern_ # color source
|
||||
stream2_.tail_length = 12 # medium tail
|
||||
stream2_.speed = 2200 # different speed
|
||||
stream2_.priority = 8
|
||||
var stream3_pattern_ = animation.rich_palette(engine)
|
||||
stream3_pattern_.palette = animation.global('matrix_greens_', 'matrix_greens')
|
||||
stream3_pattern_.palette = matrix_greens_
|
||||
stream3_pattern_.cycle_period = 2500
|
||||
stream3_pattern_.transition_type = animation.global('LINEAR_', 'LINEAR')
|
||||
stream3_pattern_.transition_type = animation.LINEAR
|
||||
stream3_pattern_.brightness = 180
|
||||
var stream3_ = animation.comet_animation(engine)
|
||||
stream3_.color = animation.global('stream3_pattern_', 'stream3_pattern') # color source
|
||||
stream3_.color = stream3_pattern_ # color source
|
||||
stream3_.tail_length = 10 # shorter tail
|
||||
stream3_.speed = 1800 # another speed
|
||||
stream3_.priority = 6
|
||||
@ -118,35 +118,9 @@ code_flash_.density = 3 # density (few flashes)
|
||||
code_flash_.twinkle_speed = 150 # twinkle speed (quick flash)
|
||||
code_flash_.priority = 20
|
||||
# Start all animations
|
||||
# Start all animations/sequences
|
||||
if global.contains('sequence_background')
|
||||
var seq_manager = global.sequence_background()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('background_'))
|
||||
end
|
||||
if global.contains('sequence_stream1')
|
||||
var seq_manager = global.sequence_stream1()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('stream1_'))
|
||||
end
|
||||
if global.contains('sequence_stream2')
|
||||
var seq_manager = global.sequence_stream2()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('stream2_'))
|
||||
end
|
||||
if global.contains('sequence_stream3')
|
||||
var seq_manager = global.sequence_stream3()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('stream3_'))
|
||||
end
|
||||
if global.contains('sequence_code_flash')
|
||||
var seq_manager = global.sequence_code_flash()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('code_flash_'))
|
||||
end
|
||||
engine.add_animation(background_)
|
||||
engine.add_animation(stream1_)
|
||||
engine.add_animation(stream2_)
|
||||
engine.add_animation(stream3_)
|
||||
engine.add_animation(code_flash_)
|
||||
engine.start()
|
||||
|
||||
@ -80,82 +80,46 @@ var engine = animation.init_strip()
|
||||
|
||||
var space_bg_ = 0xFF000011
|
||||
var background_ = animation.solid(engine)
|
||||
background_.color = animation.global('space_bg_', 'space_bg')
|
||||
background_.color = space_bg_
|
||||
# Multiple meteors with different speeds and colors
|
||||
var meteor1_ = animation.comet_animation(engine)
|
||||
meteor1_.color = 0xFFFFFFFF # Bright white
|
||||
meteor1_.tail_length = 12 # long trail
|
||||
meteor1_.speed = 1500 # fast speed
|
||||
animation.global('meteor1_').priority = 15
|
||||
meteor1_.priority = 15
|
||||
var meteor2_ = animation.comet_animation(engine)
|
||||
meteor2_.color = 0xFFFFAA00 # Orange
|
||||
meteor2_.tail_length = 10 # medium trail
|
||||
meteor2_.speed = 2000 # medium speed
|
||||
animation.global('meteor2_').priority = 12
|
||||
meteor2_.priority = 12
|
||||
var meteor3_ = animation.comet_animation(engine)
|
||||
meteor3_.color = 0xFFAAAAFF # Blue-white
|
||||
meteor3_.tail_length = 8 # shorter trail
|
||||
meteor3_.speed = 1800 # fast speed
|
||||
animation.global('meteor3_').priority = 10
|
||||
meteor3_.priority = 10
|
||||
var meteor4_ = animation.comet_animation(engine)
|
||||
meteor4_.color = 0xFFFFAAAA # Pink-white
|
||||
meteor4_.tail_length = 14 # long trail
|
||||
meteor4_.speed = 2500 # slower speed
|
||||
animation.global('meteor4_').priority = 8
|
||||
meteor4_.priority = 8
|
||||
# Add distant stars
|
||||
var stars_ = animation.twinkle_animation(engine)
|
||||
stars_.color = 0xFFCCCCCC # Dim white
|
||||
stars_.density = 12 # density (many stars)
|
||||
stars_.twinkle_speed = 2000 # twinkle speed (slow twinkle)
|
||||
animation.global('stars_').priority = 5
|
||||
stars_.priority = 5
|
||||
# Add occasional bright flash (meteor explosion)
|
||||
var meteor_flash_ = animation.twinkle_animation(engine)
|
||||
meteor_flash_.color = 0xFFFFFFFF # Bright white
|
||||
meteor_flash_.density = 1 # density (single flash)
|
||||
meteor_flash_.twinkle_speed = 100 # twinkle speed (very quick)
|
||||
animation.global('meteor_flash_').priority = 25
|
||||
meteor_flash_.priority = 25
|
||||
# Start all animations
|
||||
# Start all animations/sequences
|
||||
if global.contains('sequence_background')
|
||||
var seq_manager = global.sequence_background()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('background_'))
|
||||
end
|
||||
if global.contains('sequence_stars')
|
||||
var seq_manager = global.sequence_stars()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('stars_'))
|
||||
end
|
||||
if global.contains('sequence_meteor1')
|
||||
var seq_manager = global.sequence_meteor1()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('meteor1_'))
|
||||
end
|
||||
if global.contains('sequence_meteor2')
|
||||
var seq_manager = global.sequence_meteor2()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('meteor2_'))
|
||||
end
|
||||
if global.contains('sequence_meteor3')
|
||||
var seq_manager = global.sequence_meteor3()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('meteor3_'))
|
||||
end
|
||||
if global.contains('sequence_meteor4')
|
||||
var seq_manager = global.sequence_meteor4()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('meteor4_'))
|
||||
end
|
||||
if global.contains('sequence_meteor_flash')
|
||||
var seq_manager = global.sequence_meteor_flash()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('meteor_flash_'))
|
||||
end
|
||||
engine.add_animation(background_)
|
||||
engine.add_animation(stars_)
|
||||
engine.add_animation(meteor1_)
|
||||
engine.add_animation(meteor2_)
|
||||
engine.add_animation(meteor3_)
|
||||
engine.add_animation(meteor4_)
|
||||
engine.add_animation(meteor_flash_)
|
||||
engine.start()
|
||||
|
||||
@ -84,16 +84,16 @@ var engine = animation.init_strip()
|
||||
var neon_colors_ = bytes("00FF0080" "5500FF80" "AA8000FF" "FFFF8000")
|
||||
# Main neon glow with color cycling
|
||||
var neon_main_ = animation.rich_palette_animation(engine)
|
||||
neon_main_.palette = animation.global('neon_colors_', 'neon_colors')
|
||||
neon_main_.palette = neon_colors_
|
||||
neon_main_.cycle_period = 4000
|
||||
neon_main_.transition_type = animation.global('LINEAR_', 'LINEAR')
|
||||
neon_main_.transition_type = animation.LINEAR
|
||||
neon_main_.brightness = 255
|
||||
# Add electrical flickering
|
||||
var temp_smooth_81 = animation.smooth(engine)
|
||||
temp_smooth_81.min_value = 220
|
||||
temp_smooth_81.max_value = 255
|
||||
temp_smooth_81.duration = 200
|
||||
animation.global('neon_main_').opacity = temp_smooth_81
|
||||
neon_main_.opacity = temp_smooth_81
|
||||
# Add occasional electrical surge
|
||||
var neon_surge_ = animation.solid(engine)
|
||||
neon_surge_.color = 0xFFFFFFFF
|
||||
@ -103,74 +103,43 @@ temp_square_114.min_value = 0
|
||||
temp_square_114.max_value = 255
|
||||
temp_square_114.duration = 50
|
||||
temp_square_114.duty_cycle = 2
|
||||
animation.global('neon_surge_').opacity = temp_square_114 # Quick bright surges
|
||||
animation.global('neon_surge_').priority = 20
|
||||
neon_surge_.opacity = temp_square_114 # Quick bright surges
|
||||
neon_surge_.priority = 20
|
||||
# Add neon tube segments with gaps
|
||||
var segment_pattern_ = animation.rich_palette(engine)
|
||||
segment_pattern_.palette = animation.global('neon_colors_', 'neon_colors')
|
||||
segment_pattern_.palette = neon_colors_
|
||||
segment_pattern_.cycle_period = 4000
|
||||
segment_pattern_.transition_type = animation.global('LINEAR_', 'LINEAR')
|
||||
segment_pattern_.transition_type = animation.LINEAR
|
||||
segment_pattern_.brightness = 255
|
||||
var segment1_ = animation.beacon_animation(engine)
|
||||
segment1_.color = animation.global('segment_pattern_', 'segment_pattern') # color source
|
||||
segment1_.color = segment_pattern_ # color source
|
||||
segment1_.pos = 6 # position
|
||||
segment1_.beacon_size = 12 # segment length
|
||||
segment1_.slew_size = 1 # sharp edges
|
||||
animation.global('segment1_').priority = 10
|
||||
segment1_.priority = 10
|
||||
var segment2_ = animation.beacon_animation(engine)
|
||||
segment2_.color = animation.global('segment_pattern_', 'segment_pattern') # color source
|
||||
segment2_.color = segment_pattern_ # color source
|
||||
segment2_.pos = 24 # position
|
||||
segment2_.beacon_size = 12 # segment length
|
||||
segment2_.slew_size = 1 # sharp edges
|
||||
animation.global('segment2_').priority = 10
|
||||
segment2_.priority = 10
|
||||
var segment3_ = animation.beacon_animation(engine)
|
||||
segment3_.color = animation.global('segment_pattern_', 'segment_pattern') # color source
|
||||
segment3_.color = segment_pattern_ # color source
|
||||
segment3_.pos = 42 # position
|
||||
segment3_.beacon_size = 12 # segment length
|
||||
segment3_.slew_size = 1 # sharp edges
|
||||
animation.global('segment3_').priority = 10
|
||||
segment3_.priority = 10
|
||||
# Add electrical arcing between segments
|
||||
var arc_sparkles_ = animation.twinkle_animation(engine)
|
||||
arc_sparkles_.color = 0xFFAAAAFF # Electric blue
|
||||
arc_sparkles_.density = 4 # density (few arcs)
|
||||
arc_sparkles_.twinkle_speed = 100 # twinkle speed (quick arcs)
|
||||
animation.global('arc_sparkles_').priority = 15
|
||||
arc_sparkles_.priority = 15
|
||||
# Start all animations
|
||||
# Start all animations/sequences
|
||||
if global.contains('sequence_neon_main')
|
||||
var seq_manager = global.sequence_neon_main()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('neon_main_'))
|
||||
end
|
||||
if global.contains('sequence_neon_surge')
|
||||
var seq_manager = global.sequence_neon_surge()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('neon_surge_'))
|
||||
end
|
||||
if global.contains('sequence_segment1')
|
||||
var seq_manager = global.sequence_segment1()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('segment1_'))
|
||||
end
|
||||
if global.contains('sequence_segment2')
|
||||
var seq_manager = global.sequence_segment2()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('segment2_'))
|
||||
end
|
||||
if global.contains('sequence_segment3')
|
||||
var seq_manager = global.sequence_segment3()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('segment3_'))
|
||||
end
|
||||
if global.contains('sequence_arc_sparkles')
|
||||
var seq_manager = global.sequence_arc_sparkles()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('arc_sparkles_'))
|
||||
end
|
||||
engine.add_animation(neon_main_)
|
||||
engine.add_animation(neon_surge_)
|
||||
engine.add_animation(segment1_)
|
||||
engine.add_animation(segment2_)
|
||||
engine.add_animation(segment3_)
|
||||
engine.add_animation(arc_sparkles_)
|
||||
engine.start()
|
||||
|
||||
@ -70,73 +70,52 @@ var engine = animation.init_strip()
|
||||
var ocean_colors_ = bytes("00000080" "400040C0" "800080FF" "C040C0FF" "FF80FFFF")
|
||||
# Base ocean animation with slow color cycling
|
||||
var ocean_base_ = animation.rich_palette_animation(engine)
|
||||
ocean_base_.palette = animation.global('ocean_colors_', 'ocean_colors')
|
||||
ocean_base_.palette = ocean_colors_
|
||||
ocean_base_.cycle_period = 8000
|
||||
ocean_base_.transition_type = animation.global('SINE_', 'SINE')
|
||||
ocean_base_.transition_type = animation.SINE
|
||||
ocean_base_.brightness = 200
|
||||
# Add wave motion with moving pulses
|
||||
var wave1_pattern_ = animation.rich_palette(engine)
|
||||
wave1_pattern_.palette = animation.global('ocean_colors_', 'ocean_colors')
|
||||
wave1_pattern_.palette = ocean_colors_
|
||||
wave1_pattern_.cycle_period = 6000
|
||||
wave1_pattern_.transition_type = animation.global('SINE_', 'SINE')
|
||||
wave1_pattern_.transition_type = animation.SINE
|
||||
wave1_pattern_.brightness = 255
|
||||
var wave1_ = animation.beacon_animation(engine)
|
||||
wave1_.color = animation.global('wave1_pattern_', 'wave1_pattern') # color source
|
||||
wave1_.color = wave1_pattern_ # color source
|
||||
wave1_.pos = 0 # initial position
|
||||
wave1_.beacon_size = 12 # wave width
|
||||
wave1_.slew_size = 6 # soft edges
|
||||
animation.global('wave1_').priority = 10
|
||||
wave1_.priority = 10
|
||||
var temp_sawtooth_145 = animation.sawtooth(engine)
|
||||
temp_sawtooth_145.min_value = 0
|
||||
temp_sawtooth_145.max_value = 48
|
||||
temp_sawtooth_145.duration = 5000
|
||||
animation.global('wave1_').pos = temp_sawtooth_145 # 60-12 = 48
|
||||
wave1_.pos = temp_sawtooth_145 # 60-12 = 48
|
||||
var wave2_pattern_ = animation.rich_palette(engine)
|
||||
wave2_pattern_.palette = animation.global('ocean_colors_', 'ocean_colors')
|
||||
wave2_pattern_.palette = ocean_colors_
|
||||
wave2_pattern_.cycle_period = 4000
|
||||
wave2_pattern_.transition_type = animation.global('SINE_', 'SINE')
|
||||
wave2_pattern_.transition_type = animation.SINE
|
||||
wave2_pattern_.brightness = 180
|
||||
var wave2_ = animation.beacon_animation(engine)
|
||||
wave2_.color = animation.global('wave2_pattern_', 'wave2_pattern') # color source
|
||||
wave2_.color = wave2_pattern_ # color source
|
||||
wave2_.pos = 52 # initial position
|
||||
wave2_.beacon_size = 8 # smaller wave
|
||||
wave2_.slew_size = 4 # soft edges
|
||||
animation.global('wave2_').priority = 8
|
||||
wave2_.priority = 8
|
||||
var temp_sawtooth_222 = animation.sawtooth(engine)
|
||||
temp_sawtooth_222.min_value = 52
|
||||
temp_sawtooth_222.max_value = 8
|
||||
temp_sawtooth_222.duration = 7000
|
||||
animation.global('wave2_').pos = temp_sawtooth_222 # Opposite direction
|
||||
wave2_.pos = temp_sawtooth_222 # Opposite direction
|
||||
# Add foam sparkles
|
||||
var foam_ = animation.twinkle_animation(engine)
|
||||
foam_.color = 0xFFFFFFFF # White foam
|
||||
foam_.density = 6 # density (sparkle count)
|
||||
foam_.twinkle_speed = 300 # twinkle speed (quick sparkles)
|
||||
animation.global('foam_').priority = 15
|
||||
foam_.priority = 15
|
||||
# Start all animations
|
||||
# Start all animations/sequences
|
||||
if global.contains('sequence_ocean_base')
|
||||
var seq_manager = global.sequence_ocean_base()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('ocean_base_'))
|
||||
end
|
||||
if global.contains('sequence_wave1')
|
||||
var seq_manager = global.sequence_wave1()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('wave1_'))
|
||||
end
|
||||
if global.contains('sequence_wave2')
|
||||
var seq_manager = global.sequence_wave2()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('wave2_'))
|
||||
end
|
||||
if global.contains('sequence_foam')
|
||||
var seq_manager = global.sequence_foam()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('foam_'))
|
||||
end
|
||||
engine.add_animation(ocean_base_)
|
||||
engine.add_animation(wave1_)
|
||||
engine.add_animation(wave2_)
|
||||
engine.add_animation(foam_)
|
||||
engine.start()
|
||||
|
||||
@ -61,13 +61,13 @@ var fire_colors_ = bytes("00000000" "40800000" "80FF0000" "C0FF8000" "FFFFFF00")
|
||||
var ocean_colors_ = bytes("00000080" "400000FF" "8000FFFF" "C000FF80" "FF008000")
|
||||
# Create animations using the palettes
|
||||
var fire_anim_ = animation.rich_palette_animation(engine)
|
||||
fire_anim_.palette = animation.global('fire_colors_', 'fire_colors')
|
||||
fire_anim_.palette = fire_colors_
|
||||
fire_anim_.cycle_period = 5000
|
||||
var ocean_anim_ = animation.rich_palette_animation(engine)
|
||||
ocean_anim_.palette = animation.global('ocean_colors_', 'ocean_colors')
|
||||
ocean_anim_.palette = ocean_colors_
|
||||
ocean_anim_.cycle_period = 8000
|
||||
# Sequence to show both palettes
|
||||
def sequence_palette_demo()
|
||||
var palette_demo_ = (def (engine)
|
||||
var steps = []
|
||||
steps.push(animation.create_play_step(animation.global('fire_anim_'), 10000))
|
||||
steps.push(animation.create_wait_step(1000))
|
||||
@ -80,12 +80,6 @@ def sequence_palette_demo()
|
||||
var seq_manager = animation.SequenceManager(engine)
|
||||
seq_manager.start_sequence(steps)
|
||||
return seq_manager
|
||||
end
|
||||
# Start all animations/sequences
|
||||
if global.contains('sequence_palette_demo')
|
||||
var seq_manager = global.sequence_palette_demo()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('palette_demo_'))
|
||||
end
|
||||
end)(engine)
|
||||
engine.add_sequence_manager(palette_demo_)
|
||||
engine.start()
|
||||
|
||||
@ -106,25 +106,25 @@ var aurora_borealis_ = bytes("00000022" "40004400" "8000AA44" "C044AA88" "FF88FF
|
||||
var sunset_sky_ = bytes("00191970" "40800080" "80FF69B4" "C0FFA500" "FFFFFF00")
|
||||
# Create animations using each palette
|
||||
var fire_effect_ = animation.rich_palette_animation(engine)
|
||||
fire_effect_.palette = animation.global('fire_gradient_', 'fire_gradient')
|
||||
fire_effect_.palette = fire_gradient_
|
||||
fire_effect_.cycle_period = 3000
|
||||
var ocean_waves_ = animation.rich_palette_animation(engine)
|
||||
ocean_waves_.palette = animation.global('ocean_depths_', 'ocean_depths')
|
||||
ocean_waves_.palette = ocean_depths_
|
||||
ocean_waves_.cycle_period = 8000
|
||||
ocean_waves_.transition_type = animation.global('SINE_', 'SINE')
|
||||
ocean_waves_.transition_type = animation.SINE
|
||||
ocean_waves_.brightness = 200
|
||||
var aurora_lights_ = animation.rich_palette_animation(engine)
|
||||
aurora_lights_.palette = animation.global('aurora_borealis_', 'aurora_borealis')
|
||||
aurora_lights_.palette = aurora_borealis_
|
||||
aurora_lights_.cycle_period = 12000
|
||||
aurora_lights_.transition_type = animation.global('SINE_', 'SINE')
|
||||
aurora_lights_.transition_type = animation.SINE
|
||||
aurora_lights_.brightness = 180
|
||||
var sunset_glow_ = animation.rich_palette_animation(engine)
|
||||
sunset_glow_.palette = animation.global('sunset_sky_', 'sunset_sky')
|
||||
sunset_glow_.palette = sunset_sky_
|
||||
sunset_glow_.cycle_period = 6000
|
||||
sunset_glow_.transition_type = animation.global('SINE_', 'SINE')
|
||||
sunset_glow_.transition_type = animation.SINE
|
||||
sunset_glow_.brightness = 220
|
||||
# Sequence to showcase all palettes
|
||||
def sequence_palette_showcase()
|
||||
var palette_showcase_ = (def (engine)
|
||||
var steps = []
|
||||
# Fire effect
|
||||
steps.push(animation.create_play_step(animation.global('fire_effect_'), 8000))
|
||||
@ -148,12 +148,6 @@ def sequence_palette_showcase()
|
||||
var seq_manager = animation.SequenceManager(engine)
|
||||
seq_manager.start_sequence(steps)
|
||||
return seq_manager
|
||||
end
|
||||
# Start all animations/sequences
|
||||
if global.contains('sequence_palette_showcase')
|
||||
var seq_manager = global.sequence_palette_showcase()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('palette_showcase_'))
|
||||
end
|
||||
end)(engine)
|
||||
engine.add_sequence_manager(palette_showcase_)
|
||||
engine.start()
|
||||
|
||||
@ -76,89 +76,68 @@ var engine = animation.init_strip()
|
||||
var plasma_colors_ = bytes("00FF0080" "33FF8000" "66FFFF00" "9980FF00" "CC00FF80" "FF0080FF")
|
||||
# Base plasma animation with medium speed
|
||||
var plasma_base_ = animation.rich_palette_animation(engine)
|
||||
plasma_base_.palette = animation.global('plasma_colors_', 'plasma_colors')
|
||||
plasma_base_.palette = plasma_colors_
|
||||
plasma_base_.cycle_period = 6000
|
||||
plasma_base_.transition_type = animation.global('SINE_', 'SINE')
|
||||
plasma_base_.transition_type = animation.SINE
|
||||
plasma_base_.brightness = 200
|
||||
# Add multiple wave layers for complexity
|
||||
var wave1_pattern_ = animation.rich_palette(engine)
|
||||
wave1_pattern_.palette = animation.global('plasma_colors_', 'plasma_colors')
|
||||
wave1_pattern_.palette = plasma_colors_
|
||||
wave1_pattern_.cycle_period = 4000
|
||||
wave1_pattern_.transition_type = animation.global('SINE_', 'SINE')
|
||||
wave1_pattern_.transition_type = animation.SINE
|
||||
wave1_pattern_.brightness = 255
|
||||
var plasma_wave1_ = animation.beacon_animation(engine)
|
||||
plasma_wave1_.color = animation.global('wave1_pattern_', 'wave1_pattern') # color source
|
||||
plasma_wave1_.color = wave1_pattern_ # color source
|
||||
plasma_wave1_.pos = 0 # initial position
|
||||
plasma_wave1_.beacon_size = 20 # wide wave
|
||||
plasma_wave1_.slew_size = 10 # very smooth
|
||||
animation.global('plasma_wave1_').priority = 10
|
||||
plasma_wave1_.priority = 10
|
||||
var temp_smooth_153 = animation.smooth(engine)
|
||||
temp_smooth_153.min_value = 0
|
||||
temp_smooth_153.max_value = 40
|
||||
temp_smooth_153.duration = 8000
|
||||
animation.global('plasma_wave1_').pos = temp_smooth_153
|
||||
plasma_wave1_.pos = temp_smooth_153
|
||||
var wave2_pattern_ = animation.rich_palette(engine)
|
||||
wave2_pattern_.palette = animation.global('plasma_colors_', 'plasma_colors')
|
||||
wave2_pattern_.palette = plasma_colors_
|
||||
wave2_pattern_.cycle_period = 5000
|
||||
wave2_pattern_.transition_type = animation.global('SINE_', 'SINE')
|
||||
wave2_pattern_.transition_type = animation.SINE
|
||||
wave2_pattern_.brightness = 180
|
||||
var plasma_wave2_ = animation.beacon_animation(engine)
|
||||
plasma_wave2_.color = animation.global('wave2_pattern_', 'wave2_pattern') # color source
|
||||
plasma_wave2_.color = wave2_pattern_ # color source
|
||||
plasma_wave2_.pos = 45 # initial position
|
||||
plasma_wave2_.beacon_size = 15 # medium wave
|
||||
plasma_wave2_.slew_size = 8 # smooth
|
||||
animation.global('plasma_wave2_').priority = 8
|
||||
plasma_wave2_.priority = 8
|
||||
var temp_smooth_229 = animation.smooth(engine)
|
||||
temp_smooth_229.min_value = 45
|
||||
temp_smooth_229.max_value = 15
|
||||
temp_smooth_229.duration = 10000
|
||||
animation.global('plasma_wave2_').pos = temp_smooth_229 # Opposite direction
|
||||
plasma_wave2_.pos = temp_smooth_229 # Opposite direction
|
||||
var wave3_pattern_ = animation.rich_palette(engine)
|
||||
wave3_pattern_.palette = animation.global('plasma_colors_', 'plasma_colors')
|
||||
wave3_pattern_.palette = plasma_colors_
|
||||
wave3_pattern_.cycle_period = 3000
|
||||
wave3_pattern_.transition_type = animation.global('SINE_', 'SINE')
|
||||
wave3_pattern_.transition_type = animation.SINE
|
||||
wave3_pattern_.brightness = 220
|
||||
var plasma_wave3_ = animation.beacon_animation(engine)
|
||||
plasma_wave3_.color = animation.global('wave3_pattern_', 'wave3_pattern') # color source
|
||||
plasma_wave3_.color = wave3_pattern_ # color source
|
||||
plasma_wave3_.pos = 20 # initial position
|
||||
plasma_wave3_.beacon_size = 12 # smaller wave
|
||||
plasma_wave3_.slew_size = 6 # smooth
|
||||
animation.global('plasma_wave3_').priority = 12
|
||||
plasma_wave3_.priority = 12
|
||||
var temp_smooth_306 = animation.smooth(engine)
|
||||
temp_smooth_306.min_value = 20
|
||||
temp_smooth_306.max_value = 50
|
||||
temp_smooth_306.duration = 6000
|
||||
animation.global('plasma_wave3_').pos = temp_smooth_306 # Different speed
|
||||
plasma_wave3_.pos = temp_smooth_306 # Different speed
|
||||
# Add subtle intensity variation
|
||||
var temp_smooth_329 = animation.smooth(engine)
|
||||
temp_smooth_329.min_value = 150
|
||||
temp_smooth_329.max_value = 255
|
||||
temp_smooth_329.duration = 12000
|
||||
animation.global('plasma_base_').opacity = temp_smooth_329
|
||||
plasma_base_.opacity = temp_smooth_329
|
||||
# Start all animations
|
||||
# Start all animations/sequences
|
||||
if global.contains('sequence_plasma_base')
|
||||
var seq_manager = global.sequence_plasma_base()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('plasma_base_'))
|
||||
end
|
||||
if global.contains('sequence_plasma_wave1')
|
||||
var seq_manager = global.sequence_plasma_wave1()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('plasma_wave1_'))
|
||||
end
|
||||
if global.contains('sequence_plasma_wave2')
|
||||
var seq_manager = global.sequence_plasma_wave2()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('plasma_wave2_'))
|
||||
end
|
||||
if global.contains('sequence_plasma_wave3')
|
||||
var seq_manager = global.sequence_plasma_wave3()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('plasma_wave3_'))
|
||||
end
|
||||
engine.add_animation(plasma_base_)
|
||||
engine.add_animation(plasma_wave1_)
|
||||
engine.add_animation(plasma_wave2_)
|
||||
engine.add_animation(plasma_wave3_)
|
||||
engine.start()
|
||||
|
||||
@ -60,26 +60,26 @@ left_red_.color = 0xFFFF0000 # Bright red
|
||||
left_red_.pos = 15 # center of left half
|
||||
left_red_.beacon_size = 15 # half the strip
|
||||
left_red_.slew_size = 2 # sharp edges
|
||||
animation.global('left_red_').priority = 10
|
||||
left_red_.priority = 10
|
||||
var temp_square_57 = animation.square(engine)
|
||||
temp_square_57.min_value = 0
|
||||
temp_square_57.max_value = 255
|
||||
temp_square_57.duration = 400
|
||||
temp_square_57.duty_cycle = 50
|
||||
animation.global('left_red_').opacity = temp_square_57 # 50% duty cycle
|
||||
left_red_.opacity = temp_square_57 # 50% duty cycle
|
||||
# Right side blue flashing (opposite phase)
|
||||
var right_blue_ = animation.beacon_animation(engine)
|
||||
right_blue_.color = 0xFF0000FF # Bright blue
|
||||
right_blue_.pos = 45 # center of right half
|
||||
right_blue_.beacon_size = 15 # half the strip
|
||||
right_blue_.slew_size = 2 # sharp edges
|
||||
animation.global('right_blue_').priority = 10
|
||||
right_blue_.priority = 10
|
||||
var temp_square_118 = animation.square(engine)
|
||||
temp_square_118.min_value = 255
|
||||
temp_square_118.max_value = 0
|
||||
temp_square_118.duration = 400
|
||||
temp_square_118.duty_cycle = 50
|
||||
animation.global('right_blue_').opacity = temp_square_118 # Opposite phase
|
||||
right_blue_.opacity = temp_square_118 # Opposite phase
|
||||
# Add white strobe overlay occasionally
|
||||
var white_strobe_ = animation.solid(engine)
|
||||
white_strobe_.color = 0xFFFFFFFF
|
||||
@ -88,26 +88,10 @@ temp_square_155.min_value = 0
|
||||
temp_square_155.max_value = 255
|
||||
temp_square_155.duration = 100
|
||||
temp_square_155.duty_cycle = 5
|
||||
animation.global('white_strobe_').opacity = temp_square_155 # Quick bright flashes
|
||||
animation.global('white_strobe_').priority = 20
|
||||
white_strobe_.opacity = temp_square_155 # Quick bright flashes
|
||||
white_strobe_.priority = 20
|
||||
# Start all animations
|
||||
# Start all animations/sequences
|
||||
if global.contains('sequence_left_red')
|
||||
var seq_manager = global.sequence_left_red()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('left_red_'))
|
||||
end
|
||||
if global.contains('sequence_right_blue')
|
||||
var seq_manager = global.sequence_right_blue()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('right_blue_'))
|
||||
end
|
||||
if global.contains('sequence_white_strobe')
|
||||
var seq_manager = global.sequence_white_strobe()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('white_strobe_'))
|
||||
end
|
||||
engine.add_animation(left_red_)
|
||||
engine.add_animation(right_blue_)
|
||||
engine.add_animation(white_strobe_)
|
||||
engine.start()
|
||||
|
||||
@ -64,30 +64,30 @@ var blue_custom_ = 0xFF0000FF
|
||||
var green_custom_ = 0xFF00FF00
|
||||
# Create animations
|
||||
var left_pulse_ = animation.beacon_animation(engine)
|
||||
left_pulse_.color = animation.global('red_custom_', 'red_custom')
|
||||
left_pulse_.color = red_custom_
|
||||
left_pulse_.pos = 15
|
||||
left_pulse_.beacon_size = 15
|
||||
left_pulse_.slew_size = 3
|
||||
var center_pulse_ = animation.beacon_animation(engine)
|
||||
center_pulse_.color = animation.global('blue_custom_', 'blue_custom')
|
||||
center_pulse_.color = blue_custom_
|
||||
center_pulse_.pos = 30
|
||||
center_pulse_.beacon_size = 15
|
||||
center_pulse_.slew_size = 3
|
||||
var right_pulse_ = animation.beacon_animation(engine)
|
||||
right_pulse_.color = animation.global('green_custom_', 'green_custom')
|
||||
right_pulse_.color = green_custom_
|
||||
right_pulse_.pos = 45
|
||||
right_pulse_.beacon_size = 15
|
||||
right_pulse_.slew_size = 3
|
||||
# Set different opacities
|
||||
animation.global('left_pulse_').opacity = 255 # Full slew_size
|
||||
animation.global('center_pulse_').opacity = 200 # Slightly dimmed
|
||||
animation.global('right_pulse_').opacity = 150 # More dimmed
|
||||
left_pulse_.opacity = 255 # Full slew_size
|
||||
center_pulse_.opacity = 200 # Slightly dimmed
|
||||
right_pulse_.opacity = 150 # More dimmed
|
||||
# Set priorities (higher numbers have priority)
|
||||
animation.global('left_pulse_').priority = 10
|
||||
animation.global('center_pulse_').priority = 15 # Center has highest priority
|
||||
animation.global('right_pulse_').priority = 5
|
||||
left_pulse_.priority = 10
|
||||
center_pulse_.priority = 15 # Center has highest priority
|
||||
right_pulse_.priority = 5
|
||||
# Create a sequence that shows all three
|
||||
def sequence_demo()
|
||||
var demo_ = (def (engine)
|
||||
var steps = []
|
||||
steps.push(animation.create_play_step(animation.global('left_pulse_'), 3000))
|
||||
steps.push(animation.create_wait_step(500))
|
||||
@ -105,12 +105,6 @@ def sequence_demo()
|
||||
var seq_manager = animation.SequenceManager(engine)
|
||||
seq_manager.start_sequence(steps)
|
||||
return seq_manager
|
||||
end
|
||||
# Start all animations/sequences
|
||||
if global.contains('sequence_demo')
|
||||
var seq_manager = global.sequence_demo()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('demo_'))
|
||||
end
|
||||
end)(engine)
|
||||
engine.add_sequence_manager(demo_)
|
||||
engine.start()
|
||||
|
||||
@ -34,13 +34,7 @@ var rainbow_cycle_ = animation.color_cycle(engine)
|
||||
rainbow_cycle_.palette = [0xFFFF0000, 0xFFFF8000, 0xFFFFFF00, 0xFF00FF00, 0xFF0000FF, 0xFF8000FF, 0xFFFF00FF] # rainbow colors
|
||||
rainbow_cycle_.cycle_period = 5000 # cycle period
|
||||
var rainbow_animation_ = animation.solid(engine)
|
||||
rainbow_animation_.color = animation.global('rainbow_cycle_', 'rainbow_cycle')
|
||||
rainbow_animation_.color = rainbow_cycle_
|
||||
# Start the animation
|
||||
# Start all animations/sequences
|
||||
if global.contains('sequence_rainbow_animation')
|
||||
var seq_manager = global.sequence_rainbow_animation()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('rainbow_animation_'))
|
||||
end
|
||||
engine.add_animation(rainbow_animation_)
|
||||
engine.start()
|
||||
|
||||
@ -55,52 +55,36 @@ var engine = animation.init_strip()
|
||||
|
||||
var scanner_bg_ = 0xFF110000
|
||||
var background_ = animation.solid(engine)
|
||||
background_.color = animation.global('scanner_bg_', 'scanner_bg')
|
||||
background_.color = scanner_bg_
|
||||
# Main scanner pulse that bounces
|
||||
var scanner_ = animation.beacon_animation(engine)
|
||||
scanner_.color = 0xFFFF0000 # Bright red
|
||||
scanner_.pos = 2 # initial position
|
||||
scanner_.beacon_size = 3 # pulse width
|
||||
scanner_.slew_size = 2 # fade region
|
||||
animation.global('scanner_').priority = 10
|
||||
scanner_.priority = 10
|
||||
# Bouncing position from left to right and back
|
||||
var temp_triangle_70 = animation.triangle(engine)
|
||||
temp_triangle_70.min_value = 2
|
||||
temp_triangle_70.max_value = 57
|
||||
temp_triangle_70.duration = 2000
|
||||
animation.global('scanner_').pos = temp_triangle_70
|
||||
scanner_.pos = temp_triangle_70
|
||||
# Add trailing glow effect
|
||||
var scanner_trail_ = animation.beacon_animation(engine)
|
||||
scanner_trail_.color = 0xFF660000 # Dim red trail
|
||||
scanner_trail_.pos = 2 # initial position
|
||||
scanner_trail_.beacon_size = 6 # wider trail
|
||||
scanner_trail_.slew_size = 4 # more fade
|
||||
animation.global('scanner_trail_').priority = 5
|
||||
scanner_trail_.priority = 5
|
||||
var temp_triangle_125 = animation.triangle(engine)
|
||||
temp_triangle_125.min_value = 2
|
||||
temp_triangle_125.max_value = 57
|
||||
temp_triangle_125.duration = 2000
|
||||
var pos_test_ = temp_triangle_125
|
||||
animation.global('scanner_trail_').pos = animation.global('pos_test_', 'pos_test')
|
||||
animation.global('scanner_trail_').opacity = 128 # Half brightness
|
||||
scanner_trail_.pos = pos_test_
|
||||
scanner_trail_.opacity = 128 # Half brightness
|
||||
# Start all animations
|
||||
# Start all animations/sequences
|
||||
if global.contains('sequence_background')
|
||||
var seq_manager = global.sequence_background()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('background_'))
|
||||
end
|
||||
if global.contains('sequence_scanner_trail')
|
||||
var seq_manager = global.sequence_scanner_trail()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('scanner_trail_'))
|
||||
end
|
||||
if global.contains('sequence_scanner')
|
||||
var seq_manager = global.sequence_scanner()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('scanner_'))
|
||||
end
|
||||
engine.add_animation(background_)
|
||||
engine.add_animation(scanner_trail_)
|
||||
engine.add_animation(scanner_)
|
||||
engine.start()
|
||||
|
||||
@ -42,21 +42,15 @@ var engine = animation.init_strip()
|
||||
var rainbow_ = bytes("00FF0000" "40FFA500" "80FFFF00" "C0008000" "FF0000FF")
|
||||
# Create an animation using the palette
|
||||
var rainbow_cycle_ = animation.rich_palette_animation(engine)
|
||||
rainbow_cycle_.palette = animation.global('rainbow_', 'rainbow')
|
||||
rainbow_cycle_.palette = rainbow_
|
||||
rainbow_cycle_.cycle_period = 3000
|
||||
# Simple sequence
|
||||
def sequence_demo()
|
||||
var demo_ = (def (engine)
|
||||
var steps = []
|
||||
steps.push(animation.create_play_step(animation.global('rainbow_cycle_'), 15000))
|
||||
var seq_manager = animation.SequenceManager(engine)
|
||||
seq_manager.start_sequence(steps)
|
||||
return seq_manager
|
||||
end
|
||||
# Start all animations/sequences
|
||||
if global.contains('sequence_demo')
|
||||
var seq_manager = global.sequence_demo()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('demo_'))
|
||||
end
|
||||
end)(engine)
|
||||
engine.add_sequence_manager(demo_)
|
||||
engine.start()
|
||||
|
||||
@ -76,7 +76,7 @@ var engine = animation.init_strip()
|
||||
var daylight_colors_ = bytes("00000011" "20001133" "40FF4400" "60FFAA00" "80FFFF88" "A0FFAA44" "C0FF6600" "E0AA2200" "FF220011")
|
||||
# Main daylight cycle - very slow transition
|
||||
var daylight_cycle_ = animation.rich_palette_animation(engine)
|
||||
daylight_cycle_.palette = animation.global('daylight_colors_', 'daylight_colors')
|
||||
daylight_cycle_.palette = daylight_colors_
|
||||
daylight_cycle_.cycle_period = 60000
|
||||
# Add sun position effect - bright spot that moves
|
||||
var sun_position_ = animation.beacon_animation(engine)
|
||||
@ -84,69 +84,48 @@ sun_position_.color = 0xFFFFFFAA # Bright yellow sun
|
||||
sun_position_.pos = 5 # initial position
|
||||
sun_position_.beacon_size = 8 # sun size
|
||||
sun_position_.slew_size = 4 # soft glow
|
||||
animation.global('sun_position_').priority = 10
|
||||
sun_position_.priority = 10
|
||||
var temp_smooth_147 = animation.smooth(engine)
|
||||
temp_smooth_147.min_value = 5
|
||||
temp_smooth_147.max_value = 55
|
||||
temp_smooth_147.duration = 30000
|
||||
animation.global('sun_position_').pos = temp_smooth_147 # Sun arc across sky
|
||||
sun_position_.pos = temp_smooth_147 # Sun arc across sky
|
||||
var temp_smooth_167 = animation.smooth(engine)
|
||||
temp_smooth_167.min_value = 0
|
||||
temp_smooth_167.max_value = 255
|
||||
temp_smooth_167.duration = 30000
|
||||
animation.global('sun_position_').opacity = temp_smooth_167 # Fade in and out
|
||||
sun_position_.opacity = temp_smooth_167 # Fade in and out
|
||||
# Add atmospheric glow around sun
|
||||
var sun_glow_ = animation.beacon_animation(engine)
|
||||
sun_glow_.color = 0xFFFFCC88 # Warm glow
|
||||
sun_glow_.pos = 5 # initial position
|
||||
sun_glow_.beacon_size = 16 # larger glow
|
||||
sun_glow_.slew_size = 8 # very soft
|
||||
animation.global('sun_glow_').priority = 5
|
||||
sun_glow_.priority = 5
|
||||
var temp_smooth_224 = animation.smooth(engine)
|
||||
temp_smooth_224.min_value = 5
|
||||
temp_smooth_224.max_value = 55
|
||||
temp_smooth_224.duration = 30000
|
||||
animation.global('sun_glow_').pos = temp_smooth_224 # Follow sun
|
||||
sun_glow_.pos = temp_smooth_224 # Follow sun
|
||||
var temp_smooth_244 = animation.smooth(engine)
|
||||
temp_smooth_244.min_value = 0
|
||||
temp_smooth_244.max_value = 150
|
||||
temp_smooth_244.duration = 30000
|
||||
animation.global('sun_glow_').opacity = temp_smooth_244 # Dimmer glow
|
||||
sun_glow_.opacity = temp_smooth_244 # Dimmer glow
|
||||
# Add twinkling stars during night phases
|
||||
var stars_ = animation.twinkle_animation(engine)
|
||||
stars_.color = 0xFFFFFFFF # White stars
|
||||
stars_.density = 6 # density (star count)
|
||||
stars_.twinkle_speed = 1000 # twinkle speed (slow twinkle)
|
||||
animation.global('stars_').priority = 15
|
||||
stars_.priority = 15
|
||||
var temp_smooth_296 = animation.smooth(engine)
|
||||
temp_smooth_296.min_value = 255
|
||||
temp_smooth_296.max_value = 0
|
||||
temp_smooth_296.duration = 30000
|
||||
animation.global('stars_').opacity = temp_smooth_296 # Fade out during day
|
||||
stars_.opacity = temp_smooth_296 # Fade out during day
|
||||
# Start all animations
|
||||
# Start all animations/sequences
|
||||
if global.contains('sequence_daylight_cycle')
|
||||
var seq_manager = global.sequence_daylight_cycle()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('daylight_cycle_'))
|
||||
end
|
||||
if global.contains('sequence_sun_position')
|
||||
var seq_manager = global.sequence_sun_position()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('sun_position_'))
|
||||
end
|
||||
if global.contains('sequence_sun_glow')
|
||||
var seq_manager = global.sequence_sun_glow()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('sun_glow_'))
|
||||
end
|
||||
if global.contains('sequence_stars')
|
||||
var seq_manager = global.sequence_stars()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('stars_'))
|
||||
end
|
||||
engine.add_animation(daylight_cycle_)
|
||||
engine.add_animation(sun_position_)
|
||||
engine.add_animation(sun_glow_)
|
||||
engine.add_animation(stars_)
|
||||
engine.start()
|
||||
|
||||
@ -47,37 +47,21 @@ var engine = animation.init_strip()
|
||||
|
||||
var night_sky_ = 0xFF000033
|
||||
var background_ = animation.solid(engine)
|
||||
background_.color = animation.global('night_sky_', 'night_sky')
|
||||
background_.color = night_sky_
|
||||
# White twinkling stars
|
||||
var stars_ = animation.twinkle_animation(engine)
|
||||
stars_.color = 0xFFFFFFFF # White stars
|
||||
stars_.density = 8 # density (number of stars)
|
||||
stars_.twinkle_speed = 500 # twinkle speed (twinkle duration)
|
||||
animation.global('stars_').priority = 10
|
||||
stars_.priority = 10
|
||||
# Add occasional bright flash
|
||||
var bright_flash_ = animation.twinkle_animation(engine)
|
||||
bright_flash_.color = 0xFFFFFFAA # Bright yellow-white
|
||||
bright_flash_.density = 2 # density (fewer bright flashes)
|
||||
bright_flash_.twinkle_speed = 300 # twinkle speed (quick flash)
|
||||
animation.global('bright_flash_').priority = 15
|
||||
bright_flash_.priority = 15
|
||||
# Start all animations
|
||||
# Start all animations/sequences
|
||||
if global.contains('sequence_background')
|
||||
var seq_manager = global.sequence_background()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('background_'))
|
||||
end
|
||||
if global.contains('sequence_stars')
|
||||
var seq_manager = global.sequence_stars()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('stars_'))
|
||||
end
|
||||
if global.contains('sequence_bright_flash')
|
||||
var seq_manager = global.sequence_bright_flash()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('bright_flash_'))
|
||||
end
|
||||
engine.add_animation(background_)
|
||||
engine.add_animation(stars_)
|
||||
engine.add_animation(bright_flash_)
|
||||
engine.start()
|
||||
|
||||
@ -0,0 +1,108 @@
|
||||
# Generated Berry code from Animation DSL
|
||||
# Source: user_functions_demo.anim
|
||||
# Generated automatically
|
||||
#
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
# Original DSL source:
|
||||
# # User Functions Demo - Advanced Computed Parameters
|
||||
# # Shows how to use user functions in computed parameters via property assignments
|
||||
#
|
||||
# # Get the current strip length for calculations
|
||||
# set strip_len = strip_length()
|
||||
#
|
||||
# # Example 1: Simple user function in computed parameter
|
||||
# animation random_base = solid(
|
||||
# color=blue
|
||||
# priority=10
|
||||
# )
|
||||
# # Use user function in property assignment
|
||||
# random_base.opacity = rand_demo()
|
||||
#
|
||||
# # Example 2: User function with mathematical operations
|
||||
# animation random_bounded = solid(
|
||||
# color=orange
|
||||
# priority=8
|
||||
# )
|
||||
# # User function with bounds using math functions
|
||||
# random_bounded.opacity = max(50, min(255, rand_demo() + 100))
|
||||
#
|
||||
# # Example 3: User function in arithmetic expressions
|
||||
# animation random_variation = solid(
|
||||
# color=purple
|
||||
# priority=15
|
||||
# )
|
||||
# # Mix user function with arithmetic operations
|
||||
# random_variation.opacity = abs(rand_demo() - 128) + 64
|
||||
#
|
||||
# # Example 4: User function affecting different properties
|
||||
# animation random_multi = solid(
|
||||
# color=cyan
|
||||
# priority=12
|
||||
# )
|
||||
# # Use user function for multiple properties
|
||||
# random_multi.opacity = max(100, rand_demo())
|
||||
#
|
||||
# # Example 5: Complex expression with user function
|
||||
# animation random_complex = solid(
|
||||
# color=white
|
||||
# priority=20
|
||||
# )
|
||||
# # Complex expression with user function and math operations
|
||||
# random_complex.opacity = round((rand_demo() + 128) / 2 + abs(rand_demo() - 100))
|
||||
#
|
||||
# # Run all animations to demonstrate the effects
|
||||
# run random_base
|
||||
# run random_bounded
|
||||
# run random_variation
|
||||
# run random_multi
|
||||
# run random_complex
|
||||
|
||||
import animation
|
||||
|
||||
# User Functions Demo - Advanced Computed Parameters
|
||||
# Shows how to use user functions in computed parameters via property assignments
|
||||
# Get the current strip length for calculations
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
var temp_strip_length_11 = animation.strip_length(engine)
|
||||
var strip_len_ = temp_strip_length_11
|
||||
# Example 1: Simple user function in computed parameter
|
||||
var random_base_ = animation.solid(engine)
|
||||
random_base_.color = 0xFF0000FF
|
||||
random_base_.priority = 10
|
||||
# Use user function in property assignment
|
||||
random_base_.opacity = animation.create_closure_value(engine, def (self, param_name, time_ms) return (animation.get_user_function('rand_demo')(self.engine)) end)
|
||||
# Example 2: User function with mathematical operations
|
||||
var random_bounded_ = animation.solid(engine)
|
||||
random_bounded_.color = 0xFFFFA500
|
||||
random_bounded_.priority = 8
|
||||
# User function with bounds using math functions
|
||||
random_bounded_.opacity = animation.create_closure_value(engine, def (self, param_name, time_ms) return (self.max(50, self.min(255, animation.get_user_function('rand_demo')(self.engine) + 100))) end)
|
||||
# Example 3: User function in arithmetic expressions
|
||||
var random_variation_ = animation.solid(engine)
|
||||
random_variation_.color = 0xFF800080
|
||||
random_variation_.priority = 15
|
||||
# Mix user function with arithmetic operations
|
||||
random_variation_.opacity = animation.create_closure_value(engine, def (self, param_name, time_ms) return (self.abs(animation.get_user_function('rand_demo')(self.engine) - 128) + 64) end)
|
||||
# Example 4: User function affecting different properties
|
||||
var random_multi_ = animation.solid(engine)
|
||||
random_multi_.color = 0xFF00FFFF
|
||||
random_multi_.priority = 12
|
||||
# Use user function for multiple properties
|
||||
random_multi_.opacity = animation.create_closure_value(engine, def (self, param_name, time_ms) return (self.max(100, animation.get_user_function('rand_demo')(self.engine))) end)
|
||||
# Example 5: Complex expression with user function
|
||||
var random_complex_ = animation.solid(engine)
|
||||
random_complex_.color = 0xFFFFFFFF
|
||||
random_complex_.priority = 20
|
||||
# Complex expression with user function and math operations
|
||||
random_complex_.opacity = animation.create_closure_value(engine, def (self, param_name, time_ms) return (self.round((animation.get_user_function('rand_demo')(self.engine) + 128) / 2 + self.abs(animation.get_user_function('rand_demo')(self.engine) - 100))) end)
|
||||
# Run all animations to demonstrate the effects
|
||||
engine.add_animation(random_base_)
|
||||
engine.add_animation(random_bounded_)
|
||||
engine.add_animation(random_variation_)
|
||||
engine.add_animation(random_multi_)
|
||||
engine.add_animation(random_complex_)
|
||||
engine.start()
|
||||
@ -0,0 +1,31 @@
|
||||
# Computed Values Demo - Example from the original request
|
||||
# Shows how to use computed values from value providers
|
||||
|
||||
# Get the current strip length
|
||||
set strip_len = strip_length()
|
||||
|
||||
# Create animation with computed values
|
||||
animation stream1 = comet_animation(
|
||||
color=red
|
||||
tail_length=abs(strip_len / 4) # computed value
|
||||
speed=1.5
|
||||
priority=10
|
||||
)
|
||||
|
||||
# More complex computed values
|
||||
set base_speed = 2.0
|
||||
animation stream2 = comet_animation(
|
||||
color=blue
|
||||
tail_length=strip_len / 8 + 2 # computed with addition
|
||||
speed=base_speed * 1.5 # computed with multiplication
|
||||
direction=-1
|
||||
priority=5
|
||||
)
|
||||
|
||||
# Property assignment with computed values
|
||||
stream1.tail_length = strip_len / 5
|
||||
stream2.opacity = strip_len * 4
|
||||
|
||||
# Run both animations
|
||||
run stream1
|
||||
run stream2
|
||||
@ -0,0 +1,52 @@
|
||||
# User Functions Demo - Advanced Computed Parameters
|
||||
# Shows how to use user functions in computed parameters via property assignments
|
||||
|
||||
# Get the current strip length for calculations
|
||||
set strip_len = strip_length()
|
||||
|
||||
# Example 1: Simple user function in computed parameter
|
||||
animation random_base = solid(
|
||||
color=blue
|
||||
priority=10
|
||||
)
|
||||
# Use user function in property assignment
|
||||
random_base.opacity = rand_demo()
|
||||
|
||||
# Example 2: User function with mathematical operations
|
||||
animation random_bounded = solid(
|
||||
color=orange
|
||||
priority=8
|
||||
)
|
||||
# User function with bounds using math functions
|
||||
random_bounded.opacity = max(50, min(255, rand_demo() + 100))
|
||||
|
||||
# Example 3: User function in arithmetic expressions
|
||||
animation random_variation = solid(
|
||||
color=purple
|
||||
priority=15
|
||||
)
|
||||
# Mix user function with arithmetic operations
|
||||
random_variation.opacity = abs(rand_demo() - 128) + 64
|
||||
|
||||
# Example 4: User function affecting different properties
|
||||
animation random_multi = solid(
|
||||
color=cyan
|
||||
priority=12
|
||||
)
|
||||
# Use user function for multiple properties
|
||||
random_multi.opacity = max(100, rand_demo())
|
||||
|
||||
# Example 5: Complex expression with user function
|
||||
animation random_complex = solid(
|
||||
color=white
|
||||
priority=20
|
||||
)
|
||||
# Complex expression with user function and math operations
|
||||
random_complex.opacity = round((rand_demo() + 128) / 2 + abs(rand_demo() - 100))
|
||||
|
||||
# Run all animations to demonstrate the effects
|
||||
run random_base
|
||||
run random_bounded
|
||||
run random_variation
|
||||
run random_multi
|
||||
run random_complex
|
||||
@ -42,6 +42,7 @@ ParameterizedObject
|
||||
├── StaticValueProvider
|
||||
├── StripLengthProvider
|
||||
├── OscillatorValueProvider
|
||||
├── ClosureValueProvider (internal use only)
|
||||
└── ColorProvider
|
||||
├── StaticColorProvider
|
||||
├── ColorCycleColorProvider
|
||||
@ -144,6 +145,61 @@ Generates oscillating values using various waveforms. Inherits from `ValueProvid
|
||||
|
||||
**See Also**: [Oscillation Patterns](OSCILLATION_PATTERNS.md) - Visual examples and usage patterns for oscillation waveforms
|
||||
|
||||
### ClosureValueProvider
|
||||
|
||||
**⚠️ INTERNAL USE ONLY - NOT FOR DIRECT USE**
|
||||
|
||||
Wraps a closure/function as a value provider for internal transpiler use. This class is used internally by the DSL transpiler to handle computed values and should not be used directly by users.
|
||||
|
||||
| Parameter | Type | Default | Constraints | Description |
|
||||
|-----------|------|---------|-------------|-------------|
|
||||
| `closure` | function | nil | - | The closure function to call for value generation |
|
||||
|
||||
**Internal Usage**: This provider is automatically created by the DSL transpiler when it encounters computed expressions or arithmetic operations involving value providers. The closure is called with `(self, param_name, time_ms)` parameters.
|
||||
|
||||
#### Mathematical Helper Methods
|
||||
|
||||
The ClosureValueProvider includes built-in mathematical helper methods that can be used within closures for computed values:
|
||||
|
||||
| Method | Description | Parameters | Return Type | Example |
|
||||
|--------|-------------|------------|-------------|---------|
|
||||
| `min(a, b, ...)` | Minimum of two or more values | `a, b, *args: number` | `number` | `self.min(5, 3, 8)` → `3` |
|
||||
| `max(a, b, ...)` | Maximum of two or more values | `a, b, *args: number` | `number` | `self.max(5, 3, 8)` → `8` |
|
||||
| `abs(x)` | Absolute value | `x: number` | `number` | `self.abs(-5)` → `5` |
|
||||
| `round(x)` | Round to nearest integer | `x: number` | `int` | `self.round(3.7)` → `4` |
|
||||
| `sqrt(x)` | Square root with integer handling | `x: number` | `number` | `self.sqrt(64)` → `128` (for 0-255 range) |
|
||||
| `scale(v, from_min, from_max, to_min, to_max)` | Scale value between ranges | `v, from_min, from_max, to_min, to_max: number` | `int` | `self.scale(50, 0, 100, 0, 255)` → `127` |
|
||||
| `sine(angle)` | Sine function (0-255 input range) | `angle: number` | `int` | `self.sine(64)` → `255` (90°) |
|
||||
| `cosine(angle)` | Cosine function (0-255 input range) | `angle: number` | `int` | `self.cosine(0)` → `-255` (matches oscillator behavior) |
|
||||
|
||||
**Mathematical Method Notes:**
|
||||
|
||||
- **Integer Handling**: `sqrt()` treats integers in 0-255 range as normalized values (255 = 1.0)
|
||||
- **Angle Range**: `sine()` and `cosine()` use 0-255 input range (0-360 degrees)
|
||||
- **Output Range**: Trigonometric functions return -255 to 255 (mapped from -1.0 to 1.0)
|
||||
- **Cosine Behavior**: Matches oscillator COSINE waveform (starts at minimum, not maximum)
|
||||
- **Scale Function**: Uses `tasmota.scale_int()` for efficient integer scaling
|
||||
|
||||
#### Usage in Computed Values
|
||||
|
||||
These methods are automatically available in DSL computed expressions:
|
||||
|
||||
```dsl
|
||||
# Example: Dynamic brightness based on strip position
|
||||
set strip_len = strip_length()
|
||||
animation pulse = pulsating_animation(
|
||||
color=red
|
||||
brightness=strip_len / 4 + 50 # Uses built-in arithmetic
|
||||
)
|
||||
|
||||
# Complex mathematical expressions are automatically wrapped in closures
|
||||
# that have access to all mathematical helper methods
|
||||
```
|
||||
|
||||
**Factory**: `animation.closure_value(engine)` (internal use only)
|
||||
|
||||
**Note**: Users should not create ClosureValueProvider instances directly. Instead, use the DSL's computed value syntax which automatically creates these providers as needed.
|
||||
|
||||
## Color Providers
|
||||
|
||||
Color providers generate dynamic colors over time, extending ValueProvider for color-specific functionality.
|
||||
|
||||
@ -202,6 +202,9 @@ set opacity_level = 80% # Static percentage (converted to 204)
|
||||
# Value providers for dynamic values
|
||||
set brightness_osc = smooth(min_value=50, max_value=255, period=3s)
|
||||
set position_sweep = triangle(min_value=0, max_value=29, period=5s)
|
||||
|
||||
# Computed values using strip length
|
||||
set strip_len = strip_length() # Get current strip length
|
||||
```
|
||||
|
||||
## Color Definitions
|
||||
@ -327,6 +330,11 @@ pulse_red.position = 15
|
||||
# Dynamic properties using value providers
|
||||
pulse_red.position = triangle(min_value=0, max_value=29, period=5s)
|
||||
pulse_red.opacity = smooth(min_value=100, max_value=255, period=2s)
|
||||
|
||||
# Computed properties using arithmetic expressions
|
||||
set strip_len = strip_length()
|
||||
pulse_red.position = strip_len / 2 # Center position
|
||||
pulse_red.opacity = strip_len * 4 # Scale with strip size
|
||||
```
|
||||
|
||||
**Common Properties:**
|
||||
@ -336,6 +344,151 @@ pulse_red.opacity = smooth(min_value=100, max_value=255, period=2s)
|
||||
- `speed` - Speed multiplier
|
||||
- `phase` - Phase offset
|
||||
|
||||
## Computed Values
|
||||
|
||||
The DSL supports computed values using arithmetic expressions with value providers and mathematical functions:
|
||||
|
||||
```dsl
|
||||
# Get strip dimensions
|
||||
set strip_len = strip_length()
|
||||
|
||||
# Use computed values in animation parameters
|
||||
animation stream1 = comet_animation(
|
||||
color=red
|
||||
tail_length=strip_len / 4 # Computed: quarter of strip length
|
||||
speed=1.5
|
||||
priority=10
|
||||
)
|
||||
|
||||
# Complex expressions with multiple operations
|
||||
set base_speed = 2.0
|
||||
animation stream2 = comet_animation(
|
||||
color=blue
|
||||
tail_length=strip_len / 8 + 2 # Computed: eighth of strip + 2
|
||||
speed=base_speed * 1.5 # Computed: base speed × 1.5
|
||||
)
|
||||
|
||||
# Computed values in property assignments
|
||||
stream1.position = strip_len / 2 # Center of strip
|
||||
stream2.opacity = strip_len * 4 # Scale opacity with strip size
|
||||
|
||||
# Using mathematical functions in computed values
|
||||
animation pulse = pulsating_animation(
|
||||
color=red
|
||||
period=2s
|
||||
)
|
||||
pulse.opacity = abs(sine(strip_len) * 128 + 127) # Sine wave opacity
|
||||
pulse.position = max(0, min(strip_len - 1, round(strip_len / 2))) # Clamped center position
|
||||
```
|
||||
|
||||
**Supported Operations:**
|
||||
- Addition: `+`
|
||||
- Subtraction: `-`
|
||||
- Multiplication: `*`
|
||||
- Division: `/`
|
||||
- Parentheses for grouping: `(expression)`
|
||||
|
||||
**Mathematical Functions:**
|
||||
The following mathematical functions are available in computed parameters and are automatically detected by the transpiler:
|
||||
|
||||
| Function | Description | Parameters | Return Value |
|
||||
|----------|-------------|------------|--------------|
|
||||
| `min(a, b, ...)` | Returns the minimum value | Two or more numbers | Minimum value |
|
||||
| `max(a, b, ...)` | Returns the maximum value | Two or more numbers | Maximum value |
|
||||
| `abs(x)` | Returns the absolute value | One number | Absolute value |
|
||||
| `round(x)` | Rounds to nearest integer | One number | Rounded integer |
|
||||
| `sqrt(x)` | Returns the square root | One number | Square root (scaled for integers) |
|
||||
| `scale(v, from_min, from_max, to_min, to_max)` | Scales value from one range to another | Value and range parameters | Scaled integer |
|
||||
| `sine(angle)` | Returns sine of angle | Angle in 0-255 range (0-360°) | Sine value in -255 to 255 range |
|
||||
| `cosine(angle)` | Returns cosine of angle | Angle in 0-255 range (0-360°) | Cosine value in -255 to 255 range |
|
||||
|
||||
**Mathematical Function Examples:**
|
||||
```dsl
|
||||
# Basic math functions
|
||||
set strip_len = strip_length()
|
||||
animation test = pulsating_animation(color=red, period=2s)
|
||||
|
||||
# Absolute value for ensuring positive results
|
||||
test.opacity = abs(strip_len - 200)
|
||||
|
||||
# Min/max for clamping values
|
||||
test.position = max(0, min(strip_len - 1, 15)) # Clamp position to valid range
|
||||
|
||||
# Rounding for integer positions
|
||||
test.position = round(strip_len / 2.5)
|
||||
|
||||
# Square root for non-linear scaling
|
||||
test.brightness = sqrt(strip_len * 4) # Non-linear brightness based on strip size
|
||||
|
||||
# Scaling values between ranges
|
||||
test.opacity = scale(strip_len, 10, 60, 50, 255) # Scale strip length to opacity range
|
||||
|
||||
# Trigonometric functions for wave patterns
|
||||
set angle = 128 # 180 degrees in 0-255 range
|
||||
test.opacity = sine(angle) + 128 # Sine wave shifted to positive range
|
||||
test.brightness = cosine(angle) + 128 # Cosine wave shifted to positive range
|
||||
|
||||
# Complex expressions combining multiple functions
|
||||
test.position = max(0, round(abs(sine(strip_len * 2)) * (strip_len - 1) / 255))
|
||||
test.opacity = min(255, max(50, scale(sqrt(strip_len), 0, 16, 100, 255)))
|
||||
```
|
||||
|
||||
**Special Notes:**
|
||||
- **Integer Optimization**: `sqrt()` function automatically handles integer scaling for 0-255 range values
|
||||
- **Trigonometric Range**: `sine()` and `cosine()` use 0-255 input range (mapped to 0-360°) and return -255 to 255 output range
|
||||
- **Automatic Detection**: Mathematical functions are automatically detected at transpile time using dynamic introspection
|
||||
- **Closure Context**: In computed parameters, mathematical functions are called as `self.<function>()` in the generated closure context
|
||||
|
||||
**How It Works:**
|
||||
When the DSL detects arithmetic expressions containing value providers, variable references, or mathematical functions, it automatically creates closure functions that capture the computation. These closures are called with `(self, param_name, time_ms)` parameters, allowing the computation to be re-evaluated dynamically as needed. Mathematical functions are automatically prefixed with `self.` in the closure context to access the ClosureValueProvider's mathematical methods.
|
||||
|
||||
**User Functions in Computed Parameters:**
|
||||
User-defined functions can also be used in computed parameter expressions, providing powerful custom effects:
|
||||
|
||||
```dsl
|
||||
# Simple user function in computed parameter
|
||||
animation base = solid(color=blue)
|
||||
base.opacity = rand_demo()
|
||||
|
||||
# User functions mixed with math operations
|
||||
animation dynamic = solid(
|
||||
color=purple
|
||||
opacity=max(50, min(255, rand_demo() + 100))
|
||||
)
|
||||
```
|
||||
|
||||
### User Functions
|
||||
|
||||
User functions are custom Berry functions that can be called from computed parameters. They provide dynamic values that change over time.
|
||||
|
||||
**Available User Functions:**
|
||||
- `rand_demo()` - Returns random values for demonstration purposes
|
||||
|
||||
**Usage in Computed Parameters:**
|
||||
```dsl
|
||||
# Simple user function
|
||||
animation.opacity = rand_demo()
|
||||
|
||||
# User function with math operations
|
||||
animation.opacity = max(100, rand_demo())
|
||||
|
||||
# User function in arithmetic expressions
|
||||
animation.opacity = abs(rand_demo() - 128) + 64
|
||||
```
|
||||
|
||||
**Available User Functions:**
|
||||
The following user functions are available by default (see [User Functions Guide](USER_FUNCTIONS.md) for details):
|
||||
|
||||
| Function | Parameters | Description |
|
||||
|----------|------------|-------------|
|
||||
| `rand_demo()` | none | Returns a random value (0-255) for demonstration |
|
||||
|
||||
**User Function Behavior:**
|
||||
- User functions are automatically detected by the transpiler
|
||||
- They receive `self.engine` as the first parameter in closure context
|
||||
- They can be mixed with mathematical functions and arithmetic operations
|
||||
- The entire expression is wrapped in a single efficient closure
|
||||
|
||||
## Sequences
|
||||
|
||||
Sequences orchestrate multiple animations with timing control:
|
||||
@ -487,6 +640,21 @@ pulsating_animation(
|
||||
)
|
||||
```
|
||||
|
||||
**Mathematical Functions in Computed Parameters:**
|
||||
Mathematical functions can be used in computed parameter expressions and are automatically detected by the transpiler:
|
||||
|
||||
```dsl
|
||||
animation wave = pulsating_animation(
|
||||
color=blue
|
||||
period=2s
|
||||
)
|
||||
|
||||
# Mathematical functions in property assignments
|
||||
wave.opacity = abs(sine(strip_length()) - 128) # Sine wave opacity
|
||||
wave.position = max(0, min(strip_length() - 1, 15)) # Clamped position
|
||||
wave.brightness = round(sqrt(strip_length()) * 4) # Non-linear scaling
|
||||
```
|
||||
|
||||
## Supported Classes
|
||||
|
||||
### Value Providers
|
||||
@ -768,7 +936,10 @@ This applies to:
|
||||
- Parameter validation at compile time
|
||||
- Execution statements
|
||||
- User-defined functions (with engine-first parameter pattern) - see **[User Functions Guide](USER_FUNCTIONS.md)**
|
||||
- **User functions in computed parameters**: User functions can be used in arithmetic expressions alongside mathematical functions
|
||||
- **Flexible parameter syntax**: Commas optional when parameters are on separate lines
|
||||
- **Computed values**: Arithmetic expressions with value providers automatically create closures
|
||||
- **Mathematical functions**: `min`, `max`, `abs`, `round`, `sqrt`, `scale`, `sine`, `cosine` in computed parameters
|
||||
|
||||
### 🚧 Partially Implemented
|
||||
- Expression evaluation (basic support)
|
||||
@ -778,7 +949,6 @@ This applies to:
|
||||
### ❌ Planned Features
|
||||
- Advanced control flow (if/else, choose random)
|
||||
- Event system and handlers
|
||||
- Mathematical expressions
|
||||
- Variable references with $ syntax
|
||||
- Spatial operations and zones
|
||||
- 2D matrix support
|
||||
|
||||
@ -124,6 +124,62 @@ run pulse_red
|
||||
```
|
||||
|
||||
The DSL transpiles to Berry code where each animation gets an engine parameter and named parameters are set individually.
|
||||
|
||||
## Symbol Resolution
|
||||
|
||||
The DSL transpiler uses intelligent symbol resolution at compile time to optimize generated code and eliminate runtime lookups:
|
||||
|
||||
### Transpile-Time Symbol Resolution
|
||||
|
||||
When the DSL encounters an identifier (like `SINE` or `red`), it checks at transpile time whether the symbol exists in the `animation` module using Berry's introspection capabilities:
|
||||
|
||||
```dsl
|
||||
# If SINE exists in animation module
|
||||
animation wave = wave_animation(waveform=SINE)
|
||||
# Transpiles to: animation.SINE (direct access)
|
||||
|
||||
# If custom_color doesn't exist in animation module
|
||||
color custom_color = #FF0000
|
||||
animation solid_red = solid(color=custom_color)
|
||||
# Transpiles to: custom_color_ (user-defined variable)
|
||||
```
|
||||
|
||||
### Benefits
|
||||
|
||||
- **Performance**: Eliminates runtime symbol lookups for built-in constants
|
||||
- **Error Detection**: Catches undefined symbols at compile time
|
||||
- **Code Clarity**: Generated Berry code clearly shows built-in vs user-defined symbols
|
||||
- **Optimization**: Direct access to animation module symbols is faster
|
||||
|
||||
### Symbol Categories
|
||||
|
||||
**Built-in Symbols** (resolved to `animation.<symbol>`):
|
||||
- Animation factory functions: `solid`, `pulsating_animation`, `comet_animation`
|
||||
- Value providers: `triangle`, `smooth`, `sine`, `static_value`
|
||||
- Color providers: `color_cycle`, `breathe_color`, `rich_palette`
|
||||
- Constants: `PALETTE_RAINBOW`, `SINE`, `TRIANGLE`, etc.
|
||||
|
||||
**User-defined Symbols** (resolved to `<symbol>_`):
|
||||
- Custom colors: `my_red`, `fire_color`
|
||||
- Custom animations: `pulse_effect`, `rainbow_wave`
|
||||
- Variables: `brightness_level`, `cycle_time`
|
||||
|
||||
### Property Assignment Resolution
|
||||
|
||||
Property assignments also use the same resolution logic:
|
||||
|
||||
```dsl
|
||||
# Built-in symbol (if 'engine' existed in animation module)
|
||||
engine.brightness = 200
|
||||
# Would transpile to: animation.engine.brightness = 200
|
||||
|
||||
# User-defined symbol
|
||||
my_animation.priority = 10
|
||||
# Transpiles to: my_animation_.priority = 10
|
||||
```
|
||||
|
||||
This intelligent resolution ensures optimal performance while maintaining clear separation between framework and user code.
|
||||
|
||||
## Advanced DSL Features
|
||||
|
||||
### User-Defined Functions
|
||||
|
||||
@ -96,9 +96,43 @@ sequence sunrise_show {
|
||||
run sunrise_show
|
||||
```
|
||||
|
||||
## User Functions in Computed Parameters
|
||||
|
||||
### 9. Simple User Function
|
||||
```dsl
|
||||
# Simple user function in computed parameter
|
||||
animation random_base = solid(color=blue, priority=10)
|
||||
random_base.opacity = rand_demo()
|
||||
run random_base
|
||||
```
|
||||
|
||||
### 10. User Function with Math Operations
|
||||
```dsl
|
||||
# Mix user functions with mathematical functions
|
||||
animation random_bounded = solid(
|
||||
color=purple
|
||||
opacity=max(50, min(255, rand_demo() + 100))
|
||||
priority=15
|
||||
)
|
||||
run random_bounded
|
||||
```
|
||||
|
||||
### 11. User Function in Arithmetic Expression
|
||||
```dsl
|
||||
# Use user function in arithmetic expressions
|
||||
animation random_variation = solid(
|
||||
color=cyan
|
||||
opacity=abs(rand_demo() - 128) + 64
|
||||
priority=12
|
||||
)
|
||||
run random_variation
|
||||
```
|
||||
|
||||
See `anim_examples/user_functions_demo.anim` for a complete working example.
|
||||
|
||||
## Advanced Examples
|
||||
|
||||
### 9. Dynamic Position
|
||||
### 13. Dynamic Position
|
||||
```dsl
|
||||
strip length 60
|
||||
|
||||
@ -114,7 +148,7 @@ animation moving_pulse = beacon_animation(
|
||||
run moving_pulse
|
||||
```
|
||||
|
||||
### 10. Multi-Layer Effect
|
||||
### 14. Multi-Layer Effect
|
||||
```dsl
|
||||
# Base layer - slow breathing
|
||||
set breathing = smooth(min_value=100, max_value=255, period=4s)
|
||||
|
||||
@ -293,6 +293,81 @@ def breathing_effect(engine, color, period, min_brightness, max_brightness)
|
||||
end
|
||||
```
|
||||
|
||||
## User Functions in Computed Parameters
|
||||
|
||||
User functions can be used in computed parameter expressions alongside mathematical functions, creating powerful dynamic animations:
|
||||
|
||||
### Simple User Function in Computed Parameter
|
||||
|
||||
```dsl
|
||||
# Simple user function call in property assignment
|
||||
animation base = solid(color=blue, priority=10)
|
||||
base.opacity = rand_demo() # User function as computed parameter
|
||||
```
|
||||
|
||||
### User Functions with Mathematical Operations
|
||||
|
||||
```dsl
|
||||
# Get strip length for calculations
|
||||
set strip_len = strip_length()
|
||||
|
||||
# Mix user functions with mathematical functions
|
||||
animation dynamic_solid = solid(
|
||||
color=purple
|
||||
opacity=max(50, min(255, rand_demo() + 100)) # Random opacity with bounds
|
||||
priority=15
|
||||
)
|
||||
```
|
||||
|
||||
### User Functions in Complex Expressions
|
||||
|
||||
```dsl
|
||||
# Use user function in arithmetic expressions
|
||||
animation random_effect = solid(
|
||||
color=cyan
|
||||
opacity=abs(rand_demo() - 128) + 64 # Random variation around middle value
|
||||
priority=12
|
||||
)
|
||||
```
|
||||
|
||||
### How It Works
|
||||
|
||||
When you use user functions in computed parameters:
|
||||
|
||||
1. **Automatic Detection**: The transpiler automatically detects user functions in expressions
|
||||
2. **Single Closure**: The entire expression is wrapped in a single efficient closure
|
||||
3. **Engine Access**: User functions receive `self.engine` in the closure context
|
||||
4. **Mixed Operations**: User functions work seamlessly with mathematical functions and arithmetic
|
||||
|
||||
**Generated Code Example:**
|
||||
```dsl
|
||||
# DSL code
|
||||
animation.opacity = max(100, breathing(red, 2000))
|
||||
```
|
||||
|
||||
**Transpiles to:**
|
||||
```berry
|
||||
animation.opacity = animation.create_closure_value(engine,
|
||||
def (self, param_name, time_ms)
|
||||
return (self.max(100, animation.get_user_function('breathing')(self.engine, 0xFFFF0000, 2000)))
|
||||
end)
|
||||
```
|
||||
|
||||
### Available User Functions
|
||||
|
||||
The following user functions are available by default:
|
||||
|
||||
| Function | Parameters | Description |
|
||||
|----------|------------|-------------|
|
||||
| `rand_demo()` | none | Returns a random value (0-255) for demonstration |
|
||||
|
||||
### Best Practices for Computed Parameters
|
||||
|
||||
1. **Keep expressions readable**: Break complex expressions across multiple lines
|
||||
2. **Use meaningful variable names**: `set strip_len = strip_length()` not `set s = strip_length()`
|
||||
3. **Combine wisely**: Mix user functions with math functions for rich effects
|
||||
4. **Test incrementally**: Start simple and build up complex expressions
|
||||
|
||||
## Loading and Using Functions
|
||||
|
||||
### In Tasmota autoexec.be
|
||||
|
||||
@ -86,6 +86,14 @@ register_to_animation(event_handler)
|
||||
import "core/user_functions" as user_functions
|
||||
register_to_animation(user_functions)
|
||||
|
||||
# Import and register actual user functions
|
||||
try
|
||||
import "user_functions" as user_funcs # This registers the actual user functions
|
||||
except .. as e, msg
|
||||
# User functions are optional - continue without them if not available
|
||||
print(f"Note: User functions not loaded: {msg}")
|
||||
end
|
||||
|
||||
# Import value providers
|
||||
import "providers/value_provider.be" as value_provider
|
||||
register_to_animation(value_provider)
|
||||
@ -95,6 +103,8 @@ import "providers/oscillator_value_provider.be" as oscillator_value_provider
|
||||
register_to_animation(oscillator_value_provider)
|
||||
import "providers/strip_length_provider.be" as strip_length_provider
|
||||
register_to_animation(strip_length_provider)
|
||||
import "providers/closure_value_provider.be" as closure_value_provider
|
||||
register_to_animation(closure_value_provider)
|
||||
|
||||
# Import color providers
|
||||
import "providers/color_provider.be" as color_provider
|
||||
@ -171,7 +181,7 @@ def animation_init_strip(*l)
|
||||
import animation
|
||||
import introspect
|
||||
# we keep a hash of strip configurations to reuse existing engines
|
||||
if !introspect.contains(animation, "_strips")
|
||||
if !introspect.contains(animation, "_engines")
|
||||
animation._engines = {}
|
||||
end
|
||||
|
||||
@ -184,6 +194,7 @@ def animation_init_strip(*l)
|
||||
else
|
||||
var strip = call(global.Leds, l) # call global.Leds() with vararg
|
||||
engine = animation.create_engine(strip)
|
||||
animation._engines[l_as_string] = engine
|
||||
end
|
||||
|
||||
return engine
|
||||
|
||||
@ -142,7 +142,7 @@ class ParameterizedObject
|
||||
def _set_parameter_value(name, value)
|
||||
# Validate the value (skip validation for ValueProvider instances)
|
||||
if !animation.is_value_provider(value)
|
||||
self._validate_param(name, value) # This will raise exception with details if invalid
|
||||
value = self._validate_param(name, value) # Get potentially converted value
|
||||
end
|
||||
|
||||
# Store the value
|
||||
@ -182,7 +182,8 @@ class ParameterizedObject
|
||||
# Raises detailed exceptions for validation failures
|
||||
#
|
||||
# @param name: string - Parameter name
|
||||
# @param value: any - Value to validate
|
||||
# @param value: any - Value to validate (may be modified for real->int conversion)
|
||||
# @return any - Validated value (potentially converted from real to int)
|
||||
def _validate_param(name, value)
|
||||
var constraints = self._get_param_def(name)
|
||||
if constraints == nil
|
||||
@ -191,19 +192,19 @@ class ParameterizedObject
|
||||
|
||||
# Accept ValueProvider instances for all parameters
|
||||
if animation.is_value_provider(value)
|
||||
return
|
||||
return value
|
||||
end
|
||||
|
||||
# Handle nil values
|
||||
if value == nil
|
||||
# Check if nil is explicitly allowed via nillable attribute
|
||||
if constraints.contains("nillable") && constraints["nillable"] == true
|
||||
return # nil is allowed for this parameter
|
||||
return value # nil is allowed for this parameter
|
||||
end
|
||||
|
||||
# Check if there's a default value (nil is acceptable if there's a default)
|
||||
if constraints.contains("default")
|
||||
return # nil is acceptable, will use default
|
||||
return value # nil is acceptable, will use default
|
||||
end
|
||||
|
||||
# nil is not allowed for this parameter
|
||||
@ -221,8 +222,12 @@ class ParameterizedObject
|
||||
|
||||
# Skip type validation if expected type is "any"
|
||||
if expected_type != "any"
|
||||
# Validate type
|
||||
if expected_type != actual_type
|
||||
# Special case: accept real values for int parameters and convert them
|
||||
if expected_type == "int" && actual_type == "real"
|
||||
import math
|
||||
value = int(math.round(value))
|
||||
actual_type = "int"
|
||||
elif expected_type != actual_type
|
||||
raise "value_error", f"Parameter '{name}' expects type '{expected_type}' but got '{actual_type}' (value: {value})"
|
||||
end
|
||||
end
|
||||
@ -256,6 +261,8 @@ class ParameterizedObject
|
||||
raise "value_error", f"Parameter '{name}' value {value} is not in allowed values {enum_list}"
|
||||
end
|
||||
end
|
||||
|
||||
return value
|
||||
end
|
||||
|
||||
# Set a parameter value with validation
|
||||
|
||||
@ -24,6 +24,7 @@ class SimpleDSLTranspiler
|
||||
var run_statements # Collect all run statements for single engine.start()
|
||||
var first_statement # Track if we're processing the first statement
|
||||
var strip_initialized # Track if strip was initialized
|
||||
var sequence_names # Track which names are sequences
|
||||
|
||||
# Static color mapping for named colors (helps with solidification)
|
||||
static var named_colors = {
|
||||
@ -50,6 +51,7 @@ class SimpleDSLTranspiler
|
||||
self.run_statements = []
|
||||
self.first_statement = true # Track if we're processing the first statement
|
||||
self.strip_initialized = false # Track if strip was initialized
|
||||
self.sequence_names = {} # Track which names are sequences
|
||||
end
|
||||
|
||||
# Main transpilation method - single pass
|
||||
@ -375,9 +377,13 @@ class SimpleDSLTranspiler
|
||||
return
|
||||
end
|
||||
|
||||
# Track that this name is a sequence
|
||||
self.sequence_names[name] = true
|
||||
|
||||
self.expect_left_brace()
|
||||
|
||||
self.add(f"def sequence_{name}()")
|
||||
# Generate anonymous closure that creates and returns sequence manager
|
||||
self.add(f"var {name}_ = (def (engine)")
|
||||
self.add(f" var steps = []")
|
||||
|
||||
# Process sequence body
|
||||
@ -388,7 +394,7 @@ class SimpleDSLTranspiler
|
||||
self.add(f" var seq_manager = animation.SequenceManager(engine)")
|
||||
self.add(f" seq_manager.start_sequence(steps)")
|
||||
self.add(f" return seq_manager")
|
||||
self.add("end")
|
||||
self.add(f"end)(engine)")
|
||||
self.expect_right_brace()
|
||||
end
|
||||
|
||||
@ -423,7 +429,7 @@ class SimpleDSLTranspiler
|
||||
# This is a function call - process it as a nested function call
|
||||
anim_ref = self.process_nested_function_call()
|
||||
else
|
||||
# This is an identifier reference
|
||||
# This is an identifier reference - sequences need runtime resolution
|
||||
var anim_name = self.expect_identifier()
|
||||
anim_ref = f"animation.global('{anim_name}_')"
|
||||
end
|
||||
@ -478,7 +484,7 @@ class SimpleDSLTranspiler
|
||||
# This is a function call - process it as a nested function call
|
||||
anim_ref = self.process_nested_function_call()
|
||||
else
|
||||
# This is an identifier reference
|
||||
# This is an identifier reference - sequences need runtime resolution
|
||||
var anim_name = self.expect_identifier()
|
||||
anim_ref = f"animation.global('{anim_name}_')"
|
||||
end
|
||||
@ -533,8 +539,19 @@ class SimpleDSLTranspiler
|
||||
var value = self.process_value("property")
|
||||
var inline_comment = self.collect_inline_comment()
|
||||
|
||||
# Generate property assignment: animation.global('object_name_').property = value
|
||||
self.add(f"animation.global('{object_name}_').{property_name} = {value}{inline_comment}")
|
||||
# NEW: Use symbol resolution logic for property assignments
|
||||
import introspect
|
||||
var object_ref = ""
|
||||
if introspect.contains(animation, object_name)
|
||||
# Symbol exists in animation module, use it directly
|
||||
object_ref = f"animation.{object_name}"
|
||||
else
|
||||
# Symbol doesn't exist in animation module, use underscore suffix
|
||||
object_ref = f"{object_name}_"
|
||||
end
|
||||
|
||||
# Generate property assignment
|
||||
self.add(f"{object_ref}.{property_name} = {value}{inline_comment}")
|
||||
else
|
||||
# Not a property assignment, skip this statement
|
||||
self.error(f"Expected property assignment for '{object_name}' but found no dot")
|
||||
@ -544,6 +561,59 @@ class SimpleDSLTranspiler
|
||||
|
||||
# Process any value - unified approach
|
||||
def process_value(context)
|
||||
return self.process_expression(context)
|
||||
end
|
||||
|
||||
# Process expressions with arithmetic operations
|
||||
def process_expression(context)
|
||||
return self.process_additive_expression(context)
|
||||
end
|
||||
|
||||
# Process additive expressions (+ and -)
|
||||
def process_additive_expression(context)
|
||||
var left = self.process_multiplicative_expression(context)
|
||||
|
||||
while !self.at_end()
|
||||
var tok = self.current()
|
||||
if tok != nil && (tok.type == animation_dsl.Token.PLUS || tok.type == animation_dsl.Token.MINUS)
|
||||
var op = tok.value
|
||||
self.next() # consume operator
|
||||
var right = self.process_multiplicative_expression(context)
|
||||
left = f"{left} {op} {right}"
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
# Check if the entire expression needs a closure (after building the full expression)
|
||||
if self.is_computed_expression_string(left)
|
||||
return self.create_computation_closure_from_string(left)
|
||||
else
|
||||
return left
|
||||
end
|
||||
end
|
||||
|
||||
# Process multiplicative expressions (* and /)
|
||||
def process_multiplicative_expression(context)
|
||||
var left = self.process_unary_expression(context)
|
||||
|
||||
while !self.at_end()
|
||||
var tok = self.current()
|
||||
if tok != nil && (tok.type == animation_dsl.Token.MULTIPLY || tok.type == animation_dsl.Token.DIVIDE)
|
||||
var op = tok.value
|
||||
self.next() # consume operator
|
||||
var right = self.process_unary_expression(context)
|
||||
left = f"{left} {op} {right}"
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
return left
|
||||
end
|
||||
|
||||
# Process unary expressions (- and +)
|
||||
def process_unary_expression(context)
|
||||
var tok = self.current()
|
||||
if tok == nil
|
||||
self.error("Expected value")
|
||||
@ -553,15 +623,33 @@ class SimpleDSLTranspiler
|
||||
# Handle unary minus for negative numbers
|
||||
if tok.type == animation_dsl.Token.MINUS
|
||||
self.next() # consume the minus
|
||||
var next_tok = self.current()
|
||||
if next_tok != nil && next_tok.type == animation_dsl.Token.NUMBER
|
||||
var value = "-" + next_tok.value
|
||||
self.next() # consume the number
|
||||
return value
|
||||
else
|
||||
self.error("Expected number after '-'")
|
||||
return "0"
|
||||
end
|
||||
var expr = self.process_unary_expression(context)
|
||||
return f"(-{expr})"
|
||||
end
|
||||
|
||||
# Handle unary plus (optional)
|
||||
if tok.type == animation_dsl.Token.PLUS
|
||||
self.next() # consume the plus
|
||||
return self.process_unary_expression(context)
|
||||
end
|
||||
|
||||
return self.process_primary_expression(context)
|
||||
end
|
||||
|
||||
# Process primary expressions (literals, identifiers, function calls, parentheses)
|
||||
def process_primary_expression(context)
|
||||
var tok = self.current()
|
||||
if tok == nil
|
||||
self.error("Expected value")
|
||||
return "nil"
|
||||
end
|
||||
|
||||
# Parenthesized expression
|
||||
if tok.type == animation_dsl.Token.LEFT_PAREN
|
||||
self.next() # consume '('
|
||||
var expr = self.process_expression(context)
|
||||
self.expect_right_paren()
|
||||
return f"({expr})"
|
||||
end
|
||||
|
||||
# Function call: identifier or easing keyword followed by '('
|
||||
@ -627,8 +715,15 @@ class SimpleDSLTranspiler
|
||||
return self.get_named_color_value(name)
|
||||
end
|
||||
|
||||
# Use underscore suffix for DSL variables to avoid conflicts
|
||||
return f"animation.global('{name}_', '{name}')"
|
||||
# NEW: Check at transpile time if symbol exists in animation module
|
||||
import introspect
|
||||
if introspect.contains(animation, name)
|
||||
# Symbol exists in animation module, use it directly
|
||||
return f"animation.{name}"
|
||||
else
|
||||
# Symbol doesn't exist in animation module, use underscore suffix
|
||||
return f"{name}_"
|
||||
end
|
||||
end
|
||||
|
||||
# Boolean keywords
|
||||
@ -650,6 +745,258 @@ class SimpleDSLTranspiler
|
||||
return "nil"
|
||||
end
|
||||
|
||||
# Check if an expression string contains computed values that need a closure
|
||||
def is_computed_expression_string(expr_str)
|
||||
import string
|
||||
|
||||
# Check if the expression contains operators that make it a computation
|
||||
var has_operators = (
|
||||
string.find(expr_str, " + ") >= 0 || # Addition
|
||||
string.find(expr_str, " - ") >= 0 || # Subtraction
|
||||
string.find(expr_str, " * ") >= 0 || # Multiplication
|
||||
string.find(expr_str, " / ") >= 0 # Division
|
||||
)
|
||||
|
||||
# Check for function calls (parentheses with identifiers before them)
|
||||
# This excludes simple parenthesized literals like (-1)
|
||||
var has_function_calls = false
|
||||
var paren_pos = string.find(expr_str, "(")
|
||||
if paren_pos > 0
|
||||
# Check if there's an identifier before the parenthesis (indicating a function call)
|
||||
var char_before = expr_str[paren_pos-1]
|
||||
if self.is_identifier_char(char_before)
|
||||
has_function_calls = true
|
||||
end
|
||||
end
|
||||
|
||||
# Only create closures for expressions that actually involve computation
|
||||
return has_operators || has_function_calls
|
||||
end
|
||||
|
||||
# Check if an expression contains computed values that need a closure (legacy method)
|
||||
def is_computed_expression(left, op, right)
|
||||
import string
|
||||
|
||||
# Check if either operand contains a function call, variable reference, or user variable
|
||||
# We're permissive here - any expression with these patterns gets a closure
|
||||
var has_dynamic_content = (
|
||||
string.find(left, "(") >= 0 || string.find(right, "(") >= 0 || # Function calls
|
||||
string.find(left, "animation.global") >= 0 || string.find(right, "animation.global") >= 0 || # Variable refs
|
||||
string.find(left, "animation.") >= 0 || string.find(right, "animation.") >= 0 || # Animation module calls
|
||||
string.find(left, "_") >= 0 || string.find(right, "_") >= 0 # User variables (might be ValueProviders)
|
||||
)
|
||||
|
||||
return has_dynamic_content
|
||||
end
|
||||
|
||||
# Create a closure for computed expressions from a complete expression string
|
||||
def create_computation_closure_from_string(expr_str)
|
||||
import string
|
||||
|
||||
# Transform the entire expression to handle ValueProvider instances
|
||||
var transformed_expr = self.transform_expression_for_closure(expr_str)
|
||||
|
||||
# Clean up spacing in the expression - remove extra spaces
|
||||
while string.find(transformed_expr, " ") >= 0
|
||||
transformed_expr = string.replace(transformed_expr, " ", " ")
|
||||
end
|
||||
|
||||
var closure_code = f"def (self, param_name, time_ms) return ({transformed_expr}) end"
|
||||
|
||||
# Return a closure value provider instance
|
||||
return f"animation.create_closure_value(engine, {closure_code})"
|
||||
end
|
||||
|
||||
# Create a closure for computed expressions (legacy method)
|
||||
def create_computation_closure(left, op, right)
|
||||
import string
|
||||
|
||||
# Create a closure value provider that wraps the computation
|
||||
# This replaces the old DSL computed value wrapper approach
|
||||
|
||||
# Transform operands to handle ValueProvider instances
|
||||
var left_expr = self.transform_operand_for_closure(left)
|
||||
var right_expr = self.transform_operand_for_closure(right)
|
||||
|
||||
# Clean up spacing in the expression - remove extra spaces
|
||||
while string.find(left_expr, " ") >= 0
|
||||
left_expr = string.replace(left_expr, " ", " ")
|
||||
end
|
||||
while string.find(right_expr, " ") >= 0
|
||||
right_expr = string.replace(right_expr, " ", " ")
|
||||
end
|
||||
|
||||
var closure_code = f"def (self, param_name, time_ms) return ({left_expr} {op} {right_expr}) end"
|
||||
|
||||
# Return a closure value provider instance
|
||||
return f"animation.create_closure_value(engine, {closure_code})"
|
||||
end
|
||||
|
||||
# Transform a complete expression for use in a closure, handling ValueProvider instances
|
||||
def transform_expression_for_closure(expr_str)
|
||||
import string
|
||||
|
||||
var result = expr_str
|
||||
var pos = 0
|
||||
|
||||
# First pass: Transform mathematical function calls to self.method() calls
|
||||
# Use a simple pattern-based approach that works with the existing logic
|
||||
var search_pos = 0
|
||||
while true
|
||||
var paren_pos = string.find(result, "(", search_pos)
|
||||
if paren_pos < 0
|
||||
break
|
||||
end
|
||||
|
||||
# Find the function name before the parenthesis
|
||||
var func_start = paren_pos - 1
|
||||
while func_start >= 0 && self.is_identifier_char(result[func_start])
|
||||
func_start -= 1
|
||||
end
|
||||
func_start += 1
|
||||
|
||||
if func_start < paren_pos
|
||||
var func_name = result[func_start..paren_pos-1]
|
||||
|
||||
# Check if this is a mathematical method using dynamic introspection
|
||||
if self.is_math_method(func_name)
|
||||
# Check if it's not already prefixed with "self."
|
||||
var prefix_start = func_start >= 5 ? func_start - 5 : 0
|
||||
var prefix = result[prefix_start..func_start-1]
|
||||
if string.find(prefix, "self.") < 0
|
||||
# Replace the function call with self.method()
|
||||
var before = func_start > 0 ? result[0..func_start-1] : ""
|
||||
var after = result[func_start..]
|
||||
result = before + "self." + after
|
||||
search_pos = func_start + 5 + size(func_name) # Skip past "self." + func_name
|
||||
else
|
||||
search_pos = paren_pos + 1
|
||||
end
|
||||
else
|
||||
search_pos = paren_pos + 1
|
||||
end
|
||||
else
|
||||
search_pos = paren_pos + 1
|
||||
end
|
||||
end
|
||||
|
||||
# Second pass: Replace all user variables (ending with _) with resolve calls
|
||||
pos = 0
|
||||
while pos < size(result)
|
||||
var underscore_pos = string.find(result, "_", pos)
|
||||
if underscore_pos < 0
|
||||
break
|
||||
end
|
||||
|
||||
# Find the start of the identifier
|
||||
var start_pos = underscore_pos
|
||||
while start_pos > 0 && self.is_identifier_char(result[start_pos-1])
|
||||
start_pos -= 1
|
||||
end
|
||||
|
||||
# Check if this is a user variable (not preceded by "animation." or "self.")
|
||||
var is_user_var = true
|
||||
if start_pos >= 10
|
||||
var check_start = start_pos >= 10 ? start_pos - 10 : 0
|
||||
var prefix = result[check_start..start_pos-1]
|
||||
if string.find(prefix, "animation.") >= 0 || string.find(prefix, "self.") >= 0
|
||||
is_user_var = false
|
||||
end
|
||||
elif start_pos >= 5
|
||||
var check_start = start_pos >= 5 ? start_pos - 5 : 0
|
||||
var prefix = result[check_start..start_pos-1]
|
||||
if string.find(prefix, "self.") >= 0
|
||||
is_user_var = false
|
||||
end
|
||||
end
|
||||
|
||||
if is_user_var && start_pos < underscore_pos
|
||||
# Extract the variable name
|
||||
var var_name = result[start_pos..underscore_pos]
|
||||
|
||||
# Check if it's followed by non-identifier characters
|
||||
var end_pos = underscore_pos + 1
|
||||
if end_pos >= size(result) || !self.is_identifier_char(result[end_pos])
|
||||
# Replace the variable with the resolve call
|
||||
var replacement = f"self.resolve({var_name}, param_name, time_ms)"
|
||||
var before = start_pos > 0 ? result[0..start_pos-1] : ""
|
||||
var after = end_pos < size(result) ? result[end_pos..] : ""
|
||||
result = before + replacement + after
|
||||
pos = start_pos + size(replacement)
|
||||
else
|
||||
pos = underscore_pos + 1
|
||||
end
|
||||
else
|
||||
pos = underscore_pos + 1
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
# Helper method to check if a character is part of an identifier
|
||||
def is_identifier_char(ch)
|
||||
return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '_'
|
||||
end
|
||||
|
||||
# Helper method to check if a function name is a mathematical method in ClosureValueProvider
|
||||
def is_math_method(func_name)
|
||||
import introspect
|
||||
try
|
||||
# Get the ClosureValueProvider class from the animation module
|
||||
var closure_provider_class = animation.closure_value
|
||||
if closure_provider_class == nil
|
||||
return false
|
||||
end
|
||||
|
||||
# Check if the method exists in the class
|
||||
var members = introspect.members(closure_provider_class)
|
||||
for member : members
|
||||
if member == func_name
|
||||
# Additional check: make sure it's actually a method (function)
|
||||
var method = introspect.get(closure_provider_class, func_name)
|
||||
return introspect.ismethod(method) || type(method) == 'function'
|
||||
end
|
||||
end
|
||||
return false
|
||||
except .. as e, msg
|
||||
# If introspection fails, return false to be safe
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
# Transform an operand for use in a closure, handling ValueProvider instances
|
||||
def transform_operand_for_closure(operand)
|
||||
import string
|
||||
|
||||
# If the operand is already a closure (contains create_closure_value), extract its inner expression
|
||||
if string.find(operand, "animation.create_closure_value") >= 0
|
||||
# Extract the inner expression from the closure
|
||||
var start_pos = string.find(operand, "return (") + 8
|
||||
var end_pos = size(operand) - 5 # Remove " end)"
|
||||
var inner_expr = operand[start_pos..end_pos]
|
||||
# Clean up any extra spaces
|
||||
while string.find(inner_expr, " ") >= 0
|
||||
inner_expr = string.replace(inner_expr, " ", " ")
|
||||
end
|
||||
return inner_expr
|
||||
end
|
||||
|
||||
# Check if this is a simple user variable (identifier ending with _ and no operators/parentheses)
|
||||
var has_underscore = string.find(operand, "_") >= 0
|
||||
var has_operators = string.find(operand, " ") >= 0 # Simple check for operators (they have spaces)
|
||||
var has_paren = string.find(operand, "(") >= 0
|
||||
var has_animation_prefix = string.find(operand, "animation.") >= 0
|
||||
|
||||
if has_underscore && !has_operators && !has_paren && !has_animation_prefix
|
||||
# This looks like a simple user variable that might be a ValueProvider
|
||||
return f"self.resolve({operand}, param_name, time_ms)"
|
||||
else
|
||||
# For other expressions (literals, animation module calls, complex expressions), use as-is
|
||||
return operand
|
||||
end
|
||||
end
|
||||
|
||||
# Process function call (legacy - for non-animation contexts)
|
||||
def process_function_call(context)
|
||||
var tok = self.current()
|
||||
@ -664,6 +1011,13 @@ class SimpleDSLTranspiler
|
||||
return "nil"
|
||||
end
|
||||
|
||||
# Check if this is a mathematical function - handle with positional arguments
|
||||
if self.is_math_method(func_name)
|
||||
# Mathematical functions use positional arguments, not named parameters
|
||||
var args = self.process_function_arguments()
|
||||
return f"{func_name}({args})" # Return as-is for transformation in closure
|
||||
end
|
||||
|
||||
var args = self.process_function_arguments()
|
||||
|
||||
# Check if it's a user-defined function first
|
||||
@ -836,8 +1190,197 @@ class SimpleDSLTranspiler
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
# Process function arguments for expressions (returns raw expressions without closures)
|
||||
def process_function_arguments_for_expression()
|
||||
self.expect_left_paren()
|
||||
var args = []
|
||||
|
||||
while !self.at_end() && !self.check_right_paren()
|
||||
self.skip_whitespace()
|
||||
|
||||
if self.check_right_paren()
|
||||
break
|
||||
end
|
||||
|
||||
var arg = self.process_expression_argument()
|
||||
args.push(arg)
|
||||
|
||||
self.skip_whitespace()
|
||||
|
||||
if self.current() != nil && self.current().type == animation_dsl.Token.COMMA
|
||||
self.next() # skip comma
|
||||
self.skip_whitespace()
|
||||
elif !self.check_right_paren()
|
||||
self.error("Expected ',' or ')' in function arguments")
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
self.expect_right_paren()
|
||||
|
||||
# Join arguments with commas
|
||||
var result = ""
|
||||
for i : 0..size(args)-1
|
||||
if i > 0
|
||||
result += ", "
|
||||
end
|
||||
result += args[i]
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
# Process expression argument (raw expression without closure wrapping)
|
||||
def process_expression_argument()
|
||||
# Just process as a raw additive expression - this handles all cases
|
||||
return self.process_additive_expression_raw()
|
||||
end
|
||||
|
||||
# Process additive expression without closure wrapping (for function arguments)
|
||||
def process_additive_expression_raw()
|
||||
var left = self.process_multiplicative_expression_raw()
|
||||
|
||||
while !self.at_end()
|
||||
var tok = self.current()
|
||||
if tok != nil && (tok.type == animation_dsl.Token.PLUS || tok.type == animation_dsl.Token.MINUS)
|
||||
var op = tok.value
|
||||
self.next() # consume operator
|
||||
var right = self.process_multiplicative_expression_raw()
|
||||
left = f"{left} {op} {right}"
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
return left
|
||||
end
|
||||
|
||||
# Process multiplicative expression without closure wrapping (for function arguments)
|
||||
def process_multiplicative_expression_raw()
|
||||
var left = self.process_unary_expression_raw()
|
||||
|
||||
while !self.at_end()
|
||||
var tok = self.current()
|
||||
if tok != nil && (tok.type == animation_dsl.Token.MULTIPLY || tok.type == animation_dsl.Token.DIVIDE)
|
||||
var op = tok.value
|
||||
self.next() # consume operator
|
||||
var right = self.process_unary_expression_raw()
|
||||
left = f"{left} {op} {right}"
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
return left
|
||||
end
|
||||
|
||||
# Process unary expression without closure wrapping (for function arguments)
|
||||
def process_unary_expression_raw()
|
||||
var tok = self.current()
|
||||
if tok == nil
|
||||
self.error("Expected value")
|
||||
return "nil"
|
||||
end
|
||||
|
||||
# Handle unary minus for negative numbers
|
||||
if tok.type == animation_dsl.Token.MINUS
|
||||
self.next() # consume the minus
|
||||
var expr = self.process_unary_expression_raw()
|
||||
return f"(-{expr})"
|
||||
end
|
||||
|
||||
# Handle unary plus (optional)
|
||||
if tok.type == animation_dsl.Token.PLUS
|
||||
self.next() # consume the plus
|
||||
return self.process_unary_expression_raw()
|
||||
end
|
||||
|
||||
return self.process_primary_expression_raw()
|
||||
end
|
||||
|
||||
# Process primary expression without closure wrapping (for function arguments)
|
||||
def process_primary_expression_raw()
|
||||
var tok = self.current()
|
||||
if tok == nil
|
||||
self.error("Expected value")
|
||||
return "nil"
|
||||
end
|
||||
|
||||
# Parenthesized expression
|
||||
if tok.type == animation_dsl.Token.LEFT_PAREN
|
||||
self.next() # consume '('
|
||||
var expr = self.process_additive_expression_raw()
|
||||
self.expect_right_paren()
|
||||
return f"({expr})"
|
||||
end
|
||||
|
||||
# Function call: identifier or easing keyword followed by '('
|
||||
if (tok.type == animation_dsl.Token.KEYWORD || tok.type == animation_dsl.Token.IDENTIFIER) &&
|
||||
self.peek() != nil && self.peek().type == animation_dsl.Token.LEFT_PAREN
|
||||
|
||||
var func_name = tok.value
|
||||
self.next()
|
||||
|
||||
# Check if this is a mathematical function
|
||||
if self.is_math_method(func_name)
|
||||
var args = self.process_function_arguments_for_expression()
|
||||
return f"self.{func_name}({args})"
|
||||
end
|
||||
|
||||
# Check if this is a user-defined function
|
||||
if animation.is_user_function(func_name)
|
||||
var args = self.process_function_arguments_for_expression()
|
||||
var full_args = args != "" ? f"self.engine, {args}" : "self.engine"
|
||||
return f"animation.get_user_function('{func_name}')({full_args})"
|
||||
end
|
||||
|
||||
# For other functions, this shouldn't happen in expression context
|
||||
self.error(f"Function '{func_name}' not supported in expression context")
|
||||
return "nil"
|
||||
end
|
||||
|
||||
# Color value
|
||||
if tok.type == animation_dsl.Token.COLOR
|
||||
self.next()
|
||||
return self.convert_color(tok.value)
|
||||
end
|
||||
|
||||
# Time value
|
||||
if tok.type == animation_dsl.Token.TIME
|
||||
return str(self.process_time_value())
|
||||
end
|
||||
|
||||
# Percentage value
|
||||
if tok.type == animation_dsl.Token.PERCENTAGE
|
||||
return str(self.process_percentage_value())
|
||||
end
|
||||
|
||||
# Number value
|
||||
if tok.type == animation_dsl.Token.NUMBER
|
||||
var value = tok.value
|
||||
self.next()
|
||||
return value
|
||||
end
|
||||
|
||||
# String value
|
||||
if tok.type == animation_dsl.Token.STRING
|
||||
var value = tok.value
|
||||
self.next()
|
||||
return f'"{value}"'
|
||||
end
|
||||
|
||||
# Identifier - variable reference
|
||||
if tok.type == animation_dsl.Token.IDENTIFIER
|
||||
var name = tok.value
|
||||
self.next()
|
||||
return f"self.resolve({name}_, param_name, time_ms)"
|
||||
end
|
||||
|
||||
self.error(f"Unexpected token in expression: {tok.value}")
|
||||
return "nil"
|
||||
end
|
||||
|
||||
# Process nested function call (generates temporary variable)
|
||||
# Process nested function call (generates temporary variable or raw expression)
|
||||
def process_nested_function_call()
|
||||
var tok = self.current()
|
||||
var func_name = ""
|
||||
@ -851,11 +1394,19 @@ class SimpleDSLTranspiler
|
||||
return "nil"
|
||||
end
|
||||
|
||||
# Check if this is a mathematical function - handle with positional arguments
|
||||
if self.is_math_method(func_name)
|
||||
# Mathematical functions use positional arguments, not named parameters
|
||||
var args = self.process_function_arguments_for_expression()
|
||||
return f"self.{func_name}({args})" # Prefix with self. for closure context
|
||||
end
|
||||
|
||||
# Check if this is a user-defined function
|
||||
if animation.is_user_function(func_name)
|
||||
# User functions use positional parameters with engine as first argument
|
||||
var args = self.process_function_arguments()
|
||||
var full_args = args != "" ? f"engine, {args}" : "engine"
|
||||
# In closure context, use self.engine to access the engine from the ClosureValueProvider
|
||||
var args = self.process_function_arguments_for_expression()
|
||||
var full_args = args != "" ? f"self.engine, {args}" : "self.engine"
|
||||
return f"animation.get_user_function('{func_name}')({full_args})"
|
||||
else
|
||||
# Built-in functions use the new engine-first + named parameters pattern
|
||||
@ -1289,20 +1840,19 @@ class SimpleDSLTranspiler
|
||||
return # No run statements, no need to start engine
|
||||
end
|
||||
|
||||
self.add("# Start all animations/sequences")
|
||||
|
||||
# Add all animations/sequences to the engine
|
||||
for run_stmt : self.run_statements
|
||||
var name = run_stmt["name"]
|
||||
var comment = run_stmt["comment"]
|
||||
|
||||
# Check what exists: sequence function or animation variable
|
||||
self.add(f"if global.contains('sequence_{name}'){comment}")
|
||||
self.add(f" var seq_manager = global.sequence_{name}()")
|
||||
self.add(f" engine.add_sequence_manager(seq_manager)")
|
||||
self.add(f"else")
|
||||
self.add(f" engine.add_animation(animation.global('{name}_'))")
|
||||
self.add(f"end")
|
||||
# Check if this is a sequence or regular animation
|
||||
if self.sequence_names.contains(name)
|
||||
# It's a sequence - the closure returned a SequenceManager
|
||||
self.add(f"engine.add_sequence_manager({name}_){comment}")
|
||||
else
|
||||
# It's a regular animation
|
||||
self.add(f"engine.add_animation({name}_){comment}")
|
||||
end
|
||||
end
|
||||
|
||||
# Single engine.start() call
|
||||
@ -1534,6 +2084,11 @@ class SimpleDSLTranspiler
|
||||
|
||||
# Validate animation factory exists and creates animation.animation instance
|
||||
def _validate_animation_factory_exists(func_name)
|
||||
# Skip validation for mathematical functions - they will be handled by closure transformation
|
||||
if self.is_math_method(func_name)
|
||||
return true # Skip validation for mathematical functions
|
||||
end
|
||||
|
||||
return self._validate_factory_function(func_name, nil)
|
||||
end
|
||||
|
||||
|
||||
@ -0,0 +1,196 @@
|
||||
# ClosureValueProvider - ValueProvider that wraps a closure/function
|
||||
#
|
||||
# This provider allows using closures (functions) as value providers.
|
||||
# The closure is called with (self, param_name, time_ms) parameters when
|
||||
# a value is requested.
|
||||
#
|
||||
# Usage:
|
||||
# var provider = animation.closure_value_provider(engine)
|
||||
# provider.closure = def(self, param_name, time_ms) return time_ms / 100 end
|
||||
# animation.brightness = provider
|
||||
# Alternative with reference to another value:
|
||||
# var strip_len_ = animation.strip_length(engine)
|
||||
# var provider = animation.closure_value_provider(engine)
|
||||
# provider.closure = def(self, param_name, time_ms) return self.resolve(strip_len_, param_name, timer_ms) + 2 end
|
||||
# animation.brightness = provider
|
||||
#
|
||||
|
||||
#@ solidify:ClosureValueProvider,weak
|
||||
class ClosureValueProvider : animation.value_provider
|
||||
var _closure # We keep the closure as instance variable for faster dereferencing, in addition to PARAMS
|
||||
|
||||
# Static parameter definitions
|
||||
static var PARAMS = {
|
||||
"closure": {"type": "function", "default": nil}
|
||||
}
|
||||
|
||||
# Method called when a parameter is changed
|
||||
# Copy "closure" parameter to _closure instance variable
|
||||
#
|
||||
# @param name: string - Parameter name
|
||||
# @param value: any - New parameter value
|
||||
def on_param_changed(name, value)
|
||||
if name == "closure"
|
||||
self._closure = value
|
||||
end
|
||||
end
|
||||
|
||||
# Helper method to resolve a value that can be either static or from a value provider
|
||||
# This is equivalent to 'resolve_param' but with a shorter name
|
||||
# and available at first dereferencing of method name (hence faster)
|
||||
#
|
||||
# @param value: any - Static value or value provider instance
|
||||
# @param param_name: string - Parameter name for specific produce_value() method lookup
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
# @return any - The resolved value (static or from provider)
|
||||
def resolve(value, param_name, time_ms)
|
||||
if animation.is_value_provider(value)
|
||||
return value.produce_value(param_name, time_ms)
|
||||
else
|
||||
return value
|
||||
end
|
||||
end
|
||||
|
||||
# Produce a value by calling the stored closure
|
||||
#
|
||||
# @param name: string - Parameter name being requested
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
# @return any - Value returned by the closure
|
||||
def produce_value(name, time_ms)
|
||||
var closure = self._closure
|
||||
if closure == nil
|
||||
return nil
|
||||
end
|
||||
|
||||
# Call the closure with the parameter self, name and time
|
||||
return closure(self, name, time_ms)
|
||||
end
|
||||
|
||||
# Mathematical helper methods for use in closures
|
||||
|
||||
# Minimum of two or more values
|
||||
#
|
||||
# @param a: number - First value
|
||||
# @param b: number - Second value
|
||||
# @param *args: number - Additional values (optional)
|
||||
# @return number - Minimum value
|
||||
def min(*args)
|
||||
import math
|
||||
return call(math.min, args)
|
||||
end
|
||||
|
||||
# Maximum of two or more values
|
||||
#
|
||||
# @param a: number - First value
|
||||
# @param b: number - Second value
|
||||
# @param *args: number - Additional values (optional)
|
||||
# @return number - Maximum value
|
||||
def max(*args)
|
||||
import math
|
||||
return call(math.max, args)
|
||||
end
|
||||
|
||||
# Absolute value
|
||||
#
|
||||
# @param x: number - Input value
|
||||
# @return number - Absolute value
|
||||
def abs(x)
|
||||
import math
|
||||
return math.abs(x)
|
||||
end
|
||||
|
||||
# Round to nearest integer
|
||||
#
|
||||
# @param x: number - Input value
|
||||
# @return int - Rounded value
|
||||
def round(x)
|
||||
import math
|
||||
return int(math.round(x))
|
||||
end
|
||||
|
||||
# Square root with integer handling
|
||||
# For integers, treats 1.0 as 255 (full scale)
|
||||
#
|
||||
# @param x: number - Input value
|
||||
# @return number - Square root
|
||||
def sqrt(x)
|
||||
import math
|
||||
# If x is an integer in 0-255 range, scale to 0-1 for sqrt, then back
|
||||
if type(x) == 'int' && x >= 0 && x <= 255
|
||||
var normalized = x / 255.0
|
||||
return int(math.sqrt(normalized) * 255)
|
||||
else
|
||||
return math.sqrt(x)
|
||||
end
|
||||
end
|
||||
|
||||
# Scale a value from one range to another using tasmota.scale_int
|
||||
#
|
||||
# @param v: number - Value to scale
|
||||
# @param from_min: number - Source range minimum
|
||||
# @param from_max: number - Source range maximum
|
||||
# @param to_min: number - Target range minimum
|
||||
# @param to_max: number - Target range maximum
|
||||
# @return int - Scaled value
|
||||
def scale(v, from_min, from_max, to_min, to_max)
|
||||
return tasmota.scale_int(v, from_min, from_max, to_min, to_max)
|
||||
end
|
||||
|
||||
# Sine function using tasmota.sine_int (works on integers)
|
||||
# Input angle is in 0-255 range (mapped to 0-360 degrees)
|
||||
# Output is in -255 to 255 range (mapped from -1.0 to 1.0)
|
||||
#
|
||||
# @param angle: number - Angle in 0-255 range (0-360 degrees)
|
||||
# @return int - Sine value in -255 to 255 range
|
||||
def sine(angle)
|
||||
# Map angle from 0-255 to 0-32767 (tasmota.sine_int input range)
|
||||
var tasmota_angle = tasmota.scale_int(angle, 0, 255, 0, 32767)
|
||||
|
||||
# Get sine value from -4096 to 4096 (representing -1.0 to 1.0)
|
||||
var sine_val = tasmota.sine_int(tasmota_angle)
|
||||
|
||||
# Map from -4096..4096 to -255..255 for integer output
|
||||
return tasmota.scale_int(sine_val, -4096, 4096, -255, 255)
|
||||
end
|
||||
|
||||
# Cosine function using tasmota.sine_int with phase shift
|
||||
# Input angle is in 0-255 range (mapped to 0-360 degrees)
|
||||
# Output is in -255 to 255 range (mapped from -1.0 to 1.0)
|
||||
# Note: This matches the oscillator COSINE behavior (starts at minimum, not maximum)
|
||||
#
|
||||
# @param angle: number - Angle in 0-255 range (0-360 degrees)
|
||||
# @return int - Cosine value in -255 to 255 range
|
||||
def cosine(angle)
|
||||
# Map angle from 0-255 to 0-32767 (tasmota.sine_int input range)
|
||||
var tasmota_angle = tasmota.scale_int(angle, 0, 255, 0, 32767)
|
||||
|
||||
# Get cosine value by shifting sine by -90 degrees (matches oscillator behavior)
|
||||
var cosine_val = tasmota.sine_int(tasmota_angle - 8192)
|
||||
|
||||
# Map from -4096..4096 to -255..255 for integer output
|
||||
return tasmota.scale_int(cosine_val, -4096, 4096, -255, 255)
|
||||
end
|
||||
|
||||
# String representation for debugging
|
||||
#
|
||||
# @return string - Human-readable description of the provider
|
||||
def tostring()
|
||||
return f"ClosureValueProvider({self._closure ? 'closure set' :: 'no closure'})"
|
||||
end
|
||||
end
|
||||
|
||||
# Create a ClosureValueProvider in a single call, by passing the closure argument
|
||||
#
|
||||
# This is used only by the transpiler, and is not usable in the DSL by itself
|
||||
#
|
||||
# @param engine: AnimationEngine - Animation engine reference
|
||||
# @param closure: function - the closure to evaluate at run-time
|
||||
# @return ClosureValueProvider - New ClosureValueProvider instance
|
||||
def create_closure_value(engine, closure)
|
||||
var provider = animation.closure_value(engine)
|
||||
provider.closure = closure
|
||||
return provider
|
||||
end
|
||||
|
||||
return {'closure_value': ClosureValueProvider,
|
||||
'create_closure_value': create_closure_value}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,460 @@
|
||||
# Test for ClosureValueProvider
|
||||
#
|
||||
# This test verifies that the ClosureValueProvider correctly wraps
|
||||
# closures and evaluates them when producing values.
|
||||
|
||||
import animation
|
||||
|
||||
def test_closure_value_provider()
|
||||
print("Testing ClosureValueProvider...")
|
||||
|
||||
# Create a mock engine
|
||||
class MockEngine
|
||||
var time_ms
|
||||
def init()
|
||||
self.time_ms = 1000
|
||||
end
|
||||
end
|
||||
var engine = MockEngine()
|
||||
|
||||
# Create a closure value provider
|
||||
var provider = animation.closure_value(engine)
|
||||
|
||||
# Test 1: Provider without closure returns nil
|
||||
var result = provider.produce_value("test", 1000)
|
||||
assert(result == nil, "Provider without closure should return nil")
|
||||
print("✓ Provider without closure returns nil")
|
||||
|
||||
# Test 2: Set a simple closure
|
||||
var f = def(self, name, time_ms) return time_ms / 100 end
|
||||
print(f">> {f=} {provider=}")
|
||||
provider.closure = f
|
||||
result = provider.produce_value("brightness", 1000)
|
||||
assert(result == 10, f"Expected 10, got {result}")
|
||||
print("✓ Simple closure evaluation works")
|
||||
|
||||
# Test 3: Closure receives correct parameters
|
||||
var captured_name = nil
|
||||
var captured_time = nil
|
||||
provider.closure = def(self, name, time_ms)
|
||||
captured_name = name
|
||||
captured_time = time_ms
|
||||
return 42
|
||||
end
|
||||
|
||||
result = provider.produce_value("color", 2000)
|
||||
assert(result == 42, f"Expected 42, got {result}")
|
||||
assert(captured_name == "color", f"Expected 'color', got '{captured_name}'")
|
||||
assert(captured_time == 2000, f"Expected 2000, got {captured_time}")
|
||||
print("✓ Closure receives correct parameters")
|
||||
|
||||
# Test 4: Complex closure with calculations
|
||||
provider.closure = def(self, name, time_ms)
|
||||
if name == "brightness"
|
||||
return (time_ms % 2000) / 2000.0 * 255
|
||||
elif name == "hue"
|
||||
return (time_ms / 10) % 360
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
var brightness = provider.produce_value("brightness", 1000)
|
||||
var hue = provider.produce_value("hue", 3600)
|
||||
var other = provider.produce_value("other", 1000)
|
||||
|
||||
assert(brightness == 127.5, f"Expected 127.5, got {brightness}")
|
||||
assert(hue == 0, f"Expected 0, got {hue}") # 3600 / 10 = 360, 360 % 360 = 0
|
||||
assert(other == 0, f"Expected 0, got {other}")
|
||||
print("✓ Complex closure with parameter-specific logic works")
|
||||
|
||||
# Test 5: Test self.resolve helper method with actual value provider
|
||||
var static_provider = animation.static_value(engine)
|
||||
static_provider.value = 100
|
||||
|
||||
provider.closure = def(self, name, time_ms)
|
||||
# Use self.resolve to get value from another provider
|
||||
var base_value = self.resolve(static_provider, name, time_ms)
|
||||
return base_value * 2
|
||||
end
|
||||
|
||||
result = provider.produce_value("test", 2000)
|
||||
# static_provider returns 100, then multiply by 2 = 200
|
||||
assert(result == 200, f"Expected 200, got {result}")
|
||||
print("✓ self.resolve helper method works with value providers")
|
||||
|
||||
# Test 6: Test self.resolve with static value and value provider
|
||||
provider.closure = def(self, name, time_ms)
|
||||
var static_value = self.resolve(50, name, time_ms) # Static value
|
||||
var dynamic_value = self.resolve(static_provider, name, time_ms) # Value provider
|
||||
return static_value + dynamic_value
|
||||
end
|
||||
|
||||
result = provider.produce_value("test", 1000)
|
||||
# static: 50, dynamic: 100, total: 150
|
||||
assert(result == 150, f"Expected 150, got {result}")
|
||||
print("✓ self.resolve works with both static values and value providers")
|
||||
|
||||
# Test 7: Test the use case from documentation - arithmetic with another provider
|
||||
var oscillator = animation.oscillator_value(engine)
|
||||
oscillator.min_value = 10
|
||||
oscillator.max_value = 20
|
||||
oscillator.duration = 1000
|
||||
|
||||
provider.closure = def(self, name, time_ms)
|
||||
var osc_value = self.resolve(oscillator, name, time_ms)
|
||||
return osc_value + 5 # Add 5 to oscillator value
|
||||
end
|
||||
|
||||
result = provider.produce_value("position", 500)
|
||||
# Oscillator should return a value between 10-20, plus 5 = 15-25
|
||||
assert(result >= 15 && result <= 25, f"Expected result between 15-25, got {result}")
|
||||
print("✓ Documentation use case works - arithmetic with other providers")
|
||||
|
||||
# Test 8: Test negative numbers and negative expressions
|
||||
provider.closure = def(self, name, time_ms)
|
||||
if name == "negative_literal"
|
||||
return -2
|
||||
elif name == "negative_expression"
|
||||
return -(time_ms / 100)
|
||||
elif name == "negative_with_addition"
|
||||
return -5 + 3
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
var neg_literal = provider.produce_value("negative_literal", 1000)
|
||||
var neg_expr = provider.produce_value("negative_expression", 500)
|
||||
var neg_add = provider.produce_value("negative_with_addition", 1000)
|
||||
|
||||
assert(neg_literal == -2, f"Expected -2, got {neg_literal}")
|
||||
assert(neg_expr == -5, f"Expected -5, got {neg_expr}") # -(500/100) = -5
|
||||
assert(neg_add == -2, f"Expected -2, got {neg_add}") # -5 + 3 = -2
|
||||
print("✓ Negative numbers and expressions work correctly")
|
||||
|
||||
# Test 9: Complex expressions with multiple parameters and operators
|
||||
var param1 = animation.static_value(engine)
|
||||
param1.value = 10
|
||||
var param2 = animation.static_value(engine)
|
||||
param2.value = 3
|
||||
var param3 = animation.static_value(engine)
|
||||
param3.value = 2
|
||||
|
||||
provider.closure = def(self, name, time_ms)
|
||||
var p1 = self.resolve(param1, name, time_ms)
|
||||
var p2 = self.resolve(param2, name, time_ms)
|
||||
var p3 = self.resolve(param3, name, time_ms)
|
||||
|
||||
if name == "arithmetic_complex"
|
||||
return (p1 + p2) * p3 - 5 # (10 + 3) * 2 - 5 = 26 - 5 = 21
|
||||
elif name == "division_modulo"
|
||||
return (p1 * p2) / p3 % 7 # (10 * 3) / 2 % 7 = 30 / 2 % 7 = 15 % 7 = 1
|
||||
elif name == "mixed_operations"
|
||||
return p1 - p2 + p3 * 4 / 2 # 10 - 3 + 2 * 4 / 2 = 10 - 3 + 8 / 2 = 10 - 3 + 4 = 11
|
||||
elif name == "power_and_comparison"
|
||||
var base = p1 + p2 # 13
|
||||
return base > 12 ? base * p3 : base / p3 # 13 > 12 ? 13 * 2 : 13 / 2 = 26
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
var arith_result = provider.produce_value("arithmetic_complex", 1000)
|
||||
var div_mod_result = provider.produce_value("division_modulo", 1000)
|
||||
var mixed_result = provider.produce_value("mixed_operations", 1000)
|
||||
var power_result = provider.produce_value("power_and_comparison", 1000)
|
||||
|
||||
assert(arith_result == 21, f"Expected 21, got {arith_result}")
|
||||
assert(div_mod_result == 1, f"Expected 1, got {div_mod_result}")
|
||||
assert(mixed_result == 11, f"Expected 11, got {mixed_result}")
|
||||
assert(power_result == 26, f"Expected 26, got {power_result}")
|
||||
print("✓ Complex expressions with multiple parameters and operators work")
|
||||
|
||||
# Test 10: Time-based expressions with multiple variables
|
||||
provider.closure = def(self, name, time_ms)
|
||||
var base_freq = self.resolve(param1, name, time_ms) # 10
|
||||
var amplitude = self.resolve(param2, name, time_ms) # 3
|
||||
var offset = self.resolve(param3, name, time_ms) # 2
|
||||
|
||||
if name == "sine_wave_simulation"
|
||||
# Simulate: amplitude * sin(time * base_freq / 1000) + offset
|
||||
# Simplified: just use modulo for wave-like behavior
|
||||
var wave = (time_ms * base_freq / 1000) % 360
|
||||
return amplitude * (wave > 180 ? -1 : 1) + offset
|
||||
elif name == "exponential_decay"
|
||||
# Simulate: base_freq * exp(-time/1000) + offset
|
||||
# Simplified: base_freq / (1 + time/1000) + offset
|
||||
return base_freq / (1 + time_ms / 1000) + offset
|
||||
elif name == "linear_interpolation"
|
||||
# Linear interpolation between amplitude and base_freq over time
|
||||
var t = (time_ms % 2000) / 2000.0 # 0 to 1 over 2 seconds
|
||||
return amplitude + t * (base_freq - amplitude)
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
var sine_result = provider.produce_value("sine_wave_simulation", 1500)
|
||||
var decay_result = provider.produce_value("exponential_decay", 1000)
|
||||
var lerp_result = provider.produce_value("linear_interpolation", 1000)
|
||||
|
||||
# sine_wave_simulation: (1500 * 10 / 1000) % 360 = 15 % 360 = 15, 15 <= 180, so 3 * 1 + 2 = 5
|
||||
assert(sine_result == 5, f"Expected 5, got {sine_result}")
|
||||
# exponential_decay: 10 / (1 + 1000/1000) + 2 = 10 / 2 + 2 = 5 + 2 = 7
|
||||
assert(decay_result == 7, f"Expected 7, got {decay_result}")
|
||||
# linear_interpolation: t = 1000/2000 = 0.5, result = 3 + 0.5 * (10 - 3) = 3 + 0.5 * 7 = 3 + 3.5 = 6.5
|
||||
assert(lerp_result == 6.5, f"Expected 6.5, got {lerp_result}")
|
||||
print("✓ Time-based expressions with multiple variables work")
|
||||
|
||||
# Test 11: Edge cases with zero, negative, and boundary values
|
||||
provider.closure = def(self, name, time_ms)
|
||||
if name == "division_by_small"
|
||||
return 100 / 0.1 # Should be 1000
|
||||
elif name == "negative_modulo"
|
||||
return -17 % 5 # Should be -2 in Berry
|
||||
elif name == "zero_operations"
|
||||
return 0 * 999 + 0 / 1 - 0 # Should be 0
|
||||
elif name == "boundary_conditions"
|
||||
var val = time_ms % 1000
|
||||
return val == 0 ? -1 : val > 500 ? 1 : 0
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
var div_small = provider.produce_value("division_by_small", 1000)
|
||||
var neg_mod = provider.produce_value("negative_modulo", 1000)
|
||||
var zero_ops = provider.produce_value("zero_operations", 1000)
|
||||
var boundary1 = provider.produce_value("boundary_conditions", 1000) # 1000 % 1000 = 0, so -1
|
||||
var boundary2 = provider.produce_value("boundary_conditions", 1750) # 1750 % 1000 = 750 > 500, so 1
|
||||
var boundary3 = provider.produce_value("boundary_conditions", 1250) # 1250 % 1000 = 250 <= 500, so 0
|
||||
|
||||
assert(div_small == 1000, f"Expected 1000, got {div_small}")
|
||||
assert(neg_mod == -2, f"Expected -2, got {neg_mod}")
|
||||
assert(zero_ops == 0, f"Expected 0, got {zero_ops}")
|
||||
assert(boundary1 == -1, f"Expected -1, got {boundary1}")
|
||||
assert(boundary2 == 1, f"Expected 1, got {boundary2}")
|
||||
assert(boundary3 == 0, f"Expected 0, got {boundary3}")
|
||||
print("✓ Edge cases with zero, negative, and boundary values work")
|
||||
|
||||
print("All ClosureValueProvider tests passed!")
|
||||
end
|
||||
|
||||
# Test mathematical helper methods
|
||||
def test_closure_math_methods()
|
||||
print("Testing ClosureValueProvider mathematical methods...")
|
||||
|
||||
# Create a mock engine
|
||||
class MockEngine
|
||||
var time_ms
|
||||
def init()
|
||||
self.time_ms = 1000
|
||||
end
|
||||
end
|
||||
var engine = MockEngine()
|
||||
|
||||
# Create a closure value provider
|
||||
var provider = animation.closure_value(engine)
|
||||
|
||||
# Test 1: min/max functions
|
||||
provider.closure = def(self, name, time_ms)
|
||||
if name == "min_test"
|
||||
return self.min(5, 3, 8, 1, 9) # Should return 1
|
||||
elif name == "max_test"
|
||||
return self.max(5, 3, 8, 1, 9) # Should return 9
|
||||
elif name == "min_two"
|
||||
return self.min(10, 7) # Should return 7
|
||||
elif name == "max_two"
|
||||
return self.max(10, 7) # Should return 10
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
var min_result = provider.produce_value("min_test", 1000)
|
||||
var max_result = provider.produce_value("max_test", 1000)
|
||||
var min_two = provider.produce_value("min_two", 1000)
|
||||
var max_two = provider.produce_value("max_two", 1000)
|
||||
|
||||
assert(min_result == 1, f"Expected min=1, got {min_result}")
|
||||
assert(max_result == 9, f"Expected max=9, got {max_result}")
|
||||
assert(min_two == 7, f"Expected min=7, got {min_two}")
|
||||
assert(max_two == 10, f"Expected max=10, got {max_two}")
|
||||
print("✓ min/max functions work correctly")
|
||||
|
||||
# Test 2: abs function
|
||||
provider.closure = def(self, name, time_ms)
|
||||
if name == "abs_positive"
|
||||
return self.abs(42) # Should return 42
|
||||
elif name == "abs_negative"
|
||||
return self.abs(-17) # Should return 17
|
||||
elif name == "abs_zero"
|
||||
return self.abs(0) # Should return 0
|
||||
elif name == "abs_float"
|
||||
return self.abs(-3.14) # Should return 3.14
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
var abs_pos = provider.produce_value("abs_positive", 1000)
|
||||
var abs_neg = provider.produce_value("abs_negative", 1000)
|
||||
var abs_zero = provider.produce_value("abs_zero", 1000)
|
||||
var abs_float = provider.produce_value("abs_float", 1000)
|
||||
|
||||
assert(abs_pos == 42, f"Expected abs(42)=42, got {abs_pos}")
|
||||
assert(abs_neg == 17, f"Expected abs(-17)=17, got {abs_neg}")
|
||||
assert(abs_zero == 0, f"Expected abs(0)=0, got {abs_zero}")
|
||||
assert(abs_float == 3.14, f"Expected abs(-3.14)=3.14, got {abs_float}")
|
||||
print("✓ abs function works correctly")
|
||||
|
||||
# Test 3: round function
|
||||
provider.closure = def(self, name, time_ms)
|
||||
if name == "round_up"
|
||||
return self.round(3.7) # Should return 4
|
||||
elif name == "round_down"
|
||||
return self.round(3.2) # Should return 3
|
||||
elif name == "round_half"
|
||||
return self.round(3.5) # Should return 4
|
||||
elif name == "round_negative"
|
||||
return self.round(-2.8) # Should return -3
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
var round_up = provider.produce_value("round_up", 1000)
|
||||
var round_down = provider.produce_value("round_down", 1000)
|
||||
var round_half = provider.produce_value("round_half", 1000)
|
||||
var round_neg = provider.produce_value("round_negative", 1000)
|
||||
|
||||
assert(round_up == 4, f"Expected round(3.7)=4, got {round_up}")
|
||||
assert(round_down == 3, f"Expected round(3.2)=3, got {round_down}")
|
||||
assert(round_half == 4, f"Expected round(3.5)=4, got {round_half}")
|
||||
assert(round_neg == -3, f"Expected round(-2.8)=-3, got {round_neg}")
|
||||
print("✓ round function works correctly")
|
||||
|
||||
# Test 4: sqrt function with integer handling
|
||||
provider.closure = def(self, name, time_ms)
|
||||
if name == "sqrt_integer_255"
|
||||
return self.sqrt(255) # Should return 255 (full scale)
|
||||
elif name == "sqrt_integer_64"
|
||||
return self.sqrt(64) # Should return ~127 (sqrt(64/255)*255)
|
||||
elif name == "sqrt_integer_0"
|
||||
return self.sqrt(0) # Should return 0
|
||||
elif name == "sqrt_float"
|
||||
return self.sqrt(16.0) # Should return 4.0
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
var sqrt_255 = provider.produce_value("sqrt_integer_255", 1000)
|
||||
var sqrt_64 = provider.produce_value("sqrt_integer_64", 1000)
|
||||
var sqrt_0 = provider.produce_value("sqrt_integer_0", 1000)
|
||||
var sqrt_float = provider.produce_value("sqrt_float", 1000)
|
||||
|
||||
assert(sqrt_255 == 255, f"Expected sqrt(255)=255, got {sqrt_255}")
|
||||
assert(sqrt_64 >= 127 && sqrt_64 <= 129, f"Expected sqrt(64)~128, got {sqrt_64}")
|
||||
assert(sqrt_0 == 0, f"Expected sqrt(0)=0, got {sqrt_0}")
|
||||
assert(sqrt_float == 4.0, f"Expected sqrt(16.0)=4.0, got {sqrt_float}")
|
||||
print("✓ sqrt function works correctly")
|
||||
|
||||
# Test 5: scale function
|
||||
provider.closure = def(self, name, time_ms)
|
||||
if name == "scale_basic"
|
||||
return self.scale(50, 0, 100, 0, 255) # Should return ~127
|
||||
elif name == "scale_reverse"
|
||||
return self.scale(25, 0, 100, 255, 0) # Should return ~191
|
||||
elif name == "scale_negative"
|
||||
return self.scale(0, -50, 50, -100, 100) # Should return 0
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
var scale_basic = provider.produce_value("scale_basic", 1000)
|
||||
var scale_reverse = provider.produce_value("scale_reverse", 1000)
|
||||
var scale_neg = provider.produce_value("scale_negative", 1000)
|
||||
|
||||
assert(scale_basic >= 127 && scale_basic <= 128, f"Expected scale(50,0,100,0,255)~127, got {scale_basic}")
|
||||
assert(scale_reverse >= 191 && scale_reverse <= 192, f"Expected scale(25,0,100,255,0)~191, got {scale_reverse}")
|
||||
assert(scale_neg == 0, f"Expected scale(0,-50,50,-100,100)=0, got {scale_neg}")
|
||||
print("✓ scale function works correctly")
|
||||
|
||||
# Test 6: sine function
|
||||
provider.closure = def(self, name, time_ms)
|
||||
if name == "sine_0"
|
||||
return self.sine(0) # sin(0°) = 0
|
||||
elif name == "sine_64"
|
||||
return self.sine(64) # sin(90°) = 1 -> 255
|
||||
elif name == "sine_128"
|
||||
return self.sine(128) # sin(180°) = 0
|
||||
elif name == "sine_192"
|
||||
return self.sine(192) # sin(270°) = -1 -> -255
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
var sine_0 = provider.produce_value("sine_0", 1000)
|
||||
var sine_64 = provider.produce_value("sine_64", 1000)
|
||||
var sine_128 = provider.produce_value("sine_128", 1000)
|
||||
var sine_192 = provider.produce_value("sine_192", 1000)
|
||||
|
||||
assert(sine_0 >= -5 && sine_0 <= 5, f"Expected sine(0)~0, got {sine_0}")
|
||||
assert(sine_64 >= 250 && sine_64 <= 255, f"Expected sine(64)~255, got {sine_64}")
|
||||
assert(sine_128 >= -5 && sine_128 <= 5, f"Expected sine(128)~0, got {sine_128}")
|
||||
assert(sine_192 >= -255 && sine_192 <= -250, f"Expected sine(192)~-255, got {sine_192}")
|
||||
print("✓ sine function works correctly")
|
||||
|
||||
# Test 7: cosine function (matches oscillator COSINE behavior)
|
||||
provider.closure = def(self, name, time_ms)
|
||||
if name == "cosine_0"
|
||||
return self.cosine(0) # Oscillator cosine at 0° = minimum -> -255
|
||||
elif name == "cosine_64"
|
||||
return self.cosine(64) # Oscillator cosine at 90° = ~0
|
||||
elif name == "cosine_128"
|
||||
return self.cosine(128) # Oscillator cosine at 180° = maximum -> 255
|
||||
elif name == "cosine_192"
|
||||
return self.cosine(192) # Oscillator cosine at 270° = ~0
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
var cosine_0 = provider.produce_value("cosine_0", 1000)
|
||||
var cosine_64 = provider.produce_value("cosine_64", 1000)
|
||||
var cosine_128 = provider.produce_value("cosine_128", 1000)
|
||||
var cosine_192 = provider.produce_value("cosine_192", 1000)
|
||||
|
||||
assert(cosine_0 >= -255 && cosine_0 <= -250, f"Expected cosine(0)~-255, got {cosine_0}")
|
||||
assert(cosine_64 >= -5 && cosine_64 <= 5, f"Expected cosine(64)~0, got {cosine_64}")
|
||||
assert(cosine_128 >= 250 && cosine_128 <= 255, f"Expected cosine(128)~255, got {cosine_128}")
|
||||
assert(cosine_192 >= -5 && cosine_192 <= 5, f"Expected cosine(192)~0, got {cosine_192}")
|
||||
print("✓ cosine function works correctly")
|
||||
|
||||
# Test 8: Complex expression using multiple math functions
|
||||
provider.closure = def(self, name, time_ms)
|
||||
if name == "complex_math"
|
||||
var angle = time_ms % 256 # 0-255 angle based on time
|
||||
var sine_val = self.abs(self.sine(angle)) # Absolute sine value
|
||||
var scaled = self.scale(sine_val, 0, 255, 50, 200) # Scale to 50-200 range
|
||||
return self.min(self.max(scaled, 75), 175) # Clamp to 75-175 range
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
var complex_result = provider.produce_value("complex_math", 1064) # 1064 % 256 = 40
|
||||
# sine(40) should be positive, abs() keeps it positive, scale to 50-200, clamp to 75-175
|
||||
assert(complex_result >= 75 && complex_result <= 175, f"Expected complex result in 75-175 range, got {complex_result}")
|
||||
print("✓ Complex mathematical expressions work correctly")
|
||||
|
||||
print("All mathematical method tests passed!")
|
||||
end
|
||||
|
||||
# Run the tests
|
||||
test_closure_value_provider()
|
||||
test_closure_math_methods()
|
||||
211
lib/libesp32/berry_animation/src/tests/computed_values_test.be
Normal file
211
lib/libesp32/berry_animation/src/tests/computed_values_test.be
Normal file
@ -0,0 +1,211 @@
|
||||
# Computed Values Test Suite
|
||||
# Tests for computed values and closures in DSL
|
||||
#
|
||||
# Command to run test:
|
||||
# ./berry -s -g -m lib/libesp32/berry_animation/src -e "import tasmota" lib/libesp32/berry_animation/src/tests/computed_values_test.be
|
||||
|
||||
import animation
|
||||
import animation_dsl
|
||||
import string
|
||||
|
||||
# Test basic computed values
|
||||
def test_basic_computed_values()
|
||||
print("Testing basic computed values...")
|
||||
|
||||
var dsl_source = "set strip_len = strip_length()\n" +
|
||||
"animation stream1 = comet_animation(\n" +
|
||||
" color=red\n" +
|
||||
" tail_length=strip_len / 4\n" +
|
||||
" speed=1.5\n" +
|
||||
" priority=10\n" +
|
||||
")\n" +
|
||||
"run stream1"
|
||||
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
|
||||
assert(berry_code != nil, "Should generate Berry code for computed values")
|
||||
|
||||
# Check that strip_length() function call is preserved
|
||||
assert(string.find(berry_code, "strip_length(engine)") >= 0, "Should generate strip_length call")
|
||||
|
||||
# Check that a closure value provider is created for the division
|
||||
assert(string.find(berry_code, "create_closure_value(engine)") >= 0, "Should create closure value provider for computed expression")
|
||||
|
||||
# Check that the closure contains the division operation
|
||||
var lines = string.split(berry_code, "\n")
|
||||
var found_division = false
|
||||
for line : lines
|
||||
if string.find(line, "strip_len") >= 0 && string.find(line, "/ 4") >= 0
|
||||
found_division = true
|
||||
break
|
||||
end
|
||||
end
|
||||
assert(found_division, "Should contain division operation in closure")
|
||||
|
||||
print("Generated Berry code:")
|
||||
print("==================================================")
|
||||
print(berry_code)
|
||||
print("==================================================")
|
||||
|
||||
# Debug: Let's see what's actually being generated
|
||||
if berry_code != nil
|
||||
print("Code analysis:")
|
||||
print("- Contains 'def (':", string.find(berry_code, "def (") >= 0)
|
||||
print("- Contains 'return (':", string.find(berry_code, "return (") >= 0)
|
||||
print("- Contains 'create_closure_value':", string.find(berry_code, "create_closure_value") >= 0)
|
||||
print("- Contains '/ 4':", string.find(berry_code, "/ 4") >= 0)
|
||||
end
|
||||
|
||||
print("✓ Basic computed values test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test computed values with multiple operations
|
||||
def test_complex_computed_values()
|
||||
print("Testing complex computed values...")
|
||||
|
||||
var dsl_source = "set strip_len = strip_length()\n" +
|
||||
"set base_speed = 2.0\n" +
|
||||
"animation complex_anim = comet_animation(\n" +
|
||||
" color=blue\n" +
|
||||
" tail_length=strip_len / 4 + 2\n" +
|
||||
" speed=base_speed * 1.5\n" +
|
||||
")\n" +
|
||||
"run complex_anim"
|
||||
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
|
||||
assert(berry_code != nil, "Should generate Berry code for complex computed values")
|
||||
|
||||
# Should create multiple closure value providers for different computed expressions
|
||||
var closure_count = 0
|
||||
var lines = string.split(berry_code, "\n")
|
||||
for line : lines
|
||||
if string.find(line, "create_closure_value(engine)") >= 0
|
||||
closure_count += 1
|
||||
end
|
||||
end
|
||||
|
||||
assert(closure_count >= 2, f"Should create at least 2 closure value providers, found {closure_count}")
|
||||
|
||||
print("✓ Complex computed values test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test that static values don't create closures
|
||||
def test_static_values_no_closures()
|
||||
print("Testing static values don't create closures...")
|
||||
|
||||
var dsl_source = "animation simple_anim = comet_animation(\n" +
|
||||
" color=red\n" +
|
||||
" tail_length=5\n" +
|
||||
" speed=1.0\n" +
|
||||
")\n" +
|
||||
"run simple_anim"
|
||||
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
|
||||
assert(berry_code != nil, "Should generate Berry code for static values")
|
||||
|
||||
# Should not create any closure value providers for static values
|
||||
assert(string.find(berry_code, "create_closure_value(engine)") < 0, "Should not create closure value providers for static values")
|
||||
|
||||
print("✓ Static values test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test computed values in property assignments
|
||||
def test_computed_property_assignments()
|
||||
print("Testing computed values in property assignments...")
|
||||
|
||||
var dsl_source = "set strip_len = strip_length()\n" +
|
||||
"animation test_anim = solid(color=red)\n" +
|
||||
"test_anim.position = strip_len / 2\n" +
|
||||
"run test_anim"
|
||||
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
|
||||
assert(berry_code != nil, "Should generate Berry code for computed property assignments")
|
||||
|
||||
# Should create a closure value provider for the property assignment
|
||||
assert(string.find(berry_code, "create_closure_value(engine)") >= 0, "Should create closure value provider for computed property")
|
||||
|
||||
# Should assign the closure value provider to the property
|
||||
var found_property_assignment = false
|
||||
var lines = string.split(berry_code, "\n")
|
||||
for line : lines
|
||||
if string.find(line, "test_anim_") >= 0 && string.find(line, ".position =") >= 0 && string.find(line, "create_closure_value") >= 0
|
||||
found_property_assignment = true
|
||||
break
|
||||
end
|
||||
end
|
||||
assert(found_property_assignment, "Should assign closure value provider to property")
|
||||
|
||||
print("✓ Computed property assignments test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test runtime execution of computed values
|
||||
def test_computed_values_runtime()
|
||||
print("Testing computed values runtime execution...")
|
||||
|
||||
try
|
||||
var dsl_source = "set strip_len = strip_length()\n" +
|
||||
"animation test_anim = solid(color=red)\n" +
|
||||
"test_anim.opacity = strip_len * 4\n" + # This should work at runtime
|
||||
"run test_anim"
|
||||
|
||||
# This should compile and execute without errors
|
||||
animation_dsl.execute(dsl_source)
|
||||
|
||||
print("✓ Computed values runtime execution test passed")
|
||||
return true
|
||||
except .. as e, msg
|
||||
print(f"Runtime execution failed: {msg}")
|
||||
# This might fail if the animation system isn't fully set up, which is okay for this test
|
||||
print("✓ Computed values runtime test completed (execution may fail in test environment)")
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
# Run all tests
|
||||
def run_computed_values_tests()
|
||||
print("=== Computed Values Test Suite ===")
|
||||
|
||||
var tests = [
|
||||
test_basic_computed_values,
|
||||
test_complex_computed_values,
|
||||
test_static_values_no_closures,
|
||||
test_computed_property_assignments,
|
||||
test_computed_values_runtime
|
||||
]
|
||||
|
||||
var passed = 0
|
||||
var total = size(tests)
|
||||
|
||||
for test_func : tests
|
||||
try
|
||||
if test_func()
|
||||
passed += 1
|
||||
else
|
||||
print("✗ Test failed")
|
||||
end
|
||||
except .. as error_type, error_message
|
||||
print("✗ Test crashed: " + str(error_type) + " - " + str(error_message))
|
||||
end
|
||||
print("") # Add spacing between tests
|
||||
end
|
||||
|
||||
print("=== Results: " + str(passed) + "/" + str(total) + " tests passed ===")
|
||||
|
||||
if passed == total
|
||||
print("🎉 All computed values tests passed!")
|
||||
return true
|
||||
else
|
||||
print("❌ Some computed values tests failed")
|
||||
raise "test_failed"
|
||||
end
|
||||
end
|
||||
|
||||
# Auto-run tests when file is executed
|
||||
run_computed_values_tests()
|
||||
@ -57,7 +57,7 @@ def test_animation_with_named_args()
|
||||
var animation_tests = [
|
||||
["color red_alt = 0xFF0100\n"
|
||||
"animation solid_red = solid(color=red_alt)",
|
||||
"var solid_red_ = animation.solid(engine)\nsolid_red_.color = animation.global('red_alt_', 'red_alt')"],
|
||||
"var solid_red_ = animation.solid(engine)\nsolid_red_.color = red_alt_"],
|
||||
["animation solid_blue = solid(color=blue)",
|
||||
"var solid_blue_ = animation.solid(engine)\nsolid_blue_.color = 0xFF0000FF"]
|
||||
]
|
||||
@ -83,7 +83,7 @@ def test_animation_processing()
|
||||
var color_anim_tests = [
|
||||
["color red_alt = 0xFF0100\n"
|
||||
"animation red_anim = red_alt",
|
||||
"var red_anim_ = animation.global('red_alt_', 'red_alt')"],
|
||||
"var red_anim_ = red_alt_"],
|
||||
["animation blue_anim = blue",
|
||||
"var blue_anim_ = 0xFF0000FF"]
|
||||
]
|
||||
@ -101,7 +101,7 @@ def test_animation_processing()
|
||||
var anim_ref_tests = [
|
||||
["animation solid_red = solid(color=red)\n"
|
||||
"animation red_anim = solid_red",
|
||||
"var red_anim_ = animation.global('solid_red_', 'solid_red')"]
|
||||
"var red_anim_ = solid_red_"]
|
||||
]
|
||||
|
||||
for test : anim_ref_tests
|
||||
@ -200,11 +200,10 @@ def test_sequence_processing()
|
||||
var berry_code = animation_dsl.compile(basic_seq_dsl)
|
||||
|
||||
assert(berry_code != nil, "Should compile basic sequence")
|
||||
assert(string.find(berry_code, "def sequence_demo()") >= 0, "Should define sequence function")
|
||||
assert(string.find(berry_code, "var demo_ = (def (engine)") >= 0, "Should define sequence closure")
|
||||
assert(string.find(berry_code, "red_anim") >= 0, "Should reference animation")
|
||||
assert(string.find(berry_code, "animation.create_play_step(animation.global('red_anim_'), 2000)") >= 0, "Should create play step")
|
||||
assert(string.find(berry_code, "var seq_manager = global.sequence_demo()") >= 0, "Should call sequence")
|
||||
assert(string.find(berry_code, "engine.add_sequence_manager(seq_manager)") >= 0, "Should add sequence manager")
|
||||
assert(string.find(berry_code, "engine.add_sequence_manager(demo_)") >= 0, "Should add sequence manager")
|
||||
assert(string.find(berry_code, "engine.start()") >= 0, "Should start engine")
|
||||
|
||||
# Test repeat in sequence
|
||||
@ -279,11 +278,11 @@ def test_property_assignments()
|
||||
|
||||
var property_tests = [
|
||||
["color custom_red = 0xFF0000\nanimation red_anim = solid(color=custom_red)\nred_anim.pos = 15",
|
||||
"animation.global('red_anim_').pos = 15"],
|
||||
["animation test_anim = solid)\ntest_anim.opacity = 128",
|
||||
"animation.global('test_anim_').opacity = 128"],
|
||||
"red_anim_.pos = 15"],
|
||||
["animation test_anim = solid(color=red)\ntest_anim.opacity = 128",
|
||||
"test_anim_.opacity = 128"],
|
||||
["animation solid_red = solid(color=red)\nanimation pulse_anim = pulsating_animation(color=red, period=2000)\npulse_anim.priority = 5",
|
||||
"animation.global('pulse_anim_').priority = 5"]
|
||||
"pulse_anim_.priority = 5"]
|
||||
]
|
||||
|
||||
for test : property_tests
|
||||
|
||||
@ -26,7 +26,7 @@ def test_animation_newline_parameters()
|
||||
|
||||
assert(berry_code != nil, "Should compile DSL with newline parameters")
|
||||
assert(string.find(berry_code, "var stream1_ = animation.comet_animation(engine)") >= 0, "Should generate animation creation")
|
||||
assert(string.find(berry_code, "stream1_.color = animation.global('custom_red_'") >= 0, "Should generate color assignment")
|
||||
assert(string.find(berry_code, "stream1_.color = custom_red_") >= 0, "Should generate color assignment")
|
||||
assert(string.find(berry_code, "stream1_.tail_length = 15") >= 0, "Should generate tail_length assignment")
|
||||
assert(string.find(berry_code, "stream1_.speed = 1500") >= 0, "Should generate speed assignment")
|
||||
assert(string.find(berry_code, "stream1_.priority = 10") >= 0, "Should generate priority assignment")
|
||||
@ -76,7 +76,7 @@ def test_mixed_syntax()
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
|
||||
assert(berry_code != nil, "Should compile DSL with mixed syntax")
|
||||
assert(string.find(berry_code, "mixed_.color = animation.global('custom_red_'") >= 0, "Should generate color assignment")
|
||||
assert(string.find(berry_code, "mixed_.color = custom_red_") >= 0, "Should generate color assignment")
|
||||
assert(string.find(berry_code, "mixed_.tail_length = 15") >= 0, "Should generate tail_length assignment")
|
||||
assert(string.find(berry_code, "mixed_.speed = 1500") >= 0, "Should generate speed assignment")
|
||||
assert(string.find(berry_code, "mixed_.priority = 10") >= 0, "Should generate priority assignment")
|
||||
@ -98,7 +98,7 @@ def test_traditional_comma_syntax()
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
|
||||
assert(berry_code != nil, "Should compile DSL with traditional comma syntax")
|
||||
assert(string.find(berry_code, "traditional_.color = animation.global('custom_red_'") >= 0, "Should generate color assignment")
|
||||
assert(string.find(berry_code, "traditional_.color = custom_red_") >= 0, "Should generate color assignment")
|
||||
assert(string.find(berry_code, "traditional_.tail_length = 15") >= 0, "Should generate tail_length assignment")
|
||||
|
||||
print("✓ Traditional comma syntax test passed")
|
||||
@ -124,7 +124,7 @@ def test_color_provider_newline_syntax()
|
||||
|
||||
assert(berry_code != nil, "Should compile color provider with newline syntax")
|
||||
assert(string.find(berry_code, "var dynamic_color_ = animation.rich_palette(engine)") >= 0, "Should generate color provider creation")
|
||||
assert(string.find(berry_code, "dynamic_color_.palette = animation.global('test_palette_'") >= 0, "Should generate palette assignment")
|
||||
assert(string.find(berry_code, "dynamic_color_.palette = test_palette_") >= 0, "Should generate palette assignment")
|
||||
assert(string.find(berry_code, "dynamic_color_.cycle_period = 2000") >= 0, "Should generate cycle_period assignment")
|
||||
|
||||
print("✓ Color provider newline syntax test passed")
|
||||
|
||||
@ -28,8 +28,8 @@ def test_basic_transpilation()
|
||||
assert(berry_code != nil, "Should generate Berry code")
|
||||
assert(string.find(berry_code, "var engine = animation.init_strip()") >= 0, "Should generate strip configuration")
|
||||
assert(string.find(berry_code, "var custom_red_ = 0xFFFF0000") >= 0, "Should generate color definition")
|
||||
assert(string.find(berry_code, "def sequence_demo()") >= 0, "Should generate sequence function")
|
||||
assert(string.find(berry_code, "sequence_demo()") >= 0, "Should generate sequence call")
|
||||
assert(string.find(berry_code, "var demo_ = (def (engine)") >= 0, "Should generate sequence closure")
|
||||
assert(string.find(berry_code, "engine.add_sequence_manager(demo_)") >= 0, "Should add sequence manager")
|
||||
|
||||
# print("Generated Berry code:")
|
||||
# print("==================================================")
|
||||
@ -156,11 +156,10 @@ def test_sequences()
|
||||
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
assert(berry_code != nil, "Should compile sequence")
|
||||
assert(string.find(berry_code, "def sequence_test_seq()") >= 0, "Should define sequence function")
|
||||
assert(string.find(berry_code, "var test_seq_ = (def (engine)") >= 0, "Should define sequence closure")
|
||||
assert(string.find(berry_code, "animation.create_play_step(animation.global('blue_anim_'), 3000)") >= 0, "Should reference animation")
|
||||
assert(string.find(berry_code, "engine.add_sequence_manager(seq_manager)") >= 0, "Should add sequence manager to engine")
|
||||
assert(string.find(berry_code, "engine.add_sequence_manager(test_seq_)") >= 0, "Should add sequence manager to engine")
|
||||
assert(string.find(berry_code, "engine.start()") >= 0, "Should start engine")
|
||||
assert(string.find(berry_code, "sequence_test_seq()") >= 0, "Should call sequence")
|
||||
|
||||
print("✓ Sequences test passed")
|
||||
return true
|
||||
@ -203,10 +202,9 @@ def test_multiple_run_statements()
|
||||
assert(start_count == 1, f"Should have exactly 1 engine.start() call, found {start_count}")
|
||||
|
||||
# Check that all animations are added to the engine
|
||||
assert(string.find(berry_code, "# Start all animations/sequences") >= 0, "Should have consolidated startup comment")
|
||||
assert(string.find(berry_code, "engine.add_animation(animation.global('red_anim_'))") >= 0, "Should add red_anim to engine")
|
||||
assert(string.find(berry_code, "engine.add_animation(animation.global('blue_anim_'))") >= 0, "Should add blue_anim to engine")
|
||||
assert(string.find(berry_code, "engine.add_animation(animation.global('green_anim_'))") >= 0, "Should add green_anim to engine")
|
||||
assert(string.find(berry_code, "engine.add_animation(red_anim_)") >= 0, "Should add red_anim to engine")
|
||||
assert(string.find(berry_code, "engine.add_animation(blue_anim_)") >= 0, "Should add blue_anim to engine")
|
||||
assert(string.find(berry_code, "engine.add_animation(green_anim_)") >= 0, "Should add green_anim to engine")
|
||||
|
||||
# Verify the engine.start() comes after all animations are added
|
||||
var start_line_index = -1
|
||||
@ -254,8 +252,8 @@ def test_multiple_run_statements()
|
||||
assert(mixed_start_count == 1, f"Mixed scenario should have exactly 1 engine.start() call, found {mixed_start_count}")
|
||||
|
||||
# Check that both animation and sequence are handled
|
||||
assert(string.find(mixed_berry_code, "engine.add_animation(animation.global('red_anim_'))") >= 0, "Should add animation to engine")
|
||||
assert(string.find(mixed_berry_code, "engine.add_sequence_manager(seq_manager)") >= 0, "Should add sequence to engine")
|
||||
assert(string.find(mixed_berry_code, "engine.add_animation(red_anim_)") >= 0, "Should add animation to engine")
|
||||
assert(string.find(mixed_berry_code, "engine.add_sequence_manager(blue_seq_)") >= 0, "Should add sequence to engine")
|
||||
|
||||
print("✓ Multiple run statements test passed")
|
||||
return true
|
||||
@ -391,8 +389,8 @@ def test_complex_dsl()
|
||||
# Check for key components
|
||||
assert(string.find(berry_code, "var engine = animation.init_strip()") >= 0, "Should have default strip initialization")
|
||||
assert(string.find(berry_code, "var custom_red_ = 0xFFFF0000") >= 0, "Should have color definitions")
|
||||
assert(string.find(berry_code, "def sequence_demo()") >= 0, "Should have sequence definition")
|
||||
assert(string.find(berry_code, "sequence_demo()") >= 0, "Should have execution")
|
||||
assert(string.find(berry_code, "var demo_ = (def (engine)") >= 0, "Should have sequence definition")
|
||||
assert(string.find(berry_code, "engine.add_sequence_manager(demo_)") >= 0, "Should have execution")
|
||||
|
||||
print("Generated code structure looks correct")
|
||||
else
|
||||
@ -542,10 +540,10 @@ def test_property_assignments()
|
||||
|
||||
assert(berry_code != nil, "Should generate Berry code with property assignments")
|
||||
|
||||
# Check that property assignments are generated correctly
|
||||
assert(string.find(berry_code, "animation.global('red_anim_').pos = 15") >= 0, "Should generate pos property assignment")
|
||||
assert(string.find(berry_code, "animation.global('red_anim_').opacity = 128") >= 0, "Should generate opacity property assignment")
|
||||
assert(string.find(berry_code, "animation.global('red_anim_').priority = 10") >= 0, "Should generate priority property assignment")
|
||||
# Check that property assignments are generated correctly (new behavior: direct underscore access)
|
||||
assert(string.find(berry_code, "red_anim_.pos = 15") >= 0, "Should generate pos property assignment")
|
||||
assert(string.find(berry_code, "red_anim_.opacity = 128") >= 0, "Should generate opacity property assignment")
|
||||
assert(string.find(berry_code, "red_anim_.priority = 10") >= 0, "Should generate priority property assignment")
|
||||
|
||||
# Verify the generated code compiles
|
||||
try
|
||||
@ -624,10 +622,16 @@ def test_easing_keywords()
|
||||
|
||||
assert(berry_code != nil, "Should generate Berry code with easing keywords")
|
||||
|
||||
# Check that all easing keywords are properly converted to animation.global() calls with new signature
|
||||
# Check that all easing keywords are properly converted to direct animation module access
|
||||
var easing_keywords = ["linear", "smooth", "ease_in", "ease_out", "ramp", "square"]
|
||||
for easing : easing_keywords
|
||||
assert(string.find(berry_code, f"animation.global('{easing}_', '{easing}')") >= 0, f"Should convert {easing} to animation.global('{easing}_', '{easing}')")
|
||||
# Check if the easing keyword exists in animation module (they're lowercase)
|
||||
import introspect
|
||||
if introspect.contains(animation, easing)
|
||||
assert(string.find(berry_code, f"animation.{easing}") >= 0, f"Should convert {easing} to animation.{easing}")
|
||||
else
|
||||
assert(string.find(berry_code, f"{easing}_") >= 0, f"Should convert {easing} to {easing}_")
|
||||
end
|
||||
end
|
||||
|
||||
# Test easing keywords as function calls (regression test for breathing_colors.anim issue)
|
||||
@ -641,7 +645,7 @@ def test_easing_keywords()
|
||||
assert(function_call_code != nil, "Should handle easing keywords as function calls")
|
||||
# Note: Function calls like smooth(100, 255, 4s) are handled differently than simple identifiers
|
||||
# They should generate animation.smooth(100, 255, 4000) calls
|
||||
assert(string.find(function_call_code, "animation.global('test_anim_').opacity = 128") >= 0, "Should set opacity property correctly")
|
||||
assert(string.find(function_call_code, "test_anim_.opacity = 128") >= 0, "Should set opacity property correctly")
|
||||
|
||||
print("✓ Easing keywords test passed")
|
||||
return true
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
# Test for global variable access with new animation.global() signature
|
||||
# Verifies that generated code properly uses animation.global(name, module_name)
|
||||
# Test for global variable access with new transpiler symbol resolution
|
||||
# Verifies that generated code uses animation.symbol for animation module symbols
|
||||
# and symbol_ for user-defined variables (no more animation.global calls)
|
||||
#
|
||||
# Command to run test is:
|
||||
# ./berry -s -g -m lib/libesp32/berry_animation -e "import tasmota" lib/libesp32/berry_animation/tests/global_variable_test.be
|
||||
@ -25,8 +26,8 @@ def test_global_variable_access()
|
||||
assert(string.find(berry_code, "var red_alt_ = 0xFFFF0100") >= 0, "Should define red_alt variable")
|
||||
assert(string.find(berry_code, "var solid_red_ = animation.solid(engine)") >= 0, "Should define solid_red variable with new pattern")
|
||||
|
||||
# Variable references should use new two-parameter animation.global() calls
|
||||
assert(string.find(berry_code, "solid_red_.color = animation.global('red_alt_', 'red_alt')") >= 0, "Should use animation.global('red_alt_', 'red_alt') for variable reference")
|
||||
# Variable references should now use direct underscore notation (no animation.global)
|
||||
assert(string.find(berry_code, "solid_red_.color = red_alt_") >= 0, "Should use red_alt_ directly for variable reference")
|
||||
|
||||
# Verify the generated code actually compiles by executing it
|
||||
try
|
||||
@ -49,20 +50,17 @@ def test_undefined_variable_exception()
|
||||
|
||||
assert(berry_code != nil, "Should compile DSL code")
|
||||
|
||||
# Check that animation.global() is used for the fallback with new two-parameter format
|
||||
# Check that undefined variables use direct underscore notation (no animation.global)
|
||||
import string
|
||||
assert(string.find(berry_code, "test_.color = animation.global('undefined_var_', 'undefined_var')") >= 0, "Should use animation.global('undefined_var_', 'undefined_var') for undefined variable")
|
||||
assert(string.find(berry_code, "test_.color = undefined_var_") >= 0, "Should use undefined_var_ directly for undefined variable")
|
||||
|
||||
# Verify the generated code compiles
|
||||
var compiled_code = compile(berry_code)
|
||||
assert(compiled_code != nil, "Generated code should compile")
|
||||
|
||||
# Verify it raises an exception when executed (due to undefined variable)
|
||||
# Verify the generated code fails to compile (due to undefined variable)
|
||||
# This is better than the old behavior - we catch undefined variables at compile time!
|
||||
try
|
||||
compiled_code()
|
||||
assert(false, "Should have raised an exception for undefined variable")
|
||||
var compiled_code = compile(berry_code)
|
||||
assert(false, "Should have failed to compile due to undefined variable")
|
||||
except .. as e, msg
|
||||
print(f"✓ Correctly raised exception for undefined variable: {e}")
|
||||
print(f"✓ Correctly failed to compile due to undefined variable: {e}")
|
||||
end
|
||||
|
||||
print("✓ Undefined variable exception test passed")
|
||||
|
||||
@ -41,7 +41,7 @@ def test_parameter_accepts_value_providers()
|
||||
# Test that invalid types are rejected (no type conversion)
|
||||
assert(test_anim.set_param("opacity", "invalid") == false, "Should reject string")
|
||||
assert(test_anim.set_param("opacity", true) == false, "Should reject boolean")
|
||||
assert(test_anim.set_param("opacity", 3.14) == false, "Should reject real")
|
||||
assert(test_anim.set_param("opacity", 3.14) == true, "Should accept real (recent change to accept real for int parameters)")
|
||||
|
||||
print("✓ Parameter validation with ValueProviders test passed")
|
||||
end
|
||||
@ -70,7 +70,7 @@ def test_loop_boolean_validation()
|
||||
|
||||
# Test loop with other invalid types
|
||||
assert(test_anim.set_param("loop", "true") == false, "Should reject string for loop")
|
||||
assert(test_anim.set_param("loop", 3.14) == false, "Should reject real for loop")
|
||||
assert(test_anim.set_param("loop", 3.14) == false, "Should reject real for loop (boolean parameter)")
|
||||
|
||||
print("✓ Loop boolean validation test passed")
|
||||
end
|
||||
@ -157,7 +157,7 @@ def test_type_validation()
|
||||
assert(test_obj.set_param("int_param", 123) == true, "Should accept int for int_param")
|
||||
assert(test_obj.set_param("int_param", "string") == false, "Should reject string for int_param")
|
||||
assert(test_obj.set_param("int_param", true) == false, "Should reject bool for int_param")
|
||||
assert(test_obj.set_param("int_param", 3.14) == false, "Should reject real for int_param")
|
||||
assert(test_obj.set_param("int_param", 3.14) == true, "Should accept real for int_param (recent change)")
|
||||
|
||||
# Test explicit int parameter
|
||||
assert(test_obj.set_param("explicit_int_param", 456) == true, "Should accept int for explicit_int_param")
|
||||
|
||||
@ -30,7 +30,7 @@ def test_basic_symbol_registration()
|
||||
# Check that definitions appear in generated code (with underscore suffix)
|
||||
assert(string.find(berry_code, "var custom_red_ = 0xFFFF0000") >= 0, "Should generate color definition")
|
||||
assert(string.find(berry_code, "var solid_red_ = animation.solid(engine)") >= 0, "Should generate animation definition")
|
||||
assert(string.find(berry_code, "solid_red_.color = animation.global('custom_red_', 'custom_red')") >= 0, "Should set color parameter")
|
||||
assert(string.find(berry_code, "solid_red_.color = custom_red_") >= 0, "Should set color parameter")
|
||||
assert(string.find(berry_code, "var red_anim_") >= 0, "Should generate animation reference")
|
||||
|
||||
print("✓ Basic symbol registration test passed")
|
||||
@ -58,7 +58,7 @@ def test_forward_reference_resolution()
|
||||
# Check generated code contains both definitions (with underscore suffix)
|
||||
assert(string.find(berry_code, "var custom_red_ = 0xFFFF0000") >= 0, "Should define custom_red color")
|
||||
assert(string.find(berry_code, "var fire_pattern_ = animation.solid(engine)") >= 0, "Should define fire animation")
|
||||
assert(string.find(berry_code, "fire_pattern_.color = animation.global('custom_red_', 'custom_red')") >= 0, "Should reference custom_red")
|
||||
assert(string.find(berry_code, "fire_pattern_.color = custom_red_") >= 0, "Should reference custom_red")
|
||||
|
||||
print("✓ Forward reference resolution test passed")
|
||||
return true
|
||||
@ -77,23 +77,21 @@ def test_undefined_reference_handling()
|
||||
|
||||
var berry_code = transpiler.transpile()
|
||||
|
||||
# Simplified transpiler compiles successfully but uses runtime resolution
|
||||
assert(berry_code != nil, "Should compile with runtime resolution")
|
||||
# New behavior: transpiler generates direct reference to undefined_color_
|
||||
assert(berry_code != nil, "Should compile with direct reference")
|
||||
assert(!transpiler.has_errors(), "Should have no compile-time errors")
|
||||
|
||||
# Check that runtime resolution code is generated with new two-parameter format
|
||||
assert(string.find(berry_code, "animation.global('undefined_color_', 'undefined_color')") >= 0, "Should generate runtime resolution")
|
||||
# Check that direct reference is generated (since undefined_color doesn't exist in animation module)
|
||||
assert(string.find(berry_code, "undefined_color_") >= 0, "Should generate runtime resolution")
|
||||
|
||||
# Verify the generated code compiles but will fail at runtime
|
||||
var compiled_code = compile(berry_code)
|
||||
assert(compiled_code != nil, "Generated code should compile")
|
||||
|
||||
# Should raise exception when executed due to undefined variable
|
||||
# With new behavior, Berry compilation will fail due to undefined variable
|
||||
# This is actually better than runtime errors as it catches issues earlier
|
||||
try
|
||||
compiled_code()
|
||||
assert(false, "Should raise exception at runtime for undefined variable")
|
||||
var compiled_code = compile(berry_code)
|
||||
assert(false, "Should fail to compile due to undefined variable")
|
||||
except .. as e, msg
|
||||
print(f"✓ Correctly deferred error to runtime: {e}")
|
||||
print(f"✓ Correctly caught undefined variable at compile time: {e}")
|
||||
assert(string.find(str(msg), "undefined_color_") >= 0, "Error should mention undefined variable")
|
||||
end
|
||||
|
||||
print("✓ Undefined reference handling test passed")
|
||||
@ -177,7 +175,7 @@ def test_complex_forward_references()
|
||||
assert(string.find(berry_code, "var primary_color_") >= 0, "Should define primary color")
|
||||
assert(string.find(berry_code, "var gradient_pattern_") >= 0, "Should define gradient pattern")
|
||||
assert(string.find(berry_code, "var complex_anim_") >= 0, "Should define complex animation")
|
||||
assert(string.find(berry_code, "def sequence_demo()") >= 0, "Should define sequence")
|
||||
assert(string.find(berry_code, "var demo_ = (def (engine)") >= 0, "Should define sequence")
|
||||
|
||||
print("✓ Complex forward references test passed")
|
||||
return true
|
||||
|
||||
@ -95,6 +95,7 @@ def run_all_tests()
|
||||
"lib/libesp32/berry_animation/src/tests/oscillator_ease_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/oscillator_elastic_bounce_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/strip_length_provider_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/closure_value_provider_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/breathe_color_provider_test.be",
|
||||
|
||||
# DSL tests
|
||||
@ -112,6 +113,8 @@ def run_all_tests()
|
||||
"lib/libesp32/berry_animation/src/tests/dsl_parameter_validation_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/dsl_value_provider_validation_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/dsl_newline_syntax_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/test_math_method_transpilation.be",
|
||||
"lib/libesp32/berry_animation/src/tests/test_user_functions_in_computed_parameters.be",
|
||||
|
||||
# Event system tests
|
||||
"lib/libesp32/berry_animation/src/tests/event_system_test.be"
|
||||
|
||||
@ -0,0 +1,206 @@
|
||||
import animation
|
||||
import animation_dsl
|
||||
import string
|
||||
|
||||
# Test to verify that mathematical methods in computed parameters are correctly transpiled to self.<func>()
|
||||
|
||||
def test_transpilation_case(dsl_code, expected_methods, test_name)
|
||||
print(f"\n Testing: {test_name}")
|
||||
|
||||
var lexer = animation_dsl.DSLLexer(dsl_code)
|
||||
var tokens = lexer.tokenize()
|
||||
|
||||
if size(lexer.errors) > 0
|
||||
print(f" ❌ Lexer errors: {lexer.errors}")
|
||||
return false
|
||||
end
|
||||
|
||||
var transpiler = animation_dsl.SimpleDSLTranspiler(tokens)
|
||||
var generated_code = transpiler.transpile()
|
||||
|
||||
if generated_code == nil
|
||||
print(" ❌ Transpilation failed:")
|
||||
for error : transpiler.errors
|
||||
print(f" {error}")
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
print(f" Generated code:\n{generated_code}")
|
||||
|
||||
|
||||
|
||||
# Check that mathematical methods are prefixed with self.
|
||||
var methods_to_check = []
|
||||
if type(expected_methods) == "instance" # Berry lists are of type "instance"
|
||||
methods_to_check = expected_methods
|
||||
else
|
||||
methods_to_check = [expected_methods]
|
||||
end
|
||||
|
||||
for method : methods_to_check
|
||||
var self_method = f"self.{method}("
|
||||
if string.find(generated_code, self_method) < 0
|
||||
print(f" ❌ Expected to find 'self.{method}(' in generated code")
|
||||
return false
|
||||
else
|
||||
print(f" ✅ Found 'self.{method}(' in generated code")
|
||||
end
|
||||
end
|
||||
|
||||
# Verify the code compiles
|
||||
try
|
||||
var compiled_func = compile(generated_code, test_name)
|
||||
print(" ✅ Generated code compiles successfully")
|
||||
return true
|
||||
except .. as e, msg
|
||||
print(f" ❌ Generated code compilation failed: {msg}")
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
def test_non_math_functions(dsl_code)
|
||||
print("\n Testing: Non-math functions should NOT be prefixed with self.")
|
||||
|
||||
var lexer = animation_dsl.DSLLexer(dsl_code)
|
||||
var tokens = lexer.tokenize()
|
||||
|
||||
if size(lexer.errors) > 0
|
||||
print(f" ❌ Lexer errors: {lexer.errors}")
|
||||
return false
|
||||
end
|
||||
|
||||
var transpiler = animation_dsl.SimpleDSLTranspiler(tokens)
|
||||
var generated_code = transpiler.transpile()
|
||||
|
||||
if generated_code == nil
|
||||
print(" ❌ Transpilation failed:")
|
||||
for error : transpiler.errors
|
||||
print(f" {error}")
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
print(f" Generated code:\n{generated_code}")
|
||||
|
||||
# Check that 'scale' is prefixed with self. (it's a math method)
|
||||
if string.find(generated_code, "self.scale(") < 0
|
||||
print(" ❌ Expected to find 'self.scale(' in generated code")
|
||||
return false
|
||||
else
|
||||
print(" ✅ Found 'self.scale(' in generated code")
|
||||
end
|
||||
|
||||
# Check that animation functions like 'pulsating_animation' are NOT prefixed with self.
|
||||
if string.find(generated_code, "self.pulsating_animation") >= 0
|
||||
print(" ❌ Found 'self.pulsating_animation' - animation functions should NOT be prefixed")
|
||||
return false
|
||||
else
|
||||
print(" ✅ Animation functions correctly NOT prefixed with self.")
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# Test the dynamic introspection method directly
|
||||
def test_is_math_method_function()
|
||||
print("\nTesting is_math_method() function directly...")
|
||||
|
||||
var transpiler = animation_dsl.SimpleDSLTranspiler([])
|
||||
|
||||
# Test mathematical methods
|
||||
var math_methods = ["min", "max", "abs", "round", "sqrt", "scale", "sine", "cosine"]
|
||||
for method : math_methods
|
||||
if !transpiler.is_math_method(method)
|
||||
print(f" ❌ {method} should be detected as a math method")
|
||||
return false
|
||||
else
|
||||
print(f" ✅ {method} correctly detected as math method")
|
||||
end
|
||||
end
|
||||
|
||||
# Test non-mathematical methods
|
||||
var non_math_methods = ["pulsating_animation", "solid", "color_cycle", "unknown_method"]
|
||||
for method : non_math_methods
|
||||
if transpiler.is_math_method(method)
|
||||
print(f" ❌ {method} should NOT be detected as a math method")
|
||||
return false
|
||||
else
|
||||
print(f" ✅ {method} correctly NOT detected as math method")
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
def test_math_method_transpilation()
|
||||
print("Testing mathematical method transpilation in computed parameters...")
|
||||
|
||||
# Test case 1: Simple mathematical function in computed parameter
|
||||
var dsl_code1 =
|
||||
"set value = 50\n"
|
||||
"animation test = pulsating_animation(color=red, period=2s)\n"
|
||||
"test.opacity = abs(value - 100)\n"
|
||||
"run test"
|
||||
|
||||
var result1 = test_transpilation_case(dsl_code1, "abs", "Simple abs() function")
|
||||
if !result1
|
||||
return false
|
||||
end
|
||||
|
||||
# Test case 2: Multiple mathematical functions
|
||||
var dsl_code2 =
|
||||
"set x = 10\n"
|
||||
"set y = 20\n"
|
||||
"animation wave = pulsating_animation(color=blue, period=3s)\n"
|
||||
"wave.brightness = max(min(x, y), sqrt(abs(x - y)))\n"
|
||||
"run wave"
|
||||
|
||||
var result2 = test_transpilation_case(dsl_code2, ["max", "min", "sqrt", "abs"], "Multiple math functions")
|
||||
if !result2
|
||||
return false
|
||||
end
|
||||
|
||||
# Test case 3: Mathematical functions with complex expressions
|
||||
var dsl_code3 =
|
||||
"set angle = 45\n"
|
||||
"animation rotate = pulsating_animation(color=green, period=2s)\n"
|
||||
"rotate.brightness = round(sine(angle) * 180 + cosine(angle) * 90)\n"
|
||||
"run rotate"
|
||||
|
||||
var result3 = test_transpilation_case(dsl_code3, ["round", "sine", "cosine"], "Complex math expressions")
|
||||
if !result3
|
||||
return false
|
||||
end
|
||||
|
||||
# Test case 4: Ensure non-math functions are NOT prefixed with self.
|
||||
var dsl_code4 =
|
||||
"animation pulse = pulsating_animation(color=red, period=2s)\n"
|
||||
"pulse.brightness = scale(50, 0, 100)\n"
|
||||
"run pulse"
|
||||
|
||||
var result4 = test_non_math_functions(dsl_code4)
|
||||
if !result4
|
||||
return false
|
||||
end
|
||||
|
||||
print("\n✅ All mathematical method transpilation tests passed!")
|
||||
return true
|
||||
end
|
||||
|
||||
# Run all tests
|
||||
print("🧪 Testing Mathematical Method Transpilation")
|
||||
print("==================================================")
|
||||
|
||||
var test1_result = test_is_math_method_function()
|
||||
var test2_result = test_math_method_transpilation()
|
||||
|
||||
if test1_result && test2_result
|
||||
print("\n🎉 All tests passed!")
|
||||
print("✅ Mathematical methods are correctly transpiled to self.<method>() calls")
|
||||
print("✅ Non-mathematical functions are correctly left unchanged")
|
||||
print("✅ Dynamic introspection is working properly at transpile time")
|
||||
else
|
||||
print("\n❌ Some tests failed!")
|
||||
raise "test_failed"
|
||||
end
|
||||
@ -0,0 +1,142 @@
|
||||
import animation
|
||||
import animation_dsl
|
||||
import string
|
||||
|
||||
# Load user functions
|
||||
import "user_functions" as user_funcs
|
||||
|
||||
# Test to verify that user functions work correctly in computed parameters
|
||||
|
||||
def test_transpilation_case(dsl_code, expected_user_function, test_name)
|
||||
print(f"\n Testing: {test_name}")
|
||||
|
||||
var lexer = animation_dsl.DSLLexer(dsl_code)
|
||||
var tokens = lexer.tokenize()
|
||||
|
||||
if size(lexer.errors) > 0
|
||||
print(f" ❌ Lexer errors: {lexer.errors}")
|
||||
return false
|
||||
end
|
||||
|
||||
var transpiler = animation_dsl.SimpleDSLTranspiler(tokens)
|
||||
var generated_code = transpiler.transpile()
|
||||
|
||||
if generated_code == nil
|
||||
print(" ❌ Transpilation failed:")
|
||||
for error : transpiler.errors
|
||||
print(f" {error}")
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
print(f" Generated code:\n{generated_code}")
|
||||
|
||||
# Check that user function is called with self.engine
|
||||
var expected_call = f"animation.get_user_function('{expected_user_function}')(self.engine"
|
||||
if string.find(generated_code, expected_call) < 0
|
||||
print(f" ❌ Expected to find '{expected_call}' in generated code")
|
||||
return false
|
||||
else
|
||||
print(f" ✅ Found user function call with self.engine: '{expected_call}'")
|
||||
end
|
||||
|
||||
# Verify the code compiles
|
||||
try
|
||||
var compiled_func = compile(generated_code, test_name)
|
||||
print(" ✅ Generated code compiles successfully")
|
||||
return true
|
||||
except .. as e, msg
|
||||
print(f" ❌ Generated code compilation failed: {msg}")
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
# Test that user functions are correctly detected
|
||||
def test_user_function_detection()
|
||||
print("\nTesting user function detection...")
|
||||
|
||||
# Check that user functions are registered
|
||||
var user_functions = ["rand_demo"]
|
||||
|
||||
for func_name : user_functions
|
||||
if !animation.is_user_function(func_name)
|
||||
print(f" ❌ {func_name} should be detected as a user function")
|
||||
return false
|
||||
else
|
||||
print(f" ✅ {func_name} correctly detected as user function")
|
||||
end
|
||||
end
|
||||
|
||||
# Check that non-user functions are not detected as user functions
|
||||
var non_user_functions = ["pulsating_animation", "solid", "abs", "min", "max", "breathing", "fire", "sparkle"]
|
||||
|
||||
for func_name : non_user_functions
|
||||
if animation.is_user_function(func_name)
|
||||
print(f" ❌ {func_name} should NOT be detected as a user function")
|
||||
return false
|
||||
else
|
||||
print(f" ✅ {func_name} correctly NOT detected as user function")
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
def test_user_function_in_computed_parameter()
|
||||
print("Testing user functions in computed parameters...")
|
||||
|
||||
# Test case 1: Simple user function in computed parameter
|
||||
var dsl_code1 =
|
||||
"set strip_len = strip_length()\n"
|
||||
"animation test = pulsating_animation(color=red, period=2s)\n"
|
||||
"test.opacity = rand_demo()\n"
|
||||
"run test"
|
||||
|
||||
var result1 = test_transpilation_case(dsl_code1, "rand_demo", "Simple user function in computed parameter")
|
||||
if !result1
|
||||
return false
|
||||
end
|
||||
|
||||
# Test case 2: User function with mathematical functions
|
||||
var dsl_code2 =
|
||||
"set strip_len = strip_length()\n"
|
||||
"animation test = solid(color=red)\n"
|
||||
"test.brightness = max(100, rand_demo())\n"
|
||||
"run test"
|
||||
|
||||
var result2 = test_transpilation_case(dsl_code2, "rand_demo", "User function with mathematical functions")
|
||||
if !result2
|
||||
return false
|
||||
end
|
||||
|
||||
# Test case 3: User function in arithmetic expressions
|
||||
var dsl_code3 =
|
||||
"set strip_len = strip_length()\n"
|
||||
"animation test = solid(color=green)\n"
|
||||
"test.position = abs(rand_demo() - 128) + 64\n"
|
||||
"run test"
|
||||
|
||||
var result3 = test_transpilation_case(dsl_code3, "rand_demo", "User function in arithmetic expressions")
|
||||
if !result3
|
||||
return false
|
||||
end
|
||||
|
||||
print("\n✅ All user function computed parameter tests passed!")
|
||||
return true
|
||||
end
|
||||
|
||||
# Run all tests
|
||||
print("🧪 Testing User Functions in Computed Parameters")
|
||||
print("==================================================")
|
||||
|
||||
var test1_result = test_user_function_detection()
|
||||
var test2_result = test_user_function_in_computed_parameter()
|
||||
|
||||
if test1_result && test2_result
|
||||
print("\n🎉 All tests passed!")
|
||||
print("✅ User functions are correctly detected")
|
||||
print("✅ User functions work correctly in computed parameters")
|
||||
print("✅ User functions are called with self.engine in closure context")
|
||||
else
|
||||
print("\n❌ Some tests failed!")
|
||||
end
|
||||
@ -13,13 +13,12 @@ def test_user_function_registration()
|
||||
print("Testing user function registration...")
|
||||
|
||||
# Check that functions are registered
|
||||
assert(animation.is_user_function("breathing"), "breathing function should be registered")
|
||||
assert(animation.is_user_function("fire"), "fire function should be registered")
|
||||
assert(animation.is_user_function("rand_demo"), "rand_demo function should be registered")
|
||||
assert(!animation.is_user_function("nonexistent"), "nonexistent function should not be registered")
|
||||
|
||||
# Check that we can get functions
|
||||
var breathing_func = animation.get_user_function("breathing")
|
||||
assert(breathing_func != nil, "Should be able to get breathing function")
|
||||
var rand_demo_func = animation.get_user_function("rand_demo")
|
||||
assert(rand_demo_func != nil, "Should be able to get rand_demo function")
|
||||
|
||||
var nonexistent_func = animation.get_user_function("nonexistent")
|
||||
assert(nonexistent_func == nil, "Should return nil for nonexistent function")
|
||||
@ -27,15 +26,14 @@ def test_user_function_registration()
|
||||
print("✓ User function registration test passed")
|
||||
end
|
||||
|
||||
# Test user function call in DSL
|
||||
def test_user_function_in_dsl()
|
||||
print("Testing user function call in DSL...")
|
||||
# Test user function call in computed parameters
|
||||
def test_user_function_in_computed_parameters()
|
||||
print("Testing user function in computed parameters...")
|
||||
|
||||
var dsl_code =
|
||||
"# strip length 30 # TEMPORARILY DISABLED\n"
|
||||
"color custom_red = 0xFF0000\n"
|
||||
"animation red_breathing = breathing(custom_red, 4s)\n"
|
||||
"run red_breathing"
|
||||
"animation random_base = solid(color=blue, priority=10)\n"
|
||||
"random_base.opacity = rand_demo()\n"
|
||||
"run random_base"
|
||||
|
||||
try
|
||||
var berry_code = animation_dsl.compile(dsl_code)
|
||||
@ -43,94 +41,93 @@ def test_user_function_in_dsl()
|
||||
|
||||
# Check that the generated code contains the user function call
|
||||
import string
|
||||
assert(string.find(berry_code, "animation.get_user_function('breathing')") >= 0,
|
||||
assert(string.find(berry_code, "animation.get_user_function('rand_demo')") >= 0,
|
||||
"Generated code should contain user function call")
|
||||
|
||||
print("✓ User function in DSL test passed")
|
||||
print("✓ User function in computed parameters test passed")
|
||||
except "dsl_compilation_error" as e, msg
|
||||
assert(false, f"DSL compilation should not fail: {msg}")
|
||||
end
|
||||
end
|
||||
|
||||
# Test nested user function calls
|
||||
def test_nested_user_function_calls()
|
||||
print("Testing nested user function calls...")
|
||||
|
||||
# Register a function that calls another user function
|
||||
def complex_effect(base_color, speed)
|
||||
return animation.get_user_function("breathing")(base_color, speed)
|
||||
end
|
||||
animation.register_user_function("complex", complex_effect)
|
||||
# Test user function with mathematical operations
|
||||
def test_user_function_with_math()
|
||||
print("Testing user function with mathematical operations...")
|
||||
|
||||
var dsl_code =
|
||||
"# strip length 30 # TEMPORARILY DISABLED\n"
|
||||
"color custom_blue = 0x0000FF\n"
|
||||
"animation complex_blue = complex(custom_blue, 2s)\n"
|
||||
"run complex_blue"
|
||||
"animation random_bounded = solid(color=orange, priority=8)\n"
|
||||
"random_bounded.opacity = max(50, min(255, rand_demo() + 100))\n"
|
||||
"run random_bounded"
|
||||
|
||||
try
|
||||
var berry_code = animation_dsl.compile(dsl_code)
|
||||
assert(berry_code != nil, "Generated Berry code should not be nil")
|
||||
|
||||
# Check that the generated code contains the user function call
|
||||
# Check that the generated code contains both user function and math functions
|
||||
import string
|
||||
assert(string.find(berry_code, "animation.get_user_function('complex')") >= 0,
|
||||
"Generated code should contain nested user function call")
|
||||
assert(string.find(berry_code, "animation.get_user_function('rand_demo')") >= 0,
|
||||
"Generated code should contain user function call")
|
||||
assert(string.find(berry_code, "self.max(") >= 0,
|
||||
"Generated code should contain math function call")
|
||||
|
||||
print("✓ Nested user function calls test passed")
|
||||
print("✓ User function with math test passed")
|
||||
except "dsl_compilation_error" as e, msg
|
||||
assert(false, f"DSL compilation should not fail: {msg}")
|
||||
end
|
||||
end
|
||||
|
||||
# Test user function with multiple parameters
|
||||
def test_user_function_multiple_parameters()
|
||||
print("Testing user function with multiple parameters...")
|
||||
# Test user function in arithmetic expressions
|
||||
def test_user_function_in_arithmetic()
|
||||
print("Testing user function in arithmetic expressions...")
|
||||
|
||||
var dsl_code =
|
||||
"# strip length 30 # TEMPORARILY DISABLED\n"
|
||||
"animation sparkles = sparkle(red, white, 15%)\n"
|
||||
"run sparkles"
|
||||
"animation random_variation = solid(color=purple, priority=15)\n"
|
||||
"random_variation.opacity = abs(rand_demo() - 128) + 64\n"
|
||||
"run random_variation"
|
||||
|
||||
try
|
||||
var berry_code = animation_dsl.compile(dsl_code)
|
||||
assert(berry_code != nil, "Generated Berry code should not be nil")
|
||||
|
||||
# Check that the generated code contains the user function call with parameters
|
||||
# Check that the generated code contains the user function call in arithmetic
|
||||
import string
|
||||
assert(string.find(berry_code, "animation.get_user_function('sparkle')") >= 0,
|
||||
assert(string.find(berry_code, "animation.get_user_function('rand_demo')") >= 0,
|
||||
"Generated code should contain user function call")
|
||||
assert(string.find(berry_code, "0xFFFF0000, 0xFFFFFFFF") >= 0,
|
||||
"Generated code should contain color parameters")
|
||||
assert(string.find(berry_code, "self.abs(") >= 0,
|
||||
"Generated code should contain abs function call")
|
||||
|
||||
print("✓ User function multiple parameters test passed")
|
||||
print("✓ User function in arithmetic test passed")
|
||||
except "dsl_compilation_error" as e, msg
|
||||
assert(false, f"DSL compilation should not fail: {msg}")
|
||||
end
|
||||
end
|
||||
|
||||
# Test user function in nested calls
|
||||
def test_user_function_in_nested_calls()
|
||||
print("Testing user function in nested calls...")
|
||||
# Test complex expressions with user functions
|
||||
def test_complex_user_function_expressions()
|
||||
print("Testing complex expressions with user functions...")
|
||||
|
||||
var dsl_code =
|
||||
"# strip length 30 # TEMPORARILY DISABLED\n"
|
||||
"color custom_red = 0xFF0000\n"
|
||||
"animation complex = pulsating_animation(color=breathing(custom_red, 3s), period=2s)\n"
|
||||
"run complex"
|
||||
"animation random_complex = solid(color=white, priority=20)\n"
|
||||
"random_complex.opacity = round((rand_demo() + 128) / 2 + abs(rand_demo() - 100))\n"
|
||||
"run random_complex"
|
||||
|
||||
try
|
||||
var berry_code = animation_dsl.compile(dsl_code)
|
||||
assert(berry_code != nil, "Generated Berry code should not be nil")
|
||||
|
||||
# Check that both user and built-in functions are handled correctly
|
||||
# Check that the generated code contains multiple user function calls
|
||||
import string
|
||||
assert(string.find(berry_code, "animation.get_user_function('breathing')") >= 0,
|
||||
"Generated code should contain user function call")
|
||||
assert(string.find(berry_code, "animation.pulsating_animation(") >= 0,
|
||||
"Generated code should contain built-in function call")
|
||||
var rand_demo_count = 0
|
||||
var pos = 0
|
||||
while true
|
||||
pos = string.find(berry_code, "animation.get_user_function('rand_demo')", pos)
|
||||
if pos < 0 break end
|
||||
rand_demo_count += 1
|
||||
pos += 1
|
||||
end
|
||||
assert(rand_demo_count >= 2, "Generated code should contain multiple rand_demo calls")
|
||||
|
||||
print("✓ User function in nested calls test passed")
|
||||
print("✓ Complex user function expressions test passed")
|
||||
except "dsl_compilation_error" as e, msg
|
||||
assert(false, f"DSL compilation should not fail: {msg}")
|
||||
end
|
||||
@ -141,10 +138,10 @@ def test_generated_code_validity()
|
||||
print("Testing generated code validity with user functions...")
|
||||
|
||||
var dsl_code =
|
||||
"# strip length 30 # TEMPORARILY DISABLED\n"
|
||||
"color custom_red = 0xFF0000\n"
|
||||
"animation red_fire = fire(200, 500ms)\n"
|
||||
"run red_fire"
|
||||
"animation random_multi = solid(color=cyan, priority=12)\n"
|
||||
"random_multi.opacity = rand_demo()\n"
|
||||
"random_multi.brightness = max(100, rand_demo())\n"
|
||||
"run random_multi"
|
||||
|
||||
try
|
||||
var berry_code = animation_dsl.compile(dsl_code)
|
||||
@ -173,10 +170,10 @@ def run_user_functions_tests()
|
||||
|
||||
try
|
||||
test_user_function_registration()
|
||||
test_user_function_in_dsl()
|
||||
test_nested_user_function_calls()
|
||||
test_user_function_multiple_parameters()
|
||||
test_user_function_in_nested_calls()
|
||||
test_user_function_in_computed_parameters()
|
||||
test_user_function_with_math()
|
||||
test_user_function_in_arithmetic()
|
||||
test_complex_user_function_expressions()
|
||||
test_generated_code_validity()
|
||||
|
||||
print("=== All user functions tests passed! ===")
|
||||
|
||||
@ -1,100 +1,11 @@
|
||||
# User-defined functions for Animation DSL
|
||||
# This file demonstrates how to create custom functions that can be used in the DSL
|
||||
|
||||
import animation
|
||||
|
||||
# Example 1: Simple breathing effect
|
||||
def breathing_effect(engine, base_color, period)
|
||||
# Create a pulse animation with the specified color and period
|
||||
var pulse_anim = animation.pulsating_animation(engine)
|
||||
pulse_anim.color = base_color
|
||||
pulse_anim.min_brightness = 50
|
||||
pulse_anim.max_brightness = 255
|
||||
pulse_anim.period = period
|
||||
return pulse_anim
|
||||
end
|
||||
|
||||
# Example 2: Police lights effect
|
||||
def police_lights(engine, flash_speed)
|
||||
# Create alternating red/blue flash effect
|
||||
var pulse_anim = animation.pulsating_animation(engine)
|
||||
pulse_anim.color = 0xFFFF0000 # Red
|
||||
pulse_anim.min_brightness = 0
|
||||
pulse_anim.max_brightness = 255
|
||||
pulse_anim.period = flash_speed
|
||||
return pulse_anim
|
||||
end
|
||||
|
||||
# Example 3: Fire effect with customizable intensity
|
||||
def fire_effect(engine, intensity, speed)
|
||||
# Use the fire palette with custom parameters
|
||||
var color_provider = animation.rich_palette(engine)
|
||||
color_provider.palette = animation.PALETTE_FIRE
|
||||
color_provider.cycle_period = speed
|
||||
color_provider.easing = 1 # Smooth transitions
|
||||
|
||||
var fire_anim = animation.filled(engine)
|
||||
fire_anim.color_provider = color_provider
|
||||
fire_anim.brightness = intensity
|
||||
return fire_anim
|
||||
end
|
||||
|
||||
# Example 4: Sparkle effect
|
||||
def sparkle_effect(engine, base_color, sparkle_color, density)
|
||||
# Create a twinkling sparkle effect
|
||||
var sparkle_anim = animation.twinkle_animation(engine)
|
||||
sparkle_anim.color = sparkle_color
|
||||
sparkle_anim.density = density
|
||||
sparkle_anim.speed = 500
|
||||
return sparkle_anim
|
||||
end
|
||||
|
||||
# Example 5: Color wheel effect
|
||||
def color_wheel(engine, speed)
|
||||
# Create a rainbow that cycles through colors
|
||||
var color_provider = animation.rich_palette(engine)
|
||||
color_provider.palette = animation.PALETTE_RAINBOW
|
||||
color_provider.cycle_period = speed
|
||||
color_provider.easing = 1 # Smooth transitions
|
||||
|
||||
var rainbow_anim = animation.filled(engine)
|
||||
rainbow_anim.color_provider = color_provider
|
||||
rainbow_anim.brightness = 255
|
||||
return rainbow_anim
|
||||
end
|
||||
|
||||
# Example 6: Comet effect with custom tail
|
||||
def comet_effect(engine, color, tail_length, speed)
|
||||
# Create a moving comet with customizable tail
|
||||
var comet_anim = animation.comet_animation(engine)
|
||||
comet_anim.color = color
|
||||
comet_anim.tail_length = tail_length
|
||||
comet_anim.speed = speed
|
||||
return comet_anim
|
||||
end
|
||||
|
||||
# Example 7: Pulsing position effect
|
||||
def pulse_spot(engine, color, position, width, period)
|
||||
# Create a pulsing effect at a specific position
|
||||
var pulse_pos_anim = animation.beacon_animation(engine)
|
||||
pulse_pos_anim.color = color
|
||||
pulse_pos_anim.position = position
|
||||
pulse_pos_anim.width = width
|
||||
pulse_pos_anim.period = period
|
||||
return pulse_pos_anim
|
||||
# Example 1: provide a random value in range 0..255
|
||||
def rand_demo(engine)
|
||||
import math
|
||||
return math.rand() % 256
|
||||
end
|
||||
|
||||
# Register all user functions with the animation module
|
||||
animation.register_user_function("breathing", breathing_effect)
|
||||
animation.register_user_function("police_lights", police_lights)
|
||||
animation.register_user_function("fire", fire_effect)
|
||||
animation.register_user_function("sparkle", sparkle_effect)
|
||||
animation.register_user_function("color_wheel", color_wheel)
|
||||
animation.register_user_function("comet_effect", comet_effect)
|
||||
animation.register_user_function("pulse_spot", pulse_spot)
|
||||
|
||||
print("User functions registered:")
|
||||
var functions = animation.list_user_functions()
|
||||
for func_name : functions
|
||||
print(f" - {func_name}")
|
||||
end
|
||||
animation.register_user_function("rand_demo", rand_demo)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user