Introduction: OpenSCAD Tutorial: Spiral Cube

About: So many things to learn and make, so little time! I like things that are cool, useful, efficient, well crafted.

This Instructable explains how to build this object, using the OpenSCAD software, that is, programmatically.

After I 3D-printed this object, I saw it becoming popular among my children. Kids are curious and critical. And the ratio visual effect to design complexity being interesting, it was ideal for a tutorial.

The first 3 pictures show the finished object. The 4th shows a variation, which will be explained below. The 5th shows the principal constituent with which all is constructed: a cube frame.

The final source code is in step 8.

The OpenSCAD software

OpenSCAD is free and open-source, and runs on Linux, OSX and Windows. It allows to design arbitrarily complex objects in a programmatic way, using simple operations.

Being programmatic means precision and reproducibility, but also a certain learning curve, which is fortunately not steep.

The OpenSCAD language

OpenSCAD is really easy to learn. If you have seen code in C/C++, Java, Javascript or similar, you'll have no difficulty getting started. The OpenSCAD documentation and examples are excellent. I suggest you to browse them after (or, if you need clarifications, while) reading this Instructable.

The OpenSCAD website: http://www.openscad.org/
Also very handy, the cheat sheet: http://www.openscad.org/cheatsheet/

Step 1: OpenSCAD - Cube Edges, Part 1

The principal constituent (mentioned and shown in the previous step) is a cube frame. It consists of the 12 edges of a cube, with a given thickness.

We could build and align these 12 edges one by one, but instead we'll hollow out a cube in just 3 steps.

Here is the first step: we create the outer cube (here blue), and substract an inner cube (here red) using difference() :

WALL_THICKNESS = 3;
SIZE = 35;

module make_cube_edges(size) {
    d1 = size - WALL_THICKNESS*2; // to keep walls
    d2 = size + WALL_THICKNESS; // to dig face
    difference() {
        // full cube:
        cube([size, size, size], true);
        
        // remove face center:
        # cube([d1, d2, d1], true); // dig along Y axis
    }
}

make_cube_edges(SIZE);

Notes:

  1. In OpenSCAD, module is somehow equivalent to a function. (There are also functions, but they may only return a value.)
  2. The d1 variable defines a length smaller than the cube size, in order to leave out a border equal to the desired WALL_THICKNESS.
  3. The d2 variable defines a length greater than the cube size by a margin, in order to make sure we leave nothing from the face. This margin is here WALL_THICKNESS, but it could be anything > 0.
  4. So the red inner cube is not exactly a cube, but a parallelepiped.
  5. Notice the "#" modifier, which tells OpenSCAD, in preview mode, to draw the substracted objects in a translucent way. Very handy for debugging.

Step 2: OpenSCAD - Cube Edges, Part 2

Now we repeat the latter operation twice, but along the X and Z axes:

WALL_THICKNESS = 3;
SIZE = 35;

module make_cube_edges(size) {
    d1 = size - WALL_THICKNESS*2; // to keep walls
    d2 = size + WALL_THICKNESS; // to dig face
    difference() {
        // full cube:
        cube([size, size, size], true);
        
        // remove face centers:
        # cube([d2, d1, d1], true); // dig along X axis *NEW*
        # cube([d1, d2, d1], true); // dig along Y axis
        # cube([d1, d1, d2], true); // dig along Z axis *NEW*
    }
}

make_cube_edges(SIZE);

Note that in the difference() block, the first object is added, and all following ones are substracted from it.

Step 3: OpenSCAD- Cube Edges, Part 3

We remove the "#" modifier to get a clean desired frame:

WALL_THICKNESS = 3;
SIZE = 35;

module make_cube_edges(size) {
    d1 = size - WALL_THICKNESS*2; // to keep walls
    d2 = size + WALL_THICKNESS; // to dig face
    difference() {
        // full cube:
        cube([size, size, size], true);
        
        // remove face center:
        cube([d2, d1, d1], true); // dig along X axis *CHANGED*
        cube([d1, d2, d1], true); // dig along Y axis *CHANGED*
        cube([d1, d1, d2], true); // dig along Z axis *CHANGED*
    }
}

make_cube_edges(SIZE);

Step 4: OpenSCAD - Add Another Cube

Now we want to add another frame, slightly smaller, and slightly rotated in the horizontal plane. We introduce the parameter rotation, and just add another call to make_cube_edges():

WALL_THICKNESS = 3;
SIZE = 35;

