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

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