discuss@lists.openscad.org

OpenSCAD general discussion Mailing-list

View all threads

New feature in 2025.07.11: the object() function

JB
Jordan Brown
Sun, Jul 13, 2025 10:20 PM
  Quick summary:
  • 2025.07.11 adds the the object() function, which creates an object
    (in the style returned by textmetrics(), fontmetrics(), and import()
    of a JSON file).

  • It accumulates the object by processing the arguments left to right,
    with later settings for a particular member replacing earlier settings.

  • There are three forms for an argument:
    o /name/=/value/ - sets that name (a constant identifier) to that
    value.
    o A vector with a list of [/name/, /value/] vectors, or [/name/]
    to remove a member, where the names can be any string expression.
    o An object has its members copied.

  • There is a new function has_key(/obj/, /name/) that returns true if
    the object contains the named key.

  • These functions are currently experimental and so must be enabled
    before you can use them.

    Overview:

An "object" is a collection of names and associated values.  In other
languages this data structure might be called an object (JavaScript), a
dictionary (Python), an associative array (some UNIX shells, awk), or a
structure (C, sort of),

This change adds a function that creates an object from a series of
names, values, and other objects, and a function that queries whether a
particular member is present.  These are mostly-normal functions; there
is no new syntax introduced.

  Background:

OpenSCAD has had an internal implementation of objects for several
years, added to support the textmetrics() and fontmetrics() functions,
and later import() of a JSON file.  This existing mechanism includes
mechanisms for accessing the members of the object and for walking
through the entries.  It does not include a mechanism for the user's
program to create an object.

Given an object /o/, the current operations are:

  • /o/./name/ yields the value of the /name/ member, where /name/ must
    be an identifier (alphabetic, numeric, underscore, starting with
    alphabetic or underscore).  This syntax is very "clean", but does
    not allow for names that are derived from expressions or for names
    that are not suitable for use as identifiers.  An undefined name is
    not an error; it yields undefined.

  • /o/[/name/] also yields the value of the /name/ member, but /name/
    can be an any string expression.  This syntax is a bit more awkward
    than the /o/./name/ syntax, but allows for dynamically-created names
    and for names that are not suitable as identifiers.  Again, and
    undefined name yields undefined.

  • for (/name/ = /o/) ... is the object equivalent of the vector
    for(...).  It walks the object, setting /name/ to each member name
    in sequence.  This mechanism is usable as both a normal statement
    and as a list comprehension element.  Note that it yields only the
    name; the value is accessible as /o/[/name/].  Formally the entries
    should be assumed to be in no specific order, but for aesthetic
    reasons they are reported in the order they were added to the object.

  • is_object(/v/) returns true if /v/ is an object.

  • echo(/o/) and str(/o/) produce textual representations of objects. 
    (Note:  the textual form looks sort of like syntax, but is not legal
    OpenSCAD syntax.  It is likely to change in the future; see Future
    Directions
    below.)

    Details:

The object() function constructs a new object, processing each argument
in sequence from left to right, with later settings replacing earlier
settings.  There are three variations of arguments; they can be mixed in
any way.

  • A named parameter /name/=/value/
    o Sets the specified name to the specified value.  As with all
    named arguments to functions, the name must be an identifier.
  • a vector [ /v1/, /v2/, ... ]
    o /v1/, /v2/, ... are each two- or one-element vectors
    o [/name/, /value/]
    + Sets the specified name to the specified value.  The name
    can be any string expression.
    o [/name/]
    + Removes the specified name from the object being
    accumulated.  (Note that this is subtly different from
    setting it to undefined, in that it will not be reported for
    has_key() or when walking the names of the object.)
  • an object /o/
    o An object has each of its members copied into the new object.

The values contained in an object can (of course) be of any data type: 
numbers, strings, vectors, function references, objects, et cetera.

There is also a new function has_key(/o/, /name/) that returns true if
the object has a member with the specified name.

  Examples:
  • Create an object; access its members:
    o = object(a=1, b=2);
    echo(o.a, o["b"]);

  • Create an object with varying names, and access them:
    names = [ "apple", "banana", "string bean" ];
    o = object([ for (name=names) [name, 123] ]);
    for (name=names) echo(name, o[name]);

  • Create an object, then create modified copies of that object:
    // Ancient planets
    planets = object(mercury=1, venus=2, earth=3, mars=4, jupiter=5,
    saturn=6);
    planets1781 = object(planets, uranus=7);        // Uranus discovered
    planets1846 = object(planets1781, neptune=8);   // Neptune discovered
    planets1930 = object(planets1846, pluto=9);     // Pluto discovered
    planets2006 = object(planets1930, [["pluto"]]); // Pluto un-planeted

  • Check whether a member is present:
    echo(has_key(planets1846, "neptune")); // true
    echo(has_key(planets2006, "pluto"));   // false

    Future Directions and Related Projects:

This is the second phase (after the textmetrics() work) of a longer-term
plan to introduce "object" features into OpenSCAD.

Object literals, object comprehension:  OEP8a
https://github.com/openscad/openscad/wiki/OEP8a:--Objects-(dictionaries%3F)
adds a syntax for creating objects, including object comprehensions,
roughly modeled on JavaScript object syntax, so that { a: 1, b: 2 } is
equivalent to object(a=1, b=2). Changes echo() and str() to represent
objects using this syntax.

OEP8
https://github.com/openscad/openscad/wiki/OEP8%3A-Objects-%28dictionaries%3F%29%2C-Geometry-as-data%2C-and-Module-References
further adds geometry as data and module references.

No formal proposals:

Methods: If a function reference comes from an object or a vector, it
should see a special variable $this that refers to the containing object
or vector.  Note that although there's no inheritance per se, making a
modified copy of an object is a lot like the prototype-based OO
https://en.wikipedia.org/wiki/Prototype-based_programming.

Variable parameter lists:  a syntax for a parameter lists that says
"return the rest of the parameters in this variable", as a vector (for
positional arguments) or an object (for named arguments).

Spread syntax:  a syntax for adding a vector to an argument list as
positional arguments, or adding an object to an argument list as named
arguments.

Sets:  Some kind of syntactic sugar to make it easy to create an object
containing boolean "true", to make it easy to define a set (in the
mathematical sense) and query whether particular items are present in it.

  Questions:
  • What should these things be called?
    o They're modeled on JavaScript objects, but to a Python person
    they look more like dictionaries and to a C person they look
    more like structures.
    o Are they really "objects", when they don't have OO features? 
    See Future Directions about methods.
    o In OpenSCAD, doesn't "object" already mean a geometric figure?

    Credits / History:

  • I did the original textmetrics() work.

  • Revar Desmera did the original implementation of object().

  • Peter Kriens drove this final integration, cleaning up the
    implementation, fixing a bug, and writing test cases.

Quick summary: * 2025.07.11 adds the the object() function, which creates an object (in the style returned by textmetrics(), fontmetrics(), and import() of a JSON file). * It accumulates the object by processing the arguments left to right, with later settings for a particular member replacing earlier settings. * There are three forms for an argument: o /name/=/value/ - sets that name (a constant identifier) to that value. o A vector with a list of [/name/, /value/] vectors, or [/name/] to remove a member, where the names can be any string expression. o An object has its members copied. * There is a new function has_key(/obj/, /name/) that returns true if the object contains the named key. * These functions are currently experimental and so must be enabled before you can use them. Overview: An "object" is a collection of names and associated values.  In other languages this data structure might be called an object (JavaScript), a dictionary (Python), an associative array (some UNIX shells, awk), or a structure (C, sort of), This change adds a function that creates an object from a series of names, values, and other objects, and a function that queries whether a particular member is present.  These are mostly-normal functions; there is no new syntax introduced. Background: OpenSCAD has had an internal implementation of objects for several years, added to support the textmetrics() and fontmetrics() functions, and later import() of a JSON file.  This existing mechanism includes mechanisms for accessing the members of the object and for walking through the entries.  It does *not* include a mechanism for the user's program to create an object. Given an object /o/, the current operations are: * /o/./name/ yields the value of the /name/ member, where /name/ must be an identifier (alphabetic, numeric, underscore, starting with alphabetic or underscore).  This syntax is very "clean", but does not allow for names that are derived from expressions or for names that are not suitable for use as identifiers.  An undefined name is not an error; it yields undefined. * /o/[/name/] also yields the value of the /name/ member, but /name/ can be an any string expression.  This syntax is a bit more awkward than the /o/./name/ syntax, but allows for dynamically-created names and for names that are not suitable as identifiers.  Again, and undefined name yields undefined. * for (/name/ = /o/) ... is the object equivalent of the vector for(...).  It walks the object, setting /name/ to each member name in sequence.  This mechanism is usable as both a normal statement and as a list comprehension element.  Note that it yields only the name; the value is accessible as /o/[/name/].  Formally the entries should be assumed to be in no specific order, but for aesthetic reasons they are reported in the order they were added to the object. * is_object(/v/) returns true if /v/ is an object. * echo(/o/) and str(/o/) produce textual representations of objects.  (Note:  the textual form looks sort of like syntax, but is not legal OpenSCAD syntax.  It is likely to change in the future; see *Future Directions* below.) Details: The object() function constructs a new object, processing each argument in sequence from left to right, with later settings replacing earlier settings.  There are three variations of arguments; they can be mixed in any way. * A named parameter /name/=/value/ o Sets the specified name to the specified value.  As with all named arguments to functions, the name must be an identifier. * a vector [ /v1/, /v2/, ... ] o /v1/, /v2/, ... are each two- or one-element vectors o [/name/, /value/] + Sets the specified name to the specified value.  The name can be any string expression. o [/name/] + Removes the specified name from the object being accumulated.  (Note that this is subtly different from setting it to undefined, in that it will not be reported for has_key() or when walking the names of the object.) * an object /o/ o An object has each of its members copied into the new object. The values contained in an object can (of course) be of any data type:  numbers, strings, vectors, function references, objects, et cetera. There is also a new function has_key(/o/, /name/) that returns true if the object has a member with the specified name. Examples: * Create an object; access its members: o = object(a=1, b=2); echo(o.a, o["b"]); * Create an object with varying names, and access them: names = [ "apple", "banana", "string bean" ]; o = object([ for (name=names) [name, 123] ]); for (name=names) echo(name, o[name]); * Create an object, then create modified copies of that object: // Ancient planets planets = object(mercury=1, venus=2, earth=3, mars=4, jupiter=5, saturn=6); planets1781 = object(planets, uranus=7);        // Uranus discovered planets1846 = object(planets1781, neptune=8);   // Neptune discovered planets1930 = object(planets1846, pluto=9);     // Pluto discovered planets2006 = object(planets1930, [["pluto"]]); // Pluto un-planeted * Check whether a member is present: echo(has_key(planets1846, "neptune")); // true echo(has_key(planets2006, "pluto"));   // false Future Directions and Related Projects: This is the second phase (after the textmetrics() work) of a longer-term plan to introduce "object" features into OpenSCAD. Object literals, object comprehension:  OEP8a <https://github.com/openscad/openscad/wiki/OEP8a:--Objects-(dictionaries%3F)> adds a syntax for creating objects, including object comprehensions, roughly modeled on JavaScript object syntax, so that { a: 1, b: 2 } is equivalent to object(a=1, b=2). Changes echo() and str() to represent objects using this syntax. OEP8 <https://github.com/openscad/openscad/wiki/OEP8%3A-Objects-%28dictionaries%3F%29%2C-Geometry-as-data%2C-and-Module-References> further adds geometry as data and module references. No formal proposals: Methods: If a function reference comes from an object or a vector, it should see a special variable $this that refers to the containing object or vector.  Note that although there's no inheritance per se, making a modified copy of an object is a lot like the prototype-based OO <https://en.wikipedia.org/wiki/Prototype-based_programming>. Variable parameter lists:  a syntax for a parameter lists that says "return the rest of the parameters in this variable", as a vector (for positional arguments) or an object (for named arguments). Spread syntax:  a syntax for adding a vector to an argument list as positional arguments, or adding an object to an argument list as named arguments. Sets:  Some kind of syntactic sugar to make it easy to create an object containing boolean "true", to make it easy to define a set (in the mathematical sense) and query whether particular items are present in it. Questions: * What should these things be called? o They're modeled on JavaScript objects, but to a Python person they look more like dictionaries and to a C person they look more like structures. o Are they really "objects", when they don't have OO features?  See *Future Directions* about methods. o In OpenSCAD, doesn't "object" already mean a geometric figure? Credits / History: * I did the original textmetrics() work. * Revar Desmera did the original implementation of object(). * Peter Kriens drove this final integration, cleaning up the implementation, fixing a bug, and writing test cases.
JB
Jordan Brown
Sun, Jul 13, 2025 10:44 PM

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

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]);
NS
Nathan Sokalski
Mon, Jul 14, 2025 1:20 AM

I think this is a great step, it will definitely be great when creating libraries, especially ones with values that would be the equivalent of enumerations in other languages. I have created multiple libraries, but it was always inappropriate to use the "include" command, because so many libraries had variables with similar names, this will definitely improve the organization of shared libraries. Even though this is currently in the experimental stage, is there anywhere that the current [planned] syntax is available? I did not see the object() function on the Cheat Sheet (which is understandable)? Thanks again!

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


From: Jordan Brown via Discuss discuss@lists.openscad.org
Sent: Sunday, July 13, 2025 6:20 PM
To: OpenSCAD discuss@lists.openscad.org
Cc: Jordan Brown openscad@jordan.maileater.net
Subject: [OpenSCAD] New feature in 2025.07.11: the object() function

Quick summary:

  • 2025.07.11 adds the the
    object()
    function, which creates an object (in the style returned by
    textmetrics()
    ,
    fontmetrics()
    , and
    import()
    of a JSON file).
  • It accumulates the object by processing the arguments left to right, with later settings for a particular member replacing earlier settings.
  • There are three forms for an argument:
    *
    name=value
  • sets that name (a constant identifier) to that value.
    • A vector with a list of
      [name, value]
      vectors, or
      [name]
      to remove a member, where the names can be any string expression.
    • An object has its members copied.
  • There is a new function
    has_key(obj, name)
    that returns true if the object contains the named key.
  • These functions are currently experimental and so must be enabled before you can use them.

Overview:

An "object" is a collection of names and associated values.  In other languages this data structure might be called an object (JavaScript), a dictionary (Python), an associative array (some UNIX shells, awk), or a structure (C, sort of),

This change adds a function that creates an object from a series of names, values, and other objects, and a function that queries whether a particular member is present.  These are mostly-normal functions; there is no new syntax introduced.

Background:

