OpenSCAD User Manual/Example/HelloThere

Hello There

Animation example using objects to manage the data.

Code

// this script requires 2025.07.11.
_v = version();
assert( _v.x== 2025 && _v.y>=7 && _v.z >= 11 );

/* 
This script was presented by Jordan Brown in the OpenSCAD
forum to illustrate how an object could be used to control
an animation.

The animation runs well with Use FPS=10 and steps=100.
*/

// Best view is looking straight down at the origin.
$vpr = [0,0,0];
$vpt = [0,0,0];



// This vector is a description of everything that happens
// during the animation. You want a wide window to read it.
// The only thing that's defined is "t", the timestamp for that
// particular entry. The rest are up to your program.
// For this animation:
// pos1, pos2: the {red, green} stick man's position
// arm1, arm2: the {red, green} stick man's arm angle
// says1, says2: what the {red, green} stick man is saying
timeline = [
object(t=0, pos1=[-50,0,0], 
              arm1=-30, says1="", 
              pos2=[50,0],  arm2=-30, says2=""),
object(t=2.5, arm1=-30 ),
object(t=3,   arm1=50,  says1="Hey, George!"  ),
object(t=3.5, arm1=-30 ),
object(t=5,             says1="" ),
object(t=5.5,               arm2=-30 ),
object(t=6,                 arm2=50, says2="Hey, Fred!" ),
object(t=6.5,               arm2=-30 ),
object(t=7,                     says2="" ),
object(t=12, pos1=[-5,0,0],  pos2=[5,0] ),
object(t=13,            says1="Can I go past?" ),
object(t=14,            says1="" ),
object(t=15,                    says2="Sorry, no." ),
object(t=16,                    says2="" ),
object(t=17,            says1="I hate living on a number line!" ),
object(t=19,            says1="" ),
object(t=19.5,                  says2="Me too!"  ),
object(t=20.5,                  says2="" ),
object(t=22, pos1=[-5,0,0],
                            arm2=-30,
                        says1="", pos2=[5,0],
                            arm2=-30,
                                says2="" ),
];

// Now, create the current frame of the animation.

// Get the current values of all of the timeline columns.
a = animate(timeline);
/* Using those values,
   create the model at this moment.
   There are two stick men.
 */
translate(a.pos1) {
    color("red")
        stickman(a.says1, a.arm1);
    }
translate(a.pos2) {
    color("green")
        stickman(a.says2, a.arm2);
    }

// Create a stick man, holding his arms at the specified angle and saying what's specified.
module stickman(says, arm) {
    square([1,8], center=true);
    translate([0,5])
        circle(2);
    translate([0,2])
        rotate(arm)
            translate([0,-0.5])
                square([4,1]);
    translate([0,2])
        rotate(180-arm)
            translate([0,-0.5])
                square([4,1]);
    translate([0,-4])
        rotate(200)
            translate([-0.5,0])
                square([1,5]);
    translate([0,-4])
        rotate(160)
            translate([-0.5,0])
                square([1,5]);
    translate([0, 8])
        text(says, halign="center", valign="baseline", size=3);
    }

// The rest is generic support for using a timeline like that.

// Extract one column from an animation timeline, extracting only
// those entries where that column is present.
function animate_extract(list, key) = [
    for (e = list)
        if( !is_undef(e[key]) ) [ e.t, e[key] ]
    ];

// Get the duration of the timeline, the timestamp of the
// last entry in the timeline.
function animate_duration(list) = list[len(list)-1].t;

// Given $t, a timeline and a key, interpolate the current value
// of the key.
function animate_interpolate(list, key) =
    xlookup(
        $t * animate_duration(list), 
        animate_extract(list, key)
        );

// Get a list of all keys used in the timeline.
function animate_keys(list) =
    let( o = object(
            [
            for (e = list)
                for (k = e) [ k, true ]
            ]
            )
        )
    [ for (k = o) k ];

// Given $t and a timeline, return an aggregated object with the
// current values of all of the columns of the timeline.
function animate(timeline) =
    let(keys = animate_keys(timeline))
    object(
        [
        for (k = keys)
            [ k, animate_interpolate(timeline, k) ]
        ]
        );

// lookup() on steroids. Given a value and a lookup-like list,
// do the lookup and interpolation that lookup() does... but have
// it also work for strings, booleans, and identical-length lists
// of numbers.
function xlookup(val, list) =
    is_num(list[0][1])      ? lookup(val, list)
    : is_string(list[0][1]) ? lookup_string(val, list)
    : is_bool(list[0][1])   ? lookup_bool(  val, list)
    : is_list(list[0][1])   ? lookup_list(  val, list)
    : assert(false, "don't know how to lookup that type")
    ;

// Given a value and a lookup list, return the index of the entry
// before (or matching) the value.
function lookup_prev(val, list) =
    let (tmp = [ for (i = [0:1:len(list)-1]) 
        [ list[i][0], i ] ]
        )
    floor(lookup(val, tmp))
    ;

//Given a value and a lookup list, return the index of the entry
// after (or matching) the value.
function lookup_next(val, list) =
    let (tmp = [ for (i = [0:1:len(list)-1])
        [ list[i][0], i ] ]
        )
    ceil(lookup(val, tmp))
    ;

// Given a value and a lookup list containing strings, return the
// string before (or matching) the value.
function lookup_string(val, list) = 
    list[lookup_prev(val, list)][1]
    ;

// Given a value and a lookup list containing booleans, return the
// boolean before (or matching) the value.
function lookup_bool(val, list) = 
    list[lookup_prev(val, list)][1]
    ;

// Given a value and a lookup list containing same-length lists of
// numbers, interpolate values for the list. Note that because
// lookup_prev() and lookup_next() return the same entry on an exact
// match, and that leads to 0*0/0, that case has to be handled
// specially.
function lookup_list(val, list) =
    let(
        p = lookup_prev(val, list),
        n = lookup_next(val, list)
        )
    p == n ?
        list[p][1]
    :   list[p][1] + 
            (list[n][1]-list[p][1]) *
            (val - list[p][0]) / (list[n][0] - list[p][0])
    ;