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

  • Worn-out Zipper Tab

    I’ve carried all my stuff in a belt pack since long before such things were fashionable and, quite some years ago, a friend made me a custom-sized one that’s been in constant use ever since. Of late, one of the zippers got cranky and finally failed completely.

    An autopsy showed the middle of the cross bar on the tab had worn completely through, the stubs had bent outward, and the remains no longer engage the zipper tooth lock.

    Worn-through zipper tab
    Worn-through zipper tab

    I replaced the tab with a short length of chain and a jump ring, but I fear the pack fabric is also reaching end of life.

  • KG-UV3D GPS+Voice: Radio Base Interface

    The Wouxun KG-UV3D has three holes along the base that capture three tabs in the battery case, with tapered edges to align the case with the contacts. After a few passes to get the dimensions right, the plate matching those features came out like this:

    Base plate with tabs
    Base plate with tabs

    The solid model shows the edge tapering down to a single layer:

    Case Tab Base - Solid Model
    Case Tab Base – Solid Model

    The compound taper on the corners must match both the base and the sides of the radio. The bottom plate and shell have corresponding tapers that extend across the glued joints:

    Radio interface tapers
    Radio interface tapers

    That worked out surprisingly well, given the small dimensions and odd angles. The tabs, in particular, bumped right up against the 0.66 mm extrusion width; they’re 2.0 mm thick, so there’s barely one thread width inside the perimeter for fill. A bit of filing & slicing removed the usual enlargement at the end / start of each perimeter thread on the tabs, which is entirely acceptable for something this finicky.

    The OpenSCAD source code with dimensions is all part of that post, but here’s the radio base shape that gets subtracted from the plate to make those tabs:

    Radio Base Polygon - solid model
    Radio Base Polygon – solid model

    This seemed easier than adding a bunch of tiny pegs & triangles, but it’s certainly tedious working around a polygon:

    module RadioBase() {
    
    linear_extrude(height=(BaseOpeningDepth + Protrusion),center=false,convexity=5)
    polygon(points=[
    [-BaseOpeningMax/2,-Protrusion],
    
    [-BaseOpeningMin/2,BaseOpeningY],
    [-(BaseToothOC/2 + BaseToothBase/2),BaseOpeningY],
    
    [-(BaseToothOC/2 + BaseToothTip/2),(BaseOpeningY - BaseToothThick)],
    [-(BaseToothOC/2 - BaseToothTip/2),(BaseOpeningY - BaseToothThick)],
    [-(BaseToothOC/2 - BaseToothBase/2),BaseOpeningY],
    
    [ (BaseToothOC/2 - BaseToothBase/2),BaseOpeningY],
    [ (BaseToothOC/2 - BaseToothTip/2),(BaseOpeningY - BaseToothThick)],
    [ (BaseToothOC/2 + BaseToothTip/2),(BaseOpeningY - BaseToothThick)],
    [ (BaseToothOC/2 + BaseToothBase/2),BaseOpeningY],
    [ BaseOpeningMin/2,BaseOpeningY],
    
    [ BaseOpeningMax/2,-Protrusion],
    
    [ (BaseTabOC + BaseTabWidth/2),-Protrusion],
    [ (BaseTabOC + BaseTabWidth/2),BaseTabThick],
    [ (BaseTabOC - BaseTabWidth/2),BaseTabThick],
    [ (BaseTabOC - BaseTabWidth/2),-Protrusion],
    
    [ BaseTabWidth/2,-Protrusion],
    [ BaseTabWidth/2,BaseTabThick],
    [-BaseTabWidth/2,BaseTabThick],
    [-BaseTabWidth/2,-Protrusion],
    
    [-(BaseTabOC + BaseTabWidth/2),-Protrusion],
    [-(BaseTabOC + BaseTabWidth/2),BaseTabThick],
    [-(BaseTabOC - BaseTabWidth/2),BaseTabThick],
    [-(BaseTabOC - BaseTabWidth/2),-Protrusion],
    ],
    convexity=5
    );
    }
    

    Then subtracting that shape and some inclines…

    Radio Base Interface - solid model - thrown together
    Radio Base Interface – solid model – thrown together

    … lets the base plate pop out of this code:

    module Base() {
    
      difference() {
    
    	translate([0,0,(BaseThick + BaseOpeningDepth)/2])
    	  rotate([-90,0,0])
    		CaseEnvelope(BaseThick + BaseOpeningDepth);
    
    	translate([0,0,BaseThick])
    	  RadioBase();
    
    	translate([(BaseToothOC + BaseTabWidth/2),
    			  -(BaseThick + BaseEndLip)/tan(BaseEndAngle),
    			  0])
    	  rotate([BaseEndAngle,0,0])
    		cube([BaseEndWidth,3*BaseOpeningY,BaseOpeningDepth],center=false);
    
    	translate([-(BaseToothOC + BaseTabWidth/2 + BaseEndWidth),
    			  -(BaseThick + BaseEndLip)/tan(BaseEndAngle),
    			  0])
    	  rotate([BaseEndAngle,0,0])
    		cube([BaseEndWidth,3*BaseOpeningY,BaseOpeningDepth],center=false);
      }
    }
    

    I’m still doodling the electronics, alas…

  • LILUG Meeting Presentation

    Multicolored Chalk People
    Multicolored Chalk People

    In the admittedly unlikely event you happen to be near the left-center part of Long Island this evening, drop in on my DIY 3D Printing & the Makerbot Thing-O-Matic presentation for the Long Island Linux Users Group meeting and pick up a tchotchke!

    Many thanks to LILUG for ruthlessly eliminating all my objections to leaving the Basement Laboratory…

  • KG-UV3D GPS+Voice: Plug Mounting Plate

    Unlike my old ICOM IC-Z1A, the Wouxun KG-UV3D radio has mic and speaker jacks recessed into the case, so that a custom plug plate can absorb all the stress from forces applied to the cables without wiggling the plugs. Even better, there’s a removable cover with a mounting screw that can hold the new plate in place!

    Wouxun plug mounting plate - overview
    Wouxun plug mounting plate – overview

    The first pass at the mount required a bit of filing, as the deepest part of the recess turns out to be not exactly rectangular. That’s (probably) fixed in the source code:

    Wouxun plug plate - detail
    Wouxun plug plate – detail

    The solid model looks about like you’d expect, with terribly thin side walls between the plugs and the not-quite-rectangular section. The whole affair is asymmetrical around the long axis; the not-quite-rectangular block and hole really are offset:

    Plug Mount Plate - Solid Model
    Plug Mount Plate – Solid Model

    When printed, the thin sections come out one 0.66 mm plastic thread wide:

    Wouxun plug mounting plate - build
    Wouxun plug mounting plate – build

    I spent quite some time iterating through OpenSCAD, RepG, and SkeinLayer to make sure that came out right. This is from a later version with larger recesses around the plugs:

    Plug Mount Plate - skeinlayer
    Plug Mount Plate – skeinlayer

    Some epoxy eased down along the plugs will lock them into the plastic, with an epoxy putty turd over the top to stabilize the cables and terminal connections. That’s a T6 Torx bit to mate with the 2 mm screw (with a captive washer!) pulled from the Small Drawer o’ Salvaged Metric Screws:

    Wouxun plug plate - trial fit
    Wouxun plug plate – trial fit

    The OpenSCAD source code is part of the huge block of code at the bottom of that post, but here’s the relevant section:

    module PlugPlate() {
    
      BaseX = PlugBaseWidth/2 - PlugBaseRadius;
      BaseY = PlugBaseLength/2 - PlugBaseRadius;
    
      difference() {
    	union() {
    	  linear_extrude(height=PlugBaseThick,center=false,convexity=3)
    		hull() {
    		  translate([-BaseX,-BaseY,0])
    			circle(r=PlugBaseRadius,$fn=8);
    		  translate([-BaseX, BaseY,0])
    			circle(r=PlugBaseRadius,$fn=8);
    		  translate([ BaseX, BaseY,0])
    			circle(r=PlugBaseRadius,$fn=8);
    		  translate([ BaseX,-BaseY,0])
    			circle(r=PlugBaseRadius,$fn=8);
    		}
    
    	  translate([PlugFillOffsetX,
    				(PlugFillLength/2 - PlugBaseLength/2 + PlugFillOffsetY),
    				PlugBaseThick])
    		linear_extrude(height=PlugFillThick,center=false,convexity=5)
    		  hull() {
    			translate([0,-(PlugFillLength/2 - PlugFillRadius2),0])
    			  circle(r=PlugFillRadius2,$fn=10);
    			translate([-(PlugFillWidth/2 - PlugFillRadius1),-PlugBaseLength/2,0])
    			  circle(r=PlugFillRadius1,$fn=8);
    			translate([-(PlugFillWidth/2 - PlugFillRadius1),
    					  (PlugFillLength/2 - PlugFillRadius1),0])
    			  circle(r=PlugFillRadius1,$fn=8);
    			translate([(PlugFillWidth/2 - PlugFillRadius1),
    					  (PlugFillLength/2 - PlugFillRadius1),0])
    			  circle(r=PlugFillRadius1,$fn=8);
    			translate([(PlugFillWidth/2 - PlugFillRadius1),-PlugBaseLength/2,0])
    			  circle(r=PlugFillRadius1,$fn=8);
    		  }
    	}
    
    	translate([0,-JackOC/2,-Protrusion])
    	  rotate(360/16) {
    		PolyCyl(Plug3BezelDia,(Plug3BezelThick + Protrusion),8);
    		PolyCyl(Plug3ScrewDia,(PlugBaseThick + PlugFillThick + 2*Protrusion),8);
    	  }
    
    	translate([0,+JackOC/2,-Protrusion])
    	  rotate(360/16) {
    		PolyCyl(Plug2BezelDia,(Plug2BezelThick + Protrusion),8);
    		PolyCyl(Plug2ScrewDia,(PlugBaseThick + PlugFillThick + 2*Protrusion),8);
    	  }
    
    	translate([JackScrewOffsetX,-(PlugBaseLength/2 + JackScrewOffsetY),0])
    	  PolyCyl(JackScrewDia,(PlugBaseThick + PlugFillThick + Protrusion));
      }
    
    }
    
  • HP-48GX Calculator Disassembly: Case Rivets

    The keyboard on my trusty HP 48GX calculator finally deteriorated to the point of unusability, so I tore the thing apart following the useful instructions there. The warning about applying force to the rivets that hold the case halves together gives you not the faintest concept of how much force is actually required to pry the mumble thing apart at the battery compartment; I finally invoked force majeure with a chisel scraper

    HP-48GX case rivets
    HP-48GX case rivets

    I expected the calculator would not survive this operation and I wasn’t disappointed.

    An HP 50g is now in hand. Here in late 2011 I’d expect HP’s top-of-the-line RPN calculator to sport a crisp high-resolution display, but noooo the low-contrast 131×80 LCD seems teleported directly from the latter part of the last millennium. The manuals are PDFs, which is OK, but their content is far inferior to the HP 48GX manuals. In particular, the editing / proofreading is terrible. I infer that the HP calculator division can barely fog a mirror and is on advanced life support; HP’s diverting all their money to, uh, executive buyouts or some other non-productive purpose.

    The fact that HP sells new-manufacture HP 15C calculators doesn’t crank my tractor, even though I lived and died by one for many years. A one-line 7-segment display doesn’t cut it any more, even if the new machinery inside allegedly runs like a bat out of hell.

    My HP 16C, now, that one you’ll pry out of my cold, dead hands. At one point in the dim past, I’d programmed the Mandelbrot iteration into it to provide bit-for-bit verification of the 8051 firmware for the Mandelbrot Engine array processor I did for Circuit Cellar: slow, but perfect. That calculator has a low duty cycle these days, but when I need it, I need it bad.

  • Thing-O-Matic: Delamination

    ABS plastic shrinks as it cools and large objects with thin sections tend to delaminate, as seen in the Barbie Pistol and a few other objects. The box for the GPS+voice interface is four threads thick and 35 mm tall, which provided enough energy to rip the side apart:

    Box wall delamination
    Box wall delamination

    Solvent glue and a clamp shoved it back together again:

    Clamping delamination
    Clamping delamination

    This one was extruded at 190 °C, which works fine for small objects and isn’t quite enough to fuse something like this. I’ll crank it up to 210 °C for the next iteration to see if that improves the result.

  • KG-UV3D GPS+Voice: Box Model

    The first pass at the box that will eventually hold the GPS+voice interface for the KG-UV3D radio looks like this, from the end that engages the alignment tabs on the bottom of the radio:

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

    The other end has the opening for the TT3’s serial connector to the GPS receiver, a probably too-small hole for the external battery pack cable / helmet cable / PTT cable, and a hole on the side for the radio mic/speaker cables.

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

    The serial connector opening has a built-in support plate that’s the shape shrunken by 5% so it’s easy to punch out. That worked surprisingly well; the line just above the right edge isn’t a break, it’s a stack of Reversal Zits. This version is rectangular; the solid model shows the proper D shape.

    KG-UV3D box - connector hole support removal
    KG-UV3D box – connector hole support removal

    The bottom has battery contact recesses and counterbores (if that’s the right term for a molded feature) for the PCB mounting  screws. In retrospect, those holes should be tapping diameter and the screws inserted from the top, through the PCB.

    Case Solid Model - Battery Contact View - Fit
    Case Solid Model – Battery Contact View – Fit

    The colors mark individual pieces that get glued together. I can probably reduce the wall thickness on the top & bottom by three threads, which is in the nature of fine tuning. The latch mechanism that holds this affair to the radio is conspicuous by its absence…

    The OpenSCAD source code:

    // Wouxun KB-UV3D Battery Pack Case
    // Ed Nisley KE4ZNU September 2011
    
    include </home/ed/Thing-O-Matic/lib/MCAD/units.scad>
    include </home/ed/Thing-O-Matic/Useful Sizes.scad>
    include </home/ed/Thing-O-Matic/lib/visibone_colors.scad>
    
    // Layout options
    
    Layout = "Fit";		// Envelope Plate Base Lid Shell Fit Buildx ScrewSupport
    								// PlugPlate
    
    //- Extrusion parameters must match reality!
    //  Print with +1 shells and 3 solid layers
    //  Use 210 C extrusion temperature to improve layer bonding
    
    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 = 2.0;			// clearance for build layout
    
    //----------------------
    //- Case dimensions
    
    CaseOverallHeight = 40;
    CaseOverallWidth = 56;
    CaseOverallLength = 80.0;
    
    PlateWidthMin = 53.0;			// plate interfacing with radio contacts
    PlateWidthMax = 54.5;
    PlateLength = 75.0;
    PlateThick = IntegerMultiple(2.0,ThreadThick);
    
    ContactWidth = 7.0 + HoleWindage;
    ContactLength = 7.0 + HoleWindage;
    ContactRecess = 2*ThreadThick;	// recess for contact metal plate
    ContactGapX = 10.5;				// X space between contacts
    Contact1Y = 53.0;				// offset from base
    Contact2Y = 56.5;
    
    BaseWidthInner = PlateWidthMin;
    BaseWidthOuter = CaseOverallWidth;
    BaseLength = CaseOverallHeight;
    BaseThick = 1.0;
    BaseWidthTaper = 5.0;
    
    BaseOpeningMax = 42.0;
    BaseOpeningMin = 33.0;
    BaseOpeningY = 5.25;
    BaseOpeningDepth = 2.0;
    
    BaseTabWidth = 6.0;
    BaseTabThick = 2.0;
    BaseTabGap = 7.0;
    BaseTabOC = BaseTabWidth + BaseTabGap;
    
    BaseToothBase = 6.0;
    BaseToothTip = 3.0;
    BaseToothThick = 2.0;
    BaseToothOC = BaseTabOC;
    
    WedgeAngle = atan(BaseWidthTaper/((BaseWidthOuter - BaseWidthInner)/2));
    echo(str("Plate & Shell Wedge Angle: ",WedgeAngle));
    
    BaseEndLip = ThreadThick;			// should be 0.25 mm or so
    BaseEndWidth = (PlateWidthMin - 3*BaseToothBase - 2*BaseToothTip)/2;
    BaseEndAngle = atan((BaseOpeningDepth - BaseEndLip)/BaseOpeningY);
    
    echo(str("Plate End Angle: ",BaseEndAngle));
    
    PCBWidth = 2.00 * inch;
    PCBLength = 2.75 * inch;
    PCBMargin = Head2_56;
    PCBClearBottom = IntegerMultiple(2*Nut2_56Thick,ThreadThick);
    PCBHoleDia = Tap2_56;
    PCBHoleY = 2.50 * inch;
    PCBHoleX = 1.75 * inch;
    
    echo(str("PCB Mounting Holes OC X: ",PCBHoleX," Y: ",PCBHoleY));
    echo(str("       bottom clearance: ",PCBClearBottom));
    
    ShellHeight = CaseOverallHeight - PlateThick;
    ShellWidth = CaseOverallWidth;
    ShellLength = PlateLength;
    ShellWallX = IntegerMultiple((ShellWidth - PCBWidth)/2,ThreadWidth);
    ShellWallY = IntegerMultiple((ShellLength - PCBLength)/2,ThreadWidth);
    ShellWallMax = max(ShellWallX,ShellWallY);
    
    echo(str("Wall thick X: ",ShellWallX," Y: ",ShellWallY));
    
    LidThick = IntegerMultiple(1.0,ThreadThick);
    LidMargin = IntegerMultiple(1.0,ThreadWidth);
    LidWidth = ShellWidth - 2*LidMargin;
    LidLength = ShellLength - 2*LidMargin;
    
    LidScrewHead = Head3_48;
    LidScrewTap = Tap3_48;
    LidScrewClear = Clear3_48;
    LidScrewLength = 5.0;
    LidScrewOffsetX = ShellWidth/2 - LidMargin - 0.75*LidScrewHead;
    LidScrewOffsetY = ShellLength/2 - LidMargin - 0.75*LidScrewHead;
    
    HTCableDia = 5.0;
    HTCableAspect = 2.0;			// width of hole
    HTCableY = 65;
    HTCableZ = 10;
    
    SerialZ = ShellHeight - LidThick - 12.0;
    
    BikeCableDia = 5.0;
    BikeCableAspect = 1.5;
    BikeCableX = -20;
    BikeCableZ = 15;
    
    JackOC = 11.20;						// 14.25 OD - (3.58 + 2.58)/2
    
    JackScrewDia = 4.6;
    JackScrewOffsetX = 1.00;
    JackScrewOffsetY = 5.25;				//  mounting screw to edge of lower recess
    
    PlugBaseWidth = 9.25;				// lower section of plate
    PlugBaseLength = 22.0;
    PlugBaseThick = 2.5;
    PlugBaseRadius = 1.75;
    
    Plug3Offset = 5.25;					// edge of base recess to 3.5 mm jack
    
    Plug2BezelDia = 7.1;				// 2.5 mm plug
    Plug2BezelThick = 1.04;
    Plug2ScrewDia = 6.0;
    Plug3ScrewLength = 3.0;
    
    Plug3BezelDia = 8.13;				// 3.5 mm plug
    Plug3BezelThick = 1.6;
    Plug3ScrewDia = 7.95;
    Plug3ScrewLength = 4.0;
    
    PlugFillOffsetX = JackScrewOffsetX - 0.5;		// base recess CL to fill CL
    PlugFillOffsetY = -10.5;				//  ... to edge of fill plate
    PlugFillWidth = 11.0;
    PlugFillLength = 34.00;
    PlugFillThick = 3.0;
    PlugFillRadius1 = 1.5;
    PlugFillRadius2 = 4.5;
    
    PlugFillOffsetYTotal = 0;
    
    //----------------------
    // 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);
    
    }
    
    //- Hack job for DB-9 (DE-9) panel opening
    //  Snug fit around the shell surrounding the male pins
    //  DB-9 should mount on outside, but if it's already soldered to the board,
    //   that doesn't work and you must mount it inside the box
    
    module DSubMin9(Height=1.0) {
    
      union() {
    
    	linear_extrude(height=Height,center=false) {
    	  hull() {
    /*
    		translate([-(19.28+0.13)/2,(10.72+0.13)/2,0])			// rectangular outline
    		  circle(r=3.05/2,$fn=8);
    		translate([-(19.28+0.13)/2,-(10.72+0.13)/2,0])
    		  circle(r=3.05/2,$fn=8);
    		translate([ (19.28+0.13)/2,(10.72+0.13)/2,0])
    		  circle(r=3.05/2,$fn=8);
    		translate([ (19.28+0.13)/2,-(10.72+0.13)/2,0])
    		  circle(r=3.05/2,$fn=8);
    */
    		translate([-(17.0+0.05)/2,-(8.48+0.05)/2,0])
    		  circle(r=0.105*inch,$fn=8);
    		translate([ (17.0+0.05)/2,-(8.48+0.05)/2,0])
    		  circle(r=0.105*inch,$fn=8);
    		translate([ (15.5+0.05)/2, (8.48+0.05)/2,0])
    		  circle(r=0.105*inch,$fn=8);
    		translate([-(15.5+0.05)/2, (8.48+0.05)/2,0])
    		  circle(r=0.105*inch,$fn=8);
    	  }
    	  hull() {
    		translate([-24.99/2,0,0])
    		  circle(r=3.05/2,$fn=8);
    		translate([ 24.99/2,0,0])
    		  circle(r=3.05/2,$fn=8);
    	  }
    	}
      }
    
    }
    
    //-------------------
    
    //- Overall case outline
    //  This defines the mating taper into the radio shell
    
    module CaseEnvelope(Length=1) {
    
    	rotate([90,0,0])
    	  linear_extrude(height=Length,center=true,convexity=5)
    		polygon(points=[
    				  [-BaseWidthOuter/2,BaseLength],
    				  [-BaseWidthOuter/2,BaseWidthTaper],
    				  [-BaseWidthInner/2,0],
    				  [-BaseOpeningMax/2,0],
    
    				  [ BaseOpeningMax/2,0],
    				  [ BaseWidthInner/2,0],
    
    				  [ BaseWidthOuter/2,BaseWidthTaper],
    
    				  [ BaseWidthOuter/2,BaseLength]
    				],
    				convexity=1
    		);
    
    }
    
    //- Battery contact plate recess
    //  This gets subtracted from the bottom plate in two places
    
    module Contact() {
    
      union() {
    	translate([0,0,-(ContactRecess - Protrusion)/2])
    	  cube([ContactWidth,ContactLength,(ContactRecess + Protrusion)],center=true);
    	translate([0,0,-(PlateThick + Protrusion)])
    	PolyCyl(Clear3_48,(PlateThick + 2*Protrusion));
    	translate([0,0,-(ContactRecess + Head3_48Thick/3)])
    	  PolyCyl(Head3_48,Head3_48Thick);				// allow for solder blob
      }
    }
    
    //- Back interface plate with battery contacts
    
    module Plate() {
    
      difference() {
    
    	translate([0,PlateLength/2,0])
    	  intersection() {
    		translate([0,0,PlateThick])
    		  rotate([180,0,0])
    			CaseEnvelope(PlateLength);
    	  translate([-PlateWidthMax/2,-PlateLength/2,0])
    		cube([PlateWidthMax,PlateLength,PlateThick],center=false);
    	  }
    
    	translate([-(ContactGapX/2 + ContactWidth/2),(Contact1Y + ContactLength/2),PlateThick])
    	  Contact();
    	translate([+(ContactGapX/2 + ContactWidth/2),(Contact2Y + ContactLength/2),PlateThick])
    	  Contact();
    
    	translate([0,PlateLength/2,0])
    	  PCBHoles(PCBHoleDia,PlateThick);
    
    	translate([0,PlateLength/2,(PlateThick - 2*Head2_56Thick/3)])
    	  PCBHoles(IntegerMultiple(Head2_56,ThreadWidth),IntegerMultiple(Head2_56Thick,ThreadThick));
      }
    
    }
    
    //- Radio bottom locating feature
    //  This polygon gets subtracted from the battery pack base
    
    module RadioBase() {
    
    linear_extrude(height=(BaseOpeningDepth + Protrusion),center=false,convexity=5)
      polygon(points=[
    			[-BaseOpeningMax/2,-Protrusion],
    
    			[-BaseOpeningMin/2,BaseOpeningY],
    			[-(BaseToothOC/2 + BaseToothBase/2),BaseOpeningY],
    
    			[-(BaseToothOC/2 + BaseToothTip/2),(BaseOpeningY - BaseToothThick)],
    			[-(BaseToothOC/2 - BaseToothTip/2),(BaseOpeningY - BaseToothThick)],
    			[-(BaseToothOC/2 - BaseToothBase/2),BaseOpeningY],
    
    			[ (BaseToothOC/2 - BaseToothBase/2),BaseOpeningY],
    			[ (BaseToothOC/2 - BaseToothTip/2),(BaseOpeningY - BaseToothThick)],
    			[ (BaseToothOC/2 + BaseToothTip/2),(BaseOpeningY - BaseToothThick)],
    			[ (BaseToothOC/2 + BaseToothBase/2),BaseOpeningY],
    			[ BaseOpeningMin/2,BaseOpeningY],
    
    			[ BaseOpeningMax/2,-Protrusion],
    
    			[ (BaseTabOC + BaseTabWidth/2),-Protrusion],
    			[ (BaseTabOC + BaseTabWidth/2),BaseTabThick],
    			[ (BaseTabOC - BaseTabWidth/2),BaseTabThick],
    			[ (BaseTabOC - BaseTabWidth/2),-Protrusion],
    
    			[ BaseTabWidth/2,-Protrusion],
    			[ BaseTabWidth/2,BaseTabThick],
    			[-BaseTabWidth/2,BaseTabThick],
    			[-BaseTabWidth/2,-Protrusion],
    
    			[-(BaseTabOC + BaseTabWidth/2),-Protrusion],
    			[-(BaseTabOC + BaseTabWidth/2),BaseTabThick],
    			[-(BaseTabOC - BaseTabWidth/2),BaseTabThick],
    			[-(BaseTabOC - BaseTabWidth/2),-Protrusion],
    		  ],
    		  convexity=5
      );
    }
    
    //- PCB Mounting Holes
    
    module PCBHoles(HoleDia=PCBHoleDia,Height=1.0) {
    
      for (x=[-1,1])
    	for (y=[-1,1])
    	  translate([(x*PCBHoleX/2),
    				(y*PCBHoleY/2),
    				-Protrusion])
    		PolyCyl(HoleDia,(Height + 2*Protrusion));
    
    }
    
    //-- Battery pack base
    
    module Base() {
    
      difference() {
    
    	translate([0,0,(BaseThick + BaseOpeningDepth)/2])
    	  rotate([-90,0,0])
    		CaseEnvelope(BaseThick + BaseOpeningDepth);
    
    	translate([0,0,BaseThick])
    	  RadioBase();
    
    	translate([(BaseToothOC + BaseTabWidth/2),
    			  -(BaseThick + BaseEndLip)/tan(BaseEndAngle),
    			  0])
    	  rotate([BaseEndAngle,0,0])
    		cube([BaseEndWidth,3*BaseOpeningY,BaseOpeningDepth],center=false);
    
    	translate([-(BaseToothOC + BaseTabWidth/2 + BaseEndWidth),
    			  -(BaseThick + BaseEndLip)/tan(BaseEndAngle),
    			  0])
    	  rotate([BaseEndAngle,0,0])
    		cube([BaseEndWidth,3*BaseOpeningY,BaseOpeningDepth],center=false);
      }
    }
    
    //- Lid
    
    module Lid(WithHoles = false) {
    
      translate([0,LidLength/2,LidThick/2])
    	difference() {
    	  cube([LidWidth,LidLength,LidThick],center=true);
    	  if (WithHoles) {
    		translate([LidScrewOffsetX,LidScrewOffsetY,-(LidThick/2 + Protrusion)])
    		  PolyCyl(LidScrewClear,(LidThick + 2*Protrusion));
    		translate([-LidScrewOffsetX,-LidScrewOffsetY,-(LidThick/2 + Protrusion)])
    		  PolyCyl(LidScrewClear,(LidThick + 2*Protrusion));
    	  }
    	}
    }
    
    //- Lid screw support shape
    
    module LidScrewSupport(WithHole = false) {
    
      SupportSize = IntegerMultiple(LidScrewHead,ThreadWidth);
    
      difference() {
    	translate([0,0,LidScrewLength/2])
    	cube([SupportSize,SupportSize,LidScrewLength],center=true);
    	if (WithHole)
    	  translate([0,0,-Protrusion])
    		PolyCyl(LidScrewTap,(LidScrewLength + 2*Protrusion));
      }
    
      translate([-SupportSize/2,SupportSize/2,-2*SupportSize])
    	rotate([90,0,0])
    	  linear_extrude(height=SupportSize,center=false)
    		polygon(points=[
    				  [0,0],[0,2*SupportSize],[SupportSize,2*SupportSize]]);
    
    }
    
    //- Battery pack shell
    
    module Shell() {
    
      union() {
    	difference() {
    
    	  translate([0,0,-PlateThick])
    		intersection() {
    		  CaseEnvelope(ShellLength);
    		  translate([0,0,(ShellHeight/2 + PlateThick)])
    			cube([ShellWidth,ShellLength,ShellHeight],center=true);
    		}
    
    	  translate([0,-LidLength/2,(ShellHeight - LidThick)])
    		scale([1,1,2])				// ensure clean cut across top
    		  Lid(false);
    
    	  translate([0,0,
    				((ShellHeight - PCBClearBottom - LidThick + Protrusion)/2 + PCBClearBottom)])
    		cube([PCBWidth,PCBLength,
    			 (ShellHeight - PCBClearBottom - LidThick + Protrusion)],
    			 center=true);
    
    	  render()
    		difference() {
    		  translate([0,0,ShellHeight/2])
    			cube([(PCBWidth - 2*PCBMargin),
    				(PCBLength - 2*PCBMargin),
    				(ShellHeight + 2*Protrusion)],
    				center=true);
    		  for (x=[-1,1])
    			for (y=[-1,1])
    			  translate([(x*PCBHoleX/2),(y*PCBHoleY/2),-Protrusion])
    				cylinder(r=PCBMargin,(ShellHeight + 2*Protrusion),$fn=4);
    		}
    
    	  PCBHoles(PCBMargin);
    
    	  translate([-(PCBWidth/2 - Protrusion),(HTCableY - PlateLength/2),HTCableZ])
    		rotate([0,-90,0])
    		  scale([1/HTCableAspect,1,1])
    			PolyCyl(HTCableDia,(ShellWallMax + 2*Protrusion),8);
    
    	  translate([BikeCableX,(PCBLength/2 - Protrusion),BikeCableZ])
    		rotate([0,90,90])
    		  scale([1/BikeCableAspect,1,1])
    			PolyCyl(BikeCableDia,(ShellWallMax + 2*Protrusion),8);
    
    	  translate([0,(PCBLength/2 - Protrusion),SerialZ])
    		rotate([-90,0,0])
    		  DSubMin9(ShellWallMax + 2*Protrusion);
    
    	}
    
      	translate([0,(PCBLength/2 + ThreadWidth/2),SerialZ])
    	  rotate([-90,0,0])
    		scale([0.95,0.95,1])
    		  DSubMin9(ShellWallY - ThreadWidth);		// thin support plug in hole
    
      }
    
    }
    
    //- Speaker-Mic plug mounting plate
    
    module PlugPlate() {
    
      BaseX = PlugBaseWidth/2 - PlugBaseRadius;
      BaseY = PlugBaseLength/2 - PlugBaseRadius;
    
      difference() {
    	union() {
    	  linear_extrude(height=PlugBaseThick,center=false,convexity=3)
    		hull() {
    		  translate([-BaseX,-BaseY,0])
    			circle(r=PlugBaseRadius,$fn=8);
    		  translate([-BaseX, BaseY,0])
    			circle(r=PlugBaseRadius,$fn=8);
    		  translate([ BaseX, BaseY,0])
    			circle(r=PlugBaseRadius,$fn=8);
    		  translate([ BaseX,-BaseY,0])
    			circle(r=PlugBaseRadius,$fn=8);
    		}
    
    	  translate([PlugFillOffsetX,
    				(PlugFillLength/2 - PlugBaseLength/2 + PlugFillOffsetY),
    				PlugBaseThick])
    		linear_extrude(height=PlugFillThick,center=false,convexity=5)
    		  hull() {
    			translate([0,-(PlugFillLength/2 - PlugFillRadius2),0])
    			  circle(r=PlugFillRadius2,$fn=10);
    			translate([-(PlugFillWidth/2 - PlugFillRadius1),-PlugBaseLength/2,0])
    			  circle(r=PlugFillRadius1,$fn=8);
    			translate([-(PlugFillWidth/2 - PlugFillRadius1),
    					  (PlugFillLength/2 - PlugFillRadius1),0])
    			  circle(r=PlugFillRadius1,$fn=8);
    			translate([(PlugFillWidth/2 - PlugFillRadius1),
    					  (PlugFillLength/2 - PlugFillRadius1),0])
    			  circle(r=PlugFillRadius1,$fn=8);
    			translate([(PlugFillWidth/2 - PlugFillRadius1),-PlugBaseLength/2,0])
    			  circle(r=PlugFillRadius1,$fn=8);
    		  }
    	}
    
    	translate([0,-JackOC/2,-Protrusion])
    	  rotate(360/16) {
    		PolyCyl(Plug3BezelDia,(Plug3BezelThick + Protrusion),8);
    		PolyCyl(Plug3ScrewDia,(PlugBaseThick + PlugFillThick + 2*Protrusion),8);
    	  }
    
    	translate([0,+JackOC/2,-Protrusion])
    	  rotate(360/16) {
    		PolyCyl(Plug2BezelDia,(Plug2BezelThick + Protrusion),8);
    		PolyCyl(Plug2ScrewDia,(PlugBaseThick + PlugFillThick + 2*Protrusion),8);
    	  }
    
    	translate([JackScrewOffsetX,-(PlugBaseLength/2 + JackScrewOffsetY),0])
    	  PolyCyl(JackScrewDia,(PlugBaseThick + PlugFillThick + Protrusion));
      }
    
    }
    
    //-------------------
    // Build it!
    
    ShowPegGrid();
    
    if (Layout == "Envelope")
      CaseEnvelope(CaseOverallLength);
    
    if (Layout == "Plate")
      Plate();
    
    if (Layout == "Base")
      Base();
    
    if (Layout == "Lid")
      Lid(true);
    
    if (Layout == "ScrewSupport")
      LidScrewSupport(true);
    
    if (Layout == "Shell")
      Shell();
    
    if (Layout == "PlugPlate")
      PlugPlate();
    
    if (Layout == "DSub")
      DSubMin9();
    
    if (Layout == "Fit") {
    
      translate([0,-PlateLength/2,0]) {
    
    	translate([0,0,PlateThick])
    	  rotate([0,180,0])
    		color(LOR) Plate();
    
    	rotate([90,0,0])
    	  color(DYO) Base();
    
    	translate([0,LidMargin,10 + (CaseOverallHeight - LidThick)])
    	  color(MOR) Lid(true);
    
    	translate([0,PlateLength/2,PlateThick])
    	    color(MFG) render() Shell();
    
    	translate([-(ShellWidth/2 +10),70,15])
    	  rotate([0,-90,0])
    		color(DDY) PlugPlate();
      }
    }
    
    if (Layout == "Build1") {
    
      translate([-20,-PlateLength/2,0])
    	Plate();
    
        translate([10,0,0])
    	rotate([0,0,-90])
    	  Base();
    
    }
    
    if (Layout == "Build2") {
    
      translate([0,-LidLength/2,0])
    	Lid(true);
    
    }
    
    if (Layout == "Build3") {
    
      translate([-20,0,0])
    	Shell();
    
    }
    
    if (Layout == "Build4") {
    
      translate([0,0,(PlugBaseThick + PlugFillThick)])
    	rotate([180,0,0])
    	  PlugPlate();
    
    }