OpenSCAD has had an internal implementation of objects for several years, added to support the

textmetrics()
and
fontmetrics()
functions, and later
import()
of a JSON file.  This existing mechanism includes mechanisms for accessing the members of the object and for walking through the entries.  It does not include a mechanism for the user's program to create an object.

Given an object

o
, the current operations are:

o.name
yields the value of the
name
member, where
name
must be an identifier (alphabetic, numeric, underscore, starting with alphabetic or underscore).  This syntax is very "clean", but does not allow for names that are derived from expressions or for names that are not suitable for use as identifiers.  An undefined name is not an error; it yields
undefined
.
*
o[name]
also yields the value of the
name
member, but
name
can be an any string expression.  This syntax is a bit more awkward than the
o.name
syntax, but allows for dynamically-created names and for names that are not suitable as identifiers.  Again, and undefined name yields
undefined
.
*
for (name = o) ...
is the object equivalent of the vector
for(...)
.  It walks the object, setting
name
to each member name in sequence.  This mechanism is usable as both a normal statement and as a list comprehension element.  Note that it yields only the name; the value is accessible as
o[name]
.  Formally the entries should be assumed to be in no specific order, but for aesthetic reasons they are reported in the order they were added to the object.
*
is_object(v)
returns true if
v
is an object.
*
echo(
o
)
and
str(o)
produce textual representations of objects.  (Note:  the textual form looks sort of like syntax, but is not legal OpenSCAD syntax.  It is likely to change in the future; see Future Directions below.)

Details:

The

object()
function constructs a new object, processing each argument in sequence from left to right, with later settings replacing earlier settings.  There are three variations of arguments; they can be mixed in any way.

  • A named parameter
    name=value
    *  Sets the specified name to the specified value.  As with all named arguments to functions, the name must be an identifier.
  • a vector
    [ v1, v2, ... ]
    *  v1
    , v2
    , ... are each two- or one-element vectors
    *
    [name, value]
    • Sets the specified name to the specified value.  The name can be any string expression.
      *
      [name]
    • Removes the specified name from the object being accumulated.  (Note that this is subtly different from setting it to
      undefined
      , in that it will not be reported for
      has_key()
      or when walking the names of the object.)
  • an object
    o
    *  An object has each of its members copied into the new object.

The values contained in an object can (of course) be of any data type:  numbers, strings, vectors, function references, objects, et cetera.

There is also a new function
has_key(o, name)
that returns true if the object has a member with the specified name.
Examples:

  • Create an object; access its members:

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

  • Create an object with varying names, and access them:
    names = [ "apple", "banana", "string bean" ];
    o = object([ for (name=names) [name, 123] ]);
    for (name=names) echo(name, o[name]);
  • Create an object, then create modified copies of that object:
    // Ancient planets
    planets = object(mercury=1, venus=2, earth=3, mars=4, jupiter=5, saturn=6);
    planets1781 = object(planets, uranus=7);        // Uranus discovered
    planets1846 = object(planets1781, neptune=8);  // Neptune discovered
    planets1930 = object(planets1846, pluto=9);    // Pluto discovered
    planets2006 = object(planets1930, [["pluto"]]); // Pluto un-planeted
  • Check whether a member is present:
    echo(has_key(planets1846, "neptune")); // true
    echo(has_key(planets2006, "pluto"));  // false

Future Directions and Related Projects:

This is the second phase (after the

textmetrics()
work) of a longer-term plan to introduce "object" features into OpenSCAD.

Object literals, object comprehension:  OEP8ahttps://github.com/openscad/openscad/wiki/OEP8a:--Objects-(dictionaries%3F) adds a syntax for creating objects, including object comprehensions, roughly modeled on JavaScript object syntax, so that

{ a: 1, b: 2 }
is equivalent to
object(a=1, b=2)
. Changes
echo()
and
str()
to represent objects using this syntax.

OEP8https://github.com/openscad/openscad/wiki/OEP8%3A-Objects-%28dictionaries%3F%29%2C-Geometry-as-data%2C-and-Module-References further adds geometry as data and module references.

No formal proposals:

Methods: If a function reference comes from an object or a vector, it should see a special variable

$this
that refers to the containing object or vector.  Note that although there's no inheritance per se, making a modified copy of an object is a lot like the prototype-based OOhttps://en.wikipedia.org/wiki/Prototype-based_programming.

Variable parameter lists:  a syntax for a parameter lists that says "return the rest of the parameters in this variable", as a vector (for positional arguments) or an object (for named arguments).

Spread syntax:  a syntax for adding a vector to an argument list as positional arguments, or adding an object to an argument list as named arguments.

Sets:  Some kind of syntactic sugar to make it easy to create an object containing boolean "true", to make it easy to define a set (in the mathematical sense) and query whether particular items are present in it.

Questions:

  • What should these things be called?
    *  They're modeled on JavaScript objects, but to a Python person they look more like dictionaries and to a C person they look more like structures.
    *  Are they really "objects", when they don't have OO features?  See Future Directions about methods.
    *  In OpenSCAD, doesn't "object" already mean a geometric figure?

Credits / History:

  • I did the original textmetrics() work.
  • Revar Desmera did the original implementation of
    object()
    .
  • Peter Kriens drove this final integration, cleaning up the implementation, fixing a bug, and writing test cases.
I think this is a great step, it will definitely be great when creating libraries, especially ones with values that would be the equivalent of enumerations in other languages. I have created multiple libraries, but it was always inappropriate to use the "include" command, because so many libraries had variables with similar names, this will definitely improve the organization of shared libraries. Even though this is currently in the experimental stage, is there anywhere that the current [planned] syntax is available? I did not see the object() function on the Cheat Sheet (which is understandable)? Thanks again! Nathan Sokalski njsokalski@hotmail.com<mailto:njsokalski@hotmail.com> ________________________________ From: Jordan Brown via Discuss <discuss@lists.openscad.org> Sent: Sunday, July 13, 2025 6:20 PM To: OpenSCAD <discuss@lists.openscad.org> Cc: Jordan Brown <openscad@jordan.maileater.net> Subject: [OpenSCAD] New feature in 2025.07.11: the object() function Quick summary: * 2025.07.11 adds the the object() function, which creates an object (in the style returned by textmetrics() , fontmetrics() , and import() of a JSON file). * It accumulates the object by processing the arguments left to right, with later settings for a particular member replacing earlier settings. * There are three forms for an argument: * name=value - sets that name (a constant identifier) to that value. * A vector with a list of [name, value] vectors, or [name] to remove a member, where the names can be any string expression. * An object has its members copied. * There is a new function has_key(obj, name) that returns true if the object contains the named key. * These functions are currently experimental and so must be enabled before you can use them. Overview: An "object" is a collection of names and associated values. In other languages this data structure might be called an object (JavaScript), a dictionary (Python), an associative array (some UNIX shells, awk), or a structure (C, sort of), This change adds a function that creates an object from a series of names, values, and other objects, and a function that queries whether a particular member is present. These are mostly-normal functions; there is no new syntax introduced. Background: OpenSCAD has had an internal implementation of objects for several years, added to support the textmetrics() and fontmetrics() functions, and later import() of a JSON file. This existing mechanism includes mechanisms for accessing the members of the object and for walking through the entries. It does not include a mechanism for the user's program to create an object. Given an object o , the current operations are: * o.name yields the value of the name member, where name must be an identifier (alphabetic, numeric, underscore, starting with alphabetic or underscore). This syntax is very "clean", but does not allow for names that are derived from expressions or for names that are not suitable for use as identifiers. An undefined name is not an error; it yields undefined . * o[name] also yields the value of the name member, but name can be an any string expression. This syntax is a bit more awkward than the o.name syntax, but allows for dynamically-created names and for names that are not suitable as identifiers. Again, and undefined name yields undefined . * for (name = o) ... is the object equivalent of the vector for(...) . It walks the object, setting name to each member name in sequence. This mechanism is usable as both a normal statement and as a list comprehension element. Note that it yields only the name; the value is accessible as o[name] . Formally the entries should be assumed to be in no specific order, but for aesthetic reasons they are reported in the order they were added to the object. * is_object(v) returns true if v is an object. * echo( o ) and str(o) produce textual representations of objects. (Note: the textual form looks sort of like syntax, but is not legal OpenSCAD syntax. It is likely to change in the future; see Future Directions below.) Details: The object() function constructs a new object, processing each argument in sequence from left to right, with later settings replacing earlier settings. There are three variations of arguments; they can be mixed in any way. * A named parameter name=value * Sets the specified name to the specified value. As with all named arguments to functions, the name must be an identifier. * a vector [ v1, v2, ... ] * v1 , v2 , ... are each two- or one-element vectors * [name, value] * Sets the specified name to the specified value. The name can be any string expression. * [name] * Removes the specified name from the object being accumulated. (Note that this is subtly different from setting it to undefined , in that it will not be reported for has_key() or when walking the names of the object.) * an object o * An object has each of its members copied into the new object. The values contained in an object can (of course) be of any data type: numbers, strings, vectors, function references, objects, et cetera. There is also a new function has_key(o, name) that returns true if the object has a member with the specified name. Examples: * Create an object; access its members: o = object(a=1, b=2); echo(o.a, o["b"]); * Create an object with varying names, and access them: names = [ "apple", "banana", "string bean" ]; o = object([ for (name=names) [name, 123] ]); for (name=names) echo(name, o[name]); * Create an object, then create modified copies of that object: // Ancient planets planets = object(mercury=1, venus=2, earth=3, mars=4, jupiter=5, saturn=6); planets1781 = object(planets, uranus=7); // Uranus discovered planets1846 = object(planets1781, neptune=8); // Neptune discovered planets1930 = object(planets1846, pluto=9); // Pluto discovered planets2006 = object(planets1930, [["pluto"]]); // Pluto un-planeted * Check whether a member is present: echo(has_key(planets1846, "neptune")); // true echo(has_key(planets2006, "pluto")); // false Future Directions and Related Projects: This is the second phase (after the textmetrics() work) of a longer-term plan to introduce "object" features into OpenSCAD. Object literals, object comprehension: OEP8a<https://github.com/openscad/openscad/wiki/OEP8a:--Objects-(dictionaries%3F)> adds a syntax for creating objects, including object comprehensions, roughly modeled on JavaScript object syntax, so that { a: 1, b: 2 } is equivalent to object(a=1, b=2) . Changes echo() and str() to represent objects using this syntax. OEP8<https://github.com/openscad/openscad/wiki/OEP8%3A-Objects-%28dictionaries%3F%29%2C-Geometry-as-data%2C-and-Module-References> further adds geometry as data and module references. No formal proposals: Methods: If a function reference comes from an object or a vector, it should see a special variable $this that refers to the containing object or vector. Note that although there's no inheritance per se, making a modified copy of an object is a lot like the prototype-based OO<https://en.wikipedia.org/wiki/Prototype-based_programming>. Variable parameter lists: a syntax for a parameter lists that says "return the rest of the parameters in this variable", as a vector (for positional arguments) or an object (for named arguments). Spread syntax: a syntax for adding a vector to an argument list as positional arguments, or adding an object to an argument list as named arguments. Sets: Some kind of syntactic sugar to make it easy to create an object containing boolean "true", to make it easy to define a set (in the mathematical sense) and query whether particular items are present in it. Questions: * What should these things be called? * They're modeled on JavaScript objects, but to a Python person they look more like dictionaries and to a C person they look more like structures. * Are they really "objects", when they don't have OO features? See Future Directions about methods. * In OpenSCAD, doesn't "object" already mean a geometric figure? Credits / History: * I did the original textmetrics() work. * Revar Desmera did the original implementation of object() . * Peter Kriens drove this final integration, cleaning up the implementation, fixing a bug, and writing test cases.
AM
Adrian Mariano
Mon, Jul 14, 2025 2:23 AM

This is definitely a great step, and will be a great help for libraries and
more complicated code, but my thinking was more in code clarity and
simplification, and cleaner interfaces to (library) functions that need to
return multiple values.  Right now such functions have to return a list
which has arbitrary index values.

Were you thinking that libraries would be constructed so that the entire
library is inside an object?

On Sun, Jul 13, 2025 at 9:21 PM Nathan Sokalski via Discuss <
discuss@lists.openscad.org> wrote:

I think this is a great step, it will definitely be great when creating
libraries, especially ones with values that would be the equivalent of
enumerations in other languages. I have created multiple libraries, but it
was always inappropriate to use the "include" command, because so many
libraries had variables with similar names, this will definitely improve
the organization of shared libraries. Even though this is currently in the
experimental stage, is there anywhere that the current [planned] syntax is
available? I did not see the object() function on the Cheat Sheet (which is
understandable)? Thanks again!

Nathan Sokalski
njsokalski@hotmail.com


From: Jordan Brown via Discuss discuss@lists.openscad.org
Sent: Sunday, July 13, 2025 6:20 PM
To: OpenSCAD discuss@lists.openscad.org
Cc: Jordan Brown openscad@jordan.maileater.net
Subject: [OpenSCAD] New feature in 2025.07.11: the object() function

Quick summary:

- 2025.07.11 adds the the
object()
 function, which creates an object (in the style returned by
textmetrics()
,
fontmetrics()
, and
import()
 of a JSON file).
- It accumulates the object by processing the arguments left to right,
with later settings for a particular member replacing earlier settings.
- There are three forms for an argument:
   - *name*=*value*
    - sets that name (a constant identifier) to that value.
   - A vector with a list of
   [*name*, *value*]
    vectors, or
   [*name*]
    to remove a member, where the names can be any string expression.
   - An object has its members copied.
- There is a new function
has_key(*obj*, *name*)
 that returns true if the object contains the named key.
- These functions are currently experimental and so must be enabled
before you can use them.

Overview:

An "object" is a collection of names and associated values.  In other
languages this data structure might be called an object (JavaScript), a
dictionary (Python), an associative array (some UNIX shells, awk), or a
structure (C, sort of),

This change adds a function that creates an object from a series of names,
values, and other objects, and a function that queries whether a particular
member is present.  These are mostly-normal functions; there is no new
syntax introduced.
Background:

OpenSCAD has had an internal implementation of objects for several years,
added to support the
textmetrics()
and
fontmetrics()
functions, and later
import()
of a JSON file.  This existing mechanism includes mechanisms for
accessing the members of the object and for walking through the entries.
It does not include a mechanism for the user's program to create an
object.

Given an object
o
, the current operations are:

- *o*.*name*
 yields the value of the
*name*
 member, where
