Procedural generation of organic shapes using Bézier curves

OpenSCAD for the win! Even if it means I should implement Bézier curves from scratch.
Table of Contents

Preface

A while ago I got an idea to create some «organic» shapes for 3D-printing. I learned a bunch of interesting stuff trying to do that. This post captures the essence of my experience.

The post starts with an introduction to the tools I am using. Then I present the concept of Bézier curves. I provide a bunch of illustrations to make it more intuitive and fun. After we are familiar with both the tools and the curves, I wrap up by showing how it works in practice.

The resulting model to be 3D-printed

The resulting model to be 3D-printed

The result of 3D-printing said model

The result of 3D-printing said model

The post contains some maths, but understanding them should not be necessary for enjoying the content.

The tools

This project is something that I didn’t want to try and do using Autodesk Fusion, which I used for Hovert60 keyboard development. I also didn’t want to boot into Windows to use Fusion every time 😅

Print-in-place mail made in Blender using an Array Modifier applied to an expanded NURBS curve, and flattened with a Boolean Modifier for better bed adhesion

Print-in-place mail made in Blender using an Array Modifier applied to an expanded NURBS curve, and flattened with a Boolean Modifier for better bed adhesion

After some experiments with Blender and even with writing some custom G-code for 3D-printing, I stumbled upon a tool called OpenSCAD.

What is OpenSCAD?

OpenSCAD is a CAD program that allows modeling complex solid objects by combining simpler objects in various ways. Supported operations include:

Unlike in many other tools, models in OpenSCAD are created by writing code rather than by interacting with a GUI. I think this approach unlocks several benefits.

For instance, my existing software development skills translate to OpenSCAD nicely, as it allows storing the code for my models in Git and provides capabilities for code reuse. It removes the need to learn a complex set of keyboard shortcuts and menu items, instead providing a simple API for which it has a cheatsheet.

But OpenSCAD also has a bunch of drawbacks compared to other CAD software. It does not use the concept of constraints, and the user has to figure out the formulas even for simple things. The simplicity of the API I mentioned requires a high degree of creativity from the user when making advanced designs. As a result, creating complex models can get overwhelming.

In their work, software developers can spend some extra effort on making their code more «future-proof». The code could be organised more neatly, variables and functions could be named appropriately and so on. When working with OpenSCAD, you end up having to do the same. But organizing code and thinking of right names for parts of your designs can be quite hard.

I think these limitations of OpenSCAD pose an interesting challenge for its users. I can’t say if they make OpenSCAD less suited for professional work, but they definitely can make it more enjoyable.

Bézier curves

When I started, I didn’t know what I want to achieve, but I knew I wanted to use Bézier curves for this small project. You might have encountered these curves in programs like Inkscape or Adobe Illustrator. There are 3 ways to use them in OpenSCAD:

To implement the curves, I needed to revisit some theory, which I will explain here. Feel free to skip the maths – I think the pictures with their flavour text should be enough to get the basic idea.

The Basics

Before we can talk talk about advanced stuff, I should make sure we are on the same page with regards to the basics. I will be very brief. There are two very simple concepts that serve as a foundation for Bézier curves. They are:

In this post, we are working on a Cartesian plane. That implies:

You might have learned about vectors in school. For this post, what matters is:

Examples of operations on vectors. Left: two vectors. Middle-Top: difference of two vectors. Middle-Bottom: multiplying vectors by scalars. Right-Top: sum of two vectors. Right-Bottom: length of a vector.

Examples of operations on vectors. Left: two vectors. Middle-Top: difference of two vectors. Middle-Bottom: multiplying vectors by scalars. Right-Top: sum of two vectors. Right-Bottom: length of a vector.

Let’s look at the examples. Given two vectors A\vec{A} and B\vec{B},

A=[24],  B=[52] \vec{A} = \begin{bmatrix} 2 \\ 4 \end{bmatrix}, \; \vec{B} = \begin{bmatrix} 5 \\ 2 \end{bmatrix}

Their sum is:

D=A+B=[2+54+2]=[76] \vec{D} = \vec{A} + \vec{B} = \begin{bmatrix} 2 + 5 \\ 4 + 2 \end{bmatrix} = \begin{bmatrix} 7 \\ 6 \end{bmatrix}

Their difference is:

C=BA=[5224]=[32] \vec{C} = \vec{B} - \vec{A} = \begin{bmatrix} 5 - 2 \\ 2 - 4 \end{bmatrix} = \begin{bmatrix} 3 \\ -2 \end{bmatrix}

Multiplying them by a scalar:

32A=[322324]=[36]12B=[125122]=[2.51] \begin{align} \frac{3}{2} \vec{A} &= \begin{bmatrix} \frac{3}{2} \cdot 2 \\ \frac{3}{2} \cdot 4 \end{bmatrix} = \begin{bmatrix} 3 \\ 6 \end{bmatrix} \\ \frac{1}{2} \vec{B} &= \begin{bmatrix} \frac{1}{2} \cdot 5 \\ \frac{1}{2} \cdot 2 \end{bmatrix} = \begin{bmatrix} 2.5 \\ 1 \end{bmatrix} \\ \end{align}

For a different vector A\vec{A},

A=[43] \vec{A} = \begin{bmatrix} 4 \\ 3 \end{bmatrix}

It’s length A\Vert A \Vert can be calculated by the Pythagorean theorem:

A=42+32=16+9=25=5 \Vert \vec{A} \Vert = \sqrt{4^2 + 3^2} = \sqrt{16 + 9} = \sqrt{25} = 5

Now, the remaining piece of the puzzle is a function called lerplerp which stands for Linear interpolation. This function receives 3 values: 2 values v0v_0 and v1v_1 to interpolate between, and a parameter t[0,1]t \in [0, 1]. v0v_0 and v1v_1 can be real numbers, but they can also be vectors (with the same dimensionality).

lerplerp can be defined like this:

lerp(v0,v1,t):=(1t)v0+tv1 lerp(v_0, v_1, t) := (1 - t) v_0 + t v_1

Note the values of the function for tt at the ends of the [0,1][0, 1] interval:

lerp(v0,v1,0)=v0lerp(v0,v1,1)=v1 \begin{align} lerp(v_0, v_1, 0) &= v_0 \\ lerp(v_0, v_1, 1) &= v_1 \\ \end{align}

Now, if we calculate lerp(A,B,t)lerp(\vec{A}, \vec{B}, t) for all t[0,1]t \in [0, 1] for some fixed A\vec{A} and B\vec{B}, we get a set of points that live on the line segment connecting AA and BB.

Quadratic Bézier curves

Cubic Bézier curves

Derivative of a Bézier curve

Stroke expansion

Putting it all together

Results