discuss@lists.openscad.org

OpenSCAD general discussion Mailing-list

View all threads

New feature in 2025.07.11: the object() function

PK
Peter Kriens
Mon, Jul 14, 2025 5:51 PM

If we also want this literal support then I can easily implement it.

I am still feeling my way around here. I feel in general people want to
keep OpenSCAD extremely simple and leave the programmers with python.
(Objects were imho too basic to miss. I’d jump to python if it wasn’t for
BOSL2 :-( )

Practically I translate the desired simplicity to keeping the Cheat sheet
to a single page? Adding object literals will make this harder. Literals
still need a function like object() because of the immutable nature of
OpenSCAD. Personally I don’t think literals add that much. And we have
import of json.

But I’m retired and find this kind of fun after working 30 years with Java.

So what is the consensus/ideas on object literals?

On Mon 14 Jul 2025 at 18:38, Jordan Brown via Discuss <
discuss@lists.openscad.org> wrote:

On 7/14/2025 4:55 AM, Jon Bondy wrote:

I would welcome a few simple examples, to get my feet wet.  I used Pascal
for decades, so I am, trying to map this new feature to what I am familiar
with.

It's been forty years since I wrote Pascal, so I'm a tad rusty... but
Wikipedia to the rescue:  it's roughly equivalent to a Pascal "record" -
that is, a data structure with named elements, each of any type.

What I see looks like a table with named parameters for each row.

That is indeed the data structure that this example uses.  Its big data
structure is a vector (list, array, nothing new there) of objects
(dictionaries, records, associative arrays), each of which has some number
of named members.

I have used a similar technique using plain OpenSCAD for many years.  I
simply create a series of named vectors as a dictionary, select a specific
vector at run-time, and then transfer the vector elements into local
variables.

Before this addition, there are a number of techniques for creating a data
structure with named elements.  The most obvious is probably the
vector-of-vectors approach that is one of the forms that object() accepts
as an input:

[[ "a", 1 ], [ "b", 2 ]].

Primarily, the difference is in how concise the representation is.
Contrast:

v = [ [ "a", 1 ], [ "b", 2 ] ];
o = object(a=1, b=2);

echo(find(v, "a"));
echo(o.a);

and of course that difference multiplies as you build more complex data
structures:

vperson = [
[
"name", [
[ "given", "Jordan" ],
[ "family", "Brown" ]
]
],
[
"birth", [
[ "year", 1961 ],
[ "month", 7 ],
[ "day", 26 ],
]
]
];
operson = object(
name = object(given = "Jordan", family="Brown"),
birth = object(year=1961, month=7, day=26)
);
vbirthyear = find(find(vperson, "birth"), "year");
obirthyear = operson.birth.year;

Some of that difference is of course in how I've chosen to lay out the two
examples, but with four levels of brackets I feel a need for indentation to
keep them straight.

Another approach is to use a vector with named elements, something like:

NAME = 0;
NAME_GIVEN = 0;
NAME_FAMILY = 1;
BIRTH = 1;
BIRTH_YEAR = 0;
BIRTH_MONTH = 1;
BIRTH_DAY = 2;

v2person = [ [ "Jordan", "Brown" ], [ 1961, 7, 26 ] ];
v2birthyear = v2person[BIRTH][BIRTH_YEAR];

but then it's awkward to have the data be sparse (what if I only have a
family name, no given name?), and construction is awkward because you have
to make sure you mentioned all of the elements, in the right order.

Future: when and if we move forward with the next step, OEP8a
https://github.com/openscad/openscad/wiki/OEP8a:--Objects-(dictionaries%3F),
this form becomes available:

operson = {
name: { first: "Jordan", last:"Brown" },
birth: { year: 1961, month: 7, day: 26}
};


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org