*name*
 must be an identifier (alphabetic, numeric, underscore, starting with
alphabetic or underscore).  This syntax is very "clean", but does not allow
for names that are derived from expressions or for names that are not
suitable for use as identifiers.  An undefined name is not an error; it
yields
undefined
.
- *o*[*name*]
 also yields the value of the
*name*
 member, but
*name*
 can be an any string expression.  This syntax is a bit more awkward
than the
*o*.*name*
 syntax, but allows for dynamically-created names and for names that
are not suitable as identifiers.  Again, and undefined name yields
undefined
.
- for (*name* = *o*) ...
 is the object equivalent of the vector
for(...)
.  It walks the object, setting
*name*
 to each member name in sequence.  This mechanism is usable as both a
normal statement and as a list comprehension element.  Note that it yields
only the name; the value is accessible as
*o*[*name*]
.  Formally the entries should be assumed to be in no specific order,
but for aesthetic reasons they are reported in the order they were added to
the object.
- is_object(*v*)
 returns true if
*v*
 is an object.
- echo(
* o *
)
 and
str(*o*)
 produce textual representations of objects.  (Note:  the textual form
looks sort of like syntax, but is not legal OpenSCAD syntax.  It is likely
to change in the future; see *Future Directions* below.)

Details:

The
object()
function constructs a new object, processing each argument in sequence
from left to right, with later settings replacing earlier settings.  There
are three variations of arguments; they can be mixed in any way.

- A named parameter
*name*=*value*
- Sets the specified name to the specified value.  As with all named
   arguments to functions, the name must be an identifier.
- a vector
[ *v1*, *v2*, ... ]
- *v1*
   , *v2*
   , ... are each two- or one-element vectors
   - [*name*, *value*]
   - Sets the specified name to the specified value.  The name can be
      any string expression.
   - [*name*]
   - Removes the specified name from the object being accumulated.
      (Note that this is subtly different from setting it to
      undefined
      , in that it will not be reported for
      has_key()
       or when walking the names of the object.)
      - an object
*o*
- An object has each of its members copied into the new object.

The values contained in an object can (of course) be of any data type:
numbers, strings, vectors, function references, objects, et cetera.

There is also a new function
has_key(o, name)
that returns true if the object has a member with the specified name.
Examples:

- Create an object; access its members:

o = object(a=1, b=2);
echo(o.a, o["b"]);
- Create an object with varying names, and access them:
names = [ "apple", "banana", "string bean" ];
o = object([ for (name=names) [name, 123] ]);
for (name=names) echo(name, o[name]);
- Create an object, then create modified copies of that object:
// Ancient planets
planets = object(mercury=1, venus=2, earth=3, mars=4, jupiter=5,
saturn=6);
planets1781 = object(planets, uranus=7);        // Uranus discovered
planets1846 = object(planets1781, neptune=8);   // Neptune discovered
planets1930 = object(planets1846, pluto=9);     // Pluto discovered
planets2006 = object(planets1930, [["pluto"]]); // Pluto un-planeted
- Check whether a member is present:
echo(has_key(planets1846, "neptune")); // true
echo(has_key(planets2006, "pluto"));   // false

Future Directions and Related Projects:

This is the second phase (after the
textmetrics()
work) of a longer-term plan to introduce "object" features into OpenSCAD.

Object literals, object comprehension:  OEP8a
https://github.com/openscad/openscad/wiki/OEP8a:--Objects-(dictionaries%3F) adds
a syntax for creating objects, including object comprehensions, roughly
modeled on JavaScript object syntax, so that
{ a: 1, b: 2 }
is equivalent to
object(a=1, b=2)
. Changes
echo()
and
str()
to represent objects using this syntax.

OEP8
https://github.com/openscad/openscad/wiki/OEP8%3A-Objects-%28dictionaries%3F%29%2C-Geometry-as-data%2C-and-Module-References further
adds geometry as data and module references.

No formal proposals:

Methods: If a function reference comes from an object or a vector, it
should see a special variable
$this
that refers to the containing object or vector.  Note that although
there's no inheritance per se, making a modified copy of an object is a lot
like the prototype-based OO
https://en.wikipedia.org/wiki/Prototype-based_programming.

Variable parameter lists:  a syntax for a parameter lists that says
"return the rest of the parameters in this variable", as a vector (for
positional arguments) or an object (for named arguments).

Spread syntax:  a syntax for adding a vector to an argument list as
positional arguments, or adding an object to an argument list as named
arguments.

Sets:  Some kind of syntactic sugar to make it easy to create an object
containing boolean "true", to make it easy to define a set (in the
mathematical sense) and query whether particular items are present in it.
Questions:

- What should these things be called?
   - They're modeled on JavaScript objects, but to a Python person
   they look more like dictionaries and to a C person they look more like
   structures.
   - Are they really "objects", when they don't have OO features?  See *Future
   Directions* about methods.
   - In OpenSCAD, doesn't "object" already mean a geometric figure?

Credits / History:

- I did the original textmetrics() work.
- Revar Desmera did the original implementation of
object()
.
- Peter Kriens drove this final integration, cleaning up the
implementation, fixing a bug, and writing test cases.

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

This is definitely a great step, and will be a great help for libraries and more complicated code, but my thinking was more in code clarity and simplification, and cleaner interfaces to (library) functions that need to return multiple values. Right now such functions have to return a list which has arbitrary index values. Were you thinking that libraries would be constructed so that the entire library is inside an object? On Sun, Jul 13, 2025 at 9:21 PM Nathan Sokalski via Discuss < discuss@lists.openscad.org> wrote: > I think this is a great step, it will definitely be great when creating > libraries, especially ones with values that would be the equivalent of > enumerations in other languages. I have created multiple libraries, but it > was always inappropriate to use the "include" command, because so many > libraries had variables with similar names, this will definitely improve > the organization of shared libraries. Even though this is currently in the > experimental stage, is there anywhere that the current [planned] syntax is > available? I did not see the object() function on the Cheat Sheet (which is > understandable)? Thanks again! > > Nathan Sokalski > njsokalski@hotmail.com > > ------------------------------ > *From:* Jordan Brown via Discuss <discuss@lists.openscad.org> > *Sent:* Sunday, July 13, 2025 6:20 PM > *To:* OpenSCAD <discuss@lists.openscad.org> > *Cc:* Jordan Brown <openscad@jordan.maileater.net> > *Subject:* [OpenSCAD] New feature in 2025.07.11: the object() function > > Quick summary: > > - 2025.07.11 adds the the > object() > function, which creates an object (in the style returned by > textmetrics() > , > fontmetrics() > , and > import() > of a JSON file). > - It accumulates the object by processing the arguments left to right, > with later settings for a particular member replacing earlier settings. > - There are three forms for an argument: > - *name*=*value* > - sets that name (a constant identifier) to that value. > - A vector with a list of > [*name*, *value*] > vectors, or > [*name*] > to remove a member, where the names can be any string expression. > - An object has its members copied. > - There is a new function > has_key(*obj*, *name*) > that returns true if the object contains the named key. > - These functions are currently experimental and so must be enabled > before you can use them. > > Overview: > > An "object" is a collection of names and associated values. In other > languages this data structure might be called an object (JavaScript), a > dictionary (Python), an associative array (some UNIX shells, awk), or a > structure (C, sort of), > > This change adds a function that creates an object from a series of names, > values, and other objects, and a function that queries whether a particular > member is present. These are mostly-normal functions; there is no new > syntax introduced. > Background: > > OpenSCAD has had an internal implementation of objects for several years, > added to support the > textmetrics() > and > fontmetrics() > functions, and later > import() > of a JSON file. This existing mechanism includes mechanisms for > accessing the members of the object and for walking through the entries. > It does *not* include a mechanism for the user's program to create an > object. > > Given an object > *o* > , the current operations are: > > - *o*.*name* > yields the value of the > *name* > member, where > *name* > must be an identifier (alphabetic, numeric, underscore, starting with > alphabetic or underscore). This syntax is very "clean", but does not allow > for names that are derived from expressions or for names that are not > suitable for use as identifiers. An undefined name is not an error; it > yields > undefined > . > - *o*[*name*] > also yields the value of the > *name* > member, but > *name* > can be an any string expression. This syntax is a bit more awkward > than the > *o*.*name* > syntax, but allows for dynamically-created names and for names that > are not suitable as identifiers. Again, and undefined name yields > undefined > . > - for (*name* = *o*) ... > is the object equivalent of the vector > for(...) > . It walks the object, setting > *name* > to each member name in sequence. This mechanism is usable as both a > normal statement and as a list comprehension element. Note that it yields > only the name; the value is accessible as > *o*[*name*] > . Formally the entries should be assumed to be in no specific order, > but for aesthetic reasons they are reported in the order they were added to > the object. > - is_object(*v*) > returns true if > *v* > is an object. > - echo( > * o * > ) > and > str(*o*) > produce textual representations of objects. (Note: the textual form > looks sort of like syntax, but is not legal OpenSCAD syntax. It is likely > to change in the future; see *Future Directions* below.) > > Details: > > The > object() > function constructs a new object, processing each argument in sequence > from left to right, with later settings replacing earlier settings. There > are three variations of arguments; they can be mixed in any way. > > - A named parameter > *name*=*value* > - Sets the specified name to the specified value. As with all named > arguments to functions, the name must be an identifier. > - a vector > [ *v1*, *v2*, ... ] > - *v1* > , *v2* > , ... are each two- or one-element vectors > - [*name*, *value*] > - Sets the specified name to the specified value. The name can be > any string expression. > - [*name*] > - Removes the specified name from the object being accumulated. > (Note that this is subtly different from setting it to > undefined > , in that it will not be reported for > has_key() > or when walking the names of the object.) > - an object > *o* > - An object has each of its members copied into the new object. > > The values contained in an object can (of course) be of any data type: > numbers, strings, vectors, function references, objects, et cetera. > > There is also a new function > has_key(*o*, *name*) > that returns true if the object has a member with the specified name. > Examples: > > - Create an object; access its members: > > o = object(a=1, b=2); > echo(o.a, o["b"]); > - Create an object with varying names, and access them: > names = [ "apple", "banana", "string bean" ]; > o = object([ for (name=names) [name, 123] ]); > for (name=names) echo(name, o[name]); > - Create an object, then create modified copies of that object: > // Ancient planets > planets = object(mercury=1, venus=2, earth=3, mars=4, jupiter=5, > saturn=6); > planets1781 = object(planets, uranus=7); // Uranus discovered > planets1846 = object(planets1781, neptune=8); // Neptune discovered > planets1930 = object(planets1846, pluto=9); // Pluto discovered > planets2006 = object(planets1930, [["pluto"]]); // Pluto un-planeted > - Check whether a member is present: > echo(has_key(planets1846, "neptune")); // true > echo(has_key(planets2006, "pluto")); // false > > Future Directions and Related Projects: > > This is the second phase (after the > textmetrics() > work) of a longer-term plan to introduce "object" features into OpenSCAD. > > Object literals, object comprehension: OEP8a > <https://github.com/openscad/openscad/wiki/OEP8a:--Objects-(dictionaries%3F)> adds > a syntax for creating objects, including object comprehensions, roughly > modeled on JavaScript object syntax, so that > { a: 1, b: 2 } > is equivalent to > object(a=1, b=2) > . Changes > echo() > and > str() > to represent objects using this syntax. > > OEP8 > <https://github.com/openscad/openscad/wiki/OEP8%3A-Objects-%28dictionaries%3F%29%2C-Geometry-as-data%2C-and-Module-References> further > adds geometry as data and module references. > > No formal proposals: > > Methods: If a function reference comes from an object or a vector, it > should see a special variable > $this > that refers to the containing object or vector. Note that although > there's no inheritance per se, making a modified copy of an object is a lot > like the prototype-based OO > <https://en.wikipedia.org/wiki/Prototype-based_programming>. > > Variable parameter lists: a syntax for a parameter lists that says > "return the rest of the parameters in this variable", as a vector (for > positional arguments) or an object (for named arguments). > > Spread syntax: a syntax for adding a vector to an argument list as > positional arguments, or adding an object to an argument list as named > arguments. > > Sets: Some kind of syntactic sugar to make it easy to create an object > containing boolean "true", to make it easy to define a set (in the > mathematical sense) and query whether particular items are present in it. > Questions: > > - What should these things be called? > - They're modeled on JavaScript objects, but to a Python person > they look more like dictionaries and to a C person they look more like > structures. > - Are they really "objects", when they don't have OO features? See *Future > Directions* about methods. > - In OpenSCAD, doesn't "object" already mean a geometric figure? > > Credits / History: > > - I did the original textmetrics() work. > - Revar Desmera did the original implementation of > object() > . > - Peter Kriens drove this final integration, cleaning up the > implementation, fixing a bug, and writing test cases. > > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to discuss-leave@lists.openscad.org >
JB
Jordan Brown
Mon, Jul 14, 2025 2:56 AM

On 7/13/2025 6:20 PM, Nathan Sokalski via Discuss wrote:

Even though this is currently in the experimental stage, is there
anywhere that the current [planned] syntax is available?

I've got it in a PR, but that PR is now several years stale and I'm
pretty sure it no longer builds.  (It's also got some other stuff that I
think is cool; see PR#4478
https://github.com/openscad/openscad/pull/4478 / OEP8
https://github.com/openscad/openscad/wiki/OEP8%3A-Objects-%28dictionaries%3F%29%2C-Geometry-as-data%2C-and-Module-References.)

I did not see the object() function on the Cheat Sheet (which is
understandable)?

Peter's got a draft of updating the cheat sheet, and one of us will add
these two functions to the manual.

On 7/13/2025 6:20 PM, Nathan Sokalski via Discuss wrote: > Even though this is currently in the experimental stage, is there > anywhere that the current [planned] syntax is available? I've got it in a PR, but that PR is now several years stale and I'm pretty sure it no longer builds.  (It's also got some other stuff that I think is cool; see PR#4478 <https://github.com/openscad/openscad/pull/4478> / OEP8 <https://github.com/openscad/openscad/wiki/OEP8%3A-Objects-%28dictionaries%3F%29%2C-Geometry-as-data%2C-and-Module-References>.) > I did not see the object() function on the Cheat Sheet (which is > understandable)? Peter's got a draft of updating the cheat sheet, and one of us will add these two functions to the manual.
JB
Jordan Brown
Mon, Jul 14, 2025 3:01 AM

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

Were you thinking that libraries would be constructed so that the
entire library is inside an object? 

Entirely plausible.  You can't really do it today because we don't have
module references.

I forgot to mention in the Futures section that I want to figure out how
to have a function - call it use(), for discussion purposes, though it
would be enough different from the "use" directive that I'd probably
call it something else.  You would say something like "foo =
use("foo.scad");" and foo would get a single object with all of the
global variables, modules, and functions from foo.scad.  (Handwave on
namespace issues; quite possibly just declare that for this style of
library you must not have the same names for functions, modules, and
variables.)  That would reduce namespace problems to (a) file names and
(b) special variable names.  And we'd define it to evaluate the
variables exactly once, at use() time :-)

On 7/13/2025 7:23 PM, Adrian Mariano via Discuss wrote: > Were you thinking that libraries would be constructed so that the > entire library is inside an object?  Entirely plausible.  You can't really do it today because we don't have module references. I forgot to mention in the Futures section that I want to figure out how to have a function - call it use(), for discussion purposes, though it would be enough different from the "use" directive that I'd probably call it something else.  You would say something like "foo = use("foo.scad");" and foo would get a single object with all of the global variables, modules, and functions from foo.scad.  (Handwave on namespace issues; quite possibly just declare that for this style of library you must not have the same names for functions, modules, and variables.)  That would reduce namespace problems to (a) file names and (b) special variable names.  And we'd define it to evaluate the variables exactly once, at use() time :-)
RD
Revar Desmera
Mon, Jul 14, 2025 4:39 AM

