3D Printed Chain Mail Armor: Joined Sheets = Fabric!

Another nine hours of printing produced a second 9×13 link chain mail armor sheet that simply begged to be joined with the first. Snipping a connecting link on one sheet and attempting to thread it through the armor button on the other didn’t work nearly as well as I expected, because the pillars on the open links don’t quite pass through the slot in the side of the armor button links:

Chain Mail Armor - 4 sided
Chain Mail Armor – 4 sided

So I summoned joiner links from the digital deep:

Chain Mail Armor - Sheet Joiners
Chain Mail Armor – Sheet Joiners

Those are standard armor button links, split at the cross bar level, then laid out along the Y axis. The cap bridges across the link just as it does on the chain mail sheets, so, when they’re glued back together, the result should be exactly like a solid link. There’s no room for alignment pins and, frankly, I wouldn’t fiddle with two dozen filament snippets anyway.

The OpenSCAD code below produces joiners that work for the square arrangement, not the diamond, but that’s in the nature of fine tuning.

When I saw them pasted to the platform, just like the model:

Chain Mail Armor - joiners on platform
Chain Mail Armor – joiners on platform

It occurred to me that I could pop the caps off, then lay the sheets in position, aligned on the underlying joiner half-links. Here’s the first sheet over the left set of bars:

Chain Mail Armor - sheet and joiners on platform
Chain Mail Armor – sheet and joiners on platform

Then glue the armor caps in place:

Chain Mail Armor - joiner with solvent glue
Chain Mail Armor – joiner with solvent glue

Four dots of IPS #4 solvent glue, dispensed from a fine copper tube serving as a pipette, wet the four pillars of the joiner’s two bottom bars. I dotted each pillar to begin softening the PLA, paused for a breath, wet them again to leave enough solvent to bite into the bottom of the armor cap, pressed the cap in place, tweaked the alignment with tweezers, then pressed downward for maybe five seconds. Although the joiner link has no inherent alignment features, there’s also not much room to slide around and it worked surprisingly well.

Repeat that trick dozen times and you’re done. The aggravation scales as the square root of the overall sheet size, so it’s not as awful as assembling every single link, but it’s definitely a task for the low-caffeine part of the day.

One bottom bar came loose when I showed the result at the MHVLUG meeting, but the bar reappeared and I glued it again easily enough. I’ve now printed several spare joiners, Just In Case.

The bottom bars aren’t firmly affixed to the platform after it cools and they dislodge fairly easily: that’s how I get larger models off: let everything cool, then simply lift the plastic off. If I were joining sheets on a regular basis, I’d conjure a fixture to hold the sheets and joiner caps in position, probably with the sheets upside down, then glue the bars atop the inverted caps. That could get messy.

Perhaps a special holder to capture the bars in the proper alignment, maybe with pins matching the square openings at the corners, would help?

This is a trial fit before gluing that’s visually indistinguishable from the final product:

Chain Mail Armor - joined sheets on platform
Chain Mail Armor – joined sheets on platform

It’s not actually fabric, but it’s sufficiently bendy to cover a hand:

Chain Mail Armor - joined sheet draped on hand
Chain Mail Armor – joined sheet draped on hand

The thing just cries out to be fondled…

There’s a quarter kilogram of plastic in that 8×12 inch = 200×310 mm sheet that almost used up the last of the black PLA spool.

Remember: you must tweak the OpenSCAD code to match your extruder settings, export a suitable STL file, get really compulsive about platform alignment, use hairspray / glue stick to boost platform adhesion, and have no qualms about an all-day print run. You can’t just slice a random STL file produced for a different printer, because the link dimensions come directly from the printer’s capabilities: one size does not fit all.

The OpenSCAD source code [Update: This is the refactored version.]:

// Chain Mail Armor Buttons
// Ed Nisley KE4ZNU - December 2014

Layout = "Build";			// Link Button LB Joiner Joiners Build

//- Extrusion parameters must match reality!
//  Print with 1 shell and 2+2 solid layers

ThreadThick = 0.20;
ThreadWidth = 0.40;

HoleWindage = 0.2;

Protrusion = 0.1*ThreadThick;			// make holes end cleanly

function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);

// Dimensions

//- Set maximum sheet size

SheetSizeX = 50;	// 170 for full sheet on M2
SheetSizeY = 60;	// 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

