
// Chain Mail Armor Buttons 

// Ed Nisley KE4ZNU  December 2014 



Layout = "Build"; // Link Button LB Joiner Joiners Build PillarMod 



// 

// Extrusion parameters must match reality! 

// Print with 1 shell and 2+2 solid layers 



ThreadThick = 0.25; 

ThreadWidth = 0.40; 



HoleWindage = 0.2; 



Protrusion = 0.1; // make holes end cleanly 



function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit); 



// 

// Dimensions 



// Set maximum sheet size 



SheetSizeX = 125; // 170 for full sheet on M2 

SheetSizeY = 125; // 230 ... 



// Diamond or rectangular sheet? 



Diamond = false; // true = rotate 45 degrees, false = 0 degrees for square 



BendAround = "X"; // X or Y = maximum flexibility *around* designated axis 



Cap = true; // true = build bridge layers over links 

CapThick = 4 * ThreadThick; // flat cap on link: >= 3 layers for solid bridging 



Armor = true && Cap; // true = build armor button atop (required) cap 

ArmorThick = IntegerMultiple(2.0,ThreadThick); // height above cap surface 



ArmorSides = 4; 

ArmorAngle = true ? 180/ArmorSides : 0; // true > rotate half a side for best alignment 



// Link bar sizes 



BarThick = 3 * ThreadThick; 

BarWidth = 3.3 * ThreadWidth; 



BarClearance = 3 * ThreadThick; // vertical clearance above & below bars 



VertexHack = false; // true to slightly reduce openings to avoid coincident vertices 



// Compute link sizes from those values 



// Absolute minimum base link: bar width + corner angle + build clearance around bars 

// rounded up to multiple of thread width to ensure clean filling 

BaseSide = IntegerMultiple((4*BarWidth + 2*BarWidth/sqrt(2) + 3*(2*ThreadWidth)),ThreadWidth); 



BaseHeight = 2*BarThick + BarClearance; // both bars + clearance 



echo(str("BaseSide: ",BaseSide," BaseHeight: ",BaseHeight)); 

//echo(str(" Base elements: ",4*BarWidth,", ",2*BarWidth/sqrt(2),", ",3*(2*ThreadWidth))); 

//echo(str(" total: ",(4*BarWidth + 2*BarWidth/sqrt(2) + 3*(2*ThreadWidth)))); 



BaseOutDiagonal = BaseSide*sqrt(2)  BarWidth; 

BaseInDiagonal = BaseSide*sqrt(2)  2*(BarWidth/2 + BarWidth*sqrt(2)); 



echo(str("Outside diagonal: ",BaseOutDiagonal)); 



// Oncenter distance measured along coordinate axis 

// the links are interlaced, so this is half of what you think it should be... 



LinkOC = BaseSide/2 + ThreadWidth; 



LinkSpacing = Diamond ? (sqrt(2)*LinkOC) : LinkOC; 

echo(str("Base spacing: ",LinkSpacing)); 



// Compute how many links fit in sheet 



MinLinksX = ceil((SheetSizeX  (Diamond ? BaseOutDiagonal : BaseSide)) / LinkSpacing); 

MinLinksY = ceil((SheetSizeY  (Diamond ? BaseOutDiagonal : BaseSide)) / LinkSpacing); 

echo(str("MinLinks X: ",MinLinksX," Y: ",MinLinksY)); 



NumLinksX = ((0 == (MinLinksX % 2)) && !Diamond) ? MinLinksX + 1 : MinLinksX; 

NumLinksY = ((0 == (MinLinksY % 2) && !Diamond)) ? MinLinksY + 1 : MinLinksY; 

echo(str("Links X: ",NumLinksX," Y: ",NumLinksY)); 



// Armor button base 



ButtonHeight = BaseHeight + BarClearance + CapThick; 

echo(str("ButtonHeight: ",ButtonHeight)); 



// Armor ornament size & shape 

// Finetune OD & ID to suit the number of sides... 



TotalHeight = ButtonHeight + ArmorThick; 

echo(str("Overall Armor Height: ",TotalHeight)); 



ArmorOD = 1.0 * BaseSide; // tune for best base fit 

ArmorID = 10 * ThreadWidth; // make the tip blunt & strong 



// 



module ShowPegGrid(Space = 10.0,Size = 1.0) { 



RangeX = floor(95 / Space); 

RangeY = floor(125 / Space); 



for (x=[RangeX:RangeX]) 

for (y=[RangeY:RangeY]) 

translate([x*Space,y*Space,Size/2]) 

%cube(Size,center=true); 



} 