If we also want this literal support then I can easily implement it. I am still feeling my way around here. I feel in general people want to keep OpenSCAD extremely simple and leave the programmers with python. (Objects were imho too basic to miss. I’d jump to python if it wasn’t for BOSL2 :-( ) Practically I translate the desired simplicity to keeping the Cheat sheet to a single page? Adding object literals will make this harder. Literals still need a function like object() because of the immutable nature of OpenSCAD. Personally I don’t think literals add that much. And we have import of json. But I’m retired and find this kind of fun after working 30 years with Java. So what is the consensus/ideas on object literals? On Mon 14 Jul 2025 at 18:38, Jordan Brown via Discuss < discuss@lists.openscad.org> wrote: > On 7/14/2025 4:55 AM, Jon Bondy wrote: > > I would welcome a few simple examples, to get my feet wet. I used Pascal > for decades, so I am, trying to map this new feature to what I am familiar > with. > > > It's been forty years since I wrote Pascal, so I'm a tad rusty... but > Wikipedia to the rescue: it's roughly equivalent to a Pascal "record" - > that is, a data structure with named elements, each of any type. > > What I see looks like a table with named parameters for each row. > > > That is indeed the data structure that this example uses. Its big data > structure is a vector (list, array, nothing new there) of objects > (dictionaries, records, associative arrays), each of which has some number > of named members. > > I have used a similar technique using plain OpenSCAD for many years. I > simply create a series of named vectors as a dictionary, select a specific > vector at run-time, and then transfer the vector elements into local > variables. > > > Before this addition, there are a number of techniques for creating a data > structure with named elements. The most obvious is probably the > vector-of-vectors approach that is one of the forms that object() accepts > as an input: > > [[ "a", 1 ], [ "b", 2 ]]. > > Primarily, the difference is in how concise the representation is. > Contrast: > > v = [ [ "a", 1 ], [ "b", 2 ] ]; > o = object(a=1, b=2); > > echo(find(v, "a")); > echo(o.a); > > and of course that difference multiplies as you build more complex data > structures: > > vperson = [ > [ > "name", [ > [ "given", "Jordan" ], > [ "family", "Brown" ] > ] > ], > [ > "birth", [ > [ "year", 1961 ], > [ "month", 7 ], > [ "day", 26 ], > ] > ] > ]; > operson = object( > name = object(given = "Jordan", family="Brown"), > birth = object(year=1961, month=7, day=26) > ); > vbirthyear = find(find(vperson, "birth"), "year"); > obirthyear = operson.birth.year; > > Some of that difference is of course in how I've chosen to lay out the two > examples, but with four levels of brackets I feel a need for indentation to > keep them straight. > > > Another approach is to use a vector with named elements, something like: > > NAME = 0; > NAME_GIVEN = 0; > NAME_FAMILY = 1; > BIRTH = 1; > BIRTH_YEAR = 0; > BIRTH_MONTH = 1; > BIRTH_DAY = 2; > > v2person = [ [ "Jordan", "Brown" ], [ 1961, 7, 26 ] ]; > v2birthyear = v2person[BIRTH][BIRTH_YEAR]; > > but then it's awkward to have the data be sparse (what if I only have a > family name, no given name?), and construction is awkward because you have > to make sure you mentioned all of the elements, in the right order. > > Future: when and if we move forward with the next step, OEP8a > <https://github.com/openscad/openscad/wiki/OEP8a:--Objects-(dictionaries%3F)>, > this form becomes available: > > operson = { > name: { first: "Jordan", last:"Brown" }, > birth: { year: 1961, month: 7, day: 26} > }; > > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to discuss-leave@lists.openscad.org >
JB
Jordan Brown
Mon, Jul 14, 2025 6:37 PM

On 7/14/2025 10:51 AM, Peter Kriens wrote:

If we also want this literal support then I can easily implement it.

I implemented it a couple of years ago in PR#4478.  The delay has not
been in implementation, but in getting people to take time to look at it
and think about it and decide if it's something we want to to do the
syntax.  (They have actual lives and jobs!  The horror!  They need to
get their priorities straight!)  Also #4478 has a couple of related
features (geometry as values, module references) and I wanted to work on
them together to make sure that they worked with each other syntactically.

object() is pretty safe, because it doesn't introduce new syntax.  It's
just a function; it can't break anything else.

Literals still need a function like object() because of the immutable
nature of OpenSCAD.

#4478 addresses that; it includes both a syntax for computed keys and
object comprehension.

Personally I don’t think literals add that much.

They're just a bit more concise than object().

On 7/14/2025 10:51 AM, Peter Kriens wrote: > If we also want this literal support then I can easily implement it. I implemented it a couple of years ago in PR#4478.  The delay has not been in implementation, but in getting people to take time to look at it and think about it and decide if it's something we want to to do the syntax.  (They have actual lives and jobs!  The horror!  They need to get their priorities straight!)  Also #4478 has a couple of related features (geometry as values, module references) and I wanted to work on them together to make sure that they worked with each other syntactically. object() is pretty safe, because it doesn't introduce new syntax.  It's just a function; it can't break anything else. > Literals still need a function like object() because of the immutable > nature of OpenSCAD. #4478 addresses that; it includes both a syntax for computed keys and object comprehension. > Personally I don’t think literals add that much. They're just a bit more concise than object().
AM
Adrian Mariano
Mon, Jul 14, 2025 8:23 PM

Peter wrote:

// Functions. You can store functions in objects. However,
// the function can not have access to the object's fields
// due to OpenSCAD's architecture.

function rect( w =0, h=0) = object( w=w, h=h, area = function() w*h );
echo( rect(10,10).area()); // 100

So in this example, the area function is simply running on the w and h
parameters to rect?  That means it's the same as object(w=w,h=h,area=w*h),
right?  I guess the point is that the function could do something more
complicated with w and h that involves a new parameter?

On Mon, Jul 14, 2025 at 8:34 AM Peter Kriens via Discuss <
discuss@lists.openscad.org> wrote:

I am working on an example in the build source code tree. I can share the
current incarnation. Feel free to comment, ask questions so I can elucidate
them, or provide suggestions.

//
// examples with objects.
//
// Objects are immutable. Once an object is created, its values
// can not be changed.

//
// Construct a simple object
//
rectangle = object( w = 100, h= 20 );
echo(rectangle); // { w = 100; h = 20; }

//
// Access is via identifier
//
echo( rectangle.w, rectangle.h ); // 100, 20

//
// Or access is via string key
//
echo( rectangle["w"], rectangle["h"] ); // 100, 20

//
// You can test if a key is present
//

echo( has_key(rectangle,"w"), has_key(rectangle,"y")); // true, false

//
// You can use an object as a list of keys, where keys
// are always strings. For example, you can use them include
// in comprehension
//
values = [ for (k = rectangle) rectangle[k] ];
echo( values ); // [100, 20]

//
// To test if a parameter is an object, there is_bool
// an is_object function:
//

echo( is_object( rectangle )); // true
echo( is_object( [] )); // false

//
// You can use any type as value, key is
// always a string.

echo( object( name = "OpenSCAD.object", array=[1,2], bool=false) );
// { name = "OpenSCAD.object"; array = [1, 2]; bool = false; }

//
// Create a new object based on another object
//
volume = object( rectangle, d=10);
echo(volume); // { w = 100; h = 20; d = 10 }

//
// If you replace a field, it will take its original
// position
//
echo( object( volume, w=10) ); // { w = 10; h = 20; d = 10 }

//
// You can copy from multiple objects. This will
// be assigned in order, later objects override the early ones
//
echo( object(rectangle, volume) ); // { w = 100; h = 20; d = 10; }

//
// Keys can also be created dynamically. For this reason
// the object() function accepts a list with edit instructions.
// An element in this list is either ["k"] for a delete
// or ["k",v] for a new/override key.
//

echo( object( rectangle, [ ["w"], ["h"]] )); // {}
echo( object( [ ["w",10], ["h",10]] )); //  { w = 10; h = 10; }
echo( object( rectangle, [ ["z",20]])); //  { w = 100; h = 20; z = 20; }

//
// copy, deletes and set can be combined in one call.
//
echo( object( rectangle, [ ["z",20], ["w"]], h=10)); //  { h = 10; z =
20; }

//
// This works for large number of calculated entries
//

entries = [for ( i = [1:10000] ) [ str("_",floor(i)), floor(i) ] ];
large = object( entries );
echo( large._3012 );

//
// Functions. You can store functions in objects. However,
// the function can not have access to the object's fields
// due to OpenSCAD's architecture. To mimic object oriented
// behavior, often a function that acts as context is
// useful.
//

function rect( w =0, h=0) = object( w=w, h=h, area = function() w*h );
echo( rect(10,10).area()); // 100

On 14 Jul 2025, at 13:55, Jon Bondy via Discuss <
discuss@lists.openscad.org> wrote:

I would welcome a few simple examples, to get my feet wet.  I used Pascal
for decades, so I am, trying to map this new feature to what I am familiar
with.

What I see looks like a table with named parameters for each row.  I have
used a similar technique using plain OpenSCAD for many years.  I simply
create a series of named vectors as a dictionary, select a specific vector
at run-time, and then transfer the vector elements into local variables.

Jon

On 7/13/2025 6:44 PM, Jordan Brown via Discuss wrote:

Here's a program that I threw together that exercises the new object
features.

It provides a framework for doing a general animation - at this time, do
this, at that time, do that.  For an example of doing that without a
framework like this, look at https://openscad.org/advent-calendar-2023/
at day 24.

Remember that this requires 2025.07.11.  Zoom as desired.

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

// Demonstration animation.  Use FPS=10 and steps=100.
// Zoom as desired.

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


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org

