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.

Month: April 2015

  • Miniature PETG Printed Chain Mail

    The small patch of chain mail early in the M2’s PETG conversion had links with four threads along each bar:

    Chain Mail - PETG patches atop PLA patch
    Chain Mail – PETG patches atop PLA patch

    Dropping the bars to 3.3 threads wide produced a slightly smaller patch:

    Chain mail - 6 and 4 thread - detail
    Chain mail – 6 and 4 thread – detail

    The bars on the platform are 1.6 mm = 4 threads wide, because I’ve forced the thread width to 0.40 for that layer:

    Chain Mail - 3.3 wide - Slic3r preview - bottom layer
    Chain Mail – 3.3 wide – Slic3r preview – bottom layer

    The remainder are closer to 1.4 mm = 3.3 threads, with the preview showing Slic3r allowed a narrow gap that doesn’t appear in real life:

    Chain Mail - 3.3 wide - Slic3r preview - link bridge layer
    Chain Mail – 3.3 wide – Slic3r preview – link bridge layer

    What’s important about this is that the bridging worked perfectly: all the links emerged free of their neighbors and the patch flexed along both axes.

    Chain mail - 6 and 4 thread
    Chain mail – 6 and 4 thread

    I tried this on one layer of Elmer’s White Glue, diluted 1:3 with water, and the links bonded firmly. I’d had some trouble with a few links popping off the usual hairspray after the first few layers, so I decided to try something different.

    The fine hair strands have mostly Gone Away, perhaps due to using Concentric infill.

    All in all, PETG looks pretty good, even if it’s just as hard to photograph as red PLA.

    Update: You may prefer the source code as a GitHub gist.

    The OpenSCAD source code:

    // 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 = 55;	// 170 for full sheet on M2
    SheetSizeY = 55;	// 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 = 4 * ThreadThick;		// vertical clearance above & below bars
    
    VertexHack = 0;						// 0 = no, 1 = 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)],
    								center=true);
    						rotate(45)
    							cube([BaseInDiagonal,
    									BaseInDiagonal,
    									(BaseHeight + BarClearance + 0*ThreadThick + VertexHack*Protrusion/2)],
    									center=true);
    					}
    
    				translate([0,0,((BarThick + 2*BarClearance)/2 + BarThick)])		// openings for bars
    					cube([(BaseSide - 2*BarWidth - 2*BarWidth/sqrt(2) - VertexHack*Protrusion/2),
    						(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),
    						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);
    
  • Monthly Science: Ground and Air Temperatures

    Looks like Spring really is on the way:

    Ground and Air Temperatures - 2015-03-31
    Ground and Air Temperatures – 2015-03-31

    The blue trace shows the groundwater temperature at the inlet pipe. The minimum values should be pretty close to the actual ground temperature about four feet down, which seems roughly constant at 37 °F for March.

    The black trace comes from a datalogger tucked in the dirt under the concrete patio, so it feels some air temperature variations, too.

    The red trace comes from the datalogger dangling in the well pit in the back yard, directly under one of the vent holes, so it’s recording the air temperature in a below-grade chamber.

    The green trace shows the attic air temperature, which is strongly influenced by sun on the (white) asphalt shingle roof. That said, the air temperature gets a lot lower than any of the below-grade loggers; it’s fair to say they’re recording something fairly close to the actual ground temperature.