♫ PARTY-TIME! ♬

  • Revar

On Jul 13, 2025, at 3:20 PM, Jordan Brown via Discuss discuss@lists.openscad.org wrote:

Quick summary:

2025.07.11 adds the the object() function, which creates an object (in the style returned by textmetrics(), fontmetrics(), and import() of a JSON file).
It accumulates the object by processing the arguments left to right, with later settings for a particular member replacing earlier settings.
There are three forms for an argument:
name=value - sets that name (a constant identifier) to that value.
A vector with a list of [name, value] vectors, or [name] to remove a member, where the names can be any string expression.
An object has its members copied.
There is a new function has_key(obj, name) that returns true if the object contains the named key.
These functions are currently experimental and so must be enabled before you can use them.
Overview:

An "object" is a collection of names and associated values.  In other languages this data structure might be called an object (JavaScript), a dictionary (Python), an associative array (some UNIX shells, awk), or a structure (C, sort of),

This change adds a function that creates an object from a series of names, values, and other objects, and a function that queries whether a particular member is present.  These are mostly-normal functions; there is no new syntax introduced.

Background:

OpenSCAD has had an internal implementation of objects for several years, added to support the textmetrics() and fontmetrics() functions, and later import() of a JSON file.  This existing mechanism includes mechanisms for accessing the members of the object and for walking through the entries.  It does not include a mechanism for the user's program to create an object.

Given an object o, the current operations are:

o.name yields the value of the name member, where name must be an identifier (alphabetic, numeric, underscore, starting with alphabetic or underscore).  This syntax is very "clean", but does not allow for names that are derived from expressions or for names that are not suitable for use as identifiers.  An undefined name is not an error; it yields undefined.
o[name] also yields the value of the name member, but name can be an any string expression.  This syntax is a bit more awkward than the o.name syntax, but allows for dynamically-created names and for names that are not suitable as identifiers.  Again, and undefined name yields undefined.
for (name = o) ... is the object equivalent of the vector for(...).  It walks the object, setting name to each member name in sequence.  This mechanism is usable as both a normal statement and as a list comprehension element.  Note that it yields only the name; the value is accessible as o[name].  Formally the entries should be assumed to be in no specific order, but for aesthetic reasons they are reported in the order they were added to the object.
is_object(v) returns true if v is an object.
echo(o) and str(o) produce textual representations of objects.  (Note:  the textual form looks sort of like syntax, but is not legal OpenSCAD syntax.  It is likely to change in the future; see Future Directions below.)
Details:

The object() function constructs a new object, processing each argument in sequence from left to right, with later settings replacing earlier settings.  There are three variations of arguments; they can be mixed in any way.

A named parameter name=value
Sets the specified name to the specified value.  As with all named arguments to functions, the name must be an identifier.
a vector [ v1, v2, ... ]
v1, v2, ... are each two- or one-element vectors
[name, value]
Sets the specified name to the specified value.  The name can be any string expression.
[name]
Removes the specified name from the object being accumulated.  (Note that this is subtly different from setting it to undefined, in that it will not be reported for has_key() or when walking the names of the object.)
an object o
An object has each of its members copied into the new object.
The values contained in an object can (of course) be of any data type:  numbers, strings, vectors, function references, objects, et cetera.

There is also a new function has_key(o, name) that returns true if the object has a member with the specified name.
Examples:

Create an object; access its members:
o = object(a=1, b=2);
echo(o.a, o["b"]);
Create an object with varying names, and access them:
names = [ "apple", "banana", "string bean" ];
o = object([ for (name=names) [name, 123] ]);
for (name=names) echo(name, o[name]);
Create an object, then create modified copies of that object:
// Ancient planets
planets = object(mercury=1, venus=2, earth=3, mars=4, jupiter=5, saturn=6);
planets1781 = object(planets, uranus=7);        // Uranus discovered
planets1846 = object(planets1781, neptune=8);  // Neptune discovered
planets1930 = object(planets1846, pluto=9);    // Pluto discovered
planets2006 = object(planets1930, [["pluto"]]); // Pluto un-planeted
Check whether a member is present:
echo(has_key(planets1846, "neptune")); // true
echo(has_key(planets2006, "pluto"));  // false
Future Directions and Related Projects:

This is the second phase (after the textmetrics() work) of a longer-term plan to introduce "object" features into OpenSCAD.

Object literals, object comprehension:  OEP8a https://github.com/openscad/openscad/wiki/OEP8a:--Objects-(dictionaries%3F) adds a syntax for creating objects, including object comprehensions, roughly modeled on JavaScript object syntax, so that { a: 1, b: 2 } is equivalent to object(a=1, b=2). Changes echo() and str() to represent objects using this syntax.

OEP8 https://github.com/openscad/openscad/wiki/OEP8%3A-Objects-%28dictionaries%3F%29%2C-Geometry-as-data%2C-and-Module-References further adds geometry as data and module references.

No formal proposals:

Methods: If a function reference comes from an object or a vector, it should see a special variable $this that refers to the containing object or vector.  Note that although there's no inheritance per se, making a modified copy of an object is a lot like the prototype-based OO https://en.wikipedia.org/wiki/Prototype-based_programming.

Variable parameter lists:  a syntax for a parameter lists that says "return the rest of the parameters in this variable", as a vector (for positional arguments) or an object (for named arguments).

Spread syntax:  a syntax for adding a vector to an argument list as positional arguments, or adding an object to an argument list as named arguments.

Sets:  Some kind of syntactic sugar to make it easy to create an object containing boolean "true", to make it easy to define a set (in the mathematical sense) and query whether particular items are present in it.

Questions:

What should these things be called?
They're modeled on JavaScript objects, but to a Python person they look more like dictionaries and to a C person they look more like structures.
Are they really "objects", when they don't have OO features?  See Future Directions about methods.
In OpenSCAD, doesn't "object" already mean a geometric figure?
Credits / History:

I did the original textmetrics() work.
Revar Desmera did the original implementation of object().
Peter Kriens drove this final integration, cleaning up the implementation, fixing a bug, and writing test cases.


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

♫ PARTY-TIME! ♬  - Revar > On Jul 13, 2025, at 3:20 PM, Jordan Brown via Discuss <discuss@lists.openscad.org> wrote: > > Quick summary: > > 2025.07.11 adds the the object() function, which creates an object (in the style returned by textmetrics(), fontmetrics(), and import() of a JSON file). > It accumulates the object by processing the arguments left to right, with later settings for a particular member replacing earlier settings. > There are three forms for an argument: > name=value - sets that name (a constant identifier) to that value. > A vector with a list of [name, value] vectors, or [name] to remove a member, where the names can be any string expression. > An object has its members copied. > There is a new function has_key(obj, name) that returns true if the object contains the named key. > These functions are currently experimental and so must be enabled before you can use them. > Overview: > > An "object" is a collection of names and associated values. In other languages this data structure might be called an object (JavaScript), a dictionary (Python), an associative array (some UNIX shells, awk), or a structure (C, sort of), > > This change adds a function that creates an object from a series of names, values, and other objects, and a function that queries whether a particular member is present. These are mostly-normal functions; there is no new syntax introduced. > > Background: > > OpenSCAD has had an internal implementation of objects for several years, added to support the textmetrics() and fontmetrics() functions, and later import() of a JSON file. This existing mechanism includes mechanisms for accessing the members of the object and for walking through the entries. It does not include a mechanism for the user's program to create an object. > > Given an object o, the current operations are: > > o.name yields the value of the name member, where name must be an identifier (alphabetic, numeric, underscore, starting with alphabetic or underscore). This syntax is very "clean", but does not allow for names that are derived from expressions or for names that are not suitable for use as identifiers. An undefined name is not an error; it yields undefined. > o[name] also yields the value of the name member, but name can be an any string expression. This syntax is a bit more awkward than the o.name syntax, but allows for dynamically-created names and for names that are not suitable as identifiers. Again, and undefined name yields undefined. > for (name = o) ... is the object equivalent of the vector for(...). It walks the object, setting name to each member name in sequence. This mechanism is usable as both a normal statement and as a list comprehension element. Note that it yields only the name; the value is accessible as o[name]. Formally the entries should be assumed to be in no specific order, but for aesthetic reasons they are reported in the order they were added to the object. > is_object(v) returns true if v is an object. > echo(o) and str(o) produce textual representations of objects. (Note: the textual form looks sort of like syntax, but is not legal OpenSCAD syntax. It is likely to change in the future; see Future Directions below.) > Details: > > The object() function constructs a new object, processing each argument in sequence from left to right, with later settings replacing earlier settings. There are three variations of arguments; they can be mixed in any way. > > A named parameter name=value > Sets the specified name to the specified value. As with all named arguments to functions, the name must be an identifier. > a vector [ v1, v2, ... ] > v1, v2, ... are each two- or one-element vectors > [name, value] > Sets the specified name to the specified value. The name can be any string expression. > [name] > Removes the specified name from the object being accumulated. (Note that this is subtly different from setting it to undefined, in that it will not be reported for has_key() or when walking the names of the object.) > an object o > An object has each of its members copied into the new object. > The values contained in an object can (of course) be of any data type: numbers, strings, vectors, function references, objects, et cetera. > > There is also a new function has_key(o, name) that returns true if the object has a member with the specified name. > Examples: > > Create an object; access its members: > o = object(a=1, b=2); > echo(o.a, o["b"]); > Create an object with varying names, and access them: > names = [ "apple", "banana", "string bean" ]; > o = object([ for (name=names) [name, 123] ]); > for (name=names) echo(name, o[name]); > Create an object, then create modified copies of that object: > // Ancient planets > planets = object(mercury=1, venus=2, earth=3, mars=4, jupiter=5, saturn=6); > planets1781 = object(planets, uranus=7); // Uranus discovered > planets1846 = object(planets1781, neptune=8); // Neptune discovered > planets1930 = object(planets1846, pluto=9); // Pluto discovered > planets2006 = object(planets1930, [["pluto"]]); // Pluto un-planeted > Check whether a member is present: > echo(has_key(planets1846, "neptune")); // true > echo(has_key(planets2006, "pluto")); // false > Future Directions and Related Projects: > > This is the second phase (after the textmetrics() work) of a longer-term plan to introduce "object" features into OpenSCAD. > > Object literals, object comprehension: OEP8a <https://github.com/openscad/openscad/wiki/OEP8a:--Objects-(dictionaries%3F)> adds a syntax for creating objects, including object comprehensions, roughly modeled on JavaScript object syntax, so that { a: 1, b: 2 } is equivalent to object(a=1, b=2). Changes echo() and str() to represent objects using this syntax. > > OEP8 <https://github.com/openscad/openscad/wiki/OEP8%3A-Objects-%28dictionaries%3F%29%2C-Geometry-as-data%2C-and-Module-References> further adds geometry as data and module references. > > No formal proposals: > > Methods: If a function reference comes from an object or a vector, it should see a special variable $this that refers to the containing object or vector. Note that although there's no inheritance per se, making a modified copy of an object is a lot like the prototype-based OO <https://en.wikipedia.org/wiki/Prototype-based_programming>. > > Variable parameter lists: a syntax for a parameter lists that says "return the rest of the parameters in this variable", as a vector (for positional arguments) or an object (for named arguments). > > Spread syntax: a syntax for adding a vector to an argument list as positional arguments, or adding an object to an argument list as named arguments. > > Sets: Some kind of syntactic sugar to make it easy to create an object containing boolean "true", to make it easy to define a set (in the mathematical sense) and query whether particular items are present in it. > > Questions: > > What should these things be called? > They're modeled on JavaScript objects, but to a Python person they look more like dictionaries and to a C person they look more like structures. > Are they really "objects", when they don't have OO features? See Future Directions about methods. > In OpenSCAD, doesn't "object" already mean a geometric figure? > Credits / History: > > I did the original textmetrics() work. > Revar Desmera did the original implementation of object(). > Peter Kriens drove this final integration, cleaning up the implementation, fixing a bug, and writing test cases. > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to discuss-leave@lists.openscad.org
JB
Jon Bondy
Mon, Jul 14, 2025 11:55 AM

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 todiscuss-leave@lists.openscad.org

--
This email has been checked for viruses by AVG antivirus software.
www.avg.com

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 todiscuss-leave@lists.openscad.org -- This email has been checked for viruses by AVG antivirus software. www.avg.com
PK
Peter Kriens
Mon, Jul 14, 2025 12:32 PM

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 mailto:discuss-leave@lists.openscad.org

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 <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> <x-msg://58/#DAB4FAD8-2DD7-40BB-A1B8-4E2AA1F9FDF2>_______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to discuss-leave@lists.openscad.org
JB
Jordan Brown
Mon, Jul 14, 2025 4:37 PM

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}
};
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} };
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 >
PK
Peter Kriens
Sun, Jul 27, 2025 7:03 PM

Yes, rewriting something like BOSL2 is a huge task. I’ve been thinking how
we could simplify this task. After all, inside BOSL2 you do use objects but
you map them to arrays and indexes. I think I see possibilities to easily
map them from an array to an object and vice versa of we have some layout
description of the array. I’ll be trying to come up with some ideas this
week.

Actually, BOSL2 is my primary motivation for this work. :-) I can’t live
without the attachable but nor can I live without the object abstraction.
Life puts strange challenges on our path. 😂

Peter

On Sun 27 Jul 2025 at 20:37, Adrian Mariano via Discuss <
discuss@lists.openscad.org> wrote:

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


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