http://www.avg.com/email-signature?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=emailclient
Virus-free.www.avg.com
http://www.avg.com/email-signature?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=emailclient


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org

Peter wrote: // Functions. You can store functions in objects. However, // the function can not have access to the object's fields // due to OpenSCAD's architecture. function rect( w =0, h=0) = object( w=w, h=h, area = function() w*h ); echo( rect(10,10).area()); // 100 So in this example, the area function is simply running on the w and h parameters to rect? That means it's the same as object(w=w,h=h,area=w*h), right? I guess the point is that the function could do something more complicated with w and h that involves a new parameter? On Mon, Jul 14, 2025 at 8:34 AM Peter Kriens via Discuss < discuss@lists.openscad.org> wrote: > I am working on an example in the build source code tree. I can share the > current incarnation. Feel free to comment, ask questions so I can elucidate > them, or provide suggestions. > > // > // examples with objects. > // > // Objects are immutable. Once an object is created, its values > // can not be changed. > > // > // Construct a simple object > // > rectangle = object( w = 100, h= 20 ); > echo(rectangle); // { w = 100; h = 20; } > > // > // Access is via identifier > // > echo( rectangle.w, rectangle.h ); // 100, 20 > > // > // Or access is via string key > // > echo( rectangle["w"], rectangle["h"] ); // 100, 20 > > // > // You can test if a key is present > // > > echo( has_key(rectangle,"w"), has_key(rectangle,"y")); // true, false > > // > // You can use an object as a list of keys, where keys > // are always strings. For example, you can use them include > // in comprehension > // > values = [ for (k = rectangle) rectangle[k] ]; > echo( values ); // [100, 20] > > // > // To test if a parameter is an object, there is_bool > // an is_object function: > // > > echo( is_object( rectangle )); // true > echo( is_object( [] )); // false > > > // > // You can use any type as value, key is > // always a string. > > echo( object( name = "OpenSCAD.object", array=[1,2], bool=false) ); > // { name = "OpenSCAD.object"; array = [1, 2]; bool = false; } > > // > // Create a new object based on another object > // > volume = object( rectangle, d=10); > echo(volume); // { w = 100; h = 20; d = 10 } > > // > // If you replace a field, it will take its original > // position > // > echo( object( volume, w=10) ); // { w = 10; h = 20; d = 10 } > > // > // You can copy from multiple objects. This will > // be assigned in order, later objects override the early ones > // > echo( object(rectangle, volume) ); // { w = 100; h = 20; d = 10; } > > // > // Keys can also be created dynamically. For this reason > // the object() function accepts a list with edit instructions. > // An element in this list is either ["k"] for a delete > // or ["k",v] for a new/override key. > // > > echo( object( rectangle, [ ["w"], ["h"]] )); // {} > echo( object( [ ["w",10], ["h",10]] )); // { w = 10; h = 10; } > echo( object( rectangle, [ ["z",20]])); // { w = 100; h = 20; z = 20; } > > // > // copy, deletes and set can be combined in one call. > // > echo( object( rectangle, [ ["z",20], ["w"]], h=10)); // { h = 10; z = > 20; } > > > // > // This works for large number of calculated entries > // > > entries = [for ( i = [1:10000] ) [ str("_",floor(i)), floor(i) ] ]; > large = object( entries ); > echo( large._3012 ); > > // > // Functions. You can store functions in objects. However, > // the function can not have access to the object's fields > // due to OpenSCAD's architecture. To mimic object oriented > // behavior, often a function that acts as context is > // useful. > // > > > function rect( w =0, h=0) = object( w=w, h=h, area = function() w*h ); > echo( rect(10,10).area()); // 100 > > > > > > > On 14 Jul 2025, at 13:55, Jon Bondy via Discuss < > discuss@lists.openscad.org> wrote: > > I would welcome a few simple examples, to get my feet wet. I used Pascal > for decades, so I am, trying to map this new feature to what I am familiar > with. > > What I see looks like a table with named parameters for each row. I have > used a similar technique using plain OpenSCAD for many years. I simply > create a series of named vectors as a dictionary, select a specific vector > at run-time, and then transfer the vector elements into local variables. > > Jon > > > On 7/13/2025 6:44 PM, Jordan Brown via Discuss wrote: > > Here's a program that I threw together that exercises the new object > features. > > It provides a framework for doing a general animation - at this time, do > this, at that time, do that. For an example of doing that without a > framework like this, look at https://openscad.org/advent-calendar-2023/ > at day 24. > > Remember that this requires 2025.07.11. Zoom as desired. > > // Best view is looking straight down at the origin. > $vpr = [0,0,0]; > $vpt = [0,0,0]; > > // Demonstration animation. Use FPS=10 and steps=100. > // Zoom as desired. > > // 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]); > > > > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to discuss-leave@lists.openscad.org > > > > <http://www.avg.com/email-signature?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=emailclient> > Virus-free.www.avg.com > <http://www.avg.com/email-signature?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=emailclient> > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to discuss-leave@lists.openscad.org > > > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to discuss-leave@lists.openscad.org >
JB
Jordan Brown
Mon, Jul 14, 2025 11:40 PM

On 7/14/2025 1:23 PM, Adrian Mariano via Discuss wrote:

Peter wrote:

// Functions. You can store functions in objects. However,
// the function can not have access to the object's fields
// due to OpenSCAD's architecture.

function rect( w =0, h=0) = object( w=w, h=h, area = function() w*h );
echo( rect(10,10).area()); // 100

So in this example, the area function is simply running on the w and h
parameters to rect?  That means it's the same as
object(w=w,h=h,area=w*h), right?   I guess the point is that the
function could do something more complicated with w and h that
involves a new parameter? 

In theory, yes.  And this gives you something sort of dimly like
methods, but yields really wrong answers in more complex cases; I do not
recommend it as a general pattern.

In particular, work through what this does:

function rect( w =0, h=0) = object( w=w, h=h, area = function() w*h );
o1 = rect(10,10);
echo(o1.area()); // 100
o2 = object(o1, w=20);
echo(o2.area()); // ???

Hint:  it does not yield 200 for that second echo.

On 7/14/2025 1:23 PM, Adrian Mariano via Discuss wrote: > Peter wrote: > >> // Functions. You can store functions in objects. However, >> // the function can not have access to the object's fields >> // due to OpenSCAD's architecture. >> >> function rect( w =0, h=0) = object( w=w, h=h, area = function() w*h ); >> echo( rect(10,10).area()); // 100 > > So in this example, the area function is simply running on the w and h > parameters to rect?  That means it's the same as > object(w=w,h=h,area=w*h), right?   I guess the point is that the > function could do something more complicated with w and h that > involves a new parameter?  In theory, yes.  And this gives you something sort of dimly like methods, but yields really wrong answers in more complex cases; I do not recommend it as a general pattern. In particular, work through what this does: function rect( w =0, h=0) = object( w=w, h=h, area = function() w*h ); o1 = rect(10,10); echo(o1.area()); // 100 o2 = object(o1, w=20); echo(o2.area()); // ??? Hint:  it does *not* yield 200 for that second echo.
PK
Peter Kriens
Tue, Jul 15, 2025 8:11 AM

