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:

- In OpenSCAD,
*module*is somehow equivalent to a function. (There are also functions, but they may only return a value.) - The
*d1*variable defines a length smaller than the cube size, in order to leave out a border equal to the desired*WALL_THICKNESS*. - 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. - So the red inner cube is not exactly a cube, but a parallelepiped.
- 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:

- 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. - 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:

- The size is smaller at each step, by an exponential factor. It's a logarithmic spiral!
- 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:

- 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.
- 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.
- 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!

## 2 Discussions

10 months ago

Beautiful, visually intriuging product and very well-written Instructable and code. I particularly like your clear, simple explanation of what you are doing in OpenSCAD. Nice work!

I'm reading this from the road, and you've made me impatient to get back to my printer and give it a go!

1 year ago

Looks awesome!