Armor = true && Cap;							// true = build armor button atop (required) cap
ArmorThick = IntegerMultiple(6,ThreadThick);	// height above cap surface

// Link bar sizes

BarWidth = 6 * ThreadWidth;
BarThick = 4 * ThreadThick;

BarClearance = 5*ThreadThick;		// vertical clearance above & below bars

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

CapThick = 4 * ThreadThick;						// at least 3 layers for solid bridging

ButtonHeight = BaseHeight + BarClearance + CapThick;
echo(str("ButtonHeight: ",ButtonHeight));

//- Armor ornament size & shape
//		Fine-tune OD & ID to suit the number of sides...

ArmorSides = 4;
ArmorAngle = true ? 180/ArmorSides : 0;			// true -> rotate half a side for best alignment

TotalHeight = ButtonHeight + ArmorThick;
echo(str("Overall Armor Height: ",TotalHeight));

ArmorOD = 1.1 * 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])


// Create link with armor button as needed

module Link(Topping = false) {

LinkHeight = (Topping && Cap) ? ButtonHeight : BaseHeight;

	rotate((BendAround == "X") ? 90 : 0)
	rotate(Diamond ? 45 : 0)
		union() {
			difference() {
				translate([0,0,LinkHeight/2])	// outside shape
					intersection() {
				translate([0,0,(BaseHeight + BarClearance - Protrusion)/2])
					intersection() {		// inside shape
						cube([(BaseSide - 2*BarWidth),
								(BaseSide - 2*BarWidth),
								(BaseHeight + BarClearance + Protrusion)],
									(BaseHeight + BarClearance + Protrusion)],

				translate([0,0,((BarThick + 2*BarClearance)/2 + BarThick)])		// openings for bars
					cube([(BaseSide - 2*BarWidth - 2*BarWidth/sqrt(2)),
						BarThick + 2*BarClearance],

				translate([0,0,(BaseHeight/2 - BarThick)])
						(BaseSide - 2*BarWidth - 2*BarWidth/sqrt(2)),


			if (Topping && Armor)
				translate([0,0,(ButtonHeight - Protrusion)])		// sink slightly into the cap
							 h=(ArmorThick + Protrusion),

// Create split buttons to join sheets

module Joiner() {

		difference() {
			translate([0,0,BarThick + BarClearance + TotalHeight/2 - Protrusion])

		intersection() {
			translate([0,0,-(BarThick + BarClearance)])


// Build it!


if (Layout == "Link") {

if (Layout == "Button") {

if (Layout == "LB") {

if (Layout == "Build")
	for (ix = [0:(NumLinksX - 1)],
		 iy = [0:(NumLinksY - 1)]) {
			x = (ix - (NumLinksX - 1)/2)*LinkSpacing;
			y = (iy - (NumLinksY - 1)/2)*LinkSpacing;
			color([(ix/(NumLinksX - 1)),(iy/(NumLinksY - 1)),1.0])
				if (Diamond)
					Link((ix + iy) % 2);					// armor at odd,odd & even,even points
					if ((iy % 2) && (ix % 2))				// armor at odd,odd points
					else if (!(iy % 2) && !(ix % 2))		// connectors at even,even points

if (Layout == "Joiner")

if (Layout == "Joiners") {
	NumJoiners = max(MinLinksX,MinLinksY)/2;
	for (iy = [0:(NumJoiners - 1)]) {
		y = (iy - (NumJoiners - 1)/2)*2*LinkSpacing + LinkSpacing/2;
			color([0.5,(iy/(NumJoiners - 1)),1.0])

As a reward for reading all the way to the bottom, some further thoughts:

A mask array could control what type of link goes where, which cap style goes on each armor button, and whether to print the link at all. That way, you could produce customized armor buttons in non-rectangular (albeit coarsely pixelized) fabric sheets.

You could produce an armor sheet sporting cubic caps, then intersect the whole sheet with a model built from a height-map image to spread a picture across the sheet. The complexity of that model would probably tie OpenSCAD in knots, but perhaps an external program could intersect two properly aligned STL / AMF files.

The bars could be a thread or two thinner, shaving a few millimeters off the basic link. The printer’s ability to bridge the link to form the flying bars and cap limits making the links much larger.

Armored Chain Mail now replaces the Knurled Planetary Gear Bearing as my favorite fondletoy…

I wrote up a summary of the whole project on the MakerGear forum’s Printed Object Showcase.