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.

Category: Machine Shop

Mechanical widgetry

  • KG-UV3D GPS+Voice: Box

    The previous iteration of GPS+voice interface boxes came from the Sherline CNC mill, with a considerable amount of huffing & puffing. I got the Thing-O-Matic to simplify that process…

    The general idea is to build a box that clips onto the radio in place of the standard battery pack. External power comes into the box and goes directly to the radio’s battery contacts; this will pose a problem with the Wouxun KG-UV3D, because it wants 7.2 V rather than the stepped-up 9 V from the Li-Ion packs I’ve been using. I think a three-wire power cord is in order: +9 V for the interface, +7.2 V for the radio, and common.

    The box also interfaces with the radio’s mic and speaker jacks. Last time around, I made a gluing fixture to keep the plugs in alignment while the epoxy cured around the plugs in the plate, but maybe I can simplify that with 3D printing. Plastic will be better in one respect: the shells of the two plugs must be electrically isolated.

    This first-pass (*) approximation shows the three tabs on the pack that engage the radio’s base:

    KG-UV3D Interface Box prototype - right side
    KG-UV3D Interface Box prototype – right side

    A detail of those tabs, as seen from the bottom:

    KG-UV3D Interface Box prototype - end tabs
    KG-UV3D Interface Box prototype – end tabs

    The ICOM IC-Z1A battery pack had a set of slip-in alignment features that held the pack on the radio, so two strips of tape sufficed to hold the interface box in place. Each Wouxun battery pack includes a spring-loaded latching mechanism that engages a pair of ramped tabs on the radio body that hold the pack against the spring-loaded battery contacts. That means I must come up with an actual latch of some sort to oppose the contact springs, but I haven’t figured that out yet.

    The solid model, with the plug mounting plate floating beside it, looks like this:

    Case Solid Model - Tab End View - Fit
    Case Solid Model – Tab End View – Fit

    Tomorrow, the solid modeling…

    * It’s actually the third printing of the bottom plate with the three tabs and the base plate with the battery contacts. That’s how I figured out the 0. 5% shrinkage thing.

    [Update: The sketch with the dimensions emerged from beneath a pile o’ stuff…]

    Wouxun KG-UV3D Battery Pack Dimensions
    Wouxun KG-UV3D Battery Pack Dimensions

     

  • Thing-O-Matic: Small Features

    Strainer - knob perimeter thread
    Strainer – knob perimeter thread

    It seems most of the stuff I build with my Thing-O-Matic involves small features and thin sections that bump hard against the minimum possible sizes. I’ve found that forcing critical solid model dimensions to be integer multiples of the the extrusion width or thickness stabilizes the whole idea→model→G-Code→object chain by encouraging Skeinforge to make the choices I prefer.

    Or perhaps I’m just constraining my choices to make Skeinforge happiest. One can view reality in many ways…

    Anyhow, my OpenSCAD programs tend to have these lines up near the top:

    ThreadThick = 0.33;
    ThreadWidth = 2.0 * ThreadThick;
    
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    

    The ThreadThick parameter matches the Skeinforge thread thickness parameter(s) and the 2.0 matches the w/t setting(s). Those correspond quite closely to the actual printed results, as tediously verified through many measurements. Throughout the rest of the OpenSCAD program, I compute the dimensions of key features using those sizes as building blocks.

    The IntegerMultiple function returns the next higher multiple of the basic Unit that’s greater-than-or-equal-to the desired Size. Feeding in the thread thickness or width as the Unit ensures that the result will be an integer multiple of the smallest-possible dimension and won’t be smaller. The integer limit happens automagically, because the printer can’t lay down anything else, but a less-than-possible size can cause features to (unpredictably, in my experience) vanish without warning. This way your model reflects the printed reality and Skeinforge seems more likely to produce a predictable result.

    So the parameter controlling the thickness of a flat sheet might look like:

    PlateThick = IntegerMultiple(2.0,ThreadThick);
    

    Given ThreadThick = 0.33, the sheet will be 7 layers thick = 2.31 mm. If the sheet must not exceed 2.0 mm, however, then you need a similar function with floor(), which may eradicate very small features.

    This trick seems most useful for thin wall sections, because the wall width directly affects the fill:

    • Less  than 1 thread width can’t be built
    • Exactly 1 thread width is the thinnest possible wall
    • Widths between 1 and 2 thread widths may be either, depending on surrounding features
    • Exactly 2 thread widths produces a nice wall
    • Widths between 2 and 3 thread widths can’t fill properly
    • Exactly 3 thread widths fills perfectly
    • Over 3 thread widths generally fill properly

    So making the rim around a recessed lid become an integral number of thread widths, with a minimum width of 1.0 mm, looks like this:

    LidMargin = IntegerMultiple(1.0,ThreadWidth);
    

    With a 0.66 mm thread width, the nominal wall is 1.5 threads wide and could print as either 1 or 2 threads, depending on other factors. Rather than leave the results to chance, I force the solid model wall to be exactly 2 threads wide to make the printed result come out at 1.32 mm. Because I don’t care exactly how wide the lid margin is, as long as it’s at least one thread, that’s fine with me.

    Generally, the values come from computations based on other dimensions, so quantizing the results keeps the printed result stable over small variations of those inputs.

    If I ever get around to changing the nozzle to from 0.5 mm to 0.4 mm, I’ll probably change the thread dimensions to 0.25 mm x 0.5 mm (keeping the same 2.0 w/t ratio). A 1.0 mm wall would then still be exactly 2 threads wide and come out looking exactly the same, but with a total width of 1.00 mm.

    That’s the intent, anyway.

  • 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!

  • External USB Case vs OEM DVD Drive Mounting Bracket

    That little Lenovo Q150 doesn’t include an optical drive and, mostly, I don’t need one, but sometimes it’s handy to boot from a CD. I picked up a used DVD burner that also fits my Dell E1405 laptop (should I need a spare) and a tiny USB laptop drive case from the usual eBay sources for a grand total of $17 delivered.

    The drive had a mounting bracket on the back that obviously had to come off, because the bracket screws snuggled right in among the USB adapter electronics:

    E1405 DVD drive bracket vs USB electronics
    E1405 DVD drive bracket vs USB electronics

    In fact, that flat tab with a hole would have clunked up against the back of the case and prevented it from sliding all the way in, but the screws also foiled Plan B: flip the bracket around so the tab goes under the drive where it couldn’t get lost if I needed it again.

    So now the bracket & screws live in a little bag in the Box o’ USB Stuff.

    The DVD drive works fine with just a single USB cable, although the case came with a power-only USB cable, so the latter also lives in the bag with the bracket. Maybe I’ll need it in the unlikely event I actually burn a DVD in that drive?

  • 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?