module make_cube_edges(size, rotation) { // *CHANGED*
    d1 = size - WALL_THICKNESS*2; // to keep walls
    d2 = size + WALL_THICKNESS; // to dig face
    rotate([0, 0, rotation]) // *NEW*
    difference() {
        cube([size, size, size], true); // full cube
        cube([d2, d1, d1], true); // dig along X axis
        cube([d1, d2, d1], true); // dig along Y axis
        cube([d1, d1, d2], true); // dig along Z axis
    }        
}

make_cube_edges(SIZE, 0); // *CHANGED*
make_cube_edges(SIZE-2.8, 5); // *NEW*

Notes:

  1. The rotate() statement is placed before the object it affects. That's because rotate() is in fact a module which modifies what follows (its child object). More on this in the next step.
  2. The two frames overlap a bit. That's not an issue for this design.

Step 5: A Note About Modules

As subroutines

Modules can be used as subroutines, accepting parameters, and creating objects:

module MyModule(params) {
    ... create stuff
}

In this case, call the module followed by a semi-column:

MyModule(params);

As modifiers

Modules can also be used to modify other objects, which will be seen by the module as children():

module StretchXAndRotateZ(factor, angle) {
    rotate([0, 0, angle]) // 3. rotate them
    scale([factor, 1, 1]) // 2. stretch them
    children();           // 1. use the object(s) to act on
}

The module call shall be placed before the object(s) to act on:

StretchXAndRotateZ(2, 30) cube(10);

One great thing is that, in the module, you can call children() as many times as wanted.

Of course, all these behaviors may be used in combination.

Step 6: OpenSCAD - Add All Cubes

But we do not want to add all the cubes one by one, so let's use a for loop.

We introduce the two constants STEPS and TOTAL_ROTATION, and replace the two calls to make_cube_edges() by one call enclosed in the for loop:

WALL_THICKNESS = 3;
SIZE = 35;
STEPS = 12; // *NEW*
TOTAL_ROTATION = 60; // *NEW*

module make_cube_edges(size, rotation) {
    d1 = size - WALL_THICKNESS*2; // to keep walls
    d2 = size + WALL_THICKNESS; // to dig face
    rotate([0, 0, rotation])
    difference() {
        cube([size, size, size], true); // full cube
        cube([d2, d1, d1], true); // dig along X axis
        cube([d1, d2, d1], true); // dig along Y axis
        cube([d1, d1, d2], true); // dig along Z axis
    }        
}

// *CHANGED:*
for (i=[0:STEPS]) {
    make_cube_edges(SIZE / exp(i/STEPS), i/STEPS * TOTAL_ROTATION);
}

Notes:

  1. The size is smaller at each step, by an exponential factor. It's a logarithmic spiral!
  2. The rotation is proportional to the step index. Same angle between two successive steps.

It now really looks like the final object...

Yes, but wait!

Step 7: A Bridge Problem

If we closely examine the bottom side, we can see bridges forming an angle in the horizontal plane, with the corner hanging mid-air. I am showing this in the picture, with only two frames for clarity.

In a given slice, printers may successfully draw straight lines in mid-air (as long as the line is not "too" long), but drawing curves or angles is problematic.

So in order to have no corners mid-air, we will etch the face of each other bottom edge of the cube. We'll see that in the next step.

Step 8: OpenSCAD - Etching Bottom Sides

Except for the first (outermost) cube, we will etch each second bottom face, by a given thickness, LAYER. The value of LAYER has to be chosen to be at least one layer of your 3D printer.

The etching is done by substraction, like we did previously to hollow out the cubes and obtain the frames.

WALL_THICKNESS = 3;
SIZE = 35;
STEPS = 12;
TOTAL_ROTATION = 60;
LAYER = 0.2; // *NEW*

module make_cube_edges(size, rotation, must_etch_base) { // *CHANGED*
    d1 = size - WALL_THICKNESS*2; // to keep walls
    d2 = size + WALL_THICKNESS; // to dig face
    rotate([0, 0, rotation])
    difference() {
        cube([size, size, size], true); // full cube
        cube([d2, d1, d1], true); // dig along X axis
        cube([d1, d2, d1], true); // dig along Y axis
        cube([d1, d1, d2], true); // dig along Z axis

        // *NEW:*
        if (must_etch_base) {
            translate([0, 0, -size/2])
            cube([d1, d2, LAYER*2], true); // dig along Z axis
        }
    }        
}

for (i=[0:STEPS]) {
    make_cube_edges(SIZE / exp(i/STEPS), i/STEPS * TOTAL_ROTATION, i>0); // *CHANGED*
}

Step 9: Final OpenSCAD Code - 3D-Print It

So we have our final source code:

// Spiral Cube by P. Bauermeister, January 2018
// See https://www.instructables.com/id/OpenSCAD-Tutorial-Spiral-Cube/

