OpenSCAD: Tieing It Together With Hull()

What’s your favorite OpenSCAD command? Perhaps it’s intersection() or difference()? Or are you a polygon() and extrude() modeler? For me, the most useful, and maybe most often overlooked, function is hull(). Hull() does just what it says on the can — creates a convex hull around the objects that are passed to it as children — but that turns out to be invaluable.

Hull() solves a number of newbie problems: making things round and connecting things together. And with a little ingenuity, hull() can provide a nearly complete modelling strategy all on its own. If you use OpenSCAD and your creations end up with hard edges, or you spend too much time figuring out angles, or if you just want to experience another way to get the job done, read on!

The Rounded Box

There are multiple ways to make a rounded box. One is to draw a 3D box and minkowski() around it with an appropriate cylinder. Another method, that renders a lot faster, is to draw a 2D square, offset() it with rounded edges, and extrude this upwards. I’ve even seen people make rounding tools and difference them out of the model.

For me, the most intuitive method is to place the four cylinders that would be the box’s round edges, and connect them all together with hull(). If you’ve never really understood hull() before, this is a great place to start.

Hull() takes any number of objects and builds their convex hull; the action is like wrapping cling film tightly around the shapes and solidifying the result. To make the promised rounded box, take the hull() over four cylinders, one located at each of four edges. Not much more to say about it than that, right?

points = [ [0,0,0], [10,0,0], [0,10,0], [10,10,0] ];

module rounded_box(points, radius, height){
    hull(){
        for (p = points){
            translate(p) cylinder(r=radius, h=height);
        }
    }
}

Sure, there’s more! Rounded triangles, hexagons, heptagons, octagons, and other-gons. And there’s no reason to keep it regular either, just change the corner points. But perhaps the most useful simple application of hull() for me has been making rounded ends on plates, bars, or linkages.

module cylinders(points, diameter, thickness){
    for (p=points){
        translate(p) cylinder(d=diameter, h=thickness, center=true);
    }
}

module plate(points, diameter, thickness, hole_diameter){
    difference(){
        hull() cylinders(points, diameter, thickness);
        cylinders(points, hole_diameter, thickness+1);
    }
}

module bar(length, width, thickness, hole_diameter){
    plate([[0,0,0], [length,0,0]], width, thickness, hole_diameter);
}

Armed with bar() and plate(), and the dimensions of all the parts in a Jansen linkage, for instance, you’re most of the way to a printable Strandbeest. Notice how pleasant the workflow is: figure out where you need your holes, type them into a vector of points, and OpenSCAD takes care of the rest. Thanks, hull()!



Joining Objects Together

But wait, there’s more! In the bars-and-plates example above, all points are constrained to a plane. If you need a complex, three-dimensional mounting plate, you simply have to push the points into the third dimension, and clean up after the mess that you’ve just made.

points = [ [0,0,0], [40,0,0], [23,-10,0], [60,19,10] ];
difference(){
    plate(points, 10, 1, 5.5);
    for (p=points){
        translate(p + [0,0,1])
            cylinder(d1=10, d2=15, h=7);
        translate(p + [0,0,-1])
            mirror([0,0,1])
            cylinder(d1=10, d2=15, h=7);
    }
}



Once you start thinking of using hull() to connect up objects arbitrarily located in 3D space, you’re on the way to mastery, and we can leave simple mounting plates behind. For instance, I made a plant-lamp stand out of 20/20 aluminum extrusion with just a few simple shapes and some hull().

The lamp’s legs need to attach to the extrusion somewhere, so I started with a 25 mm cube, punched through with the profile. Next, the feet need to hit the floor, so I placed two more (thin) cubes far enough apart so that they could accommodate the plant’s pot. Each foot is individually hull()ed to the center block, the model rotated, and it’s off to the printer. Because I had the extrusion profile already worked out, the entire design took under fifteen minutes. The print took something like five hours.

Hull() came to the rescue again in a very similar design problem that might look entirely different on the surface. A piece of my bike light bracket broke. For the tab that fits into the light, a simple cube would do, and screwing the bracket onto the seat-tube clamp just required a hole. But how to connect the hole with the cube, without worrying too much about the shape?

The one refinement here is that the hull() can’t surround the entire mounting tab, because it wouldn’t fit into the light’s slot, so I made a landing pad for the hull out of another simple cube, shown here in red.

This is a general strategy: position the objects that you’d like to join together in 3D space, embed a landing pad in each of the objects, and hull() them together, in pairs or as a group as appropriate. The shape of the landing pads determines the shape of the connector, and the results of hull() over 3D objects and space can often be surprising, and often surprisingly elegant, so you’ll want to experiment. But once you get used to thinking of hull() as an all-purpose connector, you’ll find uses for this technique everywhere.

Extending, Getting Creative

With a few helper functions, you can make modeling with hull() a bit easier. For instance, in the lamp example above, we needed to hull() one foot together with the central block, and then repeat the action for the other foot:

hull(){
    mount_block();
    foot_block(1);
}
hull(){
    mount_block();
    foot_block(-1);
}

So much duplication! That won’t do. Similarly, if you want to make creative organic shapes, you’ll find it handy to hull() objects together sequentially in a chain, rather than all together in a lump.

module multiHull(){
    for (i = [1 : $children-1])
        hull(){
            children(0);
            children(i);
        }
}

module sequentialHull(){
    for (i = [0: $children-2])
        hull(){
            children(i);
            children(i+1);
        }
}

Both of these functions take advantage of the children() mechanism, which lets you embed sub-objects into a module of your own. multiHull() takes care of the lamp situation; it hull()s the first child object pairwise with each of the others. sequentialHull() connects the first object to the second, the second to the third, and so on.

The one limitation of these functions is that they can’t accept objects inside a for() loop as children, because for() loops in OpenSCAD end with an implicit union() command, joining all of the elements of the loop together in one piece. This means that, in some cases, you’ll end up writing the for() and hull() loops out explicitly yourself. I still find these functions useful. For instance, here’s the lamp foot code:

multiHull(){
    mount_block();
    foot_block(1);
    foot_block(-1);
}

To show off these functions, let’s make a generic PCB cradle — the sort of thing you want under just about any bare-board device if you have small metal parts floating around on your desk, for instance. Starting off with the locations of the mounting holes (go, go, calipers!), multiHull() some cylinders together with a central point to make a cross that connects them all together. If you want something more substantial, you can use a sequentialHull() to go around those points in a ring, making a square border. And finally, if you would also like bumper walls to fully cradle the PCB, create two rounded-edge boxes by hull()ing cylinders and taking their difference. Check out the code: everything is done with hull(), cylinder(), and difference().

All of the hull() functions here, and a few more, are in a hull.scad file so that you can include them in your projects with a simple use <hull.scad> at the top of your code. If you use OpenSCAD, but haven’t fully embraced hull() as a useful modelling strategy, I hope that this writeup will help point you in the right direction with some useful design patterns. Enjoy!

You may also like...