I’m the author/maintainer of a Haskell library for programmable CAD. I mostly use this library for design for 3D printing. This is the story of how I came to add SVG1 support to it.
Back in October of last year, I was hanging out after a Scala meetup.
The meetup organizer had a 3D printed Scala logo, made by Mikołaj Wilczek, and given to her at a conference. This was a really nice object, a well executed multicolour print, with LED backlighting 2. But it did strike me that since the Scala logo is a 3D object3, using 3D printing, an inherently three-dimensional medium, to create a 2D representation was missing out on a trick.
That evening, I went home, and modeled the Scala logo in 3D4.
It was winter when I made this, Christmas was coming up, and I was keen to finish up the roll of red filament that I was using. So, I printed a bunch of “Scala Logo Tree Ornaments”.
The company I work at may be a Scala shop, but I’m a Haskell dev: so obviously I had to make some Haskell tree ornaments too.
At this point, it’s possible that I got carried away 5.
Some of these really lent themselves to being modeled in 3D. I’m particularly pleased with the Ruby one, implemented as a series of lofts.
A lot of them (for example the Haskell Logo), really just came down to drawing the logo as a 2D path, extruding and then adding a loop to pass string through.
I recognize the irony that earlier, I said it made more sense to 3D print a 3D Scala logo than a 2D one, and then went on to 3D print a number of two dimensional logos.
Waterfall-CAD already contained a path API that maps fairly cleanly to SVG path commands.
For a good few of the logos, my process was more or less:
- Find an SVG of the logo
- Hand convert the SVG path commands into Haskell code
- Extrude some combination of those paths into a shape
I pretty soon got fed up with the middle step of manually converting SVG path data,
and realized what I wanted was to be able to paste in SVG path strings, parse and generate Waterfall.Path2D
values in one function call, and use those as the basis for the ornaments.
I found svg-tree on Hackage, containing a pathParser
function.
From there, it was relatively straightforward to convert
an svg-tree PathCommand
into a Waterfall Path2D
6.
Once I had convertPathCommands
implemented, it wasn’t that much more effort to support converting a whole SVG,
so I decided I’d release this in a library called waterfall-cad-svg
7.
At this point, the sensible thing to do would have been to embrace the “Release Early, Release Often” philosophy, and upload the package to Hackage.
Unfortunately, I fell into the trap of thinking, since I’ve got a library that can load data out of an SVG, it shouldn’t be too difficult to write code to write data into an SVG.
FreeCAD (a popular open-source CAD program that uses the same kernel (OpenCASCADE) as Waterfall-CAD) contains code that does this, so I knew I’d be able to crib from that.
I was also aware that OpenCASCADE has a bunch of “Hidden Line Removal” functionality that I’d be able to use to convert 3D geometry into 2D paths that could be saved in an SVG, letting me produce SVG diagrams of 3D models.
Unlike with SVG reading, where I’d been motivated by the “tree-ornaments” project, I didn’t have anything pushing me to complete SVG writing, and as a result it wound out taking about three months.
It took me literally a month to figure out why this line was being drawn longer than it should have.
Eventually, I was able to iron out all the bugs, and get to a point where I was able to take the example images in the README, these had previously been screenshots taken of a mesh viewer, and have now been replaced with SVGs generated by waterfall-cad-svg.

I think that this is an improvement; it’s certainly satisfying to be able to generate the images from within the framework. But I could see how it could be argued that because they don’t contain shading, they’re not quite so obviously 3D.
I also revisited my chess set project and extended that to generate images of whole chess boards. I’m quite keen on the idea of getting one of these printed on a pen plotter, if I can work out a way to make that happen.
API docs for the new library are on Hackage. The source code is bundled with the rest of the Waterfall-CAD/OpenCASCADE-hs codebase on GitHub.
If you want to try running this yourself, you’ll need to install the OpenCASCADE libraries, there are instructions describing how to do this in the README.
If this article sparks your interest in Waterfall-CAD, or you want to try designing something in it yourself, there’s a Waterfall-CAD Discord server. Alternatively, if Discord isn’t your jam, feel free to reach out to me on Mastodon.
SVG (Scalable Vector Graphics) is a type of “vector image file”. “Vector” meaning that it stores precise information about the shapes in the image, as opposed to storing an array of pixel data. Because of this, vector images can be scaled up to any size without becoming pixilated.↩︎
The backlit Scala logo was made on a Prusa i3 mk4. I’m still printing on an nine year old mk2. This still runs fine, but it’s always a shock to see better print quality on newer printers and realize how far printer tech has come in the past decade.↩︎
It’s designed after a spiral staircase in EPFL, where Scala was originally developed.↩︎
Recognising the joke potential of having designed the Scala logo using a Haskell framework, I also did a version of the Haskell logo using Scala. Using the Doodle library by Noel Welsh.↩︎
Some path commands were easier to implement than others. For example “Smooth Curves” (
's'
/'S'
/'t'
/'T'
) require you to keep track of the last control point from the previous command. I didn’t get these working while I was designing the tree ornaments, since the vector graphics program I was using (Inkscape) didn’t seem to generate them, and only added support for them once I’d committed to releasing SVG support as a library.↩︎There’s an argument to be made that this code could live in the core Waterfall-CAD library. I didn’t add it there because I was reluctant to add a dependency on an SVG parser, and because I think splitting auxiliary code off into micro-libraries will be a useful pattern if I ever want to add GUI packages.↩︎