|
// 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)); |
|
|
|
//- On-center 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 |
|
// Fine-tune 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); |