Custom light effects (new)
Skybrush Studio includes a newer, more powerful way to write custom light effects that return RGBA colors directly. Like the legacy version, your function picks the exact color for each drone. However, this version gives you more information about the scene and can be faster for complex effects.
This guide explains how to write such a function.
When to use this
Choose this approach when you need information about all drones at once (for example, to calculate distances between them) or when you want to use NumPy for faster calculations. If your needs are simple, the legacy version is easier to get started with.
| This feature is currently experimental. The details of how the function works may change in future versions. |
How it works
In the Light Effects panel, set the btn:[Effect Type] to btn:[Function]. Then select a .py file that contains your Python function. Skybrush Studio will call your function once per frame. Your function writes an RGBA color for each drone into an array, and those colors are applied directly to the drones.
Function signature
Your file must contain a function with this exact name and set of arguments:
def my_effect(effect, context, frame, *, out):
# Your code goes here
...
The function receives four arguments:
- effect
-
The light effect being evaluated. You can use this to find out how far through the effect we are (see Properties available on the effect object below).
- context
-
An object that gives you information about the drones in the current frame (see Properties available on the context object below).
- frame
-
The current frame number (an integer, e.g. 1, 2, 3, …).
- out
-
A two-dimensional NumPy array with shape
(N, 4)whereNis the number of drones. Each row contains four numbers representing the RGBA color for the corresponding drone:(red, green, blue, alpha). All values should be between 0 and 1. An alpha of 1 means fully opaque (replaces the drone’s base color); an alpha of 0 means fully transparent (leaves the drone’s base color unchanged).
Your function should not return anything (or equivalently, return None).
Simple example
The following example makes all drones pulse between black and white over the duration of the effect:
from numpy import sin, pi
def my_effect(effect, context, frame, *, out):
time_fraction = effect.get_time_fraction_for_frame(frame)
brightness = (sin(time_fraction * 4 * pi) + 1) / 2
out[:, 0:3] = brightness # red, green, blue
out[:, 3] = 1.0 # alpha (optional)
Working with individual drones
If you need different colors for different drones, use context.num_drones to find out how many there are, and write to each row of the out array separately. For example, this function lights up drones one by one over the duration of the effect:
def my_effect(effect, context, frame, *, out):
n = context.num_drones
time_fraction = effect.get_time_fraction_for_frame(frame)
for i in range(n):
threshold = i / max(n - 1, 1)
if time_fraction >= threshold:
out[i, :] = [1.0, 1.0, 1.0, 1.0] # white, fully opaque
else:
out[i, :] = [0.0, 0.0, 0.0, 0.0] # transparent
You can also use NumPy for faster calculations. For instance, to color drones based on their X position — drones on the left side of the scene turn red, drones on the right side turn blue:
import numpy as np
def my_effect(effect, context, frame, *, out):
positions = context.positions.as_array # Nx3 NumPy array
x = positions[:, 0] # X coordinates of all drones
# Normalize to [0, 1] range
x_min, x_max = x.min(), x.max()
if x_max > x_min:
t = (x - x_min) / (x_max - x_min)
else:
t = np.full(len(x), 0.5)
out[:, 0] = 1.0 - t # red component
out[:, 1] = 0.0 # green component
out[:, 2] = t # blue component
out[:, 3] = 1.0 # alpha
Returning a single color
If your function produces the same color for every drone, you can return that color directly instead of writing to out. This is slightly faster because the color is applied to all drones without writing to the array:
from numpy import sin, pi
def my_effect(effect, context, frame, *, out):
time_fraction = effect.get_time_fraction_for_frame(frame)
brightness = (sin(time_fraction * 4 * pi) + 1) / 2
return (brightness, brightness, brightness, 1.0) # same color for all drones
Properties available on the context object
The context argument gives you access to the following properties:
- context.num_drones
-
The total number of drones (an integer).
- context.positions
-
The positions of all drones. Use
context.positions.as_arrayto get a NumPy array of shape(N, 3)whereNis the number of drones and the three columns are the X, Y and Z coordinates. Usecontext.positions.as_vectorsto obtain a list of BlenderVectorobjects instead. In almost all cases the arrays are faster, except when you are calling functions from the Blender API that need vectors. - context.mapping
-
A list that maps each drone index to its corresponding marker index in the current formation, or
Noneif no formation information is available. This is useful when you want to base your effect on the formation order rather than the drone order. - context.mask
-
A boolean NumPy array.
Falsemeans the drone is targeted by the current effect;Truemeans it is not (for example, because it falls outside a spatial constraint). In most cases you can ignore this array, but you can either use it to skip calculations for certain drones or you can also update the mask to exclude drones from further consideration.
Properties available on the effect object
The effect argument gives you access to the light effect’s own properties:
- effect.frame_start
-
The start frame of the light effect.
- effect.duration
-
The total duration of the light effect in frames.
- effect.get_time_fraction_for_frame(frame)
-
Converts a frame number to a value between 0 and 1 representing how far through the light effect we are (0 at the start, 1 at the end).
Tips
-
The function is called once per frame (not once per drone), so you have access to all drone positions at the same time. This makes it easy to create effects that depend on the relative positions of drones.
-
If you are comfortable with NumPy, you can use it for faster calculations. If not, a simple
forloop works perfectly well. -
The
outarray is reused between frames, so always write to every element. Do not leave any entry unset. -
The file is automatically detected as a V2 function if it has fewer than six parameters. Make sure your function signature matches the one shown above.