WALL_THICKNESS = 3;
SIZE = 35;
STEPS = 12;
TOTAL_ROTATION = 60;
LAYER = 0.2;

module make_cube_edges(size, rotation, must_etch_base) {
    d1 = size - WALL_THICKNESS*2; // to keep walls
    d2 = size + WALL_THICKNESS; // to dig face
    rotate([0, 0, rotation])
    difference() {
        cube([size, size, size], true); // full cube
        cube([d2, d1, d1], true); // dig along X axis
        cube([d1, d2, d1], true); // dig along Y axis
        cube([d1, d1, d2], true); // dig along Z axis
        
        if (must_etch_base) {
            translate([0, 0, -size/2])
            cube([d1, d2, LAYER*2], true); // dig along Z axis
        }
    }
}

for (i=[0:STEPS]) {
    make_cube_edges(SIZE / exp(i/STEPS), i/STEPS * TOTAL_ROTATION, i>0);
}

In OpenSCAD, render it, export it as STL, and 3D-print it.

Step 10: Inspection of the Bottom Sides

As you can see, the step "Etching Bottom Sides" is paying off: when you closely examine the bottom side, the results are quite good, because all filament overhang lines are straight without mid-air corners.

Step 11: Variation 1 - Skeleton

In this step, let's explore another idea: make the successive cubes frames more distinguishable by having distinct space between each other.

The thickness of the frames shall be proportional to the cube size, otherwise small cubes will not be hollow.

There is a new difficulty: the size and angle must be such that there is no overlap between frames, and that the corners perfectly match the edges of the previous frame. This complicates the code a bit:

WALL_THICKNESS = 3;
SIZE = 30;
STEPS = 7;
TOTAL_ROTATION = 70;
LAYER = 0.2;

module make_cube_edges(size, rotation, thickness, must_etch_base) {
    d1 = size - thickness*2; // to keep walls
    d2 = size + thickness*2 + 2; // to dig face
    rotate([0, 0, rotation])
    difference() {
        cube([size, size, size], true); // full cube
        cube([d2, d1, d1], true); // dig along X axis
        cube([d1, d2, d1], true); // dig along Y axis
        cube([d1, d1, d2], true); // dig along Z axis
        
        if (must_etch_base) {
            translate([0, 0, -size/2])
            cube([d1, d2, LAYER*2], true); // dig along Z axis
        }
    }        
}

SIZE2 = SIZE - WALL_THICKNESS*2;
FACTOR = SIZE2/SIZE;
ANGLE = acos(SIZE/SIZE2/sqrt(2)) - 45;

module recurse(to_go, size, angle, thickness, first) {
    if (to_go) {
        echo(thickness);
        make_cube_edges(size, angle, thickness, !first);
        recurse(to_go -1, size*FACTOR, angle + ANGLE, thickness*FACTOR, false);
    }
}

recurse(STEPS, SIZE, 0, WALL_THICKNESS, true);

After printing we can unfortunately see that the effort is not paying off, because the frames become so thin that they do not print cleanly.

Step 12: Variation 2 - Smooth Sides

Now let's explore another idea: have so many cube frames that the result appears no longer stepped, but as continuous surfaces.

In the previous objects, the successive cubes rotation was only in the horizontal plane (around the Z axis). Here we will rotate the frames in all 3 dimensions.

We will also reduce the wall thickness.

WALL_THICKNESS = 1;
SIZE = 50;
STEPS = 150;
TOTAL_ROTATION = 30;
K = 2;

module make_cube_edges(size, rotation) {
    d1 = size - WALL_THICKNESS*2; // to keep walls
    d2 = size + WALL_THICKNESS; // to dig face
    rotate([rotation, rotation, rotation])
    difference() {
        cube([size, size, size], true); // full cube
        cube([d2, d1, d1], true); // dig along X axis
        cube([d1, d2, d1], true); // dig along Y axis
        cube([d1, d1, d2], true); // dig along Z axis
    }        
}

for (i=[0:STEPS]) {
    make_cube_edges(SIZE * (1 - i/STEPS), TOTAL_ROTATION*pow(i/STEPS*K, 2));
}

Since we have a big lot of polygons, this object may takes some time to render.

The result is in my opinion very interesting.

Notes:

  1. The obtained surfaces are not exactly smooth, but consist of a lot of thin steps. If their size is comparable to your printer's resolution, the surfaces will appear as smooth as print can.
  2. To model it as a truly smooth surface (hence with less polygons), we would need a different approach involving more math, and this would be another story.
  3. This objects has also a lot of overhangs and may be challenging to cleanly print.

Thanks for reading.

Now install OpenSCAD (http://www.openscad.org/) and have a lot of fun!