Square Chain Mail Armor: Back From The Abyss

After a Slic3r commit fixed the bridging regression, I ran off chain mail patches to celebrate:

Square Chain Mail Armor - 3.3 3.5 4.0 thread bars
Square Chain Mail Armor – 3.3 3.5 4.0 thread bars

Two more Scli3r improvements calculate thin-wall and gap infill based on the available space, then vary the extrusion width to make the answers come out right for a given nozzle diameter. As a result, infill between close-set perimeter walls works much better than before; some of my long-held assumptions became invalid.

The only differences between the sheets: tweaking the BarWidth and SheetSize parameters. The links recalculate themselves around those values.

The OpenSCAD source code as a GitHub gist:

// 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);