// 

// Create link with armor button as needed 



module Link(Topping = false) { 



LinkHeight = (Topping && Cap) ? ButtonHeight : BaseHeight; 



render(convexity=3) 

rotate((BendAround == "X") ? 90 : 0) 

rotate(Diamond ? 45 : 0) 

union() { 

difference() { 

translate([0,0,LinkHeight/2]) // outside shape 

intersection() { 

cube([BaseSide,BaseSide,LinkHeight],center=true); 

rotate(45) 

cube([BaseOutDiagonal,BaseOutDiagonal,(LinkHeight + 2*Protrusion)],center=true); 

} 



translate([0,0,(BaseHeight + BarClearance + 0*ThreadThick  Protrusion)/2]) 

intersection() { // inside shape 

cube([(BaseSide  2*BarWidth), 

(BaseSide  2*BarWidth), 

(BaseHeight + BarClearance + 0*ThreadThick + (VertexHack ? Protrusion/2 : 0))], 

center=true); 

rotate(45) 

cube([BaseInDiagonal, 

BaseInDiagonal, 

(BaseHeight + BarClearance + 0*ThreadThick + (VertexHack ? Protrusion/2 : 0))], 

center=true); 

} 



translate([0,0,((BarThick + 2*BarClearance)/2 + BarThick)]) // openings for bars 

cube([(BaseSide  2*BarWidth  2*BarWidth/sqrt(2)  (VertexHack ? Protrusion/2 : 0)), 

(2*BaseSide), 

BarThick + 2*BarClearance  Protrusion], 

center=true); 



translate([0,0,(BaseHeight/2  BarThick)]) 

cube([(2*BaseSide), 

(BaseSide  2*BarWidth  2*BarWidth/sqrt(2)  (VertexHack ? Protrusion/2 : 0)), 

BaseHeight], 

center=true); 



} 



if (Topping && Armor) 

translate([0,0,(ButtonHeight  Protrusion)]) // sink slightly into the cap 

rotate(ArmorAngle) 

cylinder(d1=ArmorOD,d2=ArmorID,h=(ArmorThick + Protrusion), $fn=ArmorSides); 

} 



} 





// 

// Create split buttons to join sheets 



module Joiner() { 



translate([LinkSpacing,0,0]) 

difference() { 

Link(false); 

translate([0,0,BarThick + BarClearance + TotalHeight/2  Protrusion]) 

cube([2*LinkSpacing,2*LinkSpacing,TotalHeight],center=true); 

} 



translate([LinkSpacing,0,0]) 

intersection() { 

translate([0,0,(BarThick + BarClearance)]) 

Link(true); 

translate([0,0,TotalHeight/2]) 

cube([2*LinkSpacing,2*LinkSpacing,TotalHeight],center=true); 

} 



} 





// 

// Build it! 



//ShowPegGrid(); 



if (Layout == "Link") { 

Link(false); 

} 



if (Layout == "Button") { 

Link(true); 

} 



if (Layout == "LB") { 

color("Brown") Link(true); 

translate([LinkSpacing,LinkSpacing,0]) 

color("Orange") Link(false); 

} 



if (Layout == "Build") 

for (ix = [0:(NumLinksX  1)], 

iy = [0:(NumLinksY  1)]) { 

x = (ix  (NumLinksX  1)/2)*LinkSpacing; 

y = (iy  (NumLinksY  1)/2)*LinkSpacing; 

translate([x,y,0]) 

color([(ix/(NumLinksX  1)),(iy/(NumLinksY  1)),1.0]) 

if (Diamond) 

Link((ix + iy) % 2); // armor at odd,odd & even,even points 

else 

if ((iy % 2) && (ix % 2)) // armor at odd,odd points 

Link(true); 

else if (!(iy % 2) && !(ix % 2)) // connectors at even,even points 

Link(false); 

} 



if (Layout == "Joiner") 

Joiner(); 



if (Layout == "Joiners") { 

NumJoiners = max(MinLinksX,MinLinksY)/2; 

for (iy = [0:(NumJoiners  1)]) { 

y = (iy  (NumJoiners  1)/2)*2*LinkSpacing + LinkSpacing/2; 

translate([0,y,0]) 

color([0.5,(iy/(NumJoiners  1)),1.0]) 

Joiner(); 

} 

} 



if (Layout == "PillarMod") // Slic3r modification volume to eliminate pillar infill 

translate([0,0,(BaseHeight + BarClearance)/2]) 

cube([1.5*SheetSizeX,1.5*SheetSizeY,BaseHeight + BarClearance],center=true); 