Yes, rewriting something like BOSL2 is a huge task. I’ve been thinking how we could simplify this task. After all, inside BOSL2 you do use objects but you map them to arrays and indexes. I think I see possibilities to easily map them from an array to an object and vice versa of we have some layout description of the array. I’ll be trying to come up with some ideas this week. Actually, BOSL2 is my primary motivation for this work. :-) I can’t live without the attachable but nor can I live without the object abstraction. Life puts strange challenges on our path. 😂 Peter On Sun 27 Jul 2025 at 20:37, Adrian Mariano via Discuss < discuss@lists.openscad.org> wrote: > 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 >> > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to discuss-leave@lists.openscad.org >
AM
Adrian Mariano
Mon, Jul 28, 2025 3:41 AM

It seems like automatic conversion of BOSL2 (or existing code generally) to
objects would be pretty difficult to do.  Some very restricted automatic
conversion would be possible, such as looking at a data structure that is
currently an array and replacing foo[number] with foo.<name> for some
appropriate name.  But things like changing all VNF references to objects
would be harder to do.  And changing functions that currently return
arbitrary arrays to objects is also going to be difficult to do in an
automated way.  Replacing struct() invocations may work for many things,
but I think it won't work for screws.scad because not all keys are text.
And I think for arg processing I also wanted the ability to create a new
structure where it is impossible to add fields, only change existing
fields.  I don't think this exists, so probably some extra logic would be
needed.

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

Yes, rewriting something like BOSL2 is a huge task. I’ve been thinking how
we could simplify this task. After all, inside BOSL2 you do use objects but
you map them to arrays and indexes. I think I see possibilities to easily
map them from an array to an object and vice versa of we have some layout
description of the array. I’ll be trying to come up with some ideas this
week.

Actually, BOSL2 is my primary motivation for this work. :-) I can’t live
without the attachable but nor can I live without the object abstraction.
Life puts strange challenges on our path. 😂

 Peter

On Sun 27 Jul 2025 at 20:37, Adrian Mariano via Discuss <
discuss@lists.openscad.org> wrote:

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


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

It seems like automatic conversion of BOSL2 (or existing code generally) to objects would be pretty difficult to do. Some very restricted automatic conversion would be possible, such as looking at a data structure that is currently an array and replacing foo[number] with foo.<name> for some appropriate name. But things like changing all VNF references to objects would be harder to do. And changing functions that currently return arbitrary arrays to objects is also going to be difficult to do in an automated way. Replacing struct() invocations may work for many things, but I think it won't work for screws.scad because not all keys are text. And I think for arg processing I also wanted the ability to create a new structure where it is impossible to add fields, only change existing fields. I don't think this exists, so probably some extra logic would be needed. On Sun, Jul 27, 2025 at 3:04 PM Peter Kriens via Discuss < discuss@lists.openscad.org> wrote: > Yes, rewriting something like BOSL2 is a huge task. I’ve been thinking how > we could simplify this task. After all, inside BOSL2 you do use objects but > you map them to arrays and indexes. I think I see possibilities to easily > map them from an array to an object and vice versa of we have some layout > description of the array. I’ll be trying to come up with some ideas this > week. > > Actually, BOSL2 is my primary motivation for this work. :-) I can’t live > without the attachable but nor can I live without the object abstraction. > Life puts strange challenges on our path. 😂 > > Peter > > > > On Sun 27 Jul 2025 at 20:37, Adrian Mariano via Discuss < > discuss@lists.openscad.org> wrote: > >> 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 >>> >> _______________________________________________ >> 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 >
NS
Nathan Sokalski
Mon, Jul 28, 2025 6:32 PM

A feature that occurred to me that I believe would make backwards compatibility (at least temporarily) easier is to have a "default" property. Normally, properties are accessed using obj.prop or obj["prop"], but having a default property would allow you to access the first property using just obj. By first, I simply mean the first name/value passed to the object() function. For example, if the following object was created:

myobj=object(prop1=value1,prop2=value2,prop3=value3);

The following would all return value1:

echo(myobj.prop1);
echo(myobj["prop1"]);
echo(myobj); //This would return value1 because value1 is the first value that was assigned by the object() function

Another possible idea would be to have a "reserved" property name ("Default", "default", "_", " ") which when assigned a value would be the default value. I think the option of having a default value would simplify the converting of existing libraries because a common technique for returning multiple values from a single function was returning an array, so if the array was assigned to the default value instances of the function would not be broken. Even though this would not replace the need to update a large amount of code, it would allow you to update a function without breaking as much code. Once again, thank you, and I very much look forward to this feature!

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


From: Adrian Mariano via Discuss discuss@lists.openscad.org
Sent: Sunday, July 27, 2025 11:41 PM
To: OpenSCAD general discussion Mailing-list discuss@lists.openscad.org
Cc: Adrian Mariano avm4@cornell.edu
Subject: [OpenSCAD] Re: New feature in 2025.07.11: the object() function

It seems like automatic conversion of BOSL2 (or existing code generally) to objects would be pretty difficult to do.  Some very restricted automatic conversion would be possible, such as looking at a data structure that is currently an array and replacing foo[number] with foo.<name> for some appropriate name.  But things like changing all VNF references to objects would be harder to do.  And changing functions that currently return arbitrary arrays to objects is also going to be difficult to do in an automated way.  Replacing struct() invocations may work for many things, but I think it won't work for screws.scad because not all keys are text.  And I think for arg processing I also wanted the ability to create a new structure where it is impossible to add fields, only change existing fields.  I don't think this exists, so probably some extra logic would be needed.

On Sun, Jul 27, 2025 at 3:04 PM Peter Kriens via Discuss <discuss@lists.openscad.orgmailto:discuss@lists.openscad.org> wrote:
Yes, rewriting something like BOSL2 is a huge task. I’ve been thinking how we could simplify this task. After all, inside BOSL2 you do use objects but you map them to arrays and indexes. I think I see possibilities to easily map them from an array to an object and vice versa of we have some layout description of the array. I’ll be trying to come up with some ideas this week.

Actually, BOSL2 is my primary motivation for this work. :-) I can’t live without the attachable but nor can I live without the object abstraction. Life puts strange challenges on our path. 😂

Peter

On Sun 27 Jul 2025 at 20:37, Adrian Mariano via Discuss <discuss@lists.openscad.orgmailto:discuss@lists.openscad.org> wrote:
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.orgmailto: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.orgmailto: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.commailto:njsokalski@hotmail.com


From: Peter Kriens via Discuss <discuss@lists.openscad.orgmailto:discuss@lists.openscad.org>
Sent: Saturday, July 26, 2025 10:54 AM
To: OpenSCAD general discussion Mailing-list <discuss@lists.openscad.orgmailto:discuss@lists.openscad.org>
Cc: Peter Kriens <peter.kriens@aqute.bizmailto: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.orgmailto: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.orgmailto:discuss-leave@lists.openscad.org


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


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


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

A feature that occurred to me that I believe would make backwards compatibility (at least temporarily) easier is to have a "default" property. Normally, properties are accessed using obj.prop or obj["prop"], but having a default property would allow you to access the first property using just obj. By first, I simply mean the first name/value passed to the object() function. For example, if the following object was created: myobj=object(prop1=value1,prop2=value2,prop3=value3); The following would all return value1: echo(myobj.prop1); echo(myobj["prop1"]); echo(myobj); //This would return value1 because value1 is the first value that was assigned by the object() function Another possible idea would be to have a "reserved" property name ("Default", "default", "_", " ") which when assigned a value would be the default value. I think the option of having a default value would simplify the converting of existing libraries because a common technique for returning multiple values from a single function was returning an array, so if the array was assigned to the default value instances of the function would not be broken. Even though this would not replace the need to update a large amount of code, it would allow you to update a function without breaking as much code. Once again, thank you, and I very much look forward to this feature! Nathan Sokalski njsokalski@hotmail.com<mailto:njsokalski@hotmail.com> ________________________________ From: Adrian Mariano via Discuss <discuss@lists.openscad.org> Sent: Sunday, July 27, 2025 11:41 PM To: OpenSCAD general discussion Mailing-list <discuss@lists.openscad.org> Cc: Adrian Mariano <avm4@cornell.edu> Subject: [OpenSCAD] Re: New feature in 2025.07.11: the object() function It seems like automatic conversion of BOSL2 (or existing code generally) to objects would be pretty difficult to do. Some very restricted automatic conversion would be possible, such as looking at a data structure that is currently an array and replacing foo[number] with foo.<name> for some appropriate name. But things like changing all VNF references to objects would be harder to do. And changing functions that currently return arbitrary arrays to objects is also going to be difficult to do in an automated way. Replacing struct() invocations may work for many things, but I think it won't work for screws.scad because not all keys are text. And I think for arg processing I also wanted the ability to create a new structure where it is impossible to add fields, only change existing fields. I don't think this exists, so probably some extra logic would be needed. On Sun, Jul 27, 2025 at 3:04 PM Peter Kriens via Discuss <discuss@lists.openscad.org<mailto:discuss@lists.openscad.org>> wrote: Yes, rewriting something like BOSL2 is a huge task. I’ve been thinking how we could simplify this task. After all, inside BOSL2 you do use objects but you map them to arrays and indexes. I think I see possibilities to easily map them from an array to an object and vice versa of we have some layout description of the array. I’ll be trying to come up with some ideas this week. Actually, BOSL2 is my primary motivation for this work. :-) I can’t live without the attachable but nor can I live without the object abstraction. Life puts strange challenges on our path. 😂 Peter On Sun 27 Jul 2025 at 20:37, Adrian Mariano via Discuss <discuss@lists.openscad.org<mailto:discuss@lists.openscad.org>> wrote: 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<mailto: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<mailto: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<mailto:discuss@lists.openscad.org>> Sent: Saturday, July 26, 2025 10:54 AM To: OpenSCAD general discussion Mailing-list <discuss@lists.openscad.org<mailto:discuss@lists.openscad.org>> Cc: Peter Kriens <peter.kriens@aqute.biz<mailto: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<mailto: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> _______________________________________________ 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<mailto:discuss-leave@lists.openscad.org>
AM
Adrian Mariano
Mon, Jul 28, 2025 7:26 PM

I think having objects return a default like that sounds like a hack that
would clutter the api without having a legitimate long term use.

The way to address the type of compatibility you’re talking about is to
freeze a version of the library that returns arrays that old code can
continue to use and then write a new non compatible version.

On Mon, Jul 28, 2025 at 14:34 Nathan Sokalski via Discuss <
discuss@lists.openscad.org> wrote:

A feature that occurred to me that I believe would make backwards
compatibility (at least temporarily) easier is to have a "default"
property. Normally, properties are accessed using obj.prop or obj["prop"],
but having a default property would allow you to access the first property
using just obj. By first, I simply mean the first name/value passed to the
object() function. For example, if the following object was created:

myobj=object(prop1=value1,prop2=value2,prop3=value3);

The following would all return value1:

echo(myobj.prop1);
echo(myobj["prop1"]);
echo(myobj); //This would return value1 because value1 is the first value
that was assigned by the object() function

Another possible idea would be to have a "reserved" property name
("Default", "default", "_", " ") which when assigned a value would be the
default value. I think the option of having a default value would simplify
the converting of existing libraries because a common technique for
returning multiple values from a single function was returning an array, so
if the array was assigned to the default value instances of the function
would not be broken. Even though this would not replace the need to update
a large amount of code, it would allow you to update a function without
breaking as much code. Once again, thank you, and I very much look forward
to this feature!

Nathan Sokalski
njsokalski@hotmail.com

From: Adrian Mariano via Discuss discuss@lists.openscad.org
Sent: Sunday, July 27, 2025 11:41 PM
To: OpenSCAD general discussion Mailing-list <discuss@lists.openscad.org

Cc: Adrian Mariano avm4@cornell.edu

Subject: [OpenSCAD] Re: New feature in 2025.07.11: the object() function

It seems like automatic conversion of BOSL2 (or existing code generally)
to objects would be pretty difficult to do.  Some very restricted automatic
conversion would be possible, such as looking at a data structure that is
currently an array and replacing foo[number] with foo.<name> for some
appropriate name.  But things like changing all VNF references to objects
would be harder to do.  And changing functions that currently return
arbitrary arrays to objects is also going to be difficult to do in an
automated way.  Replacing struct() invocations may work for many things,
but I think it won't work for screws.scad because not all keys are text.
And I think for arg processing I also wanted the ability to create a new
structure where it is impossible to add fields, only change existing
fields.  I don't think this exists, so probably some extra logic would be
needed.

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

Yes, rewriting something like BOSL2 is a huge task. I’ve been thinking how
we could simplify this task. After all, inside BOSL2 you do use objects but
you map them to arrays and indexes. I think I see possibilities to easily
map them from an array to an object and vice versa of we have some layout
description of the array. I’ll be trying to come up with some ideas this
week.

Actually, BOSL2 is my primary motivation for this work. :-) I can’t live
without the attachable but nor can I live without the object abstraction.
Life puts strange challenges on our path. 😂

 Peter

On Sun 27 Jul 2025 at 20:37, Adrian Mariano via Discuss <
discuss@lists.openscad.org> wrote:

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


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


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

