OpenSCAD User Manual/List Comprehensions
[Note: Requires version 2015.03]
Syntax of List comprehensions
List comprehensions are a powerful tool for generating lists using the syntax:
[ list-definition-element* ]
meaning that there may be one or more comma separated elements inside the square brackets each of which may be one of these generators:
- <literal>
- any literal number, string, vector
- <expression>
- any normal arithmetic, logical, expression or function call
- for( i = [start:incr:end] )
- Iteration over a range, same as
for loop - for( i = vector )
- over an existing list, same as
for loop - for( i = "string")
- over an existing string, same as
for loop - for( init;condition;next )
- C-style
forloop, each section minimum one expression.[Note: Requires version 2019.05] Note: not available as a statement , see below. - each
- unpacks following vectors, or ranges, and adds each element to the list being constructed
- if( <condition> ) <expression>
- when condition is false, nothing is added to the list, otherwise the <expression> is evaluated and its result becomes a list element
- if( <condition> ) <expression> else <expression>
- when <condition> is true, the first expression is evaluated and its result becomes a list element, otherwise the second expression is used.
The let( localvar = <expression> ) statement deserves special mention for its role allowing calculations to be done in the scope of the for loop's body.
But it is not properly a generator.
Note: in OpenSCAD vector and list are synonyms for the data structure delimited by square brackets "[]". The term "array" is also synonymous, but is used more in other languages than this one.
Multiple Generator Expressions
[Note: Requires version 2019.05]
There is no restriction on the expressions used to build a list.
Specifically, for loop generators are effectively expressions that return a number of list elements and more than one may be used so long as they are separated from one another by a comma ",".
steps = 50;
points = [
// generating points in +Y
for (a = [0 : steps]) [ a, 10 * sin(a * 360 / steps) + 10 ],
// generating in -Y
for (a = [steps : -1 : 0]) [ a, 10 * cos(a * 360 / steps) - 20 ],
// also add three more points
[ 10, -3 ],
func_returning_list(a,b,true),
x+2*y
];
polygon(points);
For Element
The for element uses the same syntax the for loop statement.
At least one loop index variable must be named and they operate as local variables in the body the for loop.
The for loop may be nested in two ways, by setting more than one index, or by nesting them:
x = [ for( i=[1,2,3], j="acb") each [i,j] ]; y = [ for( i=[1,2,3] ) for( j=i ) each [i,j] ];
A range may be used to generate a sequential list:
list1 = [ for (i = [0 : 2 : 10]) i ];
echo(list1); // ECHO: [0, 2, 4, 6, 8, 10]
A range may be used to selectively extract items from string or vector:
str = "I1n2t3e4r5e6s7t8i9n0g";
list = [ for (i = [0 : 2 : len(str) - 1]) str[i] ];
echo( list );
// ECHO: ["I", "n", "t", "e", "r", "e", "s", "t", "i", "n", "g"]
To generate an irregular sequence of list values a commonly used tactic is to put the irregular items into a list that is then used to calculate the needed values:
function func(x) = x < 1 ? 0 : x + func(x - 1);
inputs= [1, 3, 5, 8];
output = [for(a = inputs) func(a) ];
echo(output); // ECHO: [1, 6, 15, 36]
Iterate over an existing list to output lengths of string elements:
friends = ["John", "Mary", "Alice", "Bob"];
list = [ for (i = friends) len(i)];
echo(list); // ECHO: [4, 4, 5, 3]
Generate a list by looping over the characters of a string. [Note: Requires version 2019.05]
echo([ for (c = "String") c ]);
// ECHO: ["S", "t", "r", "i", "n", "g"]
Multi-section For Loop
[Note: Requires version 2019.05]
This list generator expression follows the syntax of the C Language for loop in that it has three sections in is control structure:
- initialization
- comma separated list of assignment expressions, as
a=<expr> - ending condition expression
- a single expression resulting in a boolean. When the expression evaluates to
falsethe loop terminates. - increment section
- comma separated list of assignment expressions, as
a=<expr>where each variable in the initialization list is modified to the value wanted in the next iteration. A simple form of update is :a=a+1but any expression or function call may be used.
[for(a=0,i=12,exit=false ; a<10 || exit ; a=a+1, i=newIndex(i) ) exit = calc_an_element( a, i ) ]
There are some issues of scope to pay attention to:
x = [for(a=0,i=12,exit=false; a<10 && !exit ; a=a+1, i=+12, exit = a==5 ) a*i ]; echo(x);
returns x as this vector [0, 12, 24, 36, 48] as "exit" is made false at a==5.
But in this:
x = [for(a=0,i=12,exit=false; a<10 && !exit ; a=a+1, i=+12/*no exit*/ ) let(exit = a==5) a*i ]; echo(x);
the local variable, "exit" inside for(-*-) is not visible in the body of the loop, so the let statement is creating a completely new local called "exit".
The vector x is now ECHO: [0, 12, 24, 36, 48, 60, 72, 84, 96, 108]
Note: This form of the for loop is only for list initialization. It not be used in a for loop statement
Generate a vector of [X,Y] position vectors:
echo( [for (a = 0, b = 1;a < 5;a = a + 1, b = b + 2) [ a, b * b ] ] ); // ECHO: [[0, 1], [1, 9], [2, 25], [3, 49], [4, 81]]
Generate Fibonacci sequence
echo([for (a = 0, b = 1;a < 1000;x = a + b, a = b, b = x) a]); // ECHO: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987]
A useful tactic is to use the versatility of the multi-section for initializer to generate a vector of which only the last value is wanted. For instance, a loop can keep a rolling sum as it goes, resulting in a vector of sums. To obtain the cumulative sum of all the values:
function cumsum(v) = [for (a = v[0]-v[0], i = 0; i < len(v); a = a+v[i], i = i+1) a+v[i]]; echo(cumsum([1, 2, 3, 4])); //ECHO: [1, 3, 6, 10]
Changing this slightly:
function cumsum(v) =
let( sum=[for (a = v[0]-v[0], i = 0; i < len(v); a = a+v[i], i = i+1)
a+v[i]] )
sum[len(sum)-1]
;
echo( cumsum([10,20,30]) ); //ECHO: 60
the accumulated value we want is in the last element of the vector so we return that and discard the temporary vector.
A recursive function may be written to process a vector in the same way:
function f(a, b, ...) =
condition ?
concat([expr], f(nexta, nextb, ...))
: [];
f(inita, initb, ...)
The concat() function is needed to join the element [expr] with the rest of the vector that will be returned from the function f.
Each Element
[Note: Requires version 2019.05]
The each element acts to "flatten" the generator that follows it to simplify the resulting vector.
Generate a nested list of vectors:
echo( [ for (a = [1 : 4]) [a, a*a] ] ); // ECHO: [[1, 1], [2, 4], [3, 9], [4, 16]]
Using "each" produces a flat list by extracting the values of the inner vector and adding them to the resulting vector directly:
echo([ for (a = [1 : 4]) each [a, a*a] ]); // ECHO: [1, 1, 2, 4, 3, 9, 4, 16]
Each just needs to be in front of the vectors to be simplified:
echo([ each for (a = [1 : 4]) [a, a*a] ]); // ECHO: [1, 1, 2, 4, 3, 9, 4, 16]
Remembering that each is a generator just like the for loop variants:
echo( [each [1,2,3,4], [1,2,3,4] ] ); // ECHO: [1, 2, 3, 4, [1, 2, 3, 4]]
echo( [each [1,2,3,4], each[1,2,3,4] ] ); // ECHO: [1, 2, 3, 4, 1, 2, 3, 4]
echo( [each [ [1,2,3,4], [1,2,3,4] ] ] ); // ECHO: [[1, 2, 3, 4], [1, 2, 3, 4]]
each can also be applied to a range to expand the sequence and insert its values as list element:
echo( [each [1:2:5]] ); //ECHO: [1,3,5]
or
echo( [each [6:-2:0]] ); //ECHO: [6, 4, 2, 0]
if Element
The if element may be used to check if a calculated result should be added to the list or not. In obvious use case is filtering unwanted items from a list.
[ for() if (condition()) i ] : When the condition is true, the i is added to the list.
list = [ for (a = [ 1 : 8 ]) if (a % 2 == 0) a ];
echo(list); // ECHO: [2, 4, 6, 8]
The if element must follow the closing parenthesis of for(), with the exception that a let() statement may be inserted to define some locals.
list = [-10:5]; x=[for(n=list) let(y=2) if(n%2==0 || n>=0) n%2==0 ? n/2 : n*y ]; echo(x);
Giving
ECHO: [-5, -4, -3, -2, -1, 0, 2, 1, 6, 2, 10]
if-else Element
[Note: Requires version 2019.05] This element adds the possibility for different expressions on each side of a decision.
[ for (i = list) if (condition(i)) x else y ]
When the condition is true, x is added else y is.
A more complex example will make a list where even numbers are halved, positive odd numbers are preserved, negative odd numbers are eliminated:
echo([for (a = [-3:5])
if (a % 2 == 0)
[a, a/2]
else if (a > 0)
[a, a]
]);
// ECHO: [[-2, -1], [0, 0], [1, 1], [2, 1], [3, 3], [4, 2], [5, 5]];
Note that if-else may not be nested using braces but if we note that parentheses may be used to control order of evaluation. In this example even numbers are dropped, and multiples of 4 are substituted by -1
echo([for(i=[0:10]) if(i%2==0) (if(i%4==0) -1 ) else i]);
// ECHO: [-1, 1, 3, -1, 5, 7, -1, 9]
Alternatively; in this one odd numbers are dropped, multiples of 4 are substituted by -1
echo([for(i=[0:10]) if(i%2==0) if(i%4==0) -1 else i]);
// ECHO: [-1, 2, -1, 6, -1, 10]
let Statement
The elements in a let statement is a comma separated list of assignment expressions that define local variables that will only be visible in the body of the for loop.
[ for (i = list) let( <assignment>*, ) i ]
list = [ for (a = [ 1 : 4 ]) let (b = a*a, c = 2 * b) [ a, b, c ] ];
echo(list); // ECHO: [[1, 1, 2], [2, 4, 8], [3, 9, 18], [4, 16, 32]]
Nested loops
The flexibility of the looping generators allow for a variety of methods for generating vectors.
Using multiple loop variables:
flat_result1 = [ for (a = [ 0 : 2 ], b = [ 0 : 2 ])
a == b ? 1 : 0
];
echo(flat_result1); // ECHO: [1, 0, 0, 0, 1, 0, 0, 0, 1]
Nesting for loop elements:
flat_result2 = [ for (a = [ 0 : 2 ])
for (b = [0 : 2]) a == b ? 1 : 0
];
echo(flat_result2); // ECHO: [1, 0, 0, 0, 1, 0, 0, 0, 1]
Nested loops can generate matricies:
identity_matrix = [ for (a = [ 0 : 2 ])
[ for (b = [ 0 : 2 ]) a == b ? 1 : 0 ]
];
echo(identity_matrix); // ECHO: [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
Advanced Examples
This chapter lists some advanced examples, useful idioms and use-cases for the list comprehension syntax.
Generating vertices for a polygon

