homeblogartgithub
rss

© 2026 MIT Licensed

I’m Touched: Icosahedron Hub

July 1, 2026

The part that holds the video sphere together is a single boolean operation: a 60 mm printed ball minus thirteen cylinders.

module hub() {
    difference() {
        sphere(r = sphere_r, $fn = resolution);
        for (v = vertices) radial_hole(v);
        if (include_south_pole_hole) radial_hole(south_pole_dir);
    }
}

Twelve of the holes point at the vertices of a regular icosahedron and receive the press-fit stems that carry the screens. The thirteenth points straight down and receives the long stem that mounts the whole thing on a pedestal. The file that produces every one of these parts (hub, screen stems, mounting stem, pedestal) is about 140 lines of OpenSCAD.

The white 3D-printed icosahedron hub with thin stems press-fit into its radial holes, standing on a wooden workbench

Twelve directions

An icosahedron’s twelve vertices are the cyclic permutations of (0, ±1, ±φ), where φ is the golden ratio. Picture three mutually perpendicular golden rectangles:

phi = (1 + sqrt(5)) / 2;

vertices = [
    [ 0,  1,  phi], [ 0,  1, -phi], [ 0, -1,  phi], [ 0, -1, -phi],
    [ 1,  phi,  0], [ 1, -phi,  0], [-1,  phi,  0], [-1, -phi,  0],
    [ phi,  0,  1], [ phi,  0, -1], [-phi,  0,  1], [-phi,  0, -1]
];

The count of twelve comes from ranking candidate screen counts by how evenly you can distribute them on a sphere: 12 (icosahedron) and 20 (dodecahedron) are the two counts with perfectly regular distributions, and everything in between needs a Fibonacci-sphere approximation.

The icosahedron has a second property that pays off later, on the software side: none of its normalized vertices has a |y| component above φ/√(1+φ²) ≈ 0.851. In practice that means no screen sits near a pole, so every virtual camera in the render rig can use world-Y as its up vector without special-case handling. The camera math gets a whole post (The TouchDesigner Camera Rig), but the reason it’s simple is decided right here, in the choice of solid.

OpenSCAD’s cylinder() only grows along Z, so each hole is a rotation of a Z-aligned cylinder onto its vertex direction. The axis of rotation is the cross product of Z with the target direction; the angle falls out of the dot product:

module radial_hole(direction) {
    dir  = normalize(direction);
    axis = cross([0, 0, 1], dir);
    ang  = acos(dir[2]);
    rotate(a = ang, v = axis)
        translate([0, 0, sphere_r - hole_depth])
            cylinder(h = hole_depth + 0.5, r = hole_r, $fn = 48);
}

The cylinder starts at sphere_r - hole_depth and overshoots the surface by half a millimeter so the boolean cuts cleanly. That’s the only trigonometry in the file.

Stems, chamfers, and press fits

Everything joins by friction. The stems are 10.0 mm cylinders; the holes are 10.3 mm, and stem_clearance = 0.3 is a named parameter with a comment admitting it’s printer-dependent. Each stem gets a 0.6 mm chamfer at the insertion end so it starts into the hole instead of shaving it.

The screen stems expose 100 mm. The long mounting stem exposes 220 mm (230 mm total with its socket depth, sized to fit a 256 mm print bed with margin) and is chamfered on both ends, because both ends are press-fit: one into the hub, one into the pedestal.

A pedestal

The pedestal that holds the long stem upright is a rotate_extrude of a quarter-ellipse: a 150 mm flat foot that tapers up to a narrow socket neck. The curve is chosen so the tangent is horizontal at the foot edge and vertical at the neck. Every printed layer is contained by the layer below it, so the part prints without supports by construction rather than by slicer heroics.

First full-form prototype: the white pedestal and long stem holding the hub, with three empty black screen enclosures mounted on stems

If you want the parts: the source is icosahedron-sphere.scad, with a part variable at the top ("sphere" | "stem" | "long_stem" | "base"). Set it, render, export the STL.

Part 1 of the video-sphere series
NextThe Screen Enclosure