Zonodome

G. Hart, August 2018.  
Zonohedral dome design using 3D-printed connectors
For Construct3D Conference at Ga. Tech in Atlanta, Oct. 2018.
For a design wider above the ground then tapering to the apex, choose m roughly (3/4)n.
All struts are same length except horizontal ring along floor.

n=16;    (* n-fold symmetry of structure about vertical axis *)
m=12;   (* number of layers of connectors.  Apex is layer 0.  There are m - 1/2 bands of rhombic openings.  *)
zShape=1.2;  (* Shape parameter, i.e., z-component of generators above unit circle.  smaller=squatter. *)

unit[v_]:=v/Sqrt[v.v];   (* unit length function *)
g[i_]:=unit[{Cos[2 Pi i/n],Sin[2 Pi i/n],zShape}];      (* unit-length generators. unit length edges *)

vert[i_,j_]:=     (* XYZ coords of jth vertex in band i, by parallelogram rule *)
If[i==0, {0,0,0}, Sum[g[k],{k,j+1,i+j}]];

typical[k_]:=vert[k,1];  (* choose a typical vertex of band k *) 
typicalNeighbors[k_]:=  (* neighbors of that vertex  *)
If[k==0,Append[Table[vert[1,j],{j,n}],{0,0,-1}], (* include mast hole direction for level 0 *)
If[k==1,{{0,0,0},vert[k+1,0],vert[k+1,1]}, (* only one upward strut from level 1 *)
If[k==m,{vert[k,0],vert[k,2],vert[k-1,1],vert[k-1,2]},  (* special: two horizontal struts *)
{vert[k+1,0],vert[k+1,1],vert[k-1,1],vert[k-1,2]},  (* typical case has 4 struts *)
]]];

Rendering

Produce image for visual check of parameters.

rStrut=0.03;  (* strut radius for rendering *)
rBall=0.08 ;    (* ball radius for rendering *)
rFloor=4;  (* floor size *)
zFloor=1.01 vert[m,0][[3]];  (* floor position *)

mastLength=2;   (* draw flag mast at top  *)
zFlag=1.2;  (* flag height *)
xFlag=2 ;(* flag length *)

Graphics3D[{
White,  (* connectors *)
Table[Sphere[vert[i,j],rBall],{i,0,m},{j,1,n}],

Lighter[Brown,.6],  (* struts *)
Cylinder[{{0,0,0},{0,0,-mastLength}},rStrut],
Table[Cylinder[{{0,0,0},vert[1,j]},rStrut],{j,1,n}],
Table[Cylinder[{vert[i,j],vert[i+1,j]},rStrut],{i,1,m-1},{j,1,n}],
Table[Cylinder[{vert[i,j],vert[i+1,j-1]},rStrut],{i,1,m-1},{j,1,n}],
Table[Cylinder[{vert[m,j],vert[m,j+1]},rStrut],{j,1,n}],

Lighter[Orange,.8], (* floor *)
Polygon [{{rFloor,rFloor,zFloor},{rFloor,-rFloor,zFloor},
{-rFloor,-rFloor,zFloor},{-rFloor,rFloor,zFloor}}],

Lighter[Brown,.6],  (* flag mast *)
Cylinder[{{0,0,0},{0,0,-mastLength}},rStrut],
Lighter[Green,.6],  (* flag *)
Polygon[{{0,0,-mastLength},{xFlag,0,-mastLength+zFlag/2},{0,0,-mastLength+zFlag}}]

},Background->Lighter[Yellow,.9],Boxed->False]

Dimensions

Assumes unit length struts, as measured between centers of nodes.
For the original construction we used 1-foot long dowels, so the center-to center distance 
would be about 12.5 inches if the dowels were totally inserted into their holes.

vert[m,0][[3]]  (* height without flag *)

vert[m,0][[3]]  + mastLength   (* total height including flag mast *)

mag2[v_]:=v.v;  (* length of struts along floor *)
mag[v_]:=Sqrt[v.v];
mag[vert[m,0]-vert[m,1]]

n*m+1  (* number of connectors *)

2 n*m+1  (* number of struts including flag mast *)

Connector Parts

All connectors on a given level are identical.  A bump indicates vertical.  The level is indicated with indents on the flat base.

By symmetry, connectors on level j are identical to connectors on level n-j (but used upside down).  However level m (at the floor) is unique as it has horizontal connections.   The peak connector is level 0.  Level n/2 is the widest in diameter.

The following is specific to the Construct3D version (n=16, m=12) and needs to be adapted if you change n or m:   The peak connector is level 0, then comes levels 1-8.  There is no need for files 9, 10, 11, as they are just 7, 6, and 5 (upside-down), so make twice as many.  But level 12, which is the floor level, is needed. Levels 1-8 are distinguished by 1 to 8 indents on the base.

