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])
;