Custom output functions (new)
Skybrush Studio includes a newer, more powerful way to write custom output functions. Like the legacy version, your function produces a number for each drone that is mapped through the color ramp or color image. 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:[Output X] dropdown to btn:[Custom expression (experimental)]. Then select a .py file that contains your Python function. Skybrush Studio will call your function once per frame. Your function writes a number for each drone into an array, and those numbers are mapped through the color ramp or color image to produce the final colors.
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 one-dimensional NumPy array with one entry per drone. Your function writes a number into each entry of this array. Each number should typically be between 0 and 1, where 0 maps to the left edge of the color ramp and 1 maps to the right edge.
Your function should not return anything (or equivalently, return None).
Simple example
The following example makes all drones pulse between 0 and 1 using a sine wave:
from numpy import sin, pi
def my_effect(effect, context, frame, *, out):
time_fraction = effect.get_time_fraction_for_frame(frame)
out[:] = (sin(time_fraction * 4 * pi) + 1) / 2
Working with individual drones
If you need different values for different drones, use context.num_drones to find out how many there are, and write to each element 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)
out[i] = 1.0 if time_fraction >= threshold else 0.0
You can also use NumPy for faster calculations. For instance, to assign each drone a value based on its position along the X axis:
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:
out[:] = (x - x_min) / (x_max - x_min)
else:
out[:] = 0.5
Returning a single value
If your function produces the same value for every drone, you can return that value directly instead of writing to out. This is slightly faster because the color ramp is evaluated only once:
def my_effect(effect, context, frame, *, out):
time_fraction = effect.get_time_fraction_for_frame(frame)
return time_fraction # all drones get the same value
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.