On 14 Jul 2025, at 22:23, Adrian Mariano via Discuss discuss@lists.openscad.org wrote:
Peter wrote:
// Functions. You can store functions in objects. However,
// the function can not have access to the object's fields
// due to OpenSCAD's architecture.

function rect( w =0, h=0) = object( w=w, h=h, area = function() w*h );
echo( rect(10,10).area()); // 100

So in this example, the area function is simply running on the w and h parameters to rect?  That means it's the same as object(w=w,h=h,area=w*h), right?  I guess the point is that the function could do something more complicated with w and h that involves a new parameter?

Currently the use of function in objects is rather useless since it does not have access to the actual object it came from. Context binding happens during function creating. When making this example document I posted, I realized they were even less useful than I'd hoped. The current planning document https://github.com/openscad/openscad/pull/6022 already discussed a solution: a $this reference to the current object.

So I added $this to a current draft PR. The $this value provides access to the object where the method came from.

Rect = object( 
	area	=	function() $this.w, $this.h
);
r = object(Rect, w=10,h=20);
echo(r.area()); // 200

Since putting $this in front of every member is annoying, I also proposed adding the members of $this to the current context, before the parameters. This then allows:

Rect = object(
	new	=	function(w=0,h=0) /*validate*/ object($this, w=w, h=h),
	area 	= 	function() w * h, 
	volume	=	function(z) area() * z 
);
r = Rect.new( w=10, h=20);
echo(r.area(), r.volume(10)); // 200, 2000
echo(object(r,w=20).area()); // 400

However, this is a draft PR I posted yesterday and we're in discussion.

https://github.com/openscad/openscad/pull/6022

Feedback appreciated!

Peter

On Mon, Jul 14, 2025 at 8:34 AM Peter Kriens via Discuss <discuss@lists.openscad.org mailto:discuss@lists.openscad.org> wrote:

I am working on an example in the build source code tree. I can share the current incarnation. Feel free to comment, ask questions so I can elucidate them, or provide suggestions.

//
// examples with objects.
//
// Objects are immutable. Once an object is created, its values
// can not be changed.

//
// Construct a simple object
//
rectangle = object( w = 100, h= 20 );
echo(rectangle); // { w = 100; h = 20; }

//
// Access is via identifier
//
echo( rectangle.w, rectangle.h ); // 100, 20

//
// Or access is via string key
//
echo( rectangle["w"], rectangle["h"] ); // 100, 20

//
// You can test if a key is present
//

echo( has_key(rectangle,"w"), has_key(rectangle,"y")); // true, false

//
// You can use an object as a list of keys, where keys
// are always strings. For example, you can use them include
// in comprehension
//
values = [ for (k = rectangle) rectangle[k] ];
echo( values ); // [100, 20]

//
// To test if a parameter is an object, there is_bool
// an is_object function:
//

echo( is_object( rectangle )); // true
echo( is_object( [] )); // false

//
// You can use any type as value, key is
// always a string.

echo( object( name = "OpenSCAD.object", array=[1,2], bool=false) );
// { name = "OpenSCAD.object"; array = [1, 2]; bool = false; }

//
// Create a new object based on another object
//
volume = object( rectangle, d=10);
echo(volume); // { w = 100; h = 20; d = 10 }

//
// If you replace a field, it will take its original
// position
//
echo( object( volume, w=10) ); // { w = 10; h = 20; d = 10 }

//
// You can copy from multiple objects. This will
// be assigned in order, later objects override the early ones
//
echo( object(rectangle, volume) ); // { w = 100; h = 20; d = 10; }

//
// Keys can also be created dynamically. For this reason
// the object() function accepts a list with edit instructions.
// An element in this list is either ["k"] for a delete
// or ["k",v] for a new/override key.
//

echo( object( rectangle, [ ["w"], ["h"]] )); // {}
echo( object( [ ["w",10], ["h",10]] )); //  { w = 10; h = 10; }
echo( object( rectangle, [ ["z",20]])); //  { w = 100; h = 20; z = 20; }

//
// copy, deletes and set can be combined in one call.
//
echo( object( rectangle, [ ["z",20], ["w"]], h=10)); //  { h = 10; z = 20; }

//
// This works for large number of calculated entries
//

entries = [for ( i = [1:10000] ) [ str("_",floor(i)), floor(i) ] ];
large = object( entries );
echo( large._3012 );

//
// Functions. You can store functions in objects. However,
// the function can not have access to the object's fields
// due to OpenSCAD's architecture. To mimic object oriented
// behavior, often a function that acts as context is
// useful.
//

function rect( w =0, h=0) = object( w=w, h=h, area = function() w*h );
echo( rect(10,10).area()); // 100

On 14 Jul 2025, at 13:55, Jon Bondy via Discuss <discuss@lists.openscad.org mailto:discuss@lists.openscad.org> wrote:

I would welcome a few simple examples, to get my feet wet.  I used Pascal for decades, so I am, trying to map this new feature to what I am familiar with.

What I see looks like a table with named parameters for each row.  I have used a similar technique using plain OpenSCAD for many years.  I simply create a series of named vectors as a dictionary, select a specific vector at run-time, and then transfer the vector elements into local variables.

Jon

On 7/13/2025 6:44 PM, Jordan Brown via Discuss wrote:

Here's a program that I threw together that exercises the new object features.

It provides a framework for doing a general animation - at this time, do this, at that time, do that.  For an example of doing that without a framework like this, look at https://openscad.org/advent-calendar-2023/ at day 24.

Remember that this requires 2025.07.11.  Zoom as desired.

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

// Demonstration animation.  Use FPS=10 and steps=100.
// Zoom as desired.

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


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org mailto:discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org mailto:discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org