I think having objects return a default like that sounds like a hack that would clutter the api without having a legitimate long term use. The way to address the type of compatibility you’re talking about is to freeze a version of the library that returns arrays that old code can continue to use and then write a new non compatible version. On Mon, Jul 28, 2025 at 14:34 Nathan Sokalski via Discuss < discuss@lists.openscad.org> wrote: > A feature that occurred to me that I believe would make backwards > compatibility (at least temporarily) easier is to have a "default" > property. Normally, properties are accessed using obj.prop or obj["prop"], > but having a default property would allow you to access the first property > using just obj. By first, I simply mean the first name/value passed to the > object() function. For example, if the following object was created: > > myobj=object(prop1=value1,prop2=value2,prop3=value3); > > The following would all return value1: > > echo(myobj.prop1); > echo(myobj["prop1"]); > echo(myobj); //This would return value1 because value1 is the first value > that was assigned by the object() function > > Another possible idea would be to have a "reserved" property name > ("Default", "default", "_", " ") which when assigned a value would be the > default value. I think the option of having a default value would simplify > the converting of existing libraries because a common technique for > returning multiple values from a single function was returning an array, so > if the array was assigned to the default value instances of the function > would not be broken. Even though this would not replace the need to update > a large amount of code, it would allow you to update a function without > breaking as much code. Once again, thank you, and I very much look forward > to this feature! > > Nathan Sokalski > njsokalski@hotmail.com > ------------------------------ > *From:* Adrian Mariano via Discuss <discuss@lists.openscad.org> > *Sent:* Sunday, July 27, 2025 11:41 PM > *To:* OpenSCAD general discussion Mailing-list <discuss@lists.openscad.org > > > *Cc:* Adrian Mariano <avm4@cornell.edu> > > *Subject:* [OpenSCAD] Re: New feature in 2025.07.11: the object() function > > It seems like automatic conversion of BOSL2 (or existing code generally) > to objects would be pretty difficult to do. Some very restricted automatic > conversion would be possible, such as looking at a data structure that is > currently an array and replacing foo[number] with foo.<name> for some > appropriate name. But things like changing all VNF references to objects > would be harder to do. And changing functions that currently return > arbitrary arrays to objects is also going to be difficult to do in an > automated way. Replacing struct() invocations may work for many things, > but I think it won't work for screws.scad because not all keys are text. > And I think for arg processing I also wanted the ability to create a new > structure where it is impossible to add fields, only change existing > fields. I don't think this exists, so probably some extra logic would be > needed. > > > On Sun, Jul 27, 2025 at 3:04 PM Peter Kriens via Discuss < > discuss@lists.openscad.org> wrote: > > Yes, rewriting something like BOSL2 is a huge task. I’ve been thinking how > we could simplify this task. After all, inside BOSL2 you do use objects but > you map them to arrays and indexes. I think I see possibilities to easily > map them from an array to an object and vice versa of we have some layout > description of the array. I’ll be trying to come up with some ideas this > week. > > Actually, BOSL2 is my primary motivation for this work. :-) I can’t live > without the attachable but nor can I live without the object abstraction. > Life puts strange challenges on our path. 😂 > > Peter > > > > On Sun 27 Jul 2025 at 20:37, Adrian Mariano via Discuss < > discuss@lists.openscad.org> wrote: > > 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 > > _______________________________________________ > 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 > > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to discuss-leave@lists.openscad.org >
JB
Jordan Brown
Mon, Jul 28, 2025 11:06 PM

On 7/28/2025 12:26 PM, Adrian Mariano via Discuss wrote:

I think having objects return a default like that sounds like a hack
that would clutter the api without having a legitimate long term use.

I don't think it's even possible.  When you say "myobj" does that mean
"give me the object", or does it mean "give me the first member of the
object"?  It can only mean one of those things, and it pretty much has
to mean 'give me the object".

On 7/28/2025 12:26 PM, Adrian Mariano via Discuss wrote: > I think having objects return a default like that sounds like a hack > that would clutter the api without having a legitimate long term use. I don't think it's even possible.  When you say "myobj" does that mean "give me the object", or does it mean "give me the first member of the object"?  It can only mean one of those things, and it pretty much has to mean 'give me the object".
MM
Michael Marx (spintel)
Thu, Aug 7, 2025 9:23 AM

Jordan,

You say
Formally the entries should be assumed to be in no specific order, but for aesthetic reasons they are reported in the order they were added to the object.

Why not formalise that order? Because people ARE going to rely on it.

Does len() work for objects?


From: Jordan Brown via Discuss [mailto:discuss@lists.openscad.org]
Sent: Monday, July 14, 2025 8:20 AM
To: OpenSCAD
Cc: Jordan Brown
Subject: [OpenSCAD] New feature in 2025.07.11: the object() function

Quick summary:

  • 2025.07.11 adds the the object() function, which creates an object (in the style returned by textmetrics(), fontmetrics(), and import() of a JSON file).

  • It accumulates the object by processing the arguments left to right, with later settings for a particular member replacing earlier settings.

  • There are three forms for an argument:

  • name=value - sets that name (a constant identifier) to that value.

  • A vector with a list of [name, value] vectors, or [name] to remove a member, where the names can be any string expression.

  • An object has its members copied.

  • There is a new function has_key(obj, name) that returns true if the object contains the named key.

  • These functions are currently experimental and so must be enabled before you can use them.

Overview:

An "object" is a collection of names and associated values.  In other languages this data structure might be called an object (JavaScript), a dictionary (Python), an associative array (some UNIX shells, awk), or a structure (C, sort of),

This change adds a function that creates an object from a series of names, values, and other objects, and a function that queries whether a particular member is present.  These are mostly-normal functions; there is no new syntax introduced.

Background:

OpenSCAD has had an internal implementation of objects for several years, added to support the textmetrics() and fontmetrics() functions, and later import() of a JSON file.  This existing mechanism includes mechanisms for accessing the members of the object and for walking through the entries.  It does not include a mechanism for the user's program to create an object.

Given an object o, the current operations are:

  • o.name yields the value of the name member, where name must be an identifier (alphabetic, numeric, underscore, starting with alphabetic or underscore).  This syntax is very "clean", but does not allow for names that are derived from expressions or for names that are not suitable for use as identifiers.  An undefined name is not an error; it yields undefined.
  • o[name] also yields the value of the name member, but name can be an any string expression.  This syntax is a bit more awkward than the o.name syntax, but allows for dynamically-created names and for names that are not suitable as identifiers.  Again, and undefined name yields undefined.
  • for (name = o) ... is the object equivalent of the vector for(...).  It walks the object, setting name to each member name in sequence.  This mechanism is usable as both a normal statement and as a list comprehension element.  Note that it yields only the name; the value is accessible as o[name].  Formally the entries should be assumed to be in no specific order, but for aesthetic reasons they are reported in the order they were added to the object.
  • is_object(v) returns true if v is an object.
  • echo(o) and str(o) produce textual representations of objects.  (Note:  the textual form looks sort of like syntax, but is not legal OpenSCAD syntax.  It is likely to change in the future; see Future Directions below.)

Details:

The object() function constructs a new object, processing each argument in sequence from left to right, with later settings replacing earlier settings.  There are three variations of arguments; they can be mixed in any way.

  • A named parameter name=value

  • Sets the specified name to the specified value.  As with all named arguments to functions, the name must be an identifier.

  • a vector [ v1, v2, ... ]

  • v1, v2, ... are each two- or one-element vectors

  • [name, value]

  • Sets the specified name to the specified value.  The name can be any string expression.

  • [name]

  • Removes the specified name from the object being accumulated.  (Note that this is subtly different from setting it to undefined, in that it will not be reported for has_key() or when walking the names of the object.)

  • an object o

  • An object has each of its members copied into the new object.

The values contained in an object can (of course) be of any data type:  numbers, strings, vectors, function references, objects, et cetera.

There is also a new function has_key(o, name) that returns true if the object has a member with the specified name.

Examples:

  • Create an object; access its members:
    o = object(a=1, b=2);
    echo(o.a, o["b"]);
  • Create an object with varying names, and access them:
    names = [ "apple", "banana", "string bean" ];
    o = object([ for (name=names) [name, 123] ]);
    for (name=names) echo(name, o[name]);
  • Create an object, then create modified copies of that object:
    // Ancient planets
    planets = object(mercury=1, venus=2, earth=3, mars=4, jupiter=5, saturn=6);
    planets1781 = object(planets, uranus=7);        // Uranus discovered
    planets1846 = object(planets1781, neptune=8);  // Neptune discovered
    planets1930 = object(planets1846, pluto=9);    // Pluto discovered
    planets2006 = object(planets1930, [["pluto"]]); // Pluto un-planeted
  • Check whether a member is present:
    echo(has_key(planets1846, "neptune")); // true
    echo(has_key(planets2006, "pluto"));  // false

Future Directions and Related Projects:

This is the second phase (after the textmetrics() work) of a longer-term plan to introduce "object" features into OpenSCAD.

Object literals, object comprehension:  OEP8a https://github.com/openscad/openscad/wiki/OEP8a:--Objects-(dictionaries%3F)  adds a syntax for creating objects, including object comprehensions, roughly modeled on JavaScript object syntax, so that { a: 1, b: 2 } is equivalent to object(a=1, b=2). Changes echo() and str() to represent objects using this syntax.

OEP8 https://github.com/openscad/openscad/wiki/OEP8%3A-Objects-%28dictionaries%3F%29%2C-Geometry-as-data%2C-and-Module-References  further adds geometry as data and module references.

No formal proposals:

Methods: If a function reference comes from an object or a vector, it should see a special variable $this that refers to the containing object or vector.  Note that although there's no inheritance per se, making a modified copy of an object is a lot like the prototype-based https://en.wikipedia.org/wiki/Prototype-based_programming  OO.

Variable parameter lists:  a syntax for a parameter lists that says "return the rest of the parameters in this variable", as a vector (for positional arguments) or an object (for named arguments).

Spread syntax:  a syntax for adding a vector to an argument list as positional arguments, or adding an object to an argument list as named arguments.

Sets:  Some kind of syntactic sugar to make it easy to create an object containing boolean "true", to make it easy to define a set (in the mathematical sense) and query whether particular items are present in it.

Questions:

  • What should these things be called?

  • They're modeled on JavaScript objects, but to a Python person they look more like dictionaries and to a C person they look more like structures.

  • Are they really "objects", when they don't have OO features?  See Future Directions about methods.

  • In OpenSCAD, doesn't "object" already mean a geometric figure?

Credits / History:

  • I did the original textmetrics() work.
  • Revar Desmera did the original implementation of object().
  • Peter Kriens drove this final integration, cleaning up the implementation, fixing a bug, and writing test cases.
Jordan, You say Formally the entries should be assumed to be in no specific order, but for aesthetic reasons they are reported in the order they were added to the object. Why not formalise that order? Because people ARE going to rely on it. Does len() work for objects? _____ From: Jordan Brown via Discuss [mailto:discuss@lists.openscad.org] Sent: Monday, July 14, 2025 8:20 AM To: OpenSCAD Cc: Jordan Brown Subject: [OpenSCAD] New feature in 2025.07.11: the object() function Quick summary: * 2025.07.11 adds the the object() function, which creates an object (in the style returned by textmetrics(), fontmetrics(), and import() of a JSON file). * It accumulates the object by processing the arguments left to right, with later settings for a particular member replacing earlier settings. * There are three forms for an argument: * name=value - sets that name (a constant identifier) to that value. * A vector with a list of [name, value] vectors, or [name] to remove a member, where the names can be any string expression. * An object has its members copied. * There is a new function has_key(obj, name) that returns true if the object contains the named key. * These functions are currently experimental and so must be enabled before you can use them. Overview: An "object" is a collection of names and associated values. In other languages this data structure might be called an object (JavaScript), a dictionary (Python), an associative array (some UNIX shells, awk), or a structure (C, sort of), This change adds a function that creates an object from a series of names, values, and other objects, and a function that queries whether a particular member is present. These are mostly-normal functions; there is no new syntax introduced. Background: OpenSCAD has had an internal implementation of objects for several years, added to support the textmetrics() and fontmetrics() functions, and later import() of a JSON file. This existing mechanism includes mechanisms for accessing the members of the object and for walking through the entries. It does not include a mechanism for the user's program to create an object. Given an object o, the current operations are: * o.name yields the value of the name member, where name must be an identifier (alphabetic, numeric, underscore, starting with alphabetic or underscore). This syntax is very "clean", but does not allow for names that are derived from expressions or for names that are not suitable for use as identifiers. An undefined name is not an error; it yields undefined. * o[name] also yields the value of the name member, but name can be an any string expression. This syntax is a bit more awkward than the o.name syntax, but allows for dynamically-created names and for names that are not suitable as identifiers. Again, and undefined name yields undefined. * for (name = o) ... is the object equivalent of the vector for(...). It walks the object, setting name to each member name in sequence. This mechanism is usable as both a normal statement and as a list comprehension element. Note that it yields only the name; the value is accessible as o[name]. Formally the entries should be assumed to be in no specific order, but for aesthetic reasons they are reported in the order they were added to the object. * is_object(v) returns true if v is an object. * echo(o) and str(o) produce textual representations of objects. (Note: the textual form looks sort of like syntax, but is not legal OpenSCAD syntax. It is likely to change in the future; see Future Directions below.) Details: The object() function constructs a new object, processing each argument in sequence from left to right, with later settings replacing earlier settings. There are three variations of arguments; they can be mixed in any way. * A named parameter name=value * Sets the specified name to the specified value. As with all named arguments to functions, the name must be an identifier. * a vector [ v1, v2, ... ] * v1, v2, ... are each two- or one-element vectors * [name, value] * Sets the specified name to the specified value. The name can be any string expression. * [name] * Removes the specified name from the object being accumulated. (Note that this is subtly different from setting it to undefined, in that it will not be reported for has_key() or when walking the names of the object.) * an object o * An object has each of its members copied into the new object. The values contained in an object can (of course) be of any data type: numbers, strings, vectors, function references, objects, et cetera. There is also a new function has_key(o, name) that returns true if the object has a member with the specified name. Examples: * Create an object; access its members: o = object(a=1, b=2); echo(o.a, o["b"]); * Create an object with varying names, and access them: names = [ "apple", "banana", "string bean" ]; o = object([ for (name=names) [name, 123] ]); for (name=names) echo(name, o[name]); * Create an object, then create modified copies of that object: // Ancient planets planets = object(mercury=1, venus=2, earth=3, mars=4, jupiter=5, saturn=6); planets1781 = object(planets, uranus=7); // Uranus discovered planets1846 = object(planets1781, neptune=8); // Neptune discovered planets1930 = object(planets1846, pluto=9); // Pluto discovered planets2006 = object(planets1930, [["pluto"]]); // Pluto un-planeted * Check whether a member is present: echo(has_key(planets1846, "neptune")); // true echo(has_key(planets2006, "pluto")); // false Future Directions and Related Projects: This is the second phase (after the textmetrics() work) of a longer-term plan to introduce "object" features into OpenSCAD. Object literals, object comprehension: OEP8a <https://github.com/openscad/openscad/wiki/OEP8a:--Objects-(dictionaries%3F)> adds a syntax for creating objects, including object comprehensions, roughly modeled on JavaScript object syntax, so that { a: 1, b: 2 } is equivalent to object(a=1, b=2). Changes echo() and str() to represent objects using this syntax. OEP8 <https://github.com/openscad/openscad/wiki/OEP8%3A-Objects-%28dictionaries%3F%29%2C-Geometry-as-data%2C-and-Module-References> further adds geometry as data and module references. No formal proposals: Methods: If a function reference comes from an object or a vector, it should see a special variable $this that refers to the containing object or vector. Note that although there's no inheritance per se, making a modified copy of an object is a lot like the prototype-based <https://en.wikipedia.org/wiki/Prototype-based_programming> OO. Variable parameter lists: a syntax for a parameter lists that says "return the rest of the parameters in this variable", as a vector (for positional arguments) or an object (for named arguments). Spread syntax: a syntax for adding a vector to an argument list as positional arguments, or adding an object to an argument list as named arguments. Sets: Some kind of syntactic sugar to make it easy to create an object containing boolean "true", to make it easy to define a set (in the mathematical sense) and query whether particular items are present in it. Questions: * What should these things be called? * They're modeled on JavaScript objects, but to a Python person they look more like dictionaries and to a C person they look more like structures. * Are they really "objects", when they don't have OO features? See Future Directions about methods. * In OpenSCAD, doesn't "object" already mean a geometric figure? Credits / History: * I did the original textmetrics() work. * Revar Desmera did the original implementation of object(). * Peter Kriens drove this final integration, cleaning up the implementation, fixing a bug, and writing test cases.
PK
Peter Kriens
Thu, Aug 7, 2025 1:41 PM

