I ran off a few patches of miniature chain mail for holiday handouts to a few folks who’d appreciate them:

A little patch like that makes a fondletoy that’s easier to pocket than, say, a planetary gear bearing and should be robust enough to withstand quite a bit of abuse.
Alas, it turned out that recent Slic3r development versions suffered a bridging regression. The stable 1.2.9 version does the right thing:

The hot-from-Github version goes diagonally, producing a pattern like an internal layer that normally sits atop the (omitted) bridge layer:

While that might barely work, the little bitty link bars will certainly fall into the abyss:

Given the complexity of slicing algorithms, I definitely can’t track down the problem; using the stable version for a while should suffice.
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); |
One thought on “Miniature Chain Mail: Handouts”
Comments are closed.