> On 14 Jul 2025, at 22:23, Adrian Mariano via Discuss <discuss@lists.openscad.org> wrote: > Peter wrote: > // Functions. You can store functions in objects. However, > // the function can not have access to the object's fields > // due to OpenSCAD's architecture. > > function rect( w =0, h=0) = object( w=w, h=h, area = function() w*h ); > echo( rect(10,10).area()); // 100 > So in this example, the area function is simply running on the w and h parameters to rect? That means it's the same as object(w=w,h=h,area=w*h), right? I guess the point is that the function could do something more complicated with w and h that involves a new parameter? Currently the use of function in objects is rather useless since it does not have access to the actual object it came from. Context binding happens during function creating. When making this example document I posted, I realized they were even less useful than I'd hoped. The current planning document <https://github.com/openscad/openscad/pull/6022> already discussed a solution: a $this reference to the current object. So I added $this to a current draft PR. The $this value provides access to the object where the method came from. Rect = object( area = function() $this.w, $this.h ); r = object(Rect, w=10,h=20); echo(r.area()); // 200 Since putting $this in front of every member is annoying, I also proposed adding the members of $this to the current context, before the parameters. This then allows: Rect = object( new = function(w=0,h=0) /*validate*/ object($this, w=w, h=h), area = function() w * h, volume = function(z) area() * z ); r = Rect.new( w=10, h=20); echo(r.area(), r.volume(10)); // 200, 2000 echo(object(r,w=20).area()); // 400 However, this is a draft PR I posted yesterday and we're in discussion. https://github.com/openscad/openscad/pull/6022 Feedback appreciated! Peter > > On Mon, Jul 14, 2025 at 8:34 AM Peter Kriens via Discuss <discuss@lists.openscad.org <mailto:discuss@lists.openscad.org>> wrote: >> I am working on an example in the build source code tree. I can share the current incarnation. Feel free to comment, ask questions so I can elucidate them, or provide suggestions. >> >> // >> // examples with objects. >> // >> // Objects are immutable. Once an object is created, its values >> // can not be changed. >> >> // >> // Construct a simple object >> // >> rectangle = object( w = 100, h= 20 ); >> echo(rectangle); // { w = 100; h = 20; } >> >> // >> // Access is via identifier >> // >> echo( rectangle.w, rectangle.h ); // 100, 20 >> >> // >> // Or access is via string key >> // >> echo( rectangle["w"], rectangle["h"] ); // 100, 20 >> >> // >> // You can test if a key is present >> // >> >> echo( has_key(rectangle,"w"), has_key(rectangle,"y")); // true, false >> >> // >> // You can use an object as a list of keys, where keys >> // are always strings. For example, you can use them include >> // in comprehension >> // >> values = [ for (k = rectangle) rectangle[k] ]; >> echo( values ); // [100, 20] >> >> // >> // To test if a parameter is an object, there is_bool >> // an is_object function: >> // >> >> echo( is_object( rectangle )); // true >> echo( is_object( [] )); // false >> >> >> // >> // You can use any type as value, key is >> // always a string. >> >> echo( object( name = "OpenSCAD.object", array=[1,2], bool=false) ); >> // { name = "OpenSCAD.object"; array = [1, 2]; bool = false; } >> >> // >> // Create a new object based on another object >> // >> volume = object( rectangle, d=10); >> echo(volume); // { w = 100; h = 20; d = 10 } >> >> // >> // If you replace a field, it will take its original >> // position >> // >> echo( object( volume, w=10) ); // { w = 10; h = 20; d = 10 } >> >> // >> // You can copy from multiple objects. This will >> // be assigned in order, later objects override the early ones >> // >> echo( object(rectangle, volume) ); // { w = 100; h = 20; d = 10; } >> >> // >> // Keys can also be created dynamically. For this reason >> // the object() function accepts a list with edit instructions. >> // An element in this list is either ["k"] for a delete >> // or ["k",v] for a new/override key. >> // >> >> echo( object( rectangle, [ ["w"], ["h"]] )); // {} >> echo( object( [ ["w",10], ["h",10]] )); // { w = 10; h = 10; } >> echo( object( rectangle, [ ["z",20]])); // { w = 100; h = 20; z = 20; } >> >> // >> // copy, deletes and set can be combined in one call. >> // >> echo( object( rectangle, [ ["z",20], ["w"]], h=10)); // { h = 10; z = 20; } >> >> >> // >> // This works for large number of calculated entries >> // >> >> entries = [for ( i = [1:10000] ) [ str("_",floor(i)), floor(i) ] ]; >> large = object( entries ); >> echo( large._3012 ); >> >> // >> // Functions. You can store functions in objects. However, >> // the function can not have access to the object's fields >> // due to OpenSCAD's architecture. To mimic object oriented >> // behavior, often a function that acts as context is >> // useful. >> // >> >> >> function rect( w =0, h=0) = object( w=w, h=h, area = function() w*h ); >> echo( rect(10,10).area()); // 100 >> >> >> >> >> >> >>> On 14 Jul 2025, at 13:55, Jon Bondy via Discuss <discuss@lists.openscad.org <mailto:discuss@lists.openscad.org>> wrote: >>> >>> I would welcome a few simple examples, to get my feet wet. I used Pascal for decades, so I am, trying to map this new feature to what I am familiar with. >>> >>> What I see looks like a table with named parameters for each row. I have used a similar technique using plain OpenSCAD for many years. I simply create a series of named vectors as a dictionary, select a specific vector at run-time, and then transfer the vector elements into local variables. >>> >>> Jon >>> >>> >>> >>> On 7/13/2025 6:44 PM, Jordan Brown via Discuss wrote: >>>> Here's a program that I threw together that exercises the new object features. >>>> >>>> It provides a framework for doing a general animation - at this time, do this, at that time, do that. For an example of doing that without a framework like this, look at https://openscad.org/advent-calendar-2023/ at day 24. >>>> >>>> Remember that this requires 2025.07.11. Zoom as desired. >>>> >>>> // Best view is looking straight down at the origin. >>>> $vpr = [0,0,0]; >>>> $vpt = [0,0,0]; >>>> >>>> // Demonstration animation. Use FPS=10 and steps=100. >>>> // Zoom as desired. >>>> >>>> // 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]); >>>> >>>> >>>> >>>> _______________________________________________ >>>> OpenSCAD mailing list >>>> To unsubscribe send an email to discuss-leave@lists.openscad.org <mailto:discuss-leave@lists.openscad.org> >>> >>> <http://www.avg.com/email-signature?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=emailclient> Virus-free.www.avg.com <http://www.avg.com/email-signature?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=emailclient>_______________________________________________ >>> OpenSCAD mailing list >>> To unsubscribe send an email to discuss-leave@lists.openscad.org <mailto:discuss-leave@lists.openscad.org> >> >> _______________________________________________ >> OpenSCAD mailing list >> To unsubscribe send an email to discuss-leave@lists.openscad.org <mailto:discuss-leave@lists.openscad.org> > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to discuss-leave@lists.openscad.org
JB
Jordan Brown
Sat, Jul 26, 2025 6:28 AM

Has anybody been playing with the new object() function?  Did anybody
take a look at my animation demo?

On 7/13/2025 3:44 PM, Jordan Brown via Discuss wrote:

Here's a program that I threw together that exercises the new object
features.