On 7 Aug 2025, at 11:23, Michael Marx (spintel) via Discuss discuss@lists.openscad.org wrote:

Jordan,

You say
Formally the entries should be assumed to be in no specific order, but for aesthetic reasons they are reported in the order they were added to the object.
Why not formalise that order? Because people ARE going to rely on it.

I agree. These ordering issues are very important, especially if you want to have repeatable builds.

Peter

Does len() work for objects?

From: Jordan Brown via Discuss [mailto:discuss@lists.openscad.org]
Sent: Monday, July 14, 2025 8:20 AM
To: OpenSCAD
Cc: Jordan Brown
Subject: [OpenSCAD] New feature in 2025.07.11: the object() function

Quick summary:

2025.07.11 adds the the object() function, which creates an object (in the style returned by textmetrics(), fontmetrics(), and import() of a JSON file).
It accumulates the object by processing the arguments left to right, with later settings for a particular member replacing earlier settings.
There are three forms for an argument:
name=value - sets that name (a constant identifier) to that value.
A vector with a list of [name, value] vectors, or [name] to remove a member, where the names can be any string expression.
An object has its members copied.
There is a new function has_key(obj, name) that returns true if the object contains the named key.
These functions are currently experimental and so must be enabled before you can use them.
Overview:

An "object" is a collection of names and associated values.  In other languages this data structure might be called an object (JavaScript), a dictionary (Python), an associative array (some UNIX shells, awk), or a structure (C, sort of),

This change adds a function that creates an object from a series of names, values, and other objects, and a function that queries whether a particular member is present.  These are mostly-normal functions; there is no new syntax introduced.

Background:

OpenSCAD has had an internal implementation of objects for several years, added to support the textmetrics() and fontmetrics() functions, and later import() of a JSON file.  This existing mechanism includes mechanisms for accessing the members of the object and for walking through the entries.  It does not include a mechanism for the user's program to create an object.

Given an object o, the current operations are:

o.name yields the value of the name member, where name must be an identifier (alphabetic, numeric, underscore, starting with alphabetic or underscore).  This syntax is very "clean", but does not allow for names that are derived from expressions or for names that are not suitable for use as identifiers.  An undefined name is not an error; it yields undefined.
o[name] also yields the value of the name member, but name can be an any string expression.  This syntax is a bit more awkward than the o.name syntax, but allows for dynamically-created names and for names that are not suitable as identifiers.  Again, and undefined name yields undefined.
for (name = o) ... is the object equivalent of the vector for(...).  It walks the object, setting name to each member name in sequence.  This mechanism is usable as both a normal statement and as a list comprehension element.  Note that it yields only the name; the value is accessible as o[name].  Formally the entries should be assumed to be in no specific order, but for aesthetic reasons they are reported in the order they were added to the object.
is_object(v) returns true if v is an object.
echo(o) and str(o) produce textual representations of objects.  (Note:  the textual form looks sort of like syntax, but is not legal OpenSCAD syntax.  It is likely to change in the future; see Future Directions below.)
Details:

The object() function constructs a new object, processing each argument in sequence from left to right, with later settings replacing earlier settings.  There are three variations of arguments; they can be mixed in any way.

A named parameter name=value
Sets the specified name to the specified value.  As with all named arguments to functions, the name must be an identifier.
a vector [ v1, v2, ... ]
v1, v2, ... are each two- or one-element vectors
[name, value]
Sets the specified name to the specified value.  The name can be any string expression.
[name]
Removes the specified name from the object being accumulated.  (Note that this is subtly different from setting it to undefined, in that it will not be reported for has_key() or when walking the names of the object.)
an object o
An object has each of its members copied into the new object.
The values contained in an object can (of course) be of any data type:  numbers, strings, vectors, function references, objects, et cetera.

There is also a new function has_key(o, name) that returns true if the object has a member with the specified name.
Examples:

Create an object; access its members:
o = object(a=1, b=2);
echo(o.a, o["b"]);
Create an object with varying names, and access them:
names = [ "apple", "banana", "string bean" ];
o = object([ for (name=names) [name, 123] ]);
for (name=names) echo(name, o[name]);
Create an object, then create modified copies of that object:
// Ancient planets
planets = object(mercury=1, venus=2, earth=3, mars=4, jupiter=5, saturn=6);
planets1781 = object(planets, uranus=7);        // Uranus discovered
planets1846 = object(planets1781, neptune=8);  // Neptune discovered
planets1930 = object(planets1846, pluto=9);    // Pluto discovered
planets2006 = object(planets1930, [["pluto"]]); // Pluto un-planeted
Check whether a member is present:
echo(has_key(planets1846, "neptune")); // true
echo(has_key(planets2006, "pluto"));  // false
Future Directions and Related Projects:

This is the second phase (after the textmetrics() work) of a longer-term plan to introduce "object" features into OpenSCAD.

Object literals, object comprehension:  OEP8a https://github.com/openscad/openscad/wiki/OEP8a:--Objects-(dictionaries%3F) adds a syntax for creating objects, including object comprehensions, roughly modeled on JavaScript object syntax, so that { a: 1, b: 2 } is equivalent to object(a=1, b=2). Changes echo() and str() to represent objects using this syntax.

OEP8 https://github.com/openscad/openscad/wiki/OEP8%3A-Objects-%28dictionaries%3F%29%2C-Geometry-as-data%2C-and-Module-References further adds geometry as data and module references.

No formal proposals:

Methods: If a function reference comes from an object or a vector, it should see a special variable $this that refers to the containing object or vector.  Note that although there's no inheritance per se, making a modified copy of an object is a lot like the prototype-based OO https://en.wikipedia.org/wiki/Prototype-based_programming.

Variable parameter lists:  a syntax for a parameter lists that says "return the rest of the parameters in this variable", as a vector (for positional arguments) or an object (for named arguments).

Spread syntax:  a syntax for adding a vector to an argument list as positional arguments, or adding an object to an argument list as named arguments.

Sets:  Some kind of syntactic sugar to make it easy to create an object containing boolean "true", to make it easy to define a set (in the mathematical sense) and query whether particular items are present in it.

Questions:

What should these things be called?
They're modeled on JavaScript objects, but to a Python person they look more like dictionaries and to a C person they look more like structures.
Are they really "objects", when they don't have OO features?  See Future Directions about methods.
In OpenSCAD, doesn't "object" already mean a geometric figure?
Credits / History:

I did the original textmetrics() work.
Revar Desmera did the original implementation of object().
Peter Kriens drove this final integration, cleaning up the implementation, fixing a bug, and writing test cases.


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

> On 7 Aug 2025, at 11:23, Michael Marx (spintel) via Discuss <discuss@lists.openscad.org> wrote: > > Jordan, > > You say > Formally the entries should be assumed to be in no specific order, but for aesthetic reasons they are reported in the order they were added to the object. > Why not formalise that order? Because people ARE going to rely on it. I agree. These ordering issues are very important, especially if you want to have repeatable builds. Peter > > Does len() work for objects? > > > From: Jordan Brown via Discuss [mailto:discuss@lists.openscad.org] > Sent: Monday, July 14, 2025 8:20 AM > To: OpenSCAD > Cc: Jordan Brown > Subject: [OpenSCAD] New feature in 2025.07.11: the object() function > > Quick summary: > > 2025.07.11 adds the the object() function, which creates an object (in the style returned by textmetrics(), fontmetrics(), and import() of a JSON file). > It accumulates the object by processing the arguments left to right, with later settings for a particular member replacing earlier settings. > There are three forms for an argument: > name=value - sets that name (a constant identifier) to that value. > A vector with a list of [name, value] vectors, or [name] to remove a member, where the names can be any string expression. > An object has its members copied. > There is a new function has_key(obj, name) that returns true if the object contains the named key. > These functions are currently experimental and so must be enabled before you can use them. > Overview: > > An "object" is a collection of names and associated values. In other languages this data structure might be called an object (JavaScript), a dictionary (Python), an associative array (some UNIX shells, awk), or a structure (C, sort of), > > This change adds a function that creates an object from a series of names, values, and other objects, and a function that queries whether a particular member is present. These are mostly-normal functions; there is no new syntax introduced. > > Background: > > OpenSCAD has had an internal implementation of objects for several years, added to support the textmetrics() and fontmetrics() functions, and later import() of a JSON file. This existing mechanism includes mechanisms for accessing the members of the object and for walking through the entries. It does not include a mechanism for the user's program to create an object. > > Given an object o, the current operations are: > > o.name yields the value of the name member, where name must be an identifier (alphabetic, numeric, underscore, starting with alphabetic or underscore). This syntax is very "clean", but does not allow for names that are derived from expressions or for names that are not suitable for use as identifiers. An undefined name is not an error; it yields undefined. > o[name] also yields the value of the name member, but name can be an any string expression. This syntax is a bit more awkward than the o.name syntax, but allows for dynamically-created names and for names that are not suitable as identifiers. Again, and undefined name yields undefined. > for (name = o) ... is the object equivalent of the vector for(...). It walks the object, setting name to each member name in sequence. This mechanism is usable as both a normal statement and as a list comprehension element. Note that it yields only the name; the value is accessible as o[name]. Formally the entries should be assumed to be in no specific order, but for aesthetic reasons they are reported in the order they were added to the object. > is_object(v) returns true if v is an object. > echo(o) and str(o) produce textual representations of objects. (Note: the textual form looks sort of like syntax, but is not legal OpenSCAD syntax. It is likely to change in the future; see Future Directions below.) > Details: > > The object() function constructs a new object, processing each argument in sequence from left to right, with later settings replacing earlier settings. There are three variations of arguments; they can be mixed in any way. > > A named parameter name=value > Sets the specified name to the specified value. As with all named arguments to functions, the name must be an identifier. > a vector [ v1, v2, ... ] > v1, v2, ... are each two- or one-element vectors > [name, value] > Sets the specified name to the specified value. The name can be any string expression. > [name] > Removes the specified name from the object being accumulated. (Note that this is subtly different from setting it to undefined, in that it will not be reported for has_key() or when walking the names of the object.) > an object o > An object has each of its members copied into the new object. > The values contained in an object can (of course) be of any data type: numbers, strings, vectors, function references, objects, et cetera. > > There is also a new function has_key(o, name) that returns true if the object has a member with the specified name. > Examples: > > Create an object; access its members: > o = object(a=1, b=2); > echo(o.a, o["b"]); > Create an object with varying names, and access them: > names = [ "apple", "banana", "string bean" ]; > o = object([ for (name=names) [name, 123] ]); > for (name=names) echo(name, o[name]); > Create an object, then create modified copies of that object: > // Ancient planets > planets = object(mercury=1, venus=2, earth=3, mars=4, jupiter=5, saturn=6); > planets1781 = object(planets, uranus=7); // Uranus discovered > planets1846 = object(planets1781, neptune=8); // Neptune discovered > planets1930 = object(planets1846, pluto=9); // Pluto discovered > planets2006 = object(planets1930, [["pluto"]]); // Pluto un-planeted > Check whether a member is present: > echo(has_key(planets1846, "neptune")); // true > echo(has_key(planets2006, "pluto")); // false > Future Directions and Related Projects: > > This is the second phase (after the textmetrics() work) of a longer-term plan to introduce "object" features into OpenSCAD. > > Object literals, object comprehension: OEP8a <https://github.com/openscad/openscad/wiki/OEP8a:--Objects-(dictionaries%3F)> adds a syntax for creating objects, including object comprehensions, roughly modeled on JavaScript object syntax, so that { a: 1, b: 2 } is equivalent to object(a=1, b=2). Changes echo() and str() to represent objects using this syntax. > > OEP8 <https://github.com/openscad/openscad/wiki/OEP8%3A-Objects-%28dictionaries%3F%29%2C-Geometry-as-data%2C-and-Module-References> further adds geometry as data and module references. > > No formal proposals: > > Methods: If a function reference comes from an object or a vector, it should see a special variable $this that refers to the containing object or vector. Note that although there's no inheritance per se, making a modified copy of an object is a lot like the prototype-based OO <https://en.wikipedia.org/wiki/Prototype-based_programming>. > > Variable parameter lists: a syntax for a parameter lists that says "return the rest of the parameters in this variable", as a vector (for positional arguments) or an object (for named arguments). > > Spread syntax: a syntax for adding a vector to an argument list as positional arguments, or adding an object to an argument list as named arguments. > > Sets: Some kind of syntactic sugar to make it easy to create an object containing boolean "true", to make it easy to define a set (in the mathematical sense) and query whether particular items are present in it. > > Questions: > > What should these things be called? > They're modeled on JavaScript objects, but to a Python person they look more like dictionaries and to a C person they look more like structures. > Are they really "objects", when they don't have OO features? See Future Directions about methods. > In OpenSCAD, doesn't "object" already mean a geometric figure? > Credits / History: > > I did the original textmetrics() work. > Revar Desmera did the original implementation of object(). > Peter Kriens drove this final integration, cleaning up the implementation, fixing a bug, and writing test cases. > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to discuss-leave@lists.openscad.org <mailto:discuss-leave@lists.openscad.org>
V
vulcan_@mac.com
Fri, Aug 8, 2025 12:30 AM

Peter Kriens wrote:

Does len() work for objects?

as it happens i was testing this today as i checked out the len() function .. no indeed ..

len( object(this=”that”) );

gives me

[WARNING: len() parameter could not be converted: argument 0: expected string, found object ({ this = "that"; }) in file ., line 1](1,C:/Program Files/OpenSCAD (Nightly))

ECHO: undef

Peter Kriens wrote: > > > > > Does len() work for objects? as it happens i was testing this today as i checked out the len() function .. no indeed .. `len( object(this=”that”) );` gives me > [WARNING: len() parameter could not be converted: argument 0: expected string, found object ({ this = "that"; }) in file ., line 1](1,C:/Program Files/OpenSCAD (Nightly)) > > ECHO: undef
RD
Revar Desmera
Fri, Aug 8, 2025 5:47 AM

