OpenSCAD is an incredibly powerful tool for generating functional 3D models by using code instead of a visual interface. The parts you create can be exported to STL files and are immediately ready-to-go for 3D Printing. (You can download OpenSCAD here!)

As part of an experimental feature in OpenSCAD (available in the snapshot builds since October 2019), it is possible to pass higher-order functions as parameters. In this post, I'll explain how I used higher-order functions to create parametric polygons and curvature! My interest in this topic was specifically to model the involute angles in a gear. But you can use this technique for anything!

What is a parametric curve?

Imagine you have a mathematical function which describes some kind of curve. For example: $$ -2x^2 + 2 $$ you can plot this expression in a graphing calculator and perhaps see the enviable arc you're after

but how can you get that shape added to some OpenSCAD part? How about doing it in a way where you can adjust the curvature simply by altering the formula? The ability to change the shape easily by adjusting a mathematical formula and seeing the resulting output in OpenSCAD is what I mean by parametric curve.

The first order of business to discuss is, er, higher order functions.

Higher order functions

Simply put, a higher order function is just a bit of code which takes any other function as a parameter. Here's a very basic example. Let's say we want to write a test which outputs the value of any mathematical function provided to it, evaluated with the input of 5.

We can express the test harness with this line of code:

function evaluate_method(fn) = fn(5);

evaluate_method will take a function as a parameter, and return the result of that function when it is passed the number 5 as an argument.

To see this concept in action, we can simply pass in different mathematical expressions to it and echo the output. For example; to evaluate an expression like this:

$$ f(x) = x^2 $$

We can run the following code:

func1 = function(x) x * x;
echo(evaluate_method(func1));

# output is 25

Similarly, if we want to evaluate this different expression:

$$ f(x) = x^3 $$

The only thing that needs to change is the argument being passed into evaluate_method()

func2 = function(x) x * x * x;
echo(evaluate_method(func2));

# output is 125

In those examples, we're passing in an actual function as the argument instead of a number. The evaluate_method is able to run any function we give it, provided the expected parameters are what we think they are. This is really cool and a very powerful mechanism! With this language feature, we'll be able to accept curve functions as parameters.

Among other things, using higher order functions in this way can help:

  • Make our code reusable
  • Allow us to swap out the definition of our geometry at a later date
  • Make dynamic adjustments without changing the primary bits of code

Interpolation

If you've ever used excel, you may have run into the concept of interpolation. Filling out a few cells of data and then letting the application "populate the rest" is interpolation. We will be using a similar concept to map over the parametric functions and evaluate them at various points in time.

That rather confusing statement can be distilled into this block of code:

function parametric_interpolation(fn, t0, t1, delta) = [for(i = [t0:delta:t1]) fn(i)];

Let's unpack this... We're defining a new function called parametric_interpolation which accepts the following arguments:

  • higher-order method representing a mathematical expression
  • starting value
  • ending value
  • a delta

Given those inputs, the function will begin passing values to the function, starting at t0 and incrementing by delta each time until it reaches t1. It will collect all the values and return an array containing each output. Here's a table to further describe what is happening:

f(x) t delta output
2x 0 1 0
2x 1 1 2
2x 2 1 4

In the table above, t is increasing by delta=1 each iteration, and then calling the function f(x) and evaluating the output. In OpenSCAD, this would result in an array of values.

This is the secret sauce which makes higher-order functions super useful. But it's important to note that the code above is still incomplete. It's just evaluating a single function. To achieve total control for our parametric curve, we'll need to specify a mathematic expression for both f(x) and f(y)

Putting it all together

In order to leverage higher-order functions to specify separate expressions for both an x and y, we'll need to write a method which takes an f(x) function, f(y) function, t0, t1, and delta. And then uses that information to generate a tuple of points. Here's an example:

function parametric_points(fx, fy, t0=0, t1=10, delta=0.01)
= [for(i = [t0:delta:t1]) [fx(i), fy(i)]];

This function looks almost identical to the previous one, with the added feature of returning two values instead of 1. The X position and the Y position. Now we have something to work with.

Going back to our earlier example

$$ -2x^2 + 2 $$

Is particularly easy to incorporate by defining these math functions:

// this represents time (t). We don't need anything fancy, so we can keep this an identity fn
x = function(t) t;

// this is simply the mathematical expression in code form
y = function(t) -2 * pow(t, 2) + 2;

/*  And lastly, to give our shape a bit of width when it is extruded, I've duplicated the f(y)
    method but added a small offset, which is used in conjunction with the polygon module to
    create a thickness of 0.25mm */
y2 = function(t) y(t) + .25;

Next we just need to pass those functions into our points plotter, and then use the resulting dataset with the OpenSCAD polygon module.

points_1 = parametric_points(fx=x, fy=y, t0=-1, t1=1);
points_2 = parametric_points(fx=x, fy=y2,t0=-1, t1=1);

color("lime")
linear_extrude(2)
union() {
    polygon(
        concat(
            points_1,
            points_2
        )
    );

    // Add another shape to the screen for fun
    translate([0,0 + 0.1,0])
    square(center=true, [2,1]);
}

The full source code can be viewed on (github)[https://gist.github.com/SharpCoder/3c63781813ece0c1f139a414d33b1865].

Conclusion

With this code, we've defined a generic and reusable mechanism for specifying curvature with mathematical expressions and brought the resulting shapes alive with OpenSCAD. That's all I've got today. Feel free to reach out to me on twitter @inventor_josh if you have any questions or feedback.