It provides a framework for doing a general animation - at this time,
do this, at that time, do that.  For an example of doing that without
a framework like this, look at
https://openscad.org/advent-calendar-2023/ at day 24.

Remember that this requires 2025.07.11.  Zoom as desired.

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

// Demonstration animation.  Use FPS=10 and steps=100.
// Zoom as desired.

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


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org

Has anybody been playing with the new object() function?  Did anybody take a look at my animation demo? On 7/13/2025 3:44 PM, Jordan Brown via Discuss wrote: > Here's a program that I threw together that exercises the new object > features. > > It provides a framework for doing a general animation - at this time, > do this, at that time, do that.  For an example of doing that without > a framework like this, look at > https://openscad.org/advent-calendar-2023/ at day 24. > > Remember that this requires 2025.07.11.  Zoom as desired. > > // Best view is looking straight down at the origin. > $vpr = [0,0,0]; > $vpt = [0,0,0]; > > // Demonstration animation. Use FPS=10 and steps=100. > // Zoom as desired. > > // 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]); > > > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to discuss-leave@lists.openscad.org
PK
Peter Kriens
Sat, Jul 26, 2025 2:54 PM

I somehow missed it, nice :-)

It is a wonderful new function. It is a pity that libraries cannot use it yet because it is experimental.

Peter

On 26 Jul 2025, at 08:28, Jordan Brown via Discuss discuss@lists.openscad.org wrote:

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

// Demonstration animation.  Use FPS=10 and steps=100.
// Zoom as desired.

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

I somehow missed it, nice :-) It is a wonderful new function. It is a pity that libraries cannot use it yet because it is experimental. Peter > On 26 Jul 2025, at 08:28, Jordan Brown via Discuss <discuss@lists.openscad.org> wrote: > >> // Best view is looking straight down at the origin. >> $vpr = [0,0,0]; >> $vpt = [0,0,0]; >> >> // Demonstration animation. Use FPS=10 and steps=100. >> // Zoom as desired. >> >> // 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]); >
NS
Nathan Sokalski
Sat, Jul 26, 2025 3:55 PM

It is a feature that I have looked forward to almost ever since I started using OpenSCAD (which I did when I first started doing 3D printing). One thing that I (and I am guessing many others) will need to do as well is figure out how to as well is convert & modify my existing projects & libraries. This will definitely be time consuming, and I have multiple libraries that I use in almost all of my projects. I am trying to figure out the best way to do this, since it will require either making a whole new version of the library (making a version of mylibrary.scad called mylibrary_obj.scad) for future use or updating every project in which I have used the library (which has human error and missing instance written all over it). Don't get me wrong, this is by no means a complaint, everything comes with a price, and this is a price I think is worth paying. I am also going to mention (although it probably can't be done until this is out of experimental stage) that I often use the Visual Studio Code extension for editing my projects, which will need updated (although that is obviously more a topic for their site), so if anybody has any association with working on that, I would suggest working on updating that (and the same probably applies to any other 3rd party editors) ASAP. So once again, thanks and I look forward to this feature, and hopefully everyone else does as well!

Nathan Sokalski
njsokalski@hotmail.commailto:njsokalski@hotmail.com


From: Peter Kriens via Discuss discuss@lists.openscad.org
Sent: Saturday, July 26, 2025 10:54 AM
To: OpenSCAD general discussion Mailing-list discuss@lists.openscad.org
Cc: Peter Kriens peter.kriens@aqute.biz
Subject: [OpenSCAD] Re: New feature in 2025.07.11: the object() function

I somehow missed it, nice :-)

It is a wonderful new function. It is a pity that libraries cannot use it yet because it is experimental.

Peter

On 26 Jul 2025, at 08:28, Jordan Brown via Discuss discuss@lists.openscad.org wrote:

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

// Demonstration animation.  Use FPS=10 and steps=100.
// Zoom as desired.

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

It is a feature that I have looked forward to almost ever since I started using OpenSCAD (which I did when I first started doing 3D printing). One thing that I (and I am guessing many others) will need to do as well is figure out how to as well is convert & modify my existing projects & libraries. This will definitely be time consuming, and I have multiple libraries that I use in almost all of my projects. I am trying to figure out the best way to do this, since it will require either making a whole new version of the library (making a version of mylibrary.scad called mylibrary_obj.scad) for future use or updating every project in which I have used the library (which has human error and missing instance written all over it). Don't get me wrong, this is by no means a complaint, everything comes with a price, and this is a price I think is worth paying. I am also going to mention (although it probably can't be done until this is out of experimental stage) that I often use the Visual Studio Code extension for editing my projects, which will need updated (although that is obviously more a topic for their site), so if anybody has any association with working on that, I would suggest working on updating that (and the same probably applies to any other 3rd party editors) ASAP. So once again, thanks and I look forward to this feature, and hopefully everyone else does as well! Nathan Sokalski njsokalski@hotmail.com<mailto:njsokalski@hotmail.com> ________________________________ From: Peter Kriens via Discuss <discuss@lists.openscad.org> Sent: Saturday, July 26, 2025 10:54 AM To: OpenSCAD general discussion Mailing-list <discuss@lists.openscad.org> Cc: Peter Kriens <peter.kriens@aqute.biz> Subject: [OpenSCAD] Re: New feature in 2025.07.11: the object() function I somehow missed it, nice :-) It is a wonderful new function. It is a pity that libraries cannot use it yet because it is experimental. Peter On 26 Jul 2025, at 08:28, Jordan Brown via Discuss <discuss@lists.openscad.org> wrote: // Best view is looking straight down at the origin. $vpr = [0,0,0]; $vpt = [0,0,0]; // Demonstration animation. Use FPS=10 and steps=100. // Zoom as desired. // 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]);
PK
Peter Kriens
Sun, Jul 27, 2025 12:59 PM

Hi Nathan,

Just a heads up. I am trying to get an additional feature in so that function objects can act as "methods". This will make it easy to add functions to your objects that will be bound to their 'current' object and not the original object.

Fixed binding to the original object can be very confusing. Since OpenSCAD has no mutability, you always need to make copies. However, if you'd copy the function object they remain bound to the original data.

a_prism = object( size=[10,20], size2=5, h=10, shift=[4,0], 
	function slice(begin=0,end=h) { ... }
);

You can now use a_prism.slice(2,8) but if you make a copy:

another_prism = object( a_prism, h=20)

The function another_prism.slice(2,8) will use the values of a_prism sadly.

Referencing the current object is imho a necessary feature to make objects really shine in libraries. The alternative, writing functions that take a data-only object works of course. However, this has the disadvantage that these functions are in the global shared namespace.

The advantage of functions in objects (aka methods) is that you can use nice short and simple names:

prism_slice( a_prism, begin=5, end=8);