Workaround: len([for(x=obj)x])

  • Revar

On Aug 7, 2025, at 5:30 PM, vulcan_--- via Discuss discuss@lists.openscad.org wrote:

Peter Kriens wrote:

Does len() work for objects?

as it happens i was testing this today as i checked out the len() function .. no indeed ..

len( object(this=”that”) );

gives me

WARNING: len() parameter could not be converted: argument 0: expected string, found object ({ this = "that"; }) in file ., line 1 x-msg://1/1,C:/Program%20Files/OpenSCAD%20(Nightly)
ECHO: undef


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

Workaround: `len([for(x=obj)x])` - Revar > On Aug 7, 2025, at 5:30 PM, vulcan_--- via Discuss <discuss@lists.openscad.org> wrote: > > Peter Kriens wrote: > > > > Does len() work for objects? > > as it happens i was testing this today as i checked out the len() function .. no indeed .. > > len( object(this=”that”) ); > > gives me > > WARNING: len() parameter could not be converted: argument 0: expected string, found object ({ this = "that"; }) in file ., line 1 <x-msg://1/1,C:/Program%20Files/OpenSCAD%20(Nightly)> > ECHO: undef > > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to discuss-leave@lists.openscad.org
SL
Steve Lelievre
Fri, Aug 8, 2025 6:33 AM

Revar, Peter,

Revar’s suggestion  returning the length of a vector of one object, which
isn’t quite the same thing as the length of the object per se.

I’m not seeing why len should  produce a result when applied to an object
but if it does, wouldn’t the simple value

1

be enough?

For comparison, what does len do for any other non-vector, non-string
variable? (I’m away from home so can’t check)

Steve

On Fri, 8 Aug 2025 at 01:47, Revar Desmera via Discuss <
discuss@lists.openscad.org> wrote:

Workaround: len([for(x=obj)x])

  • Revar

On Aug 7, 2025, at 5:30 PM, vulcan_--- via Discuss <
discuss@lists.openscad.org> wrote:

Peter Kriens wrote:

Does len() work for objects?

as it happens i was testing this today as i checked out the len() function
.. no indeed ..

len( object(this=”that”) );

gives me

WARNING: len() parameter could not be converted: argument 0: expected
string, found object ({ this = "that"; }) in file ., line 1

ECHO: undef


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

Revar, Peter, Revar’s suggestion returning the length of a vector of one object, which isn’t quite the same thing as the length of the object per se. I’m not seeing why len should produce a result when applied to an object but if it does, wouldn’t the simple value 1 be enough? For comparison, what does len do for any other non-vector, non-string variable? (I’m away from home so can’t check) Steve On Fri, 8 Aug 2025 at 01:47, Revar Desmera via Discuss < discuss@lists.openscad.org> wrote: > Workaround: `len([for(x=obj)x])` > > - Revar > > > On Aug 7, 2025, at 5:30 PM, vulcan_--- via Discuss < > discuss@lists.openscad.org> wrote: > > Peter Kriens wrote: > > > Does len() work for objects? > > as it happens i was testing this today as i checked out the len() function > .. no indeed .. > > len( object(this=”that”) ); > > gives me > > WARNING: len() parameter could not be converted: argument 0: expected > string, found object ({ this = "that"; }) in file ., line 1 > > ECHO: undef > > _______________________________________________ > 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
Fri, Aug 8, 2025 7:54 AM

On 8/7/2025 10:47 PM, Revar Desmera via Discuss wrote:

Workaround: len([for(x=obj)x])

Yeah, I thought about that, but if there's a need for a "workaround"
then we should just make it work.

On 8/7/2025 10:47 PM, Revar Desmera via Discuss wrote: > Workaround: `len([for(x=obj)x])` Yeah, I thought about that, but if there's a need for a "workaround" then we should just make it work.
RD
Revar Desmera
Fri, Aug 8, 2025 7:58 AM

I'm not sure when I'de ever actually check how many keys are in an object, except to see if no keys are in the object, in which case I'd just test for !object.
But I don't see why not to support it.

  • Revar

On Aug 8, 2025, at 12:54 AM, Jordan Brown openscad@jordan.maileater.net wrote:

On 8/7/2025 10:47 PM, Revar Desmera via Discuss wrote:

Workaround: len([for(x=obj)x])

Yeah, I thought about that, but if there's a need for a "workaround" then we should just make it work.

I'm not sure when I'de ever actually check how many keys are in an object, except to see if no keys are in the object, in which case I'd just test for `!object`. But I don't see why not to support it. - Revar > On Aug 8, 2025, at 12:54 AM, Jordan Brown <openscad@jordan.maileater.net> wrote: > > On 8/7/2025 10:47 PM, Revar Desmera via Discuss wrote: >> Workaround: `len([for(x=obj)x])` > > Yeah, I thought about that, but if there's a need for a "workaround" then we should just make it work. > >
JB
Jordan Brown
Fri, Aug 8, 2025 7:59 AM

On 8/7/2025 11:33 PM, Steve Lelievre via Discuss wrote:

Revar’s suggestion  returning the length of a vector of one object,
which isn’t quite the same thing as the length of the object per se.

First, you need to define the length of an object.  Revar's definition,
and the one that I would think of would be "the number of elements in
the object", along the same lines as len(vector). I'm not immediately
coming up with a different definition that would be more useful.  (Heck,
I'm not immediately coming up with a different definition that would be
useful at all.)

For comparison, what does len do for any other non-vector, non-string
variable? (I’m away from home so can’t check)

It's an error.

On 8/7/2025 11:33 PM, Steve Lelievre via Discuss wrote: > Revar’s suggestion  returning the length of a vector of one object, > which isn’t quite the same thing as the length of the object per se. First, you need to define the length of an object.  Revar's definition, and the one that I would think of would be "the number of elements in the object", along the same lines as len(vector). I'm not immediately coming up with a different definition that would be more useful.  (Heck, I'm not immediately coming up with a different definition that would be useful at all.) > For comparison, what does len do for any other non-vector, non-string > variable? (I’m away from home so can’t check) It's an error.
JB
Jordan Brown
Fri, Aug 8, 2025 8:15 AM

On 8/8/2025 12:58 AM, Revar Desmera via Discuss wrote:

I'm not sure when I'de ever actually check how many keys are in an object, except to see if no keys are in the object, in which case I'd just test for !object.
But I don't see why not to support it.

I'm in pretty much the same boat there, except that it's pretty rare
that I even want to know whether there are no keys.

On 8/8/2025 12:58 AM, Revar Desmera via Discuss wrote: > I'm not sure when I'de ever actually check how many keys are in an object, except to see if no keys are in the object, in which case I'd just test for `!object`. > But I don't see why not to support it. I'm in pretty much the same boat there, except that it's pretty rare that I even want to know whether there are no keys.
PK
Peter Kriens
Fri, Aug 8, 2025 8:21 AM

I created a PR.

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

On 8 Aug 2025, at 09:54, Jordan Brown via Discuss discuss@lists.openscad.org wrote:

On 8/7/2025 10:47 PM, Revar Desmera via Discuss wrote:

Workaround: len([for(x=obj)x])

Yeah, I thought about that, but if there's a need for a "workaround" then we should just make it work.


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

I created a PR. https://github.com/openscad/openscad/pull/6080 > On 8 Aug 2025, at 09:54, Jordan Brown via Discuss <discuss@lists.openscad.org> wrote: > > On 8/7/2025 10:47 PM, Revar Desmera via Discuss wrote: >> Workaround: `len([for(x=obj)x])` > > Yeah, I thought about that, but if there's a need for a "workaround" then we should just make it work. > > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to discuss-leave@lists.openscad.org
SL
Steve Lelievre
Fri, Aug 8, 2025 9:25 AM

To me an object’s key count is conceptually different to length so perhaps
instead of monkeying with the behaviour of len to get the key count, a new
built-in function that returns a vector of the keys. Then we could do
things like

echo(len(keys(myObj));

but also process them sequentially

for(i = keys(myObj)) … something …myObj[i];

Kills two birds with one stone.

Steve

On Fri, 8 Aug 2025 at 04:22, Peter Kriens via Discuss <
discuss@lists.openscad.org> wrote:

I created a PR.

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

On 8 Aug 2025, at 09:54, Jordan Brown via Discuss <
discuss@lists.openscad.org> wrote:

On 8/7/2025 10:47 PM, Revar Desmera via Discuss wrote:

Workaround: len([for(x=obj)x])

Yeah, I thought about that, but if there's a need for a "workaround" then
we should just make it work.


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

To me an object’s key count is conceptually different to length so perhaps instead of monkeying with the behaviour of len to get the key count, a new built-in function that returns a vector of the keys. Then we could do things like echo(len(keys(myObj)); but also process them sequentially for(i = keys(myObj)) … something …myObj[i]; Kills two birds with one stone. Steve On Fri, 8 Aug 2025 at 04:22, Peter Kriens via Discuss < discuss@lists.openscad.org> wrote: > I created a PR. > > https://github.com/openscad/openscad/pull/6080 > > > > On 8 Aug 2025, at 09:54, Jordan Brown via Discuss < > discuss@lists.openscad.org> wrote: > > On 8/7/2025 10:47 PM, Revar Desmera via Discuss wrote: > > Workaround: `len([for(x=obj)x])` > > > Yeah, I thought about that, but if there's a need for a "workaround" then > we should just make it work. > > _______________________________________________ > 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
Fri, Aug 8, 2025 12:15 PM

On 8/8/2025 2:25 AM, Steve Lelievre via Discuss wrote:

To me an object’s key count is conceptually different to length

I don't entirely disagree, but what else would "length" mean?

a new built-in function that returns a vector of the keys. Then we
could do things like

echo(len(keys(myObj));

but also process them sequentially

for(i = keys(myObj)) … something …myObj[i];

Iteration already works directly; if you say "for (k = myObj)" k is set
to each key in sequence.

Your "keys()" is thus

function keys(obj) = [ for (k = obj) k ];

... but that's not really all that interesting.

That's why len( [ for (k = obj) k ] ) works.

On 8/8/2025 2:25 AM, Steve Lelievre via Discuss wrote: > To me an object’s key count is conceptually different to length I don't entirely disagree, but what *else* would "length" mean? > a new built-in function that returns a vector of the keys. Then we > could do things like > > echo(len(keys(myObj)); > > but also process them sequentially > > for(i = keys(myObj)) … something …myObj[i]; Iteration already works directly; if you say "for (k = myObj)" k is set to each key in sequence. Your "keys()" is thus function keys(obj) = [ for (k = obj) k ]; ... but that's not really all that interesting. That's why len( [ for (k = obj) k ] ) works.
SL
Steve Lelievre
Fri, Aug 8, 2025 12:22 PM

Ah okay,

Thanks for teaching me.

Steve

On Fri, 8 Aug 2025 at 08:15, Jordan Brown openscad@jordan.maileater.net
wrote:

On 8/8/2025 2:25 AM, Steve Lelievre via Discuss wrote:

To me an object’s key count is conceptually different to length

I don't entirely disagree, but what else would "length" mean?

a new built-in function that returns a vector of the keys. Then we could
do things like

echo(len(keys(myObj));

but also process them sequentially

for(i = keys(myObj)) … something …myObj[i];

Iteration already works directly; if you say "for (k = myObj)" k is set to
each key in sequence.

Your "keys()" is thus

function keys(obj) = [ for (k = obj) k ];

... but that's not really all that interesting.

That's why len( [ for (k = obj) k ] ) works.

Ah okay, Thanks for teaching me. Steve On Fri, 8 Aug 2025 at 08:15, Jordan Brown <openscad@jordan.maileater.net> wrote: > > On 8/8/2025 2:25 AM, Steve Lelievre via Discuss wrote: > > To me an object’s key count is conceptually different to length > > > I don't entirely disagree, but what *else* would "length" mean? > > > a new built-in function that returns a vector of the keys. Then we could > do things like > > echo(len(keys(myObj)); > > but also process them sequentially > > for(i = keys(myObj)) … something …myObj[i]; > > > Iteration already works directly; if you say "for (k = myObj)" k is set to > each key in sequence. > > Your "keys()" is thus > > function keys(obj) = [ for (k = obj) k ]; > > ... but that's not really all that interesting. > > That's why len( [ for (k = obj) k ] ) works. > > >
V
vulcan_@mac.com
Fri, Aug 8, 2025 2:36 PM

Steve Lelievre wrote:

To me an object’s key count is conceptually different to length

Len() currently returns the number of characters in a string, and the number of top-level elements of a vector:

v = [ 1,2, [3,4] ];
echo( len( v ) ); // ECHO: 3

built-in function that returns a vector of the keys.

too easy to do

keys = [ for( o=object( this=”that” ) o ) ]

will make a vector of strings, where the strings are the names of the object’s elements in order of creation

echo(len(keys(myObj));

I would like to see a built in member function:

o=object( this=”that” );
k = o.keys();
echo( k ); // ECHO: [“that“]

but then we would also want

ell = o.len()

wouldn’t we?

Steve Lelievre wrote: > To me an object’s key count is conceptually different to length Len() currently returns the number of characters in a string, and the number of top-level elements of a vector: `v = [ 1,2, [3,4] ];`\ `echo( len( v ) ); // ECHO: 3` > > built-in function that returns a vector of the keys. too easy to do `keys = [ for( o=object( this=”that” ) o ) ]` will make a vector of strings, where the strings are the names of the object’s elements in order of creation > echo(len(keys(myObj)); I would like to see a built in member function: `o=object( this=”that” );`\ `k = o.keys();`\ `echo( k ); // ECHO: [“that“]` but then we would also want `ell = o.len()` wouldn’t we?
JB
Jordan Brown
Fri, Aug 8, 2025 3:01 PM

I would like to see a built in member function:

|o=object( this=”that” );
k = o.keys();
echo( k ); // ECHO: [“that“]|

but then we would also want

|ell = o.len()|

wouldn’t we?

I would be hesitant to put predefined member functions on objects,
because that intrudes on namespace that the user owns.

(And of course we don't have a $this-like mechanism yet, so this is
premature.)

> I would like to see a built in member function: > > |o=object( this=”that” ); > k = o.keys(); > echo( k ); // ECHO: [“that“]| > > but then we would also want > > |ell = o.len()| > > wouldn’t we? > I would be hesitant to put predefined member functions on objects, because that intrudes on namespace that the user owns. (And of course we don't have a $this-like mechanism yet, so this is premature.)