The Smell of Molten Projects in the Morning

Ed Nisley's Blog: Shop notes, electronics, firmware, machinery, 3D printing, laser cuttery, and curiosities. Contents: 100% human thinking, 0% AI slop.

Tag: CNC

Making parts with mathematics

  • 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])
    		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],center=true);
    					}
    				translate([0,0,(BaseHeight + BarClearance - Protrusion)/2])
    					intersection() {		// inside shape
    						cube([(BaseSide - 2*BarWidth),
    								(BaseSide - 2*BarWidth),
    								(BaseHeight + BarClearance + Protrusion)],
    								center=true);
    						rotate(45)
    							cube([BaseInDiagonal,
    									BaseInDiagonal,
    									(BaseHeight + BarClearance + Protrusion)],
    									center=true);
    				}
    
    				translate([0,0,((BarThick + 2*BarClearance)/2 + BarThick)])		// openings for bars
    					cube([(BaseSide - 2*BarWidth - 2*BarWidth/sqrt(2)),
    						(2*BaseSide),
    						BarThick + 2*BarClearance],
    						center=true);
    
    				translate([0,0,(BaseHeight/2 - BarThick)])
    					cube([(2*BaseSide),
    						(BaseSide - 2*BarWidth - 2*BarWidth/sqrt(2)),
    						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") {
    	Link(true);
    	translate([LinkSpacing,LinkSpacing,0])
    		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();
    	}
    }
    

    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.

  • 3D Printed Chain Mail Armor: Cosplay Edition

    Starting from the improved chain mail link design, extend the top bars enough to clear the cross links, then bridge across them to form a flat cap:

    Chain Mail - Armor and Link
    Chain Mail – Armor and Link

    The OpenSCAD code makes the links as small as they can possibly be, based on the bar size and clearances, then rounds up to a multiple of the thread width so the flat cap will fill properly. Given the extrusion thread dimensions and the bar sizes, the OpenSCAD code computes everything else: the link model matches the slicer settings that define the printer’s output.

    Given:

    • Thread: 0.4 mm wide x 0.2 mm thick
    • Bar: 6 thread wide x 4 thread thick = 2.4 x 0.8 mm
    • Clearances: 2 thread horizontal x 5 thread vertical = 0.8 x 1.0 mm

    All the links measure 15.6 mm from side to side, the short connecting links are 2.6 mm tall, and the flat caps are 4.4 mm tall. Interlinked links sit 8.2 mm on center = half the link side plus one thread width clearance, which is 16.4 mm on center for adjacent links.

    Duplicated appropriately, the caps resemble turtle armor:

    Chain Mail - Flat Armor
    Chain Mail – Flat Armor

    Which look about the same in real life, minus the cheerful colors:

    Armor Buttons - on platform - side
    Armor Buttons – on platform – side

    Now, however, you can plunk an armor button atop the cap:

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

    With any number of sides:

    Chain Mail Armor - 6 sided
    Chain Mail Armor – 6 sided

    Up to a truncated cone:

    Chain Mail Armor - 24 sided
    Chain Mail Armor – 24 sided

    The flat tip makes the button more durable and user-friendly, but you can make it a bit more pointy if you favor that sort of thing. The button adds 6 mm to the link base, making armor links 10.4 mm tall.

    Other printable stuff could fit on that cap: letters, decorations, widgets, whatever.

    I think square armor buttons look ever so imposing when they’re arrayed in a sheet:

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

    The general idea being that you could attach the armor sheet to a cloth / leather backing to form a gauntlet or greave; the border of bottom links around the button array should serve for that purpose.

    The plastic prints just like the model and pops off the M2’s platform ready to use, with no finishing required:

    Chain Mail Armor - square on desk
    Chain Mail Armor – square on desk

    The two-color effect came from hot-swapping black filament as the red PLA ran out. The 6×6 armor button array and the 7×7 connecting link array holding it together required 14 meters of filament and I guesstimated the red spool held 9 meters: I was ready when the last of the red vanished just after completing the bridging layer under the flat caps. Filament swaps work reasonably well; I’d hate to do that on a production basis.

    If you don’t mind my saying so, everybody thinks it’s spectacular:

    Chain Mail Armor - square on arm
    Chain Mail Armor – square on arm

    The sheet has a definite “grain” defined by the orientation of the bottom links, making it far more bendy in one direction than the other:

    Chain Mail Armor - square rolled
    Chain Mail Armor – square rolled

    The sheet layout orients the more-bendy direction along the M2’s (longer) Y axis, so that sheets can wrap snugly around your arm (or leg) and extend straight-ish along the bones in the other direction. That should be configurable, I think.

    There’s an option to rotate the links by 45° to produce diamond-theme arrays:

    Chain Mail Armor - diamond - 8 sided
    Chain Mail Armor – diamond – 8 sided

    Which would make good patch armor, if you’re into that sort of thing:

    Chain Mail Armor - diamond on hand
    Chain Mail Armor – diamond on hand

    Those have octagonal buttons, which IMHO don’t look nearly as crisp as the four-sided version.

    Ah! I should generalize the diamond rotation option to select all four useful rotations.

    The 6×6 square sheet requires three hours on the M2, with the intial print time estimates being low by nearly a factor of two. The M2 has a 200×250 mm platform and I’ll definitely try a full-size array just to see how it works.

    The OpenSCAD source code, which stands badly in need of refactoring:

    // Chain Mail Armor Buttons
    // Ed Nisley KE4ZNU - November 2014
    
    Layout = "Build";			// Link Button LB 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;			// make holes end cleanly
    
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    
    //-------
    // Dimensions
    
    //- Set maximum sheet size
    
    SheetSizeX = 70;
    SheetSizeY = 80;
    
    //- Diamond or rectangular sheet?
    
    Diamond = false;					// true = rotate 45 degrees, false = 0 degrees for square
    ArmorButton = true;					// true = build button atop cap
    
    // 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));
    
    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
    
    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," Total: ",NumLinksX*NumLinksY));
    
    //- Armor button base
    
    CapThick = BarThick;
    
    ButtonHeight = BaseHeight + BarClearance + CapThick;
    echo(str("ButtonHeight: ",ButtonHeight));
    
    //- Armor ornament size & shape
    
    ArmorSides = 4;
    ArmorAngle = true ? 180/ArmorSides : 0;			// rotate half a side?
    
    ArmorThick = IntegerMultiple(6,ThreadThick);	// keep it relatively short
    
    ArmorOD = 1.1 * BaseSide;						// tune for best fit at base
    
    ArmorID = 10 * ThreadWidth;						// make the tip wide & 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 base link
    
    module BaseLink() {
    
    render()
    	rotate(Diamond ? 45 : 90)			// 90 = more bendy around X axis
    		difference() {
    			translate([0,0,BaseHeight/2]) {
    				difference(convexity=2) {
    					intersection() {		// outside shape
    						cube([BaseSide,BaseSide,BaseHeight],center=true);
    						rotate(45)
    							cube([BaseOutDiagonal,BaseOutDiagonal,BaseHeight],center=true);
    					}
    					intersection() {		// inside shape
    						cube([(BaseSide - 2*BarWidth),
    							  (BaseSide - 2*BarWidth),
    							  (BaseHeight + 2*Protrusion)],
    							 center=true);
    						rotate(45)
    							cube([BaseInDiagonal,
    								  BaseInDiagonal,
    								  (BaseHeight +2*Protrusion)],
    								 center=true);
    					}
    				}
    			}
    
    			translate([0,0,(BaseHeight/2 + BarThick)])
    				cube([(BaseSide - 2*BarWidth - 2*BarWidth/sqrt(2)),
    					  (2*BaseSide),
    					  BaseHeight],
    					 center=true);
    			translate([0,0,(BaseHeight - BaseHeight/2 - BarThick)])
    				cube([(2*BaseSide),
    					  (BaseSide - 2*BarWidth - 2*BarWidth/sqrt(2)),
    					  BaseHeight],
    					 center=true);
    		}
    }
    
    //-------
    // Create button link
    
    module ButtonLink() {
    
    render()
    	rotate(Diamond ? 45 : 90)			// 90 = more bendy around X axis
    		union() {
    			difference() {
    				translate([0,0,ButtonHeight/2])		// outside shape
    					intersection() {
    						cube([BaseSide,BaseSide,ButtonHeight],center=true);
    						rotate(45)
    							cube([BaseOutDiagonal,BaseOutDiagonal,ButtonHeight],center=true);
    					}
    				translate([0,0,(BaseHeight + BarClearance - Protrusion)/2])
    					intersection() {		// inside shape
    						cube([(BaseSide - 2*BarWidth),
    								(BaseSide - 2*BarWidth),
    								(BaseHeight + BarClearance + Protrusion)],
    								center=true);
    						rotate(45)
    							cube([BaseInDiagonal,
    									BaseInDiagonal,
    									(BaseHeight + BarClearance + Protrusion)],
    									center=true);
    				}
    
    				translate([0,0,((BarThick + 2*BarClearance)/2 + BarThick)])
    					cube([(BaseSide - 2*BarWidth - 2*BarWidth/sqrt(2)),
    						(2*BaseSide),
    						BarThick + 2*BarClearance],
    						center=true);
    
    				translate([0,0,(BaseHeight/2 - BarThick)])
    					cube([(2*BaseSide),
    						(BaseSide - 2*BarWidth - 2*BarWidth/sqrt(2)),
    						BaseHeight],
    						center=true);
    
    			}
    
    			if (ArmorButton)
    				translate([0,0,(ButtonHeight - Protrusion)])		// armor on cap
    					rotate(ArmorAngle)
    					cylinder(d1=ArmorOD,
    							 d2=ArmorID,
    							 h=(ArmorThick + Protrusion),
    							 $fn=ArmorSides);
    		}
    }
    
    //-------
    // Build it!
    
    ShowPegGrid();
    
    if (Layout == "Link") {
    	BaseLink();
    }
    
    if (Layout == "Button") {
    	ButtonLink();
    }
    
    if (Layout == "LB") {
    	ButtonLink();
    	translate([LinkSpacing,LinkSpacing,0])
    		BaseLink();
    }
    
    if (Layout == "Build") {
    	for (ix = [0:(NumLinksX - 1)],
    		 iy = [0:(NumLinksY - 1)])
    			assign(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)
    					if ((ix + iy) % 2)						// armor at odd,odd & even, even points
    						ButtonLink();
    					else
    						BaseLink();							// connectors otherwise
    				else
    					if ((iy % 2) && (ix % 2))				// armor at odd,odd points
    						ButtonLink();
    					else if ((!(iy % 2) && !(ix % 2)))		// connectors at even,even points
    						BaseLink();
    }
    
    
  • Improved Chain Mail Link

    The rectangular posts in my chain mail resemble Zomboe’s original design, but with dimensions computed directly from the bar (and, thus, thread) widths and thicknesses to ensure good fill and simple bridging:

    Chain Mail Link
    Chain Mail Link

    They fit together well, but the angled post edges make the bridge threads longer than absolutely necessary along the outside edge of each link:

    Chain Mail Sheet - detail
    Chain Mail Sheet – detail

    A bit of fiddling produces a squared-off version:

    Chain Mail Link - Improved Posts
    Chain Mail Link – Improved Posts

    Which nest together like this:

    Chain Mail - Improved Posts - Bottom View
    Chain Mail – Improved Posts – Bottom View

    Now all the bridge threads have the same length, which should produce better results.

    The OpenSCAD source code for the link:

    module BaseLink() {
    
    	render(convexity=2)
    		difference() {
    			translate([0,0,BaseHeight/2]) {
    				difference(convexity=2) {
    					intersection() {		// outside shape
    						cube([BaseSide,BaseSide,BaseHeight],center=true);
    						rotate(45)
    							cube([BaseOutDiagonal,BaseOutDiagonal,BaseHeight],center=true);
    					}
    					intersection() {		// inside shape
    						cube([(BaseSide - 2*BarWidth),
    							  (BaseSide - 2*BarWidth),
    							  (BaseHeight + 2*Protrusion)],
    							 center=true);
    						rotate(45)
    							cube([BaseInDiagonal,
    								  BaseInDiagonal,
    								  (BaseHeight +2*Protrusion)],
    								 center=true);
    					}
    				}
    			}
    
    			translate([0,0,(BaseHeight/2 + BarThick)])
    				cube([(BaseSide - 2*BarWidth - 2*BarWidth/sqrt(2)),
    					  (2*BaseSide),
    					  BaseHeight],
    					 center=true);
    			translate([0,0,(BaseHeight - BaseHeight/2 - BarThick)])
    				cube([(2*BaseSide),
    					  (BaseSide - 2*BarWidth - 2*BarWidth/sqrt(2)),
    					  BaseHeight],
    					 center=true);
    		}
    }
    
  • Kenmore 158: LED Strip Light Cable Clips

    Commercial LED strip lights for sewing machines mount their cables with little stick-on anchors and cable ties. I wasn’t happy with the cable tie thing and finally figured this out:

    Kenmore 158 - LED strip light cable clips
    Kenmore 158 – LED strip light cable clips

    The clips have that size & shape because they fit exactly atop some pre-cut foam squares from the Tape Lookaside Buffer:

    LED strip light cable clips
    LED strip light cable clips

    You can see the shape better in the solid model:

    LED Cable Clips
    LED Cable Clips

    The central bollard has a slight taper to retain the cable, the quarter-posts are straight, and they’re both twice the cable diameter tall. The clearance between the center and corner posts at the top matches the cable diameter, so there’s a bit of bending room at the bottom, and, with the cable bent around the center, it won’t fall out on its own.

    The cute coaxial cable I’m misusing for the LED strips measures just shy of 2 mm, making these into little bitty things. The corner posts seem surprisingly strong, despite 3D printing’s reputation for crappy quality; I haven’t been able to break one off with more effort than seemed warranted.

    The OpenSCAD source code:

    // LED Cable Clips
    // Ed Nisley - KE4ZNU - October 2014
    
    //- Extrusion parameters must match reality!
    
    ThreadThick = 0.20;
    ThreadWidth = 0.40;
    
    HoleWindage = 0.2;			// extra clearance
    
    Protrusion = 0.1;			// make holes end cleanly
    
    AlignPinOD = 1.70;			// assembly alignment pins: filament dia
    
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    
    //----------------------
    // Dimensions
    
    Base = [12.0,12.0,IntegerMultiple(2.0,ThreadThick)];	// base over sticky square
    
    CableOD = 2.0;
    
    BendRadius = 3.0;
    
    Bollard = [BendRadius,(sqrt(2)*Base[0]/2 - CableOD - BendRadius),2*CableOD];
    B_BOT = 0;
    B_TOP = 1;
    B_LEN = 2;
    
    NumSides = 5*4;
    
    //----------------------
    // Useful routines
    
    module PolyCyl(Dia,Height,ForceSides=0) {			// based on nophead's polyholes
    
      Sides = (ForceSides != 0) ? ForceSides : (ceil(Dia) + 2);
    
      FixDia = Dia / cos(180/Sides);
    
      cylinder(r=(FixDia + HoleWindage)/2,
               h=Height,
               $fn=Sides);
    }
    
    module ShowPegGrid(Space = 10.0,Size = 1.0) {
    
      RangeX = floor(100 / Space);
      RangeY = floor(125 / Space);
    
    	for (x=[-RangeX:RangeX])
    	  for (y=[-RangeY:RangeY])
    		translate([x*Space,y*Space,Size/2])
    		  %cube(Size,center=true);
    
    }
    
    //----------------------
    // Build it
    
    ShowPegGrid();
    
    intersection() {
    	translate([0,0,(Base[2] + Bollard[2])/2])			// overall XYZ outline
    		cube(Base + [0,0,Bollard[2]],center=true);
    	
    	union() {
    		translate([0,0,Base[2]/2])						// oversize mount base
    			scale([2,2,1])
    				cube(Base,center=true);
    				
    		for (i=[-1,1] , j=[-1,1]) {						// corner bollards
    			translate([i*Base[0]/2,j*Base[1]/2,(Base[2] - Protrusion)])
    				rotate(180/NumSides)
    				cylinder(r=Bollard[B_BOT],h=(Bollard[B_LEN] + Protrusion),center=false,$fn=NumSides);
    
    		translate([0,0,(Base[2] - Protrusion)])			// center tapered bollard
    			cylinder(r1=Bollard[B_BOT],r2=Bollard[B_TOP],h=(Bollard[B_LEN] + Protrusion),center=false,$fn=NumSides);
    		}
    	}
    }
    

    Now that I think of it, maybe a round clip would look nicer. The central bollard would stay, but the circular outside rim could have three cutouts. When these fall off, I’ll give that a try.

    They may be square and clunky, but they look much better than Gorilla Tape…

     

  • Low Voltage Interface Adapter Plate

    The Dell GX270 chassis has a small support plate under the CPU, evidently to support the heatsink and fan:

    Optiplex GX270 CPU heatsink mount
    Optiplex GX270 CPU heatsink mount

    It slides neatly into those clips on the system board tray, but it’s not actually locked into position. I think that allows it to slide around a bit under the system board, providing vertical support without constraining the board’s horizontal position. Anyhow, it looked like the easiest way to support the prototyping board that will hold the low voltage interface circuitry.

    By some mischance, I found a nice aluminum plate exactly the right width, so only one side needed a saw cut and squaring. Coordinate drilling four #6 clearance holes matched the support:

    LV Interface Adapter Plate - drilling
    LV Interface Adapter Plate – drilling

    That corner of the tray had another system board retaining clip, but rather than bashing it flat, I just sawed a slit in the plate so it can slide right into position. Note the perfect alignment of that screw hole:

    LV Interface Adapter Plate - retainer
    LV Interface Adapter Plate – retainer

    I love it when all my mistakes cancel out!

    Four more holes matched the prototyping circuit board and, while I had some epoxy mixed up for another part, I fastened four standoffs over the holes. A washer under each original screw soaked up exactly enough space that the screws barely indented the case and, as if by magic, hold the support plate firmly in place:

    LV Interface Adapter Plate - installed
    LV Interface Adapter Plate – installed

    Of course, that means I must remove the circuit board to get the tray out, but the AC interface board must also come out, so we’re not talking a spur-of-the-moment operation.

    The switch in the lower left corner is the original Dell “intrusion monitoring” switch harvested from a complex metal stamping in the diagonally opposite corner of the case. It’s epoxied to the case wall, with the plunger contacting a shim epoxied to the top of the case, and will eventually disconnect the AC line power from the drive electronics: case open = switch closed = lethal power off.

  • ET227 Transistor: Monster Heatsink Mounting

    Back in the day, heatsinks like this sat atop Moah Powah Pentium CPUs:

    ET227 transistor on heatsink
    ET227 transistor on heatsink

    I picked it because the hulking ET227 transistor fit neatly on its backside, it seemed capable of handling 30 to 50 W of power, and I have several of them in the Big Box o’ Heatsinks. No careful thermal analysis was involved…

    Mounting it on the polycarbonate sheet inside the repurposed GX270 case involved drilling & tapping a pair of 6-32 holes in one side:

    ET227 Heatsink - tapping
    ET227 Heatsink – tapping

    That’s not rigid tapping on a Sherline, it’s aligning a hand-turned tap in the spindle bore. Sorry.

    And, yeah, you’re not supposed to leave the semiconductors mounted when you’re drilling the heatsink. I figure there’s nothing I can possibly do without using a hammer that will bother that transistor in the slightest. What, me worry?

    The transistor collector runs at line voltage, which means the entire heatsink will pose a lethal shock hazard. I thought about isolating the collector and failed to come up with anything I’d trust to be both thermally conductive and electrically insulating over the long term; the screw heads must be isolated from the collector plate, too.

    The screws stick out below the polycarbonate sheet, just above the grounded EMI shell lining the case, so I flattened them a bit:

    ET227 Heatsink - mounting screws
    ET227 Heatsink – mounting screws

    The simple rectangular strip to the rear of the chassis mounting clips is just slightly thicker than the screw heads, so they can’t possibly contact the case:

    Chassis Clips
    Chassis Clips

    It gets glued to the underside of the nearly invisible sheet:

    ET227 heatsink - gluing screw shield
    ET227 heatsink – gluing screw shield

    With Kapton tape over the heads, Just In Case:

    ET227 Heatsink - mounted
    ET227 Heatsink – mounted

    It makes a nice linear counterpoint to the jumble of AC interface wiring:

    AC Interface Chassis
    AC Interface Chassis

    The insulating sheet on the case lid came from the bottom of the original GX270 system board, where I think it served much the same purpose. It’s surely not rated for AC line voltages, but the thought must count for something:

    AC Interface Chassis
    AC Interface Chassis

    More of the parts are flying in formation…

  • AC Interface Chassis Mounting Clips

    The Dell GX270 system board mounts on a tray, latching into small tabs, with a single screw locking it in place. The tray then slides into the metal EMI shield / case, latching onto more tabs, with a spring-loaded pair of tabs snapping into a slot under the green latch:

    Optiplex GX270 - system board tray
    Optiplex GX270 – system board tray

    All that is well and good for a mass-production PC system board, but poses a problem for mounting anything else: there’s no room for screw heads below the tray, adhesives really don’t bond to slightly flexible aluminum sheets, and I definitely can’t do large-scale precision metal bending.

    So a cheat seems in order. The general idea is to support a 6 mm polycarbonate sheet on clips that slide under the small tabs along the front, support the sheet on the rear tabs, and secure it with the screw. That’s thick enough to allow tapping holes for mounting screws, so everything else can mount to the sheet.

    The sheet fits around the power supply on the right, protrudes over the rear of the tray to the back of the case (with a recess around the green latch), and clears the hinge assembly on the left. There are no dimensions, as it’s all done by eye with the Joggy Thing.

    AC Chassis Shaping
    AC Chassis Shaping

    A drive bay EMI plug from a long-discarded PC provided some nice springy steel strips that slide neatly under those tray tabs:

    Drive EMI shield
    Drive EMI shield

    That actually took a bit of trial-and-error:

    AC Chassis mounting brackets - practice makes perfect
    AC Chassis mounting brackets – practice makes perfect

    My first attempts used slightly thicker steel that didn’t fit nearly as well, plus I wasn’t quite sure how wide they should be.

    As with nearly all plastic doodads around here, the white plastic mounting clips / brackets come from the M2:

    Chassis Clips
    Chassis Clips

    The two brackets in the middle of the solid model slide around the tabs at the rear corners of the tray and capture the bent-over top section below the polycarbonate sheet.

    The strip in the rear goes around the screws holding the heatsink to the sheet; more on that later.

    The PLA brackets get themselves glued to the sheet with IPS #4 solvent adhesive, a hellish mixture of chlorinated hydrocarbons that attacks most plastics with gleeful enthusiasm. I positioned the brackets on the tray, slobbered adhesive on their tops, slapped the polycarbonate sheet in place, and applied clamps:

    AC Chassis - gluing bracket blocks
    AC Chassis – gluing bracket blocks

    The final bonds weren’t as uniform as I’d like, but they seem rugged enough. The lip along the rear of the tray was slightly higher on the left edge, which may have interfered with the clamping pressure; it’s obviously not a controlled dimension.

    The tapped holes in the sheet accommodate screws for various bits & pieces.

    All in all, that worked out pretty well…

    The OpenSCAD source code:

    // AC Interface sheet mounting brackets
    // Ed Nisley - KE4ZNU - August 2014
    
    Layout = "Build";		// FrontClip RearClip HeatSink Build
    
    Gap = 5.0;					// between Build objects
    
    //- Extrusion parameters must match reality!
    
    ThreadThick = 0.20;
    ThreadWidth = 0.40;
    
    HoleWindage = 0.2;			// extra clearance
    
    Protrusion = 0.1;			// make holes end cleanly
    
    AlignPinOD = 1.70;			// assembly alignment pins: filament dia
    
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    
    //----------------------
    // Dimensions
    
    FC_Block = [45.0,30.0,IntegerMultiple(5.6,ThreadThick)];
    FC_Retainer = [15.5,9.0,3.0,15.0];					// central section: L,W,H, inset from front
    
    RC_Block = [30.0,25.0,IntegerMultiple(5.6,ThreadThick)];
    RC_RecessOffset = [9.0,5.0,IntegerMultiple(4.8,ThreadThick)];	// X,Y,thickness
    RC_SlotWidth = 2.5;
    
    HS_Insulation = [80.0,16.0,2.5];
    HS_Hole = [8.0,40.0];					// screw clearance dia,on-center
    
    //----------------------
    // Useful routines
    
    module PolyCyl(Dia,Height,ForceSides=0) {			// based on nophead's polyholes
    
      Sides = (ForceSides != 0) ? ForceSides : (ceil(Dia) + 2);
    
      FixDia = Dia / cos(180/Sides);
    
      cylinder(r=(FixDia + HoleWindage)/2,
               h=Height,
               $fn=Sides);
    }
    
    module ShowPegGrid(Space = 10.0,Size = 1.0) {
    
      RangeX = floor(100 / Space);
      RangeY = floor(125 / Space);
    
    	for (x=[-RangeX:RangeX])
    	  for (y=[-RangeY:RangeY])
    		translate([x*Space,y*Space,Size/2])
    		  %cube(Size,center=true);
    
    }
    
    //----------------------
    // Front clips
    
    module FrontClip() {
    
    	difference() {
    		translate([0,0,FC_Block[2]/2])
    			cube(FC_Block,center=true);
    
    		translate([0,(FC_Retainer[3] - FC_Block[1]/2),(FC_Retainer[2] + FC_Block[2]/2)])
    			cube([(FC_Block[0] - 12*ThreadWidth),FC_Retainer[1],FC_Block[2]],center=true);
    
    		translate([0,FC_Retainer[3] - FC_Retainer[1]/2,FC_Block[2]/2])
    			cube([FC_Retainer[0],FC_Block[1],2*FC_Block[2]],center=true);
    	}
    
    }
    
    //----------------------
    // Rear clips
    
    module RearClip(Hand="Left") {
    
    HandSign = (Hand == "Left") ? -1 : 1;
    
    	difference() {
    		translate([0,0,RC_Block[2]/2])
    			cube(RC_Block,center=true);
    
    		translate([0,RC_RecessOffset[1],RC_RecessOffset[2] + RC_Block[2]/2])
    			cube([RC_Block[0] - 2*RC_RecessOffset[0],
    				  RC_Block[1],
    				  RC_Block[2]],center=true);
    
    		translate([HandSign*(RC_Block[0]/2 - RC_RecessOffset[0]),
    				   RC_RecessOffset[1],
    				   0])
    			cube([RC_SlotWidth,RC_Block[1],3*RC_Block[2]],center=true);
    
    	}
    
    }
    
    //----------------------
    // Heatsink bumper
    
    module HeatSink() {
    
    	difference() {
    		translate([0,0,HS_Insulation[2]/2])
    			cube(HS_Insulation,center=true);
    
    	for (x=[-1,1])
    		translate([x*HS_Hole[1]/2,0,-HS_Insulation[2]])
    			PolyCyl(HS_Hole[0],3*HS_Insulation[2],8);
    	}
    
    }
    
    ShowPegGrid();
    
    if (Layout == "FrontClip") {
    	FrontClip();
    }
    
    if (Layout == "RearClip") {
    	RearClip("Left");
    }
    
    if (Layout == "HeatSink") {
    	HeatSink();
    }
    
    if (Layout == "Build") {
    	for (x=[-1,1]) {
    		translate([x*(Gap + FC_Block[0])/2,(Gap + FC_Block[1])/2,0])
    			FrontClip();
    		translate([x*(Gap + RC_Block[0])/2,-(Gap + RC_Block[1])/2,0])
    			RearClip((x == -1) ? "Left" : "Right");
    	}
    	translate([0,-(RC_Block[1] + HS_Insulation[1]/2 + 3*Gap/2),0])
    		HeatSink();
    }