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: Thing-O-Matic

Using and tweaking a Makerbot Thing-O-Matic 3D printer

  • 3D Printing Presentation for Long Island LUG

    Helmet mirror mount - 3D model - Fit layout
    Helmet mirror mount – 3D model – Fit layout

    The folks at the Long Island LUG asked me to give my DIY 3D Printing & the Makerbot Thing-O-Matic presentation, which will happen 11 October at 8 pm. Details & directions at lilug.org.

    Should you happen to be in the area that evening, drop in and pick up a tchotchke!

  • Reversal Zits: Early Action Variations

    While cranking out some Tux Cookie Cutters, I varied the Reversal settings to see what effect they’d have on a single object with a smooth perimeter. I’d previously settled on 25 rpm for 125 ms with no early action, so this series tests three different times with early action turned on.

    Position 1, where the perimeter threads join. Yes, I have Jitter activated and cranked up to something like 10, but it obviously has no effect on this object:

    Position 1 - Reversal 125 100 50 - early
    Position 1 – Reversal 125 100 50 – early

    Position 2, where the nozzle enters from the outside to start a new thread. The snot hanging off the end makes for an ugly wad:

    Position 2 - Reversal 125 100 50 - early
    Position 2 – Reversal 125 100 50 – early

    Position 3, another nozzle entry point:

    Position 3 - Reversal 125 100 50 - early
    Position 3 – Reversal 125 100 50 – early

    Early Reversal action simply doesn’t work well. With retraction times sufficient to prevent drooling, stopping the extruder before the end of the thread produces unacceptable gaps and starting it before reaching the thread produces hanging snots when the nozzle passes over an existing wall.

    Shorter retraction times produce strands all over the object, because the extruder still contains pressurized plastic and drools.

    I’d previously discovered, although I didn’t write up, that unbalanced Reversal times didn’t provide any benefit: inhale and exhale times must be essentially equal to prevent either starving the first part of each thread or serious drooling. So there’s really only one degree of freedom: the total volume of plastic = rpm x duration.

    Perhaps having separate early action times would help: adjust the shutdown and startup delay times independently of the total Reversal inhale/exhale time. Right now, those delays are simply the inhale/exhale times, evidently assuming clean cutoffs and startups, which obviously isn’t the case.

    And, alas, the Reversal Threshold bug remains unfixed, so you (well, I) can’t tell Reversal to not operate across short motions like the end of one thread and the not-quite-adjacent start of the next.

  • Thing-O-Matic: Axis Calibration vs. ABS Shrinkage

    In the process of adapting my HT GPS interface to a Wouxun KG-UV3D radio, I printed some trial-fit pieces that consistently came out a little short. A bit of division showed that the larger pieces tended to be small in the X & Y axes by about 0.5%. This makes no difference for most 3D printed objects, but in this case the pieces must match up precisely with the radio’s existing battery interface layout. Half a percent matters a lot across a 75 mm part.

    The advice found with most calibration pieces seems to boil down to fudging the printer’s steps/mm setting to make the answer come out right. The default Thing-O-Matic calibration (in machines/thingomatic.xml, wherever that’s hidden in your installation) looks like this:

    <axis id="x" length="106" maxfeedrate="6000" homingfeedrate="2500" stepspermm="47.069852" endstops="min"/>  <!-- Pulley dia: 10.82mm / 1/8 step = 1/(10.82 * pi / 1600) -->
    <axis id="y" length="120" maxfeedrate="6000" homingfeedrate="2500" stepspermm="47.069852" endstops="min"/>  <!-- Pulley dia: 10.82mm / 1/8 step = 1/(10.82 * pi / 1600) -->
    

    You will, of course, have twiddled the maxfeedrate, homingfeedrate, and maybe even the comments to make the answers work on your machine.

    Nophead slapped me upside the head when I made the same mistake that produced the stock stepspermm values: the pulley moves the belt by a fixed number of teeth on each revolution, so you just multiply by the belt tooth pitch to find the distance per revolution. Divide that into the number of (micro)steps per revolution and you get the exact stepspermm value. The stock MBI pulleys have 17 teeth and the belt has a 2 mm tooth pitch, so:

    47.05882 step/mm = 1600 step / (17 * 2 mm)

    That differs from the stock value by not very much at all:

    0.999766 = 47.05882 / 47.069852

    Given that these steppers aren’t losing steps (don’t start with me, you know how I get), I’m quite confident that the X and Y stages move by exactly the commanded distance every time.

    The printer uses a heated build plate and the first layer is 0.33 mm, give-or-take about 0.05 mm, and the objects come out with essentially straight vertical walls. However, the walls aren’t quite perfect, tending to be a bit larger where they contact the plate, and I finally asked the obvious question (abs plastic shrinkage), which produces, among many other hits, that useful table.

    The money quote is that ABS shrinks just about exactly 0.5% as it cools. That’s modulo the starting temperature, the molding process, and so forth and so on, but it’s a pretty nice match.

    Therefore, fudging the printer’s scale isn’t appropriate, because that affects everything you might do with it. Such as, for example, the initial homing sequence, which depends on fairly precise locations that must match up with reality and have no shrinkage problems whatsoever.

    Skeinforge’s Scale plugin applies a factor to the object, so that’s (probably) a more appropriate location for this adjustment. The myriad SF settings get broken down by Craft (extrusion, milling, whatever) and material (ABS, PLA, whatever), so if you can keep all that straight, then you can apply the appropriate Scale for each process and material.

    The Scale doc may seem a tad sparse, but the plugin does have separate settings for the XY plane and the Z height. The latter (probably) doesn’t need scaling, because the nozzle height sets the actual extrusion level; the top layer or two will stretch to make the vertical size come out right as the object cools while it’s a-building.

    I’ll toss a 1.005 scale factor into the XY mix and see what horrors that unleashes by way of unintended consequences.

    More on the radio interface & suchlike in a while…

  • NB-5L Holder: Coil Springs

    Having twice failed to make music-wire springs work, I rummaged around in the Big Box o’ Small Springs with more diligence and unearthed a pair of coil compression springs that exactly match the pin ferrule OD. Twiddling the solid model produced this longer & flatter version with in-line springs and cylindrical plugs holding them in place:

    NB-5L Holder - Coil spring - solid model
    NB-5L Holder – Coil spring – solid model

    A closeup of the pin arrangement, which now looks very clean and easy to build:

    NB-5L Holder - Coil spring - detail
    NB-5L Holder – Coil spring – detail

    The OpenSCAD code will print out a quartet of plugs (pick the best two), but having thought of that too late, I turned a pair from a random acrylic rod:

    Turning spring plugs
    Turning spring plugs

    I did remember to solder the wires before assembling the pins this time…

    Pin assemblies
    Pin assemblies

    Because the pins now index on their shoulder with the springs at partial extension, I set the drills into the pin vice vise [Update: One can probably be arrested for pin vice] to produce depths displayed by the OpenSCAD program before reaming out the  printed holes:

    ECHO: "Depth to taper end: 24.72"
    ECHO: "         ferrule end: 15.62"
    ECHO: "         plug end: 4.62"
    

    Then glue the pin plugs into the holder and the flat lid atop the case to capture the battery, clamping everything to the corner of the Sherline’s countertop:

    Gluing pin assemblies
    Gluing pin assemblies

    And it Just Worked: nice travel between the limits, smooth operation, it’s the way I should have done it from the beginning*. You knew that all along, right?

    Here are the three NB-5L Battery Holder versions, all snuggled up together. The longer and flatter coil-spring version sits on the right:

    Variations on an NB-5L holder theme
    Variations on an NB-5L holder theme

    Now I can take some data…

    The OpenSCAD source code:

    // Holder for Canon NB-5L Li-Ion battery
    // Ed Nisley KE4ZNU August 2011
    
    include </home/ed/Thing-O-Matic/lib/MCAD/units.scad>
    include </home/ed/Thing-O-Matic/lib/MCAD/boxes.scad>
    include </home/ed/Thing-O-Matic/Useful Sizes.scad>
    
    // Layout options
    
    Layout = "Build";					// Case Lid Plugs Show Build Fit
    
    //- Extrusion parameters - must match reality!
    //  Print with +2 shells and 3 solid layers
    
    ThreadThick = 0.33;
    ThreadWidth = 2.0 * ThreadThick;
    
    HoleWindage = 0.2;
    
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    
    Protrusion = 0.1;			// make holes end cleanly
    
    BuildOffset = 3.0;			// clearance for build layout
    
    //- Battery dimensions - rationalized from several samples
    //  Coordinate origin at battery corner by contact plates on bottom surface
    
    BatteryLength = 45.25;
    BatteryWidth = 32.17;
    BatteryThick =  7.85;
    
    ContactWidth = 2.10;
    ContactLength = 4.10;
    ContactRecess = 0.85;
    
    ContactOC = 3.18;			// center-to-center across contact face
    ContactOffset = 4.45;		// offset from battery edge
    ContactHeight = 3.05;		// offset from battery bottom plane
    
    AlignThick = 2.2;			// alignment recesses on contact face
    AlignDepth = 2.0;			// into face
    AlignWidth1 = 0.7;			// across face at contacts
    AlignWidth2 = 2.8;			//  ... other edge
    
    //- Pin dimensions
    
    PinTipDia = 1.6;
    PinTipLength = 10.0;
    
    PinTaperLength = 2.3;
    
    PinShaftDia = 2.4;
    PinShaftLength = 6.8;
    
    PinFerruleDia = 3.1;
    PinFerruleLength = 2.0;
    
    PinLength = PinTipLength + PinTaperLength + PinShaftLength + PinFerruleLength;
    
    ExtendRelax = 1.5 + ContactRecess;		// pin extension when no battery is present
    ExtendOvertravel = 1.0;					//  ... beyond engaged position
    
    //- Spring dimensions
    
    SpringDia = 3.1;						// coil OD
    SpringMax = 9.3;
    SpringLength = SpringMax - 0.3;			// slightly compressed
    SpringMin = 4.5;
    
    SpringPlugDia = 5.0;					// plug retaining the spring
    SpringPlugLength = IntegerMultiple(4.0,ThreadWidth);
    SpringPlugSides = 12;
    
    SpringTravel = ExtendRelax + ExtendOvertravel;
    
    //- Holder dimensions
    
    GuideRadius = ThreadWidth;						// friction fit ridges
    GuideOffset = 10;
    WallThick = 4*ThreadWidth;						// holder sidewalls
    
    BaseThick = 6*ThreadThick;			// bottom of holder to bottom of battery
    TopThick = 4*ThreadThick;			// top of battery to top of holder
    
    ThumbRadius = 10.0;			// thumb opening at end of battery
    
    CornerRadius = 3*ThreadThick;			// nice corner rounding
    
    CaseLength = SpringPlugLength + SpringLength + PinLength - ExtendRelax
    			+ BatteryLength + GuideRadius + WallThick;
    CaseWidth = 2*WallThick + 2*GuideRadius + BatteryWidth;
    CaseThick = BaseThick + BatteryThick + TopThick;
    
    //- XY origin at front left battery corner, Z on platform below that
    
    CaseLengthOffset = -(SpringPlugLength + SpringLength + PinLength - ExtendRelax);
    CaseWidthOffset = -(WallThick + GuideRadius);
    CaseThickOffset = BaseThick;
    
    LidLength = ExtendRelax - CaseLengthOffset;
    
    echo(str("Depth to taper end: ",
    		 (SpringPlugLength + SpringLength + PinFerruleLength + PinShaftLength + PinTaperLength)));
    echo(str("         ferrule end: ",
    		  (SpringPlugLength + SpringLength + PinFerruleLength)));
    echo(str("         plug end: ",SpringPlugLength));
    
    //----------------------
    // 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) {
    
      Range = floor(50 / Space);
    
    	for (x=[-Range:Range])
    	  for (y=[-Range:Range])
    		translate([x*Space,y*Space,Size/2])
    		  %cube(Size,center=true);
    
    }
    
    //-------------------
    
    //-- Guides for tighter friction fit
    
    module Guides() {
      	  translate([GuideOffset,-GuideRadius,CaseThickOffset])
    		PolyCyl(2*GuideRadius,(BatteryThick - Protrusion),4);
    	  translate([GuideOffset,(BatteryWidth + GuideRadius),CaseThickOffset])
    		PolyCyl(2*GuideRadius,(BatteryThick - Protrusion),4);
    	  translate([(BatteryLength - GuideOffset),-GuideRadius,CaseThickOffset])
    		PolyCyl(2*GuideRadius,(BatteryThick - Protrusion),4);
    	  translate([(BatteryLength - GuideOffset),(BatteryWidth + GuideRadius),CaseThickOffset])
    		PolyCyl(2*GuideRadius,(BatteryThick - Protrusion),4);
    	  translate([(BatteryLength + GuideRadius),GuideOffset/2,CaseThickOffset])
    		PolyCyl(2*GuideRadius,(BatteryThick - Protrusion),4);
    	  translate([(BatteryLength + GuideRadius),(BatteryWidth - GuideOffset/2),CaseThickOffset])
    		PolyCyl(2*GuideRadius,(BatteryThick - Protrusion),4);
    
    }
    
    //-- Contact pins (holes therefore)
    
    module PinShape() {
    
      union() {
    	cylinder(r=(PinTipDia + HoleWindage)/2,h=(PinTipLength + Protrusion),$fn=6);
    
    	translate([0,0,PinTipLength])
    	  cylinder(r=(PinShaftDia + HoleWindage)/2,
    			   h=(PinTaperLength + PinShaftLength + Protrusion),$fn=6);
    
    	translate([0,0,(PinLength - PinFerruleLength)])
    	  cylinder(r=(PinFerruleDia + HoleWindage)/2,
    				h=(PinFerruleLength + Protrusion),$fn=6);
    
    	translate([0,0,(PinLength)])
    	  cylinder(r=(SpringDia + HoleWindage)/2,
    				h=(SpringLength + Protrusion),$fn=6);
    
    	translate([0,0,(PinLength + SpringLength)])
    	  cylinder(r=(SpringPlugDia + HoleWindage)/2,h=(SpringPlugLength + Protrusion),$fn=SpringPlugSides);
    
    	  translate([0,0,(PinLength + SpringLength + SpringPlugLength)])
    	  cylinder(r=(SpringPlugDia + HoleWindage)/2,h=2*SpringPlugLength,$fn=SpringPlugSides);	// extend hole
      }
    
    }
    
    module PinAssembly() {
    
      translate([ExtendRelax,ContactOffset,CaseThickOffset + ContactHeight]) {
    	rotate([0,270,0]) {
    	  PinShape();												// pins
    	  translate([0,(2*ContactOC),0])
    		PinShape();
    	}
      }
    
    }
    
    //-- Case with origin at battery corner
    
    module Case() {
    
      difference() {
    
    	union() {
    
    	  difference() {
    		translate([(CaseLength/2 + CaseLengthOffset),
    				  (CaseWidth/2 + CaseWidthOffset),
    				  (CaseThick/2)])
    		  roundedBox([CaseLength,CaseWidth,CaseThick],CornerRadius); 	// basic case shape
    
    		translate([-ExtendOvertravel,-GuideRadius,CaseThickOffset])
    		  cube([(BatteryLength + GuideRadius + ExtendOvertravel),
    				(BatteryWidth + 2* GuideRadius),
    				(BatteryThick + Protrusion)]);						// battery space
    
    	  }
    
    	  Guides();
    
    	  translate([-ExtendOvertravel,-GuideRadius,BaseThick])
    		cube([(AlignDepth + ExtendOvertravel),
    			  (AlignWidth1 + GuideRadius),
    			  AlignThick]);											// alignment blocks
    	  translate([-ExtendOvertravel,
    				 (BatteryWidth - AlignWidth2),
    				 BaseThick])
    		cube([(AlignDepth + ExtendOvertravel),
    			  (AlignWidth2 + GuideRadius),
    			  AlignThick]);
    
    	}
    
    	translate([(-ExtendOvertravel),
    			   (CaseWidthOffset - Protrusion),
    			   (CaseThickOffset + BatteryThick)])
    	  cube([CaseLength,
    		    (CaseWidth + 2*Protrusion),
    		    (TopThick + Protrusion)]);								// battery access
    
    	translate([(CaseLengthOffset - Protrusion),
    			   (CaseWidthOffset - Protrusion),
    			   (CaseThickOffset + BatteryThick)])
    	  cube([(CaseLength + 2*Protrusion),
    		    (CaseWidth + 2*Protrusion),
    		    (TopThick + Protrusion)]);								// battery insertion allowance
    
    	translate([(BatteryLength - Protrusion),
    			    (CaseWidth/2 + CaseWidthOffset),
    			    (CaseThickOffset + ThumbRadius)])
    	  rotate([90,0,0])
    		rotate([0,90,0])
    		  cylinder(r=ThumbRadius,
    				   h=(WallThick + GuideRadius + 2*Protrusion),
    				   $fn=22);											// remove thumb notch
    
    	PinAssembly();
    
      }
    
    }
    
    module Lid() {
    
      difference() {
    	translate([0,0,(CaseThick/2 - BaseThick - BatteryThick)])
    	  roundedBox([LidLength,
    				 CaseWidth,CaseThick],CornerRadius);
    
    	translate([0,0,-(CaseThick/2)])
    	  cube([(LidLength + 2*Protrusion),
    		    (CaseWidth + 2*Protrusion),
    		    (CaseThick)],center=true);
      }
    
    }
    
    module PlugShape() {
    
      difference() {
    	cylinder(r=SpringPlugDia/2,h=SpringPlugLength,$fn=SpringPlugSides);
    	translate([0,0,-Protrusion])
    	  PolyCyl(PinShaftDia,(SpringPlugLength + 2*Protrusion),SpringPlugSides/2);
      }
    }
    
    module Plugs() {
      translate([0,ContactOC,0])
    	PlugShape();
      translate([0,-ContactOC,0])
    	PlugShape();
    }
    
    //-------------------
    // Build it!
    
    ShowPegGrid();
    
    if (Layout == "Case")
      Case();
    
    if (Layout == "Lid")
      Lid();
    
    if (Layout == "Plugs")
      Plugs();
    
    if (Layout == "Show") {								// reveal pin assembly
      difference() {
    	Case();
    
    	translate([(CaseLengthOffset - Protrusion),
    			   (CaseWidthOffset - Protrusion + WallThick + ContactOffset + ContactOC),
    			   (BaseThick + ContactHeight)])
    	  cube([(-CaseLengthOffset + Protrusion),
    			 (CaseWidth + 2*Protrusion),
    			 CaseThick + BaseThick - ContactHeight + Protrusion]);
    
    	translate([(CaseLengthOffset - Protrusion),
    			   (CaseWidthOffset - Protrusion),
    			   -Protrusion])
    	  cube([(-CaseLengthOffset + Protrusion),
    			 (WallThick + GuideRadius + ContactOffset + Protrusion),
    			 CaseThick]);
      }
    
      translate([ExtendRelax,ContactOffset,(CaseThickOffset + ContactHeight)]) {	// pins
    	rotate([0,270,0]) {
    	  %PinShape();
    //	  translate([0,(2*ContactOC),0])
    //		%PinShape();
    	}
      }
    
      translate([CaseLengthOffset,ContactOffset,(CaseThickOffset + ContactHeight)])
    	rotate([0,90,0])
    	  PlugShape();
    }
    
    if (Layout == "Build") {
      translate([-(CaseLength/2 + CaseLengthOffset),-(CaseWidthOffset - BuildOffset),0])
    	Case();
      translate([0,(CaseLengthOffset/2 - BuildOffset),0])
    	rotate([0,0,90])
    	  Lid();
      translate([CaseLengthOffset - SpringPlugDia,-CaseWidth/2,0])
    	Plugs();
      translate([(CaseLengthOffset + SpringPlugDia),-CaseWidth/2,0])	// extra set of plugs
    	Plugs();
    }
    
    if (Layout == "Fit") {
      Case();
      translate([(-LidLength/2 + ExtendRelax),
    			(CaseWidth/2 + CaseWidthOffset),
    			(BaseThick + BatteryThick)])
    	  Lid();
      translate([ExtendRelax,ContactOffset,CaseThickOffset + ContactHeight]) {	// pins
    	rotate([0,270,0]) {
    	  %PinShape();
    	  translate([0,(2*ContactOC),0])
    		%PinShape();
    	}
      }
    
      translate([CaseLengthOffset,
    			(ContactOffset + ContactOC),
    			(CaseThickOffset + ContactHeight)])
      rotate([0,90,0])
    	Plugs();
    
    }
    

    (*) Modulo, of course, simply buying a $5 charger from eBay and gutting it. What’s the fun in that?

  • NB-5L Holder: Plug Spring Holder

    After that failure, I thought maybe making the spring guide pocket a bit wider and seating the spring wire in a solid plug would work. A tweak to the OpenSCAD script produced this, along with slightly larger locating ribs around the battery compartment:

    Plug spring - solid model
    NB-5L Holder – Plug spring – solid model

    A closer look at the plug spring assembly:

    NB-5L Holder - Plug spring - detail
    NB-5L Holder – Plug spring – detail

    The hole is now slightly larger, distinct from the side of the pocket, and the partition between the pocket and plug (although something of a formality) seats the plug during assembly. The plug started out at 3 mm in diameter, as I intended to try ramming a heated wire into a length of filament. That worked, mmmm, somewhat poorly, so I drilled a hole in a length of filament:

    Music wire in filament plug
    Music wire in filament plug

    Unfortunately, that whole bodge didn’t work any better than the spring in the first pass at a holder, so I gave up and cast the springs in epoxy. The OpenSCAD code produces a 5 mm diameter hole that should provide a larger epoxy plate with better grip than the 3 mm holes in this picture, but it probably won’t make much difference:

    Music wire in epoxy plug
    Music wire in epoxy plug

    The alert reader will note a complete faceplant: yeah, I forgot to solder the wires into the pins before blobbing the springs in place. Fortunately, the epoxy cures slowly enough that I could:

    • Take the picture
    • Immediately see the obvious problem
    • Ease the music wire springs out just a tidge
    • Extract the pins
    • Quick-like-a-bunny solder wires to pins
    • Insert pins with proper polarity
    • Ease springs back in place

    I hate it when that happens…

    With springs & wires properly in place and the epoxy cured overnight, the pins had considerably better springiness and free motion than before, although they didn’t have quite the range of travel I wanted. I think the spring wire bent slightly on the first push, as the pins never came quite as far out after that.

    So this was a qualified success, but not a solid win. Time for round three…

    The OpenSCAD source code:

    // Holder for Canon NB-5L Li-Ion battery
    // Ed Nisley KE4ZNU August 2011
    
    include </home/ed/Thing-O-Matic/lib/MCAD/units.scad>
    include </home/ed/Thing-O-Matic/lib/MCAD/boxes.scad>
    include </home/ed/Thing-O-Matic/Useful Sizes.scad>
    
    // Layout options
    
    Layout = "Show";					// Case Lid Show Build Fit
    
    //- Extrusion parameters - must match reality!
    //  Print with +2 shells and 3 solid layers
    
    ThreadThick = 0.33;
    ThreadWidth = 2.0 * ThreadThick;
    
    HoleWindage = 0.2;
    
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    
    Protrusion = 0.1;			// make holes end cleanly
    
    BuildOffset = 3.0;			// clearance for build layout
    
    //- Battery dimensions - rationalized from several samples
    //  Coordinate origin at battery corner by contact plates on bottom surface
    
    BatteryLength = 45.25;
    BatteryWidth = 32.17;
    BatteryThick =  7.85;
    
    ContactWidth = 2.10;
    ContactLength = 4.10;
    ContactRecess = 0.85;
    
    ContactOC = 3.18;			// center-to-center across contact face
    ContactOffset = 4.45;		// offset from battery edge
    ContactHeight = 3.05;		// offset from battery bottom plane
    
    AlignThick = 2.2;			// alignment recesses on contact face
    AlignDepth = 2.0;			// into face
    AlignWidth1 = 0.7;			// across face at contacts
    AlignWidth2 = 2.8;			//  ... other edge
    
    //- Pin dimensions
    
    PinTipDia = 1.6;
    PinTipLength = 10.0;
    
    PinTaperLength = 2.3;
    
    PinShaftDia = 2.4;
    PinShaftLength = 6.8;
    
    PinFerruleDia = 3.0;
    PinFerruleLength = 2.0;
    
    PinLength = PinTipLength + PinTaperLength + PinShaftLength + PinFerruleLength;
    
    PinHoleOffset = 13.9;			// tip to spring hole
    
    ExtendRelax = 1.5 + ContactRecess;		// pin extension when no battery is present
    ExtendOvertravel = 1.0;					//  ... beyond engaged position
    
    //- Holder dimensions
    
    GuideRadius = ThreadWidth;						// friction fit ridges
    GuideOffset = 10;
    WallThick = 4*ThreadWidth;						// holder sidewalls
    
    BaseThick = IntegerMultiple(6.0,ThreadThick);	// bottom of holder to bottom of battery
    TopThick = 6*ThreadThick;	// top of battery to top of holder
    
    ThumbRadius = 10.0;			// thumb opening at end of battery
    
    CornerRadius = 3*ThreadThick;			// nice corner rounding
    
    CaseLength = 2*WallThick + PinLength - ExtendRelax + ExtendOvertravel + BatteryLength + GuideRadius;
    CaseWidth = 2*WallThick + 2*GuideRadius + BatteryWidth;
    CaseThick = BaseThick + BatteryThick + TopThick;
    
    //- XY origin at front left battery corner, Z on platform below that
    
    CaseLengthOffset = -(WallThick + PinLength - ExtendRelax + ExtendOvertravel);
    CaseWidthOffset = -(WallThick + GuideRadius);
    CaseThickOffset = BaseThick;
    
    LidLength = ExtendRelax - CaseLengthOffset;
    
    //- Spring dimensions
    
    SpringPlugDia = 5.0;					// epoxy plug holding spring wire
    SpringPlugLength = IntegerMultiple(1.5,ThreadThick);
    
    SpringDia = 0.024 * inch;	// music wire spring
    SpringTravel = ExtendRelax + ExtendOvertravel;
    SpringLength = BaseThick + ContactHeight - SpringPlugLength - 2*ThreadThick;
    
    echo(str("Spring wire from end: ",WallThick + PinLength - PinHoleOffset));
    echo(str("            from side: ",WallThick + GuideRadius + ContactOffset));
    echo(str("Pin spacing on centers: ",ContactOC));
    
    //----------------------
    // 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) {
    
      Range = floor(50 / Space);
    
    	for (x=[-Range:Range])
    	  for (y=[-Range:Range])
    		translate([x*Space,y*Space,Size/2])
    		  %cube(Size,center=true);
    
    }
    
    //-------------------
    
    //-- Guides for tighter friction fit
    
    module Guides() {
      	  translate([GuideOffset,-GuideRadius,CaseThickOffset])
    		PolyCyl(2*GuideRadius,(BatteryThick - Protrusion),4);
    	  translate([GuideOffset,(BatteryWidth + GuideRadius),CaseThickOffset])
    		PolyCyl(2*GuideRadius,(BatteryThick - Protrusion),4);
    	  translate([(BatteryLength - GuideOffset),-GuideRadius,CaseThickOffset])
    		PolyCyl(2*GuideRadius,(BatteryThick - Protrusion),4);
    	  translate([(BatteryLength - GuideOffset),(BatteryWidth + GuideRadius),CaseThickOffset])
    		PolyCyl(2*GuideRadius,(BatteryThick - Protrusion),4);
    	  translate([(BatteryLength + GuideRadius),GuideOffset/2,CaseThickOffset])
    		PolyCyl(2*GuideRadius,(BatteryThick - Protrusion),4);
    	  translate([(BatteryLength + GuideRadius),(BatteryWidth - GuideOffset/2),CaseThickOffset])
    		PolyCyl(2*GuideRadius,(BatteryThick - Protrusion),4);
    
    }
    
    //-- Contact pins (holes therefore)
    
    module PinShape() {
    
    PolyPin = false;
    
      union() {
    	if (PolyPin)
    	  PolyCyl(PinTipDia,(PinTipLength + Protrusion));
    	else
    	  cylinder(r=(PinTipDia + HoleWindage)/2,h=(PinTipLength + Protrusion),$fn=6);
    
    	translate([0,0,PinTipLength])
    	  if (PolyPin)
    		PolyCyl(PinShaftDia,(PinTaperLength + PinShaftLength + Protrusion));
    	  else
    		cylinder(r=(PinShaftDia + HoleWindage)/2,
    				 h=(PinTaperLength + PinShaftLength + Protrusion),$fn=6);
    
    	translate([0,0,(PinLength - PinFerruleLength)])
    	  if (PolyPin)
    		PolyCyl(PinFerruleDia,(PinFerruleLength + Protrusion));
    	  else
    		cylinder(r=(PinFerruleDia + HoleWindage)/2,
    				 h=(PinFerruleLength + Protrusion),$fn=6);
    
    	translate([0,0,PinLength])
    	  if (PolyPin)
    		PolyCyl(PinFerruleDia,PinLength);			// very long holes to punch case
    	  else
    		cylinder(r=(PinFerruleDia + HoleWindage)/2,h=PinLength,$fn=6);
      }
    
    }
    
    module PinAssembly() {
    
      translate([ExtendRelax,ContactOffset,CaseThickOffset + ContactHeight]) {
    	rotate([0,270,0]) {
    	  PinShape();												// pins
    	  translate([0,(2*ContactOC),0])
    		PinShape();
    	}
      }
    
      translate([-(PinHoleOffset - ExtendRelax + SpringTravel/2 - SpringDia/2 - HoleWindage/2),
    			 ContactOffset,
    			 (CaseThickOffset + ContactHeight - SpringLength/2 - Protrusion)]) {
    	  cube([(SpringTravel + SpringDia/2 + HoleWindage),
    		    PinShaftDia,
    			(SpringLength + 2*Protrusion)],
    		   center=true);										// spring deflection pocket
    	  translate([0,(2*ContactOC),0])
    		cube([(SpringTravel + SpringDia/2 + HoleWindage),
    			 PinShaftDia,
    			 (SpringLength + 2*Protrusion)],
    			 center=true);
      }
    
      translate([-(PinHoleOffset - ExtendRelax),
    			 ContactOffset,
    			 (-Protrusion/2)]) {
    	PolyCyl(SpringDia,(BaseThick + ContactHeight + Protrusion),4);		// spring wire
    	PolyCyl(SpringPlugDia,(SpringPlugLength + Protrusion));				// wire holder
    	translate([0,(2*ContactOC),0]) {
    	  PolyCyl(SpringDia,(BaseThick + ContactHeight + Protrusion),4);
    	  PolyCyl(SpringPlugDia,(SpringPlugLength + Protrusion));
    	}
      }
    
    }
    
    //-- Case with origin at battery corner
    
    module Case() {
    
      difference() {
    
    	union() {
    
    	  difference() {
    		translate([(CaseLength/2 + CaseLengthOffset),
    				  (CaseWidth/2 + CaseWidthOffset),
    				  (CaseThick/2)])
    		  roundedBox([CaseLength,CaseWidth,CaseThick],CornerRadius); 	// basic case shape
    
    		translate([-ExtendOvertravel,-GuideRadius,CaseThickOffset])
    		  cube([(BatteryLength + GuideRadius + ExtendOvertravel),
    				(BatteryWidth + 2* GuideRadius),
    				(BatteryThick + Protrusion)]);						// battery space
    
    	  }
    
    	  Guides();
    
    	  translate([-ExtendOvertravel,-GuideRadius,BaseThick])
    		cube([(AlignDepth + ExtendOvertravel),
    			  (AlignWidth1 + GuideRadius),
    			  AlignThick]);											// alignment blocks
    	  translate([-ExtendOvertravel,
    				 (BatteryWidth - AlignWidth2),
    				 BaseThick])
    		cube([(AlignDepth + ExtendOvertravel),
    			  (AlignWidth2 + GuideRadius),
    			  AlignThick]);
    
    	}
    
    	translate([(-ExtendOvertravel),
    			   (CaseWidthOffset - Protrusion),
    			   (CaseThickOffset + BatteryThick)])
    	  cube([CaseLength,
    		    (CaseWidth + 2*Protrusion),
    		    (TopThick + Protrusion)]);								// battery access
    
    	translate([(CaseLengthOffset - Protrusion),
    			   (CaseWidthOffset - Protrusion),
    			   (CaseThickOffset + BatteryThick)])
    	  cube([(CaseLength + 2*Protrusion),
    		    (CaseWidth + 2*Protrusion),
    		    (TopThick + Protrusion)]);								// battery insertion allowance
    
    	translate([(BatteryLength - Protrusion),
    			    (CaseWidth/2 + CaseWidthOffset),
    			    (CaseThickOffset + ThumbRadius)])
    	  rotate([90,0,0])
    		rotate([0,90,0])
    		  cylinder(r=ThumbRadius,
    				   h=(WallThick + GuideRadius + 2*Protrusion),
    				   $fn=22);											// remove thumb notch
    
    	PinAssembly();
    
      }
    
    }
    
    module Lid() {
    
      difference() {
    	translate([0,0,(CaseThick/2 - BaseThick - BatteryThick)])
    	  roundedBox([LidLength,
    				 CaseWidth,CaseThick],CornerRadius);
    
    	translate([0,0,-(CaseThick/2)])
    	  cube([(LidLength + 2*Protrusion),
    		    (CaseWidth + 2*Protrusion),
    		    (CaseThick)],center=true);
      }
    
    }
    
    //-------------------
    // Build it!
    
    ShowPegGrid();
    
    if (Layout == "Case")
      Case();
    
    if (Layout == "Lid")
      Lid();
    
    if (Layout == "Show") {								// reveal pin assembly
      difference() {
    	Case();
    
    	translate([(CaseLengthOffset - Protrusion),
    			   (CaseWidthOffset - Protrusion + WallThick + ContactOffset + ContactOC),
    			   (BaseThick + ContactHeight)])
    	  cube([(-CaseLengthOffset + Protrusion),
    			 (CaseWidth + 2*Protrusion),
    			 CaseThick + BaseThick - ContactHeight + Protrusion]);
    
    	translate([(CaseLengthOffset - Protrusion),
    			   (CaseWidthOffset - Protrusion),
    			   -Protrusion])
    	  cube([(-CaseLengthOffset + Protrusion),
    			 (WallThick + GuideRadius + ContactOffset + Protrusion),
    			 CaseThick]);
      }
    
      translate([ExtendRelax,ContactOffset,CaseThickOffset + ContactHeight]) {	// pins
    	rotate([0,270,0]) {
    	  %PinShape();
    //	  translate([0,(2*ContactOC),0])
    //		%PinShape();
    	}
      }
    
    }
    
    if (Layout == "Build") {
      translate([-(CaseLength/2 + CaseLengthOffset),-(CaseWidthOffset - BuildOffset),0])
    	Case();
      translate([0,(CaseLengthOffset/2 - BuildOffset),0])
    	rotate([0,0,90])
    	  Lid();
    
    }
    
    if (Layout == "Fit") {
      Case();
      translate([(-LidLength/2 + ExtendRelax),
    			(CaseWidth/2 + CaseWidthOffset),
    			(BaseThick + BatteryThick)])
    	  Lid();
      translate([ExtendRelax,ContactOffset,CaseThickOffset + ContactHeight]) {	// pins
    	rotate([0,270,0]) {
    	  %PinShape();
    	  translate([0,(2*ContactOC),0])
    		%PinShape();
    	}
      }
    
    }
    
  • NB-5L Holder: Internal Springs

    The first pass at a holder for a Canon NB-5L battery didn’t quite work, but the failure was instructive. The overall layout was fine; the battery fit well, it’s just that the pins and springs didn’t function properly.

    NB-5L Holder - Internal spring - solid model
    NB-5L Holder – Internal spring – solid model

    I thought a simple straight music wire spring pushed into a hole with a pocket to limit the pin travel would suffice. Watching it build showed that the pocket came out too small and the spring hole was almost completely closed, despite a bit of HoleWindage.

    Here’s a closer look at the spring arrangement, with a pocket at the bottom for an epoxy blob after it became obvious the ABS couldn’t properly anchor the pin.

    NB-5L Holder - Internal spring - detail
    NB-5L Holder – Internal spring – detail

    Drilling out the holes to allow free pin movement ended up with #52 for the tip, #40 for the shaft, and #31 for the ferrule at the end; choose for an easy slip fit.

    I used 0.024 inch (0.6 mm) music wire, which fit neatly into the pin’s inspection hole. Drilling that (#73 drill) into the nearly closed hole through the bottom showed that things weren’t working well: far too much resistance along what should be a half-open channel.

    With the pin in place and a stub of wire pushed upward into the pin, the pins moved very stiffly and tended to not return to their rest position. Minus the spring wire, they slid freely.

    After a bit of this and that, I tried 0.020 music wire, which didn’t have enough return force at all.

    The end of the spring wire moved around a lot more than I think it should have, gradually turning the hole into an oval. I drilled two pockets in the bottom (and changed the solid model to match what you see above) and cast a dab of epoxy into each hole; that solved the moving-around problem, but the pins were still too stiff.

    NB-5L Holder - first version - bottom
    NB-5L Holder – first version – bottom

    The weird orange color comes from a few layers of Safety Orange filament that melded into white in mid-flight. No reason to use fancy filament on a prototype, methinks; that’s what was in place when I started.

    But I glued the cap on anyway to see if the pins would work well enough to run some early battery tests. This is what it looked like before gluing the cap:

    NB-5L Holder - first version
    NB-5L Holder – first version

    The Powerpole connectors came from one of those packs, with the wires soldered into the ends of the pins so as to not block the inspection holes that I’m using for the music wire springs.

    The OpenSCAD source code:

    // Holder for Canon NB-5L Li-Ion battery
    // Ed Nisley KE4ZNU August 2011
    
    include </home/ed/Thing-O-Matic/lib/MCAD/units.scad>
    include </home/ed/Thing-O-Matic/lib/MCAD/boxes.scad>
    include </home/ed/Thing-O-Matic/Useful Sizes.scad>
    
    // Layout options
    
    Layout = "Show";					// Case Lid Show Build Fit
    
    //- Extrusion parameters - must match reality!
    //  Print with +2 shells and 3 solid layers
    
    ThreadThick = 0.33;
    ThreadWidth = 2.0 * ThreadThick;
    
    HoleWindage = 0.2;
    
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    
    Protrusion = 0.1;			// make holes end cleanly
    
    BuildOffset = 3.0;			// clearance for build layout
    
    //- Battery dimensions - rationalized from several samples
    //  Coordinate origin at battery corner by contact plates on bottom surface
    
    BatteryLength = 45.25;
    BatteryWidth = 32.17;
    BatteryThick =  7.85;
    
    ContactWidth = 2.10;
    ContactLength = 4.10;
    ContactRecess = 0.85;
    
    ContactOC = 3.18;			// center-to-center across contact face
    ContactOffset = 4.45;		// offset from battery edge
    ContactHeight = 3.05;		// offset from battery bottom plane
    
    AlignThick = 2.2;			// alignment recesses on contact face
    AlignDepth = 2.0;			// into face
    AlignWidth1 = 0.7;			// across face at contacts
    AlignWidth2 = 2.8;			//  ... other edge
    
    //- Pin dimensions
    
    PinTipDia = 1.6;
    PinTipLength = 10.0;
    
    PinTaperLength = 2.3;
    
    PinShaftDia = 2.4;
    PinShaftLength = 6.8;
    
    PinFerruleDia = 3.0;
    PinFerruleLength = 2.0;
    
    PinLength = PinTipLength + PinTaperLength + PinShaftLength + PinFerruleLength;
    
    PinHoleOffset = 13.9;			// tip to spring hole
    
    //- Spring dimensions
    
    ExtendRelax = 1.5 + ContactRecess;		// pin extension when no battery is present
    ExtendOvertravel = 1.0;					//  ... beyond engaged position
    
    SpringDia = 0.024 * inch;	// music wire spring
    SpringTravel = ExtendRelax + ExtendOvertravel;
    SpringLength = 4.0 + PinShaftDia/2;			// free length below pin centerline
    
    //- Holder dimensions
    
    GuideRadius = ThreadWidth;						// friction fit ridges
    GuideOffset = 10;
    WallThick = 4*ThreadWidth;						// holder sidewalls
    
    BaseThick = IntegerMultiple(6.0,ThreadThick);	// bottom of holder to bottom of battery
    TopThick = 6*ThreadThick;	// top of battery to top of holder
    
    ThumbRadius = 10.0;			// thumb opening at end of battery
    
    CornerRadius = 3*ThreadThick;			// nice corner rounding
    
    CaseLength = 2*WallThick + PinLength - ExtendRelax + ExtendOvertravel + BatteryLength + GuideRadius;
    CaseWidth = 2*WallThick + 2*GuideRadius + BatteryWidth;
    CaseThick = BaseThick + BatteryThick + TopThick;
    
    //- XY origin at front left battery corner, Z on platform below that
    
    CaseLengthOffset = -(WallThick + PinLength - ExtendRelax + ExtendOvertravel);
    CaseWidthOffset = -(WallThick + GuideRadius);
    CaseThickOffset = BaseThick;
    
    LidLength = ExtendRelax - CaseLengthOffset;
    
    SpringPlugDia = 3.0;			// filament snippet holding spring wire
    SpringPlugLength = IntegerMultiple(1.0,ThreadThick);
    
    echo(str("Spring wire from end: ",WallThick + PinLength - PinHoleOffset));
    echo(str("            from side: ",WallThick + GuideRadius + ContactOffset));
    echo(str("Pin spacing on centers: ",ContactOC));
    
    //----------------------
    // 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) {
    
      Range = floor(50 / Space);
    
    	for (x=[-Range:Range])
    	  for (y=[-Range:Range])
    		translate([x*Space,y*Space,Size/2])
    		  %cube(Size,center=true);
    
    }
    
    //-------------------
    
    //-- Guides for tighter friction fit
    
    module Guides() {
      	  translate([GuideOffset,-GuideRadius,CaseThickOffset])
    		PolyCyl(2*GuideRadius,(BatteryThick - Protrusion),4);
    	  translate([GuideOffset,(BatteryWidth + GuideRadius),CaseThickOffset])
    		PolyCyl(2*GuideRadius,(BatteryThick - Protrusion),4);
    	  translate([(BatteryLength - GuideOffset),-GuideRadius,CaseThickOffset])
    		PolyCyl(2*GuideRadius,(BatteryThick - Protrusion),4);
    	  translate([(BatteryLength - GuideOffset),(BatteryWidth + GuideRadius),CaseThickOffset])
    		PolyCyl(2*GuideRadius,(BatteryThick - Protrusion),4);
    	  translate([(BatteryLength + GuideRadius),GuideOffset/2,CaseThickOffset])
    		PolyCyl(2*GuideRadius,(BatteryThick - Protrusion),4);
    	  translate([(BatteryLength + GuideRadius),(BatteryWidth - GuideOffset/2),CaseThickOffset])
    		PolyCyl(2*GuideRadius,(BatteryThick - Protrusion),4);
    
    }
    
    //-- Contact pins (holes therefore)
    
    module PinShape() {
    
    PolyPin = false;
    
      union() {
    	if (PolyPin)
    	  PolyCyl(PinTipDia,(PinTipLength + Protrusion));
    	else
    	  cylinder(r=(PinTipDia + HoleWindage)/2,h=(PinTipLength + Protrusion),$fn=6);
    
    	translate([0,0,PinTipLength])
    	  if (PolyPin)
    		PolyCyl(PinShaftDia,(PinTaperLength + PinShaftLength + Protrusion));
    	  else
    		cylinder(r=(PinShaftDia + HoleWindage)/2,
    				 h=(PinTaperLength + PinShaftLength + Protrusion),$fn=6);
    
    	translate([0,0,(PinLength - PinFerruleLength)])
    	  if (PolyPin)
    		PolyCyl(PinFerruleDia,(PinFerruleLength + Protrusion));
    	  else
    		cylinder(r=(PinFerruleDia + HoleWindage)/2,
    				 h=(PinFerruleLength + Protrusion),$fn=6);
    
    	translate([0,0,PinLength])
    	  if (PolyPin)
    		PolyCyl(PinFerruleDia,PinLength);			// very long holes to punch case
    	  else
    		cylinder(r=(PinFerruleDia + HoleWindage)/2,h=PinLength,$fn=6);
      }
    
    }
    
    module PinAssembly() {
    
      translate([ExtendRelax,ContactOffset,CaseThickOffset + ContactHeight]) {	// pins
    	rotate([0,270,0]) {
    	  PinShape();
    	  translate([0,(2*ContactOC),0])
    		PinShape();
    	}
      }
    
      translate([-(PinHoleOffset - ExtendRelax + SpringTravel/2),
    			 ContactOffset,
    			 (CaseThickOffset + ContactHeight - SpringLength/2 + Protrusion/2)]) {
    	  cube([SpringTravel,
    		    (2*SpringDia),
    			(SpringLength + Protrusion)],
    		   center=true);										// spring deflection pocket
    	  translate([0,(2*ContactOC),0])
    		cube([SpringTravel,
    			 (2*SpringDia),
    			 (SpringLength + Protrusion)],
    			 center=true);
      }
    
      translate([-(PinHoleOffset - ExtendRelax),
    			 ContactOffset,
    			 (-Protrusion/2)]) {
    	PolyCyl(SpringDia,(BaseThick + ContactHeight + Protrusion));		// spring wire
    	PolyCyl(SpringPlugDia,(SpringPlugLength + Protrusion));				// wire holder
    	translate([0,(2*ContactOC),0]) {
    	  PolyCyl(SpringDia,(BaseThick + ContactHeight + Protrusion));
    	  PolyCyl(SpringPlugDia,(SpringPlugLength + Protrusion));
    	}
      }
    
    }
    
    //-- Case with origin at battery corner
    
    module Case() {
    
      difference() {
    
    	union() {
    
    	  difference() {
    		translate([(CaseLength/2 + CaseLengthOffset),
    				  (CaseWidth/2 + CaseWidthOffset),
    				  (CaseThick/2)])
    		  roundedBox([CaseLength,CaseWidth,CaseThick],CornerRadius); 	// basic case shape
    
    		translate([-ExtendOvertravel,-GuideRadius,CaseThickOffset])
    		  cube([(BatteryLength + GuideRadius + ExtendOvertravel),
    				(BatteryWidth + 2* GuideRadius),
    				(BatteryThick + Protrusion)]);						// battery space
    
    	  }
    
    	  Guides();
    
    	  translate([-ExtendOvertravel,-GuideRadius,BaseThick])
    		cube([(AlignDepth + ExtendOvertravel),
    			  (AlignWidth1 + GuideRadius),
    			  AlignThick]);											// alignment blocks
    	  translate([-ExtendOvertravel,
    				 (BatteryWidth - AlignWidth2),
    				 BaseThick])
    		cube([(AlignDepth + ExtendOvertravel),
    			  (AlignWidth2 + GuideRadius),
    			  AlignThick]);
    
    	}
    
    	translate([(-ExtendOvertravel),
    			   (CaseWidthOffset - Protrusion),
    			   (CaseThickOffset + BatteryThick)])
    	  cube([CaseLength,
    		    (CaseWidth + 2*Protrusion),
    		    (TopThick + Protrusion)]);								// battery access
    
    	translate([(CaseLengthOffset - Protrusion),
    			   (CaseWidthOffset - Protrusion),
    			   (CaseThickOffset + BatteryThick)])
    	  cube([(CaseLength + 2*Protrusion),
    		    (CaseWidth + 2*Protrusion),
    		    (TopThick + Protrusion)]);								// battery insertion allowance
    
    	translate([(BatteryLength - Protrusion),
    			    (CaseWidth/2 + CaseWidthOffset),
    			    (CaseThickOffset + ThumbRadius)])
    	  rotate([90,0,0])
    		rotate([0,90,0])
    		  cylinder(r=ThumbRadius,
    				   h=(WallThick + GuideRadius + 2*Protrusion),
    				   $fn=22);											// remove thumb notch
    
    	PinAssembly();
    
      }
    
    }
    
    module Lid() {
    
      difference() {
    	translate([0,0,(CaseThick/2 - BaseThick - BatteryThick)])
    	  roundedBox([LidLength,
    				 CaseWidth,CaseThick],CornerRadius);
    
    	translate([0,0,-(CaseThick/2)])
    	  cube([(LidLength + 2*Protrusion),
    		    (CaseWidth + 2*Protrusion),
    		    (CaseThick)],center=true);
      }
    
    }
    
    //-------------------
    // Build it!
    
    ShowPegGrid();
    
    if (Layout == "Case")
      Case();
    
    if (Layout == "Lid")
      Lid();
    
    if (Layout == "Show") {								// reveal pin assembly
      difference() {
    	Case();
    
    	translate([(CaseLengthOffset - Protrusion),
    			   (CaseWidthOffset - Protrusion + WallThick + ContactOffset + ContactOC),
    			   (BaseThick + ContactHeight)])
    	  cube([(-CaseLengthOffset + Protrusion),
    			 (CaseWidth + 2*Protrusion),
    			 CaseThick + BaseThick - ContactHeight + Protrusion]);
    
    	translate([(CaseLengthOffset - Protrusion),
    			   (CaseWidthOffset - Protrusion),
    			   -Protrusion])
    	  cube([(-CaseLengthOffset + Protrusion),
    			 (WallThick + GuideRadius + ContactOffset + Protrusion),
    			 CaseThick]);
      }
    
      translate([ExtendRelax,ContactOffset,CaseThickOffset + ContactHeight]) {	// pins
    	rotate([0,270,0]) {
    	  %PinShape();
    //	  translate([0,(2*ContactOC),0])
    //		%PinShape();
    	}
      }
    
    }
    
    if (Layout == "Build") {
      translate([-(CaseLength/2 + CaseLengthOffset),-(CaseWidthOffset - BuildOffset),0])
    	Case();
      translate([0,(CaseLengthOffset/2 - BuildOffset),0])
    	rotate([0,0,90])
    	  Lid();
    
    }
    
    if (Layout == "Fit") {
      Case();
      translate([(-LidLength/2 + ExtendRelax),
    			(CaseWidth/2 + CaseWidthOffset),
    			(BaseThick + BatteryThick)])
    	  Lid();
      translate([ExtendRelax,ContactOffset,CaseThickOffset + ContactHeight]) {	// pins
    	rotate([0,270,0]) {
    	  %PinShape();
    	  translate([0,(2*ContactOC),0])
    		%PinShape();
    	}
      }
    
    }
    
  • Toy Dump Truck Convoy

    Dump Truck fleet
    Dump Truck fleet

    The Tiny Toy Dump Truck by madscifi makes a fine tchotchke for a presentation, serving to illustrate the risks and rewards of printing flat overhangs without support. A fleet of six printed well, after repairing some failures as those tall, tall rings and posts near the dump bed pivot fell over. I pasted them down in mid-print using ABS slurry, which is not a technique to emulate.

    A closer look shows the overhang problem in the dump bed and the broken pillars behind the wheels.

    Truck overhang failure - grayscale
    Truck overhang failure – grayscale

    It’s in monochrome because the camera choked on that much Safety Orange filament in the image, to the extent that no amount of color correction produced a usable result.