Build an OpenSCAD WASM Configurator

I like building 3D models with OpenSCAD. As a software developer, I find it the most intuitive and scalable way to build models. Sites like Thingiverse looked like the solution but I was looking for something that I could put on my website, that would have zero cost, and where I could fully customize the user experience. So, I got to work and after a few months of hard effort I created this: DSchroer/openscad-wasm. A WebAssembly port of OpenSCAD that can be run in any modern browser. With this port, anyone can package and share OpenSCAD projects on the internet.

In this post I will be going through an example of how to build a configurator where anyone can customize the 3D models that you design.

The Model

The first thing you need is a SCAD model. For example here is one with a few variables. It generates a 3D gear using four variables to control the pitch (density of teeth), number of teeth, thickness of the gear and size of the center bore hole. I am using the MCAD library to generate everything.

use <MCAD/involute_gears.scad>;

PITCH=2;
TEETH=12;
THICKNESS=3;
BORE_DIAMETER=2;

gear(circular_pitch=PITCH, gear_thickness=THICKNESS, number_of_teeth=TEETH, bore_diameter=BORE_DIAMETER, hub_diameter=BORE_DIAMETER+2);

If you wanted a gear with 100 teeth, running the following command line code on your computer would generate a STL file containing such a gear.

openscad ./gear.scad -o out.stl -DTEETH=100

This process of setting variables in the model is the exact same approach that the configurator on this site will use.

The UI

Now we need to make things more user friendly. Command line is great for programmers but is scary for most people. Instead lets use some web development skills and build a form using HTML.

This is the same kind of form as you have seen all over the internet, there is one input for each of the variables in our SCAD file. All of the inputs have default values. At the bottom of the form there is a button that the user can click when they are finished customizing the gear.

<form id="configurator">
<div>
<label for="pitch">Pitch:</label>
<input id="pitch" name="pitch" type="number" value="2" />
</div>

<div>
<label for="teeth"># Of Teeth:</label>
<input id="teeth" name="teeth" type="number" value="12" />
</div>

<div>
<label for="thickness">Thickness:</label>
<input id="thickness" name="thickness" type="number" value="3" />
</div>

<div>
<label for="bore-diameter">Bore Diameter:</label>
<input id="bore-diameter" name="bore-diameter" type="number" value="2" />
</div>

<button id="generate" type="submit">Generate STL</button>
</form>

Hook this form up in any website and it will look seamless. The user wont even know that they are editing OpenSCAD models unless you tell them.

The Worker

Now comes the hard part. We need a way to run OpenSCAD when the user clicks the generate button. We could run it in the page but that would freeze the browser while the model generates. Having a frozen browser sucks. Luckily there is a tool in browsers called web workers that we can use to avoid the freezing.

Web workers are like background tasks for websites. By running our code in a worker, it can take as much time as it wants while the site stays fast and the user can keep browsing.

Here lets make a file called openscad.worker.js. The worker code waits for a message to be sent to it from the webpage, then it will run the exact same command line we used as the example above. When it is finished generating the 3D file, it will pass the data back to the webpage.

import { addMCAD } from "./openscad.mcad.js";

onmessage = async (e) => {
globalThis.OpenSCAD = {
noInitialRun: true,
};
importScripts("./openscad.wasm.js");
await new Promise((resolve) => {
globalThis.OpenSCAD.onRuntimeInitialized = () => resolve();
});

const inst = globalThis.OpenSCAD;

addMCAD(inst);

const sourceRes = await fetch("./gear.scad");
inst.FS.writeFile("/source.scad", await sourceRes.text());

inst.callMain([
"/source.scad",
"-o",
"out.stl",
`-DPITCH=${e.data.pitch}`,
`-DTEETH=${e.data.teeth}`,
`-DTHICKNESS=${e.data.thickness}`,
`-DBORE_DIAMETER=${e.data.boreDiameter}`,
]);

postMessage(inst.FS.readFile("/out.stl"));
};

Next, put the worker code in a folder next to all the files downloaded from the releases page at DSchroer/openscad-wasm. Currently workers don’t support ES6 imports like we used on the first line. Use a bundler like Rollup or Webpack to combine all the imports. This will make sure that the worker will load safely.

Finally we need to tell the webpage how to talk to the worker. Add the code below to your page. It will create the worker and send it a message with all the variables from our UI. Then when we get results back from the worker we download them as a file to the users computer.

const worker = new Worker("./configurator.worker.js");
const values = new FormData(document.getElementById("configurator"));

worker.onmessage = (e) => {
downloadFile(e.data, "gear.stl"); // Do whatever you want with the model in e.data
worker.terminate();
};

worker.postMessage({
pitch: values.get("pitch"),
teeth: values.get("teeth"),
thickness: values.get("thickness"),
boreDiameter: values.get("bore-diameter"),
});

Bringing It All Together

Put all three pieces (the model, the UI and the worker) together into a website, add some CSS to make it look pretty, and publish it for the world to see. With these tools and a little bit of work, anyone can share their own 3D projects and let others customize them. You can even build more complex things such as 3D viewers and full CAD editors. My hope is that now that OpenSCAD can run in the browser it will convince more people to use it and help drive more CAD as Code projects.

Here is my example gear configurator:

If you want to make your own configurator, feel free to fork this project on GitHub as a base: DSchroer/openscad-gear-configurator. Also give it a star so I know you came from this post.

20/03/2022