Versus

a_prism.slice(begin=4, end=15);

So I'd wait a bit before you convert any libraries if you think this is interesting.

It might be nice if we could develop common conventions for this "object oriented" use of objects in OpenSCAD so don't hesitate to discuss issues and choices you encounter here.

Peter

On 26 Jul 2025, at 17:55, Nathan Sokalski via Discuss discuss@lists.openscad.org wrote:

It is a feature that I have looked forward to almost ever since I started using OpenSCAD (which I did when I first started doing 3D printing). One thing that I (and I am guessing many others) will need to do as well is figure out how to as well is convert & modify my existing projects & libraries. This will definitely be time consuming, and I have multiple libraries that I use in almost all of my projects. I am trying to figure out the best way to do this, since it will require either making a whole new version of the library (making a version of mylibrary.scad called mylibrary_obj.scad) for future use or updating every project in which I have used the library (which has human error and missing instance written all over it). Don't get me wrong, this is by no means a complaint, everything comes with a price, and this is a price I think is worth paying. I am also going to mention (although it probably can't be done until this is out of experimental stage) that I often use the Visual Studio Code extension for editing my projects, which will need updated (although that is obviously more a topic for their site), so if anybody has any association with working on that, I would suggest working on updating that (and the same probably applies to any other 3rd party editors) ASAP. So once again, thanks and I look forward to this feature, and hopefully everyone else does as well!

Nathan Sokalski
njsokalski@hotmail.com mailto:njsokalski@hotmail.com
From: Peter Kriens via Discuss discuss@lists.openscad.org
Sent: Saturday, July 26, 2025 10:54 AM
To: OpenSCAD general discussion Mailing-list discuss@lists.openscad.org
Cc: Peter Kriens peter.kriens@aqute.biz
Subject: [OpenSCAD] Re: New feature in 2025.07.11: the object() function

I somehow missed it, nice :-)

It is a wonderful new function. It is a pity that libraries cannot use it yet because it is experimental.

Peter

On 26 Jul 2025, at 08:28, Jordan Brown via Discuss discuss@lists.openscad.org wrote:

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

// Demonstration animation.  Use FPS=10 and steps=100.
// Zoom as desired.

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


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org mailto:discuss-leave@lists.openscad.org

Hi Nathan, Just a heads up. I am trying to get an additional feature in so that function objects can act as "methods". This will make it easy to add functions to your objects that will be bound to their 'current' object and not the original object. Fixed binding to the original object can be very confusing. Since OpenSCAD has no mutability, you always need to make copies. However, if you'd copy the function object they remain bound to the original data. a_prism = object( size=[10,20], size2=5, h=10, shift=[4,0], function slice(begin=0,end=h) { ... } ); You can now use `a_prism.slice(2,8)` but if you make a copy: another_prism = object( a_prism, h=20) The function `another_prism.slice(2,8)` will use the values of `a_prism` sadly. Referencing the current object is imho a necessary feature to make objects really shine in libraries. The alternative, writing functions that take a data-only object works of course. However, this has the disadvantage that these functions are in the global shared namespace. The advantage of functions in objects (aka methods) is that you can use nice short and simple names: prism_slice( a_prism, begin=5, end=8); Versus a_prism.slice(begin=4, end=15); So I'd wait a bit before you convert any libraries if you think this is interesting. It might be nice if we could develop common conventions for this "object oriented" use of objects in OpenSCAD so don't hesitate to discuss issues and choices you encounter here. Peter > On 26 Jul 2025, at 17:55, Nathan Sokalski via Discuss <discuss@lists.openscad.org> wrote: > > It is a feature that I have looked forward to almost ever since I started using OpenSCAD (which I did when I first started doing 3D printing). One thing that I (and I am guessing many others) will need to do as well is figure out how to as well is convert & modify my existing projects & libraries. This will definitely be time consuming, and I have multiple libraries that I use in almost all of my projects. I am trying to figure out the best way to do this, since it will require either making a whole new version of the library (making a version of mylibrary.scad called mylibrary_obj.scad) for future use or updating every project in which I have used the library (which has human error and missing instance written all over it). Don't get me wrong, this is by no means a complaint, everything comes with a price, and this is a price I think is worth paying. I am also going to mention (although it probably can't be done until this is out of experimental stage) that I often use the Visual Studio Code extension for editing my projects, which will need updated (although that is obviously more a topic for their site), so if anybody has any association with working on that, I would suggest working on updating that (and the same probably applies to any other 3rd party editors) ASAP. So once again, thanks and I look forward to this feature, and hopefully everyone else does as well! > > Nathan Sokalski > njsokalski@hotmail.com <mailto:njsokalski@hotmail.com> > From: Peter Kriens via Discuss <discuss@lists.openscad.org> > Sent: Saturday, July 26, 2025 10:54 AM > To: OpenSCAD general discussion Mailing-list <discuss@lists.openscad.org> > Cc: Peter Kriens <peter.kriens@aqute.biz> > Subject: [OpenSCAD] Re: New feature in 2025.07.11: the object() function > > I somehow missed it, nice :-) > > It is a wonderful new function. It is a pity that libraries cannot use it yet because it is experimental. > > Peter > > > > > > > >> On 26 Jul 2025, at 08:28, Jordan Brown via Discuss <discuss@lists.openscad.org> wrote: >> >>> // Best view is looking straight down at the origin. >>> $vpr = [0,0,0]; >>> $vpt = [0,0,0]; >>> >>> // Demonstration animation. Use FPS=10 and steps=100. >>> // Zoom as desired. >>> >>> // 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]); >> > > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to discuss-leave@lists.openscad.org <mailto:discuss-leave@lists.openscad.org>
AM
Adrian Mariano
Sun, Jul 27, 2025 6:36 PM

This feature is something I’ve been waiting for and even if Peter doesn’t
contrive a way to make methods it will still make a big difference. But
BOSL2 supports the stable openscad with just a few very localized
exceptions for textmetrics so I have not actually tried the new feature
yet.

Also as someone else noted the task of rewriting to use the new feature is
a big one and not backwards compatible (except in the case of changes that
are entirely internal).

On Sun, Jul 27, 2025 at 09:01 Peter Kriens via Discuss <
discuss@lists.openscad.org> wrote:

Hi Nathan,

Just a heads up. I am trying to get an additional feature in so that
function objects can act as "methods". This will make it easy to add
functions to your objects that will be bound to their 'current' object and
not the original object.

Fixed binding to the original object can be very confusing. Since OpenSCAD
has no mutability, you always need to make copies. However, if you'd copy
the function object they remain bound to the original data.

a_prism = object( size=[10,20], size2=5, h=10, shift=[4,0],
function slice(begin=0,end=h) { ... }
);

You can now use a_prism.slice(2,8) but if you make a copy:

another_prism = object( a_prism, h=20)

The function another_prism.slice(2,8) will use the values of a_prism
sadly.

Referencing the current object is imho a necessary feature to make objects
really shine in libraries. The alternative, writing functions that take a
data-only object works of course. However, this has the disadvantage that
these functions are in the global shared namespace.

The advantage of functions in objects (aka methods) is that you can use
nice short and simple names:

prism_slice( a_prism, begin=5, end=8);

Versus

a_prism.slice(begin=4, end=15);

So I'd wait a bit before you convert any libraries if you think this is
interesting.

It might be nice if we could develop common conventions for this "object
oriented" use of objects in OpenSCAD so don't hesitate to discuss issues
and choices you encounter here.

Peter

On 26 Jul 2025, at 17:55, Nathan Sokalski via Discuss <
discuss@lists.openscad.org> wrote:

It is a feature that I have looked forward to almost ever since I started
using OpenSCAD (which I did when I first started doing 3D printing). One
thing that I (and I am guessing many others) will need to do as well is
figure out how to as well is convert & modify my existing projects &
libraries. This will definitely be time consuming, and I have multiple
libraries that I use in almost all of my projects. I am trying to figure
out the best way to do this, since it will require either making a whole
new version of the library (making a version of mylibrary.scad called
mylibrary_obj.scad) for future use or updating every project in which I
have used the library (which has human error and missing instance written
all over it). Don't get me wrong, this is by no means a complaint,
everything comes with a price, and this is a price I think is worth paying.
I am also going to mention (although it probably can't be done until this
is out of experimental stage) that I often use the Visual Studio Code
extension for editing my projects, which will need updated (although that
is obviously more a topic for their site), so if anybody has any
association with working on that, I would suggest working on updating that
(and the same probably applies to any other 3rd party editors) ASAP. So
once again, thanks and I look forward to this feature, and hopefully
everyone else does as well!

Nathan Sokalski
njsokalski@hotmail.com

From: Peter Kriens via Discuss discuss@lists.openscad.org
Sent: Saturday, July 26, 2025 10:54 AM
To: OpenSCAD general discussion Mailing-list <discuss@lists.openscad.org

Cc: Peter Kriens peter.kriens@aqute.biz
Subject: [OpenSCAD] Re: New feature in 2025.07.11: the object() function

I somehow missed it, nice :-)