Dimensions here are assumed to be in inches for generating US stl files. Note that Mathematica’s Export function often produces files that are not “watertight” and must be cleaned up externally, e.g., with MeshMixer software.

Set res higher (e.g., 110-150) for more accurate output (slow!), or lower (e.g., 40) for quick production.

res=80; (* marching cube resolution in RegionPlot3D, typically 40 to 150 *)

r1=1.5/2;  (* ball radius *)
r2=(3/16)/2;  (* hole radius = strut radius.  Half of 3/16 dowel diameter *)
rStop=0.2;  (* distance from center to end of hole *)
rFlat=0.35;  (* distance to slicing plane *)
rBump=.8/16;  (* small bump to indicate vertical *)
rMark=0.1;  (* indented bumps to mark level *)

mark= (* XY pattern of indents on base, to mark level (1 to 9) *)
0.16 *   (* unit spacing *)
{{{0,0}},
{{-1,0},{1,0}},
{{-2,0},{0,0},{2,0}},
{{-1,-1},{1,-1},{-1,1},{1,1}},
{{-2,0},{0,0},{2,0},{0,2},{0,-2}},
{{-2,1},{0,1},{2,1},{-2,-1},{0,-1},{2,-1}},
{{-2,0},{0,0},{2,0},{-1,-2},{1,-2},{-1,2},{1,2}},
{{-1,0},{1,0},{-2,2},{0,2},{2,2},{-2,-2},{0,-2},{2,-2}},
{{-2,0},{0,0},{2,0},{-2,2},{0,2},{2,2},{-2,-2},{0,-2},{2,-2}}
};

rods[v_,neighbors_]:=     (* holes to subtract from connector *)
With[{u=Map[unit[#-v]&,neighbors]},
RegionUnion[Map[Cylinder[{v+rStop#,v+1.1 r1 #},r2]&,u]]];
cBall[v_,neighbors_]:=   (* ball-shaped connector *)
RegionDifference[Ball[v,r1],rods[v,neighbors]];
cFlat[v_,neighbors_]:=  (* ball with flat inside *)
With[{dir=unit[Cross[neighbors[[1]]-neighbors[[2]],neighbors[[1]]-neighbors[[3]]]]},
RegionDifference[cBall[v,neighbors],Cylinder[{v+rFlat dir,v+1.1 r1 dir},r1]]];
cBump[v_,neighbors_,k_]:= (* add bump to indicate vertical unless in lower half *)
If[k>n/2,cFlat[v,neighbors],  
RegionUnion[cFlat[v,neighbors],Ball[v+r1 {0,0,-1},rBump]]];

connector[v_,neighbors_,k_]:= (* add indents on base to indicate level *)
If[k<1 || k>9, cBump[v,neighbors,k], (* no indents unless 1≤k≤9 *)
With[{dir1=unit[Cross[neighbors[[1]]-neighbors[[2]],neighbors[[1]]-neighbors[[3]]]]},
With[{dir2=Cross[dir1,{0,0,-1}]},
With [{dir3=Cross[dir1,dir2]},
RegionDifference[cBump[v,neighbors,k],RegionUnion[Map[Ball[v+ rFlat dir1+ #[[1]] dir2 + #[[2]] dir3,rMark]&,mark[[k]]]]]]]]];

name[k_]:="connector-"<>ToString[k]<>".stl"

k=7;  (* level to test *)

cTest=RegionPlot3D[connector[typical[k],typicalNeighbors[k],k],PlotPoints->res]

Export[name[k],cTest]

Production Loop

Do[(
cG=RegionPlot3D[connector[typical[k],typicalNeighbors[k],k],PlotPoints->res];
Export[name[k],cG];
Print[k];
), {k,1,n/2}]

Apex

(* apex connector has special parameters for larger size *)

k=0;
r1=1.5;  (* ball radius *)
rStop=0.7;  (* distance from center to end of hole *)
rFlat=1.2;  (* distance to slicing plane *)
RegionPlot3D[cFlat[typical[0],typicalNeighbors[0]],PlotPoints->res]

Export[name[0],%]

Special kludge for level  2

The top two holes are very close, so the struts would interfere and not both fit all the way in, so translate them apart slightly.  (They will not be exactly radial, but struts can adapt.)  So re-define rods[] to include a slight offset.

offsetDist=0.08;

rods[v_,neighbors_]:=     (* holes to subtract from connector *)
With[{u=Map[unit[#-v]&,neighbors]},
With[{kludge=offsetDist unit[u[[4]]-u[[3]]]},
With[{offset={{0,0,0},{0,0,0},-kludge,kludge}},
RegionUnion[Table[Cylinder[{v+rStop u[[i]]+offset[[i]],v+1.1 r1 u[[i]]+offset[[i]]},r2],{i,1,4}]]]]];

k=2;cG=RegionPlot3D[connector[typical[k],typicalNeighbors[k],k],PlotPoints->res]

Export[name[k],cG]