These slides are for a talk I'm giving (or possibly have given) at Munihac 2025.
You can see the slides in "fullscreen" format.
As well as this version, with speaker notes and controls.
Hi my name’s Joe.
I’d like to talk to you about how I use Haskell for 3d printing.
In my opinion, the story of 3d printing really starts in 2009.
This is when a patent expires, which was held by a company called Stratasys.
The patent’s for a process called:
Fused Deposition Modeling (trademarked term)
Filament Freeform Fabrication (generic term)
Manufacturing technique used by 3d printers
Founded By Adrian Bowyer at the University of Bath.
Aiming to make a 3d Printer that can be used to manufacture other 3d Printers.
Anticipated the patent expiration 3 years later.
RepRap is an open hardware project, and this leads to a boom of open source 3d printer designs.
Ten years after the RepRap project was founded, I buy a 3d printer.
This is a Prusa i3 mk2, which is a variation on the RepRap design.
I think It’s easier to show how FDM printing works than to describe it.
There’s a spool of filament, this is melted and fed through a print head.
The print head can move in three axes.
It traces a path, forming a layer, which it’ll go on to print more layers on top of.
In this way it builds up a 3d object.
When you’re preparing a 3d print, you generally work with two different types of data before you get to a solid object.
You have the path that the print head traces out, and this is stored in a format called GCode.
GCode is generally generated from a 3d model using a program called a Slicer.
A slicer generally reads 3d geometry in a mesh format, like a list of triangles, often in a format called STL.
This format is also used in 3d computer graphics.
The rest of this talk is going to deal entirely with stuff that’s to the left of this diagram.
I’m pretending “how do I take a 3d file, slice it, and then print it” as a solved problem.
Instead, I’m focusing on how you come up with the mesh at the start of this process.
In the year 2010, one year after the Stratasys patent expires, a program called OpenSCAD is released.
Started by a developer called Marius Kintel.
This is described as “The Programmers Solid 3D CAD Modeller”.
It’s a DSL for designing 3d printable objects.
Modeling in OpenSCAD is largely based around a concept called Constructive Solid Geometry or CSG.
CSG involves taking primitive shapes, like spheres, cylinders and
cubes, transforming them into a position, and then combining them with
boolean operators, like intersection, union,
and difference (or subtraction).
With these relatively simple primitive operations, you can build up quite complicated forms.
This example’s pulled from Wikipedia, and often used to demonstrate CSG modeling
It’s taking the intersection of a cube and a sphere, to make a rounded cube.
Combining cylinders that have been rotated into different axes, union-ing them to form a cross.
Then it’s subtracting the cross from the rounded cube to form this final shape.
CSG approaches are also used in some graphics frameworks (such as raytracers).
This is what the code for that CSG example object looks like in OpenSCAD.
You can hopefully see the relationship between this code and the CSG object:
We have spheres, cubes and cylinders, as well as rotations, and intersections, unions and differences.
And this is what OpenSCAD gives you if you run that code.
I think it’s good to be a little careful about voicing criticisms of other peoples work, especially open source work.
And I would like to acknowledge that OpenSCAD invented the whole category of Programmable CAD framework.
With that said, I’m going to be talking about why I prefer to do Programmable CAD without OpenSCAD, which implies criticising it a bit.
The best way I think I can explain my feelings about OpenSCAD is to show a quote from the Author.
I think of this first quote as “Rob Pike coded”
(When he said “They’re not capable of understanding a brilliant language”, about Go developers)
I’m very sympathetic to the line about backwards compatibility. But also, breaking changes in APIs are something we as a profession have ways to deal with. You don’t need every program written with a tool to work with every version.
It’s not that there’s not a place for OpenSCAD, but there’s also room for tools with a different design philosophy.
I also want to get in a classic OpenSCAD bit of weirdness.
This code, from an old version the official OpenSCAD user manual prints 5 twice.
The user manual motivated this by saying OpenSCAD is a functional language.
They’ve cleaned up this documentation since, but this is still weird.
That example was borrowed from a talk by Matt Adereth, given in 2015.
The talk is about designing a keyboard, called a Dactyl, using OpenSCAD code generated with Clojure.
A relatively popular to deal with OpenSCAD language weirdness is by doing metaprogramming: writing programs in a different language, and having these programs produce OpenSCAD code.
I use the keyboard from that talk: this is my dactyl.
Matt correctly identifies a number of issues with OpenSCAD, which are more subtle than just “it lets you declare values multiple times”. The key one being that objects in OpenSCAD aren’t really “first class”: you can’t write higher order functions over objects.
I don’t think he identifies every issue with OpenSCAD.
A limitation of OpenSCAD is that it has a two pass execution model.
It evaluates the code, and builds up a tree of CSG operations, and only once it’s built that tree does it do a second pass to produce geometry.
The result of this, is that you can’t get data out of objects at all, you can’t have a function that takes an object, and returns it’s volume.
This limits the kinds of program you can write: you can’t say generate this object, and keep transforming it in a certain way until it’s above a specific size.
Notably, you can’t meta-program your way out of the issues with the execution model.
At some point in 2017, I learn Haskell, and, for a range of reasons, some good, some not so good, decide that Haskell would be a good programming language in which to do programmable CAD.
In 2018, I decide to write a Constructive Solid Modeling library, in Haskell.
A couple of years later, I give a talk on this, at a conference called Lambdale in London.
This is still on YouTube.
It mostly works as an “Algorithms” talk, going into how you can use BSP trees to represent Solid objects.
This is what that example object looks like under that framework.
It’s relatively similar to the OpenSCAD example, but it also demonstrates things like using folds.
And this is the output of running the code on the previous slide.
It looks pretty similar to the output of the OpenSCAD code.
But this library is far from perfect.
There are superficial issues with it, like using the word
BspTree for the core “Solid Object” type, when this is
really an implementation detail.
But there’s also one big fundamental issue with it.
The issue with the library, is that for the core data structure, I didn’t choose to represent meshes with Polygon’s, I use triangles.
When the library combines shapes,
If you take like one face, it’s very easy to split that up into more triangles.
But it’s harder to combine those triangles and reduce the number of triangles that are in your object.
So I just don’t do this, and I write a CSG library where every CSG operation you do exponentially increases the number of triangles in your object.
This issue with the number of triangles isn’t a complete show stopper.
So, at this point, I’m using this CSG framework as my go-to way to design for 3d printing.
I do design some relatively complicated things in it, like this robot arm.
But it’s bad enough that it does stop me from recommending it to people.
I never for instance upload the library to Hackage.
While slicers use mesh data, and so does OpenSCAD, traditional CAD frameworks don’t generally Mesh data directly.
Instead, they use “Non-Uniform Rational B-Splines” (NURBS) Modeling.
Curved surfaces, similar to Bezier curves.
Using NURBS means you can manipulate solids without loss of resolution.
Around eight hundred thousand lines of C++
Matra Datavision started development in 1991, were bought out by Dassault Systèmes (3DS) in ’98. OpenCascade itself is open-sourced in 1999.
Company called Open Cascade SAS is founded to generate profit from Open Cascade. Acquired and passed around a series of French engineering consultancies. Eventually wind out owned by Capgemini.
All this detail is intended to give you an impression of the kind of library we’re talking about.
Enterprisey, but powerful in the sense that it contains a lot of “stuff”.
This is a screenshot of an application called FreeCAD.
FreeCAD is a graphical CAD tool, built using OpenCascade as a CAD kernel.
Development on FreeCAD started in 2001, not long after OpenCascade was open-sourced.
FreeCAD’s used by, amongst other people, the European Space Agency.
I have this idea that I could build a Haskell 3d modeling library, which uses OpenCASCADE under the hood.
In 2023, I do this, and call it Waterfall-CAD.
This is the code for the CSG example in Waterfall CAD.
It’s not massively different from the old framework.
Probably the main thing that might jump out at you is that I’m using
the Linear linear algebra library rather than using tuples
to represent vectors.
And this is what the CSG example generated with Waterfall-CAD looks like
I want to talk about Monoid instances briefly.
For Solids to have a semigroup, there needs to be an associative way of combining them.
Lets say we have a cube and a sphere, how can we combine these associatively.
I think there are two main contenders, union and
intersection.
I’ve settled on a default instance where mconcat is
union.
The reason for that is that it’s much more common to want to overlay the geometry in two shapes than it is to want to compute the intersection.
It’s also the case that we don’t exactly have an empty value for
intersection.
Boundary representations are generally quite bad at modeling infinitely large shapes.
We do have an instance for the Lattice typeclass from
the lattices package, which gives us a newtype wrapper with
the intersection monoid which is called
Meet.
This all falls out of the fact that in Constructive Solid Geometry,
Solids are manipulated with a Boolean Algebra, hence they have a
Lattice.
I’ve talked a lot about Constructive Solid Geometry.
But a big advantage of hooking into an existing CAD Kernel is that you get a lot of other modeling operations effectively for free.
These are generally concepts from traditional CAD, such as Fillets and Chamfers, or Lofting, which is a technique from boat building.
I’m not going to talk in detail about all of the library features.
But I did want to show some more example code.
This is a solid of revolution.
There are a lot of lines of code here, but the structure’s relatively simple.
It’s defining a path, and constructing a solid of revolution from that path.
This is the result of that code.
It’s a chess piece (a pawn).
Having designed a single chess piece for the example code, I thought I’d finish the chess set.
I’ve got a blog post about the design of this chess set, which is linked from the slides.
But in short, I think designing a chess set is nice because it shows off how one of the strengths of programmable CAD.
Which is parametricity.
There’s this concept in CAD of parametric designs, which are designs that have a number of variables, that can be varied to produce subtly different objects.
And in programmable CAD, you get this more or less for free.
I’m going to go on a bit of a tangent now.
I’m a Haskell developer, I live in London, and to the best of my knowledge London doesn’t have much of a Haskell meetup scene anymore.
So sometimes, I’ll go to a meetup called “London Scala”, because that’s one way to meet people who care about Functional Programming.
At one meetup, the meetup organiser had a 3d Printed Scala Logo, that she’d got from a conference.
This was made by a Mikołaj Wilczek.
Now, this is a really nice object, nicer print quality than I get on a ten year old printer.
But it struck me that since the Scala logo is a 3D object.
If you’re going to print a 2D representation of it, you’re missing out.
So that evening, I went home, and modeled the Scala logo in 3D.
I may be a Haskell developer, but the company I work for is largely a Scala Shop.
And it was winter when I made this, so, I printed a bunch of “Scala Logo Tree Ornaments”.
But I’m still a Haskell Developer, so I had to print some Haskell ornaments too.
And at this point I got carried away, and do 14 other programming language logos.
I’m a bit of a hypocrite.
Because, earlier when I was showing off the Scala logo, I said it doesn’t make sense to 3d print a 2D image.
But partway through this process, which is to find an SVG file of the logo.
Convert the paths in the SVG file into the Haskell path DSL by hand.
And then figure out how best to glue the resulting shapes together into an ornament.
While doing this, I got fed up converting SVG path data.
So I find the svg-tree library on Hackage, and write a
wrapper library to import SVG files.
And since I was doing SVG import, I also added SVG export.
I’m going to run through the alternatives to Waterfall CAD: maybe I’ve convinced you about programmable CAD, but not about my implementation of it.
OpenSCAD, we’ve covered.
scad-clj is the clojure library used by Matt Adereth to design a keyboard.
csg.js is a JavaScript library, which exposes a very similar set of functionality to OpenSCAD, but without the DSL.
CadQuery is similar to Waterfall CAD, in that it’s a DSL wrapping OpenCASCADE, but in Python.
I think it’s noteworthy that people have done similar things in different languages.
Zoo.dev, formally known as KittyCAD, is a commercial programmable CAD framework, with their own CAD kernel written in Rust, and their own DSL called KCL.
It’s interesting to me that there are funded companies working in the Programmable CAD space.
Unfortunately, I’m not personally keen to build a workflow around a proprietary language.
ImplicitCAD, is a Haskell library, which contains a Haskell implementation of parts of OpenSCAD.
Writen by Christopher Olah, maintained by Julia Longtin.
It’s significantly older than Waterfall CAD.
I bounced off this, because the docs push you to use the OpenSCAD implementation.
It is possible to use just the Haskell API to ImplicitCAD.
ImplicitCAD makes very different design tradeoffs to Waterfall-CAD.