It is a wonderful new function. It is a pity that libraries cannot use it
yet because it is experimental.

Peter

On 26 Jul 2025, at 08:28, Jordan Brown via Discuss <
discuss@lists.openscad.org> wrote:

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

// Demonstration animation.  Use FPS=10 and steps=100.
// Zoom as desired.

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


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org

This feature is something I’ve been waiting for and even if Peter doesn’t contrive a way to make methods it will still make a big difference. But BOSL2 supports the stable openscad with just a few very localized exceptions for textmetrics so I have not actually tried the new feature yet. Also as someone else noted the task of rewriting to use the new feature is a big one and not backwards compatible (except in the case of changes that are entirely internal). On Sun, Jul 27, 2025 at 09:01 Peter Kriens via Discuss < discuss@lists.openscad.org> wrote: > Hi Nathan, > > Just a heads up. I am trying to get an additional feature in so that > function objects can act as "methods". This will make it easy to add > functions to your objects that will be bound to their 'current' object and > not the original object. > > Fixed binding to the original object can be very confusing. Since OpenSCAD > has no mutability, you always need to make copies. However, if you'd copy > the function object they remain bound to the original data. > > a_prism = object( size=[10,20], size2=5, h=10, shift=[4,0], > function slice(begin=0,end=h) { ... } > ); > > You can now use `a_prism.slice(2,8)` but if you make a copy: > > another_prism = object( a_prism, h=20) > > The function `another_prism.slice(2,8)` will use the values of `a_prism` > sadly. > > Referencing the current object is imho a necessary feature to make objects > really shine in libraries. The alternative, writing functions that take a > data-only object works of course. However, this has the disadvantage that > these functions are in the global shared namespace. > > The advantage of functions in objects (aka methods) is that you can use > nice short and simple names: > > prism_slice( a_prism, begin=5, end=8); > > Versus > > a_prism.slice(begin=4, end=15); > > So I'd wait a bit before you convert any libraries if you think this is > interesting. > > It might be nice if we could develop common conventions for this "object > oriented" use of objects in OpenSCAD so don't hesitate to discuss issues > and choices you encounter here. > > Peter > > > > > On 26 Jul 2025, at 17:55, Nathan Sokalski via Discuss < > discuss@lists.openscad.org> wrote: > > It is a feature that I have looked forward to almost ever since I started > using OpenSCAD (which I did when I first started doing 3D printing). One > thing that I (and I am guessing many others) will need to do as well is > figure out how to as well is convert & modify my existing projects & > libraries. This will definitely be time consuming, and I have multiple > libraries that I use in almost all of my projects. I am trying to figure > out the best way to do this, since it will require either making a whole > new version of the library (making a version of mylibrary.scad called > mylibrary_obj.scad) for future use or updating every project in which I > have used the library (which has human error and missing instance written > all over it). Don't get me wrong, this is by no means a complaint, > everything comes with a price, and this is a price I think is worth paying. > I am also going to mention (although it probably can't be done until this > is out of experimental stage) that I often use the Visual Studio Code > extension for editing my projects, which will need updated (although that > is obviously more a topic for their site), so if anybody has any > association with working on that, I would suggest working on updating that > (and the same probably applies to any other 3rd party editors) ASAP. So > once again, thanks and I look forward to this feature, and hopefully > everyone else does as well! > > Nathan Sokalski > njsokalski@hotmail.com > ------------------------------ > *From:* Peter Kriens via Discuss <discuss@lists.openscad.org> > *Sent:* Saturday, July 26, 2025 10:54 AM > *To:* OpenSCAD general discussion Mailing-list <discuss@lists.openscad.org > > > *Cc:* Peter Kriens <peter.kriens@aqute.biz> > *Subject:* [OpenSCAD] Re: New feature in 2025.07.11: the object() function > > I somehow missed it, nice :-) > > It is a wonderful new function. It is a pity that libraries cannot use it > yet because it is experimental. > > Peter > > > > > > > > On 26 Jul 2025, at 08:28, Jordan Brown via Discuss < > discuss@lists.openscad.org> wrote: > > // Best view is looking straight down at the origin. > $vpr = [0,0,0]; > $vpt = [0,0,0]; > > // Demonstration animation. Use FPS=10 and steps=100. > // Zoom as desired. > > // 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]); > > > > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to discuss-leave@lists.openscad.org > > > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to discuss-leave@lists.openscad.org >