-
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 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
BarWidthandSheetSizeparameters. The links recalculate themselves around those values.The OpenSCAD source code as a GitHub gist:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters// 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);