Using list comprehension, a parametric equation can be calculated at a number of points to approximate many curves, such as the following example for an ellipse (using polygon()):
sma = 20; // semi-minor axis
smb = 30; // semi-major axis
polygon(
[ for (a = [0 : 5 : 355])
[ sma * sin(a), smb * cos(a) ]
]
);
Flattening a nested vector
Defining a function to perform a task on a vector is a good way to document the calculation being performed. It is also then possible to use it to define more complex procedures with a reduced risk of introducing errors as complexity increases.
Here we take two lists and join them together:
function flatten(l) = [ for (a = l) for (b = a) b ] ;
nested_list = [ [ 1, 2, 3 ], [ 4, 5, 6 ] ];
echo(flatten(nested_list)); // ECHO: [1, 2, 3, 4, 5, 6]
The nested for loops unpack the two as the output list is processed.
The following example on concatenating lists shows a number of other way the same operation may be done.
Quicksort Using Vectors
The Quicksort algorithm may be implemented using for() and if() generators along let() and recursion:
Given as list of items that have a sorting order this function returns the same list sorted in ascending order:
function quicksort( list ) =
len(arr) <= 0 ? [] : // nothing to sort, return empty list
let(
pivot = arr[floor(len(arr)/2)],
lesser = [ for (y = arr) if (y < pivot) y ],
equal = [ for (y = arr) if (y == pivot) y ],
greater = [ for (y = arr) if (y > pivot) y ]
)
concat( quicksort(lesser), equal, quicksort(greater) )
;
// use seed in rands() to get reproducible results
unsorted = [for (a = rands(0, 10, 6, 3)) ceil(a)];
echo(unsorted); // ECHO: [6, 1, 8, 9, 3, 2]
echo(quicksort(unsorted)); // ECHO: [1, 2, 3, 6, 8, 9]
unsorted = "aqasdf19edkdz0adfga.";
echo(quicksort(unsorted));
// ECHO: [".", "0", "1", "9", "a", "a", "a", "a", "d", "d", "d", "d", "e", "f", "f", "g", "k", "q", "s", "z"]
The input list is broken into three parts, based on the pivot, the lesser, equal, and greater.
The if() filter leaves out the unwanted elements as each of the three are processed, and the result is assembled by concat().
The calls to the quicksort() function inside the concat() are the recursion elements.
Selecting elements of a vector
This user-defined select() function may be used on a given vector to extract items .. selectively.
function select(vector, indices) = [ for (index = indices) vector[index] ];
vector1 = [[0,0],[1,1],[2,2],[3,3],[4,4]];
selector1 = [4,0,3];
vector2 = select(vector1,selector1); // [[4, 4], [0, 0], [3, 3]]
vector3 = select(vector1,[0,2,4,4,2,0]);// [[0, 0], [2, 2], [4, 4],[4, 4], [2, 2], [0, 0]]
// a range also works as indices
vector4 = select(vector1, [4:-1:0]); // [[4, 4], [3, 3], [2, 2], [1, 1], [0, 0]]
Concatenating two vectors
Concatenating two OpenSCAD lists, [1,2,3] and [4,5]
function cat(L1, L2) = [for (i=[0:len(L1)+len(L2)-1])
i < len(L1)? L1[i] : L2[i-len(L1)]] ;
echo(cat([1,2,3],[4,5])); //ECHO: [1, 2, 3, 4, 5]
But the same results may be achieved using only concat() or each:
echo( concat( [1,2,3],[4,5]) ); // ditto echo( [each [1,2,3], each [4,5]]); // ditto
The list based forms also work. Note the use of the temporary L variable that concatenates the L1 and L2 vectors.
This only works inside the generator for() parentheses.
Also note that second local "a" is necessary to unpack the joined vectors:
function cat(L1, L2) = [for(L=[L1, L2], a=L) a];
This construction has the same effect as using each:
function cat3(L1, L2) = [for(L=[ each L1, each L2] ) L];