The Smell of Molten Projects in the Morning

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

Tag: CNC

Making parts with mathematics

  • Wouxun KG-UV3D Plug Plate

    Based on those measurements that suggest spacing the plugs at 11.5 mm on center, I tweaked that parameter in the source code there and printed another one, just like the other one. Actually, I printed four of the fool things this time:

    Wouxun plug plates - 11.5 mm fixture
    Wouxun plug plates – 11.5 mm fixture

    With the plugs in the gluing fixture and the fixture in the vise, a ring of epoxy around the threaded sides holds them in place:

    Wouxun plug plate - wired
    Wouxun plug plate – wired

    A trial fit in the Wouxun KG-UV3D shows that the jacks prefer the 11.2 mm spacing I measured on the Wouxun headset, but they’ll accept plugs on 11.5 mm centers. I don’t know if that’s a real specification difference, a manufacturing tolerance, or what.

    FWIW, I’ve been using snippets of that cable forever, because it’s perfect for this application: two unshielded conductors and three more inside a braid, supple as a snake. It’s surplus, of course, with a gorgeous push-lock plug (and the jack!) on one end that must have cost a fortune… and which I’ll never to use for anything. Got two of them, just in case.

    Mushing an epoxy putty turd on the top anchors everything in place and protects the wires:

    Wouxun plug plate - epoxy cap
    Wouxun plug plate – epoxy cap

    In point of fact, the cable insulation isn’t anchored inside the blob and a minor tug could pull it loose. There will be a bit of slack at the case to allow for unlatching it from the radio, but the lashup will spend its entire life inside a snug pouch, so it shouldn’t come to any harm. We shall see.

  • Kenwood / Wouxun Headset Jack Spacing

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

    Try as I might, I cannot uncover a definitive answer to this simple question: What’s the center-to-center spacing of the mic and earphone jacks on the side of Kenwood and Wouxun HTs?

    The usual searches produce answers like 11 and 12 mm, both of which are obviously wrong, as can be determined eyeballometrically just by holding a scale against the plugs.

    Based on measurements I made on a Wouxun headset, the yellow plug mounting plate put the plugs on 11.2 mm centers and they fit into the KG-UV3D radio; it’s been working fine ever since.

    However, having just measured a speaker/mic and a headset, both from Kenwood, I come up with 11.5 mm. Frankly, I trust the Kenwood hardware a bit more: the plugs seem more rugged and the overall production values are higher.

    The calculation is simple: measure the pin diameters, then subtract half their sum from the outside distance across the pins. Cross-check by adding half the sum to the inside distance between the pins, which should give the same answer. It helps if the pins are actually round.

    The jacks in the Kenwood and Wouxun radios have enough compliance to accept either a Wouxun or a Kenwood headset plug without complaint. Maybe it doesn’t matter?

    Despite that, I made another gluing fixture with 11.5 mm spacing:

    Plug alignment plate - 11.5 mm spacing
    Plug alignment plate – 11.5 mm spacing

    Those are 0.1 inch grids; it’s a little bitty block of smoke-gray polycarbonate from the scrap heap. The plugs are nominally 3.5 mm (which is not 1/8 inch in this universe) and 2.5 mm, with clearance drills #28 and #39.

    Then I tried poking those 11.2 mm spaced plugs, now firmly epoxied in place in the yellow plate, and guess what: they don’t fit, no how no way. That’s not surprising, because there’s no compliance on either side of the joint and the plugs aren’t on the right centers for the fixture. Makes for a good No-Go gauge, I suppose.

    However, I think I’ll tweak the solid model spacing to 11.5 mm and run off another plug mounting plate for the next radio.

    FWIW, our ICOM IC-Z1A HTs use a sensible 10.0 mm spacing and that old fixture worked fine.

  • Maximum PCB Platen: Hold-Down Screws

    The whole point of tweaking the Sherline was to get it ready to drill the Wouxun KG-UV3D GPS+voice PCB. While setting up for that, I drilled two #5 holes in the maximum-size PCB platen for 10-32 socket head cap screws to hold it to the tooling plate:

    Sherline with maximum PCB platen
    Sherline with maximum PCB platen

    The sloppy hole fit lets the platen align to the tooling plate with the outer two 6-32 screws on the back edge.

    Most of the PCB boards I make aren’t nearly as wide as the platen, which means the new SHCS won’t get in the way. The screws require a nut (as a spacer) to keep them from bottoming out on the Sherline’s table underneath the tooling plate and the washers are just because I can’t do it any other way; I should just shorten the screws and store them with the platen.

    Masking tape holds small PCBs to the platen reasonably well, probably because I use an unreasonably high 50 mil travel clearance. I have a defunct dehumidifier that might make a dandy low-volume vacuum pump to eliminate any lifting in the middle: a project that has been on the to-do list for far too long…

  • Sherline CNC Mill Y Axis Home Switch: To The Front!

    Reassembling the mill provided an opportunity to move the Y axis Home switch from the rear of the axis to the front. The key discovery happened during the teardown: I can get the saddle off the Y axis dovetail by removing the gib, without sliding it off the front, which means a front switch can remain firmly glued in place.

    A few random hunks of steel and a wire nut held the switch in position while the epoxy cured:

    Mounting Y axis home switch
    Mounting Y axis home switch

    The switch actuator bottoms out with the saddle just touching the preload nut, so the saddle can’t dislodge the switch: the switch trips just before the saddle hits the nut, at which point all motion stops and the motor stalls.

    Moving the switch means I can remove all the gimcrackery that poked the rear switch with the tooling plate in place; I was never happy with that setup. I also removed the small block that trapped the rear end of the Y leadscrew, under the assumption that, as I haven’t yet dropped anything on the leadscrew, I probably won’t. That adds about 1/4 inch to the maximum travel and allows the tooling plate to whack into the column.

    The switch wire runs along the stepper cable, a tidy technique that hasn’t introduced any glitches into the shared Home signal from the X axis drivers:

    Sherline mill - X and Y axis home switches
    Sherline mill – X and Y axis home switches

    The Y axis now seeks the Home switch in the positive Y direction, so that stanza in Sherline.ini looks like this:

    [AXIS_1]
    TYPE = LINEAR
    MAX_VELOCITY = 0.400
    MAX_ACCELERATION = 5.0
    STEPGEN_MAXACCEL = 10.0
    SCALE = 16000.0
    FERROR = 0.05
    MIN_FERROR = 0.01
    MIN_LIMIT = -0.5
    MAX_LIMIT = 4.90
    BACKLASH = 0.003
    HOME_IS_SHARED = 1
    HOME_SEQUENCE = 2
    HOME_SEARCH_VEL = 0.3
    HOME_LATCH_VEL = 0.016
    HOME_FINAL_VEL = -0.4
    HOME_OFFSET = 5.125
    HOME = 5.0
    
  • Wouxun KG-UV3D GPS+Voice Interface: Improved Case

    This case has a few refinements beyond that one, but it’s recognizably a descendant. The main changes:

    • The HT cable port on the side has a nice polygonal roof to reduce overhang
    • The serial connector sits in a recess to allow a thicker top plate
    • Smaller opening for the LEDs; I’ll get a window in this one, fer shure, yeah
    • 4-40 screws hold the base plate on; setscrews may work and look better

    Looks like I’ll be using blue filament for this version, having just discovered the last of the weird colors in the bottom of the 5 gallon bucket serving as a storage bin.

    A view from the top:

    Solid Model - Oblique Exploded Top
    Solid Model – Oblique Exploded Top

    And from the base:

    Solid Model - Oblique Exploded Base
    Solid Model – Oblique Exploded Base

    The OpenSCAD source code:

    // Wouxun KB-UV3D Battery Pack Case
    // Ed Nisley KE4ZNU July 2012
    
    include </home/ed/Thing-O-Matic/lib/MCAD/units.scad>
    include </home/ed/Thing-O-Matic/Useful Sizes.scad>
    
    // Layout options
    
    Layout = "Show";
    					// Overall layout: Fit Show
    					// Printing plates: Build1 .. Buildn (see bottom!)
    					// Parts: TT3 Audio DSub Shell Base Top
    					// Shapes: RadioBase Contact
    					// Speaker-mic mount: PlugPlate
    
    ShowGap = 10;		// spacing between parts in Show layout
    
    //- Extrusion parameters must match reality!
    //  Print with +1 shells and 3 solid layers
    
    ThreadThick = 0.25;
    ThreadWidth = 2.0 * ThreadThick;
    
    HoleWindage = 0.2;
    
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    
    Protrusion = 0.1;			// make holes end cleanly
    
    //----------------------
    // Dimensions
    
    CaseOverallHeight = 31.5;				// from battery surface, must clear PCBs!
    CaseOverallWidth = 56;
    CaseOverallLength = 80.25;				// inside of base to end of compartment
    
    BatteryClearance = 1.5;					// contact seal height = air gap to compartment
    
    // Interface to radio battery contacts
    //	Length = shell length
    //		calculated after everything else, so as to fill the compartment
    
    ContactDia = 6.0;				// use rounded contact for simplicity
    ContactRecess = IntegerMultiple(0.75,ThreadThick);	// recess for contact plate
    ContactGapX = 10.5;				// X space between contacts
    Contact1Y = 52.5;				// offset from base to edge of contact
    Contact2Y = 56.5;
    ContactStudDia = Clear4_40;
    ContactStudHead = IntegerMultiple(Head4_40,ThreadWidth);
    ContactStudHeadThick = Head4_40Thick;
    
    PlateWidthMin = 53.0;
    PlateWidthMax = 54.5;
    PlateThick = IntegerMultiple(ContactRecess + ContactStudHeadThick,ThreadThick);
    PlateAngle = atan(PlateThick/(PlateWidthMax/2 - PlateWidthMin/2));
    
    echo("Battery plate thick: ",PlateThick);
    
    // Offsets from battery surface to PCB centerlines
    //	TT3 must be above HT back shell for DB9 clearance
    //	These must cooperate with the numbers in the case shell module
    
    TT3Offset = 17.5 + PlateThick;
    AudioOffset = 4.0 + PlateThick;
    
    // Plate interface to base alignment holes and notches
    
    BaseWidthInner = PlateWidthMin;
    BaseWidthOuter = CaseOverallWidth;
    BaseLength = CaseOverallHeight;					// perpendicular to battery surface
    BaseThick = IntegerMultiple(1.0,ThreadThick);	// minimum sheet thickness below teeth
    BaseWidthTaper = 5.0;							// ramp across entire width
    
    BaseOpeningMax = 43.0;
    BaseOpeningMin = 33.0;
    BaseOpeningY = 5.3;
    BaseOpeningDepth = IntegerMultiple(2.25,ThreadThick);
    
    BaseTotalThick = BaseThick + BaseOpeningDepth;
    echo("Base min thick: ",BaseThick," total: " ,BaseTotalThick);
    
    BaseTabWidth = 6.0;
    BaseTabThick = 2.0;
    BaseTabGap = 7.0;
    BaseTabOC = BaseTabWidth + BaseTabGap;
    BaseToothSection = 3*BaseTabWidth + 2*BaseTabGap;
    
    BaseToothBase = 5.8;
    BaseToothTip = 2.8;
    BaseToothThick = 2.0;
    BaseToothAngle = atan(BaseOpeningDepth/0.6);
    BaseToothOC = BaseTabOC;
    
    WedgeAngle = atan(BaseWidthTaper/((BaseWidthOuter - BaseWidthInner)/2));
    
    BaseEndLip = ThreadThick;			// should be 0.25 mm or so
    BaseEndWidth = (PlateWidthMin - 3*BaseToothBase - 2*BaseToothTip)/2;
    BaseEndAngle = atan((BaseOpeningDepth - BaseEndLip)/BaseOpeningY);
    
    SwitchBody = [8.6,3.7,3.3];			// mode switch
    
    // Plate interface to HT battery latch, cables, and connectors
    
    TopThick = IntegerMultiple(5.5,ThreadThick);	// plate thickness for stiffness behind latch bar
    echo("Top plate thick: ",TopThick);
    
    DB9Recess = TopThick - 4.0;			// recess to max TT3 PCB clearance behind DB9 plate
    
    TabEngageLength = 1.6;				// tab engaging surface length
    TabWidth = 3.0;						//  ... width
    TabEngageHeight = 4.5;				//  ... above battery compartment floor
    TabHeight = 7.5;					// tab ramp top above battery compartment floor
    TabOC = 40.0;
    
    LatchBarWidth = 3.4;				// sliding latch mechanism (brass L stock)
    LatchBarDepth = 3.4;
    LatchBarThick = 0.35;
    
    echo(" ... minimum: ",TopThick - LatchBarDepth);
    
    SplitOffset = TT3Offset - 3.5;
    
    TopBevel = 1.0;						// bevel at top of battery compartment
    TopBevelAngle = 45;
    
    PinOffsetWidth = 2.5;				// choose to center in sides of case shell
    PinOffsetHeight = 13.5;				// above baseplate bottom
    PinDepth = 7.0;						// into case shell
    PinDia = 1.2;
    
    ShellLength = CaseOverallLength - BaseThick - TopThick;
    
    echo("Shell length: ",ShellLength);
    
    // Speaker-mic plug plate
    
    PlugBaseThick = 2.5;				// recess depth
    PlugFillThick = 3.0;				// outer plate thickness
    
    //----------------------
    // 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);
    
    }
    
    //-------------------
    // Component parts
    
    //-----
    // TinyTrak3+ PCB and component envelope
    //	Some dimensions should feed into the case shell, but don't
    
    module TinyTrak3(Length = 1.0) {
    
    PCBThick = 1.6;
    PCBWide = 36.5;
    TopHigh = 9.5;
    TopWide = PCBWide - 1.5;
    BotHigh = 2.5;
    BotWide = 35.0;
    
    PCBx = PCBWide/2;
    PCBy = (PCBThick + HoleWindage)/2;
    URx = TopWide/2;
    URy = PCBy + TopHigh;
    LRx = BotWide/2;
    LRy = PCBy + BotHigh;
    
    linear_extrude(height=Length,center=false,convexity=2) {
    	polygon(points=[[URx,URy],[URx,PCBy],[PCBx,PCBy],[PCBx,-PCBy],[LRx,-PCBy],[LRx,-LRy],
    					[-LRx,-LRy],[-LRx,-PCBy],[-PCBx,-PCBy],[-PCBx,PCBy],[-URx,PCBy],[-URx,URy]
    				   ]);
    }
    }
    
    //-----
    // Interface PCB and component envelope
    //	Some dimensions should feed into the case shell, but don't
    
    module AudioInterface(Length = 1.0) {
    
    PCBThick = 2.0;
    PCBWide = 49.5;
    TopHigh = 9.0 + Protrusion;
    TopWide = 46.0;
    BotHigh = 3.0;
    BotWide = 44.0;
    
    PCBx = PCBWide/2;
    PCBy = (PCBThick + HoleWindage)/2;
    URx = TopWide/2;
    URy = PCBy + TopHigh;
    LRx = BotWide/2;
    LRy = PCBy + BotHigh;
    
    linear_extrude(height=Length,center=false,convexity=2) {
    	polygon(points=[[URx,URy],[URx,PCBy],[PCBx,PCBy],[PCBx,-PCBy],[LRx,-PCBy],[LRx,-LRy],
    					[-LRx,-LRy],[-LRx,-PCBy],[-PCBx,-PCBy],[-PCBx,PCBy],[-URx,PCBy],[-URx,URy]
    				   ]);
    }
    }
    
    //-----
    // DB-9 (DE-9) panel opening
    // http://www.interfacebus.com/Connector_D-Sub_Mechanical_Dimensions.html
    //  DB-9 shell mounts on outside surface of case
    // This is for the solder terminal side
    
    module DSubMin9(Length = 1.0) {
    
    Holex = 0.984/2 * inch;
    HoleDia = Tap4_40;
    
    URx = 0.769/2 * inch;
    URy = 0.432/2 * inch;
    
    	linear_extrude(height=Length,center=false,convexity=3) {
    	  polygon(points=[[URx,URy],[URx,-URy],[-URx,-URy],[-URx,URy]]);
    	  for (x = [-1,1]) {
    		translate([x*Holex,0,0])
    		  rotate(45) circle(r=(HoleDia + HoleWindage)/2,$fn=4);
    	  }
    	}
    
    }
    
    //-----
    // Central case shape
    //	This *should* depend directly on the circuit board sizes, but doesn't
    //	The "Offset" parameters attempt to bottle up all the board sizes
    //	Support in LED window must be hand-fit to work correctly... and isn't needed!
    
    module CaseShell(Length=(ShellLength),Holes="true") {
    
    // Polygon coordinates are in XY plane
    
    URx = 40.0/2;
    URy = CaseOverallHeight;
    
    MRx = CaseOverallWidth/2;
    MRy = 15.0;
    
    LRx = CaseOverallWidth/2;
    LRy = (LRx - PlateWidthMin/2)*tan(PlateAngle);
    
    BRx = PlateWidthMax/2;
    BRy = PlateThick - 0*Protrusion;
    
    PRx = PlateWidthMin/2;				// combined battery plate
    PRy = 0;
    
    ScrewOffset = 20.0;					// from top end of case
    
    LEDWindow = [26.0,5.0,6];			// with case aligned vertically
    LEDOffset = [15,URy,(Length + TopThick - 25.0)];
    
    TrimPot1 = [-14,TT3Offset,(Length + TopThick - 30)];
    TrimPot2 = [-14,TT3Offset,(Length + TopThick - 37.5)];
    
    HTCableDia = 3.5;
    HTCableOffset = AudioOffset + HTCableDia/2 + 1.0;
    
    rotate([90,0,180])
    	union() {
    	  difference() {
    
    		  linear_extrude(height=Length,center=false,convexity=5)
    			polygon(points=[[URx,URy],[MRx,MRy],[LRx,LRy],[BRx,BRy],[PRx,PRy],
    							[-PRx,PRy],[-BRx,BRy],[-LRx,LRy],[-MRx,MRy],[-URx,URy]]);
    
    		if (Holes) {
    		  translate([0,AudioOffset,-Protrusion])
    			AudioInterface(Length + 2*Protrusion);
    
    		  translate([0,TT3Offset,-Protrusion])
    			TinyTrak3(Length + 2*Protrusion);
    
    		  for (y = [TT3Offset,AudioOffset])
    			translate([-CaseOverallWidth,y,(Length - ScrewOffset)])
    			  rotate([0,90,0])
    				rotate(0)					// Z rotation puts point upward for printing
    				PolyCyl(Tap4_40,CaseOverallWidth);
    
    		  translate(LEDOffset)
    			rotate([90,90,0])
    			  translate([-LEDWindow[0]/2,-LEDWindow[1]/2,-Protrusion])
    			  cube(LEDWindow,center=false);
    
    		  for (p = [TrimPot1,TrimPot2])
    			translate(p)
    			  rotate([-90,90,0])				// Y rotation puts point upward for printing
    				PolyCyl(3.0,URy);
    
    		  for (x=[-1,1]) {
    			translate([x*(CaseOverallWidth/2 - PinOffsetWidth),
    					  PinOffsetHeight,
    					  (Length - PinDepth)])
    			  rotate(45)						// align hole sides with case sides
    				  PolyCyl(PinDia,2*TopThick);
    			translate([x*(CaseOverallWidth/2 - PinOffsetWidth),
    					  PinOffsetHeight,
    					  -PlateThick])
    			  rotate(45)						// align hole sides with case sides
    				  PolyCyl(PinDia,(PlateThick + PinDepth));
    		  }
    
    		  for (x=[-1,1])						// setscrews to secure base plate
    			translate([x*(CaseOverallWidth/2 - 3*Tap4_40),
    					  TT3Offset,-Protrusion])
    			  rotate(360/(5*4))
    				PolyCyl(Tap4_40,2*TopThick);
    
    		  translate([-(ContactGapX/2 + ContactDia/2),0,(Contact1Y + ContactDia/2)])
    			rotate([90,0,0])
    			  Contact();
    		  translate([+(ContactGapX/2 + ContactDia/2),0,(Contact2Y + ContactDia/2)])
    			rotate([90,0,0])
    			  Contact();
    
    		  translate([CaseOverallWidth/2,HTCableOffset,(Length - HTCableDia/4)])
    			rotate([0,90,0])
    			  cube([(HTCableDia/2 + Protrusion),HTCableDia,CaseOverallWidth],center=true);
    		  translate([0,HTCableOffset,(Length - HTCableDia/2)])
    			rotate([0,90,0])
    			  cylinder(r=(1/cos(30))*HTCableDia/2,h=CaseOverallWidth,$fn=6);
    		}
    	  }
    
    if (false)
    	  if (Holes)
    		translate(LEDOffset)						// support plug in LED window
    		  rotate([90,90,0])
    			translate([-0.95*LEDWindow[0]/2,-0.80*LEDWindow[1]/2,ThreadWidth/2])
    			  cube([0.95*LEDWindow[0],0.80*LEDWindow[1],2*ThreadWidth],center=false);
    
    	}
    
    }
    
    //-----
    // Battery contact recess
    //  This gets subtracted from the bottom plate in two places
    // 	Align points to print upward
    
    module Contact() {
    
    if (true)
    union() {													// vertical printing with case
    	translate([0,0,-(ContactRecess + Protrusion)/2])
    	  PolyCyl(ContactDia,(ContactRecess + Protrusion),8);
    	translate([0,0,-(PlateThick + Protrusion)])
    	  rotate(60/2)
    	  PolyCyl(ContactStudDia,PlateThick,6);
    	translate([0,0,-(ContactRecess + ContactStudHeadThick/3)])
    	  PolyCyl(ContactStudHead,ContactStudHeadThick,8);				// allow for solder blob
    }
    else
    union() {												// horizontal printing alone
    	translate([0,0,-(ContactRecess - Protrusion)/2])
    	  PolyCyl(ContactDia,(ContactRecess + Protrusion),8);
    	translate([0,0,-(PlateThick + Protrusion)])
    	  PolyCyl(ContactStudDia,(PlateThick + 2*Protrusion));
    	translate([0,0,-(ContactRecess + ContactStudHeadThick/3)])
    	  PolyCyl(ContactStudHead,ContactStudHeadThick,8);				// allow for solder blob
    }
    
    }
    
    //-----
    // 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
    );
    }
    
    //-----
    // Battery pack base
    
    module Base() {
    
    difference() {
    
    	rotate([-90,180,0])						// main case shape
    	  CaseShell(BaseTotalThick,false);
    
    	translate([0,0,BaseThick])				// radio base interface
    	  RadioBase();
    
    	translate([0,0,BaseThick])				// tooth bevel
    	  rotate([(-90 + BaseToothAngle),0,0])
    		translate([0,-0.5,0])
    		  cube([(BaseToothSection + 2*Protrusion),1.0,10],center=true);
    
    	translate([-BaseWidthOuter,				// surface slope
    			  -(BaseThick + BaseEndLip)/tan(BaseEndAngle),0])
    	  rotate([BaseEndAngle,0,0])
    		difference() {
    		  cube([2*BaseWidthOuter,3*BaseOpeningY,BaseOpeningDepth],center=false);
    		  translate([(BaseWidthOuter - (BaseToothSection + 2*Protrusion)/2),0,0])
    			cube([(BaseToothSection + 2*Protrusion),1.2*BaseOpeningY,BaseOpeningDepth],center=false);
    		}
    
    	for (x=[-1,1])							// alignment pin holes
    	  translate([x*(CaseOverallWidth/2 - PinOffsetWidth),PinOffsetHeight,-Protrusion])
    		rotate(45)							// align hole side with plate side
    		  PolyCyl(PinDia,2*TopThick);
    
    	for (x=[-1,1])							// mounting setscews
    	  translate([x*(CaseOverallWidth/2 - 3*Tap4_40),
    				TT3Offset,-Protrusion])
    		rotate(-360/(-5*4))
    		  PolyCyl(Tap4_40,2*TopThick);
    
    	translate([(-SwitchBody[0]/2),TT3Offset,-SwitchBody[2]/2])	// mode switch
    	  scale([1,1,2])
    		cube(SwitchBody);
    
    }
    }
    
    //-----
    // Top plate with latch
    //	Split around TinyTrak3 serial connector
    //	 ... which must be at the same height as in the shell!
    //	The cable hole sizes & locations are entirely ad-hoc
    
    module TopPlate() {
    
    Cable1Dia = 5.0;
    Cable2Dia = 5.0;
    CableHoleLength = TopThick + 2*Protrusion;
    CableHoleZ = -Protrusion;
    
    DB9Plate = [32.0,13.5,1.25];					// plate surrounding connector body
    
    difference() {
    
    	rotate([-90,180,180])
    	  CaseShell(TopThick,false);
    
    	translate([0,-TT3Offset,-Protrusion])
    	  DSubMin9(TopThick + 2*Protrusion);
    
    	translate([0,-TT3Offset,(TopThick - DB9Plate[2]/2)])
    	  cube([DB9Plate[0],DB9Plate[1],(DB9Plate[2] + Protrusion)],center=true);
    
    	translate([-CaseOverallWidth,-SplitOffset,-2*Protrusion])		// split the plate
    	  cube([2*CaseOverallWidth,4*Protrusion,(TopThick + 2*Protrusion)]);
    
    	translate([0,0,(TopThick - TopBevel)])
    	  rotate([-TopBevelAngle,0,0])
    		translate([-CaseOverallWidth,-TopThick,0])
    		  cube([2*CaseOverallWidth,2*TopThick,2*TopThick],center=false);
    
    	for (x=[-1,1])
    	  translate([(x*TabOC/2),
    				(-TabHeight/2 + Protrusion),
    				(TopThick - TabEngageLength/2 + Protrusion/2)])
    		rotate([90,0,0])
    		  cube([TabWidth,
    				(TabEngageLength + Protrusion),
    				(TabHeight + Protrusion)],center=true);
    
    	translate([-CaseOverallWidth,
    			  -(TabEngageHeight + LatchBarWidth - BatteryClearance),
    			  (TopThick - LatchBarDepth)])
    	  cube([2*CaseOverallWidth,(LatchBarWidth + LatchBarThick),(LatchBarDepth + Protrusion)]);
    
    	for (x=[-1,1])
    	  translate([(x*CaseOverallWidth/4),
    				-(TabEngageHeight + LatchBarWidth + Clear2_56/2 - BatteryClearance + Protrusion),
    				0]) {
    		translate([0,0,-Protrusion])
    		  rotate(45)						// align sides with slot
    			PolyCyl(Tap2_56,(TopThick + 2*Protrusion));
    		translate([0,0,(TopThick - LatchBarDepth)])
    		  rotate(60)						// align sides with slot
    			PolyCyl((Head2_56 + Protrusion),TopThick,6);		// extra extra clearance
    	  }
    
    	for (x=[-1,1])
    	  translate([x*(CaseOverallWidth/2 - PinOffsetWidth),-PinOffsetHeight,-Protrusion])
    		rotate(45)						// align hole side with plate side
    		  PolyCyl(PinDia,2*TopThick);
    
    	for (x=[-1,1])						// coincidentally line up with latch tabs
    	  translate([(x*TabOC/2),-(SplitOffset - 3.0),-Protrusion])
    		scale([1,1.7,1])
    		  PolyCyl(Cable1Dia,CableHoleLength,6);
    }
    
    }
    
    //-----
    // Speaker-Mic plug mounting plate
    
    module PlugPlate() {
    
    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;
    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;
    PlugFillRadius1 = 1.5;
    PlugFillRadius2 = 4.5;
    
    PlugFillOffsetYTotal = 0;
    
    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 things...
    
    ShowPegGrid();
    
    if (Layout == "TT3")
    TinyTrak3();
    
    if (Layout == "Audio")
    AudioInterface();
    
    if (Layout == "DSub")
    DSubMin9();
    
    if (Layout == "Shell")
    CaseShell(CaseOverallLength);
    
    if (Layout == "Top")
    TopPlate();
    
    if (Layout == "Base")
    Base();
    
    if (Layout == "RadioBase")
    RadioBase();
    
    if (Layout == "PlugPlate")
    PlugPlate();
    
    if (Layout == "Contact")
    rotate([180,0,0])
    	Contact();
    
    if (Layout == "Show" || Layout == "Fit") {
    
    translate([0,-ShellLength/2,0]) {
    
    	translate([0,(Layout == "Show")?-ShowGap:0,0])
    	  rotate([90,0,0])
    		color("SandyBrown") Base();
    
    	translate([0,0,0])
    	    color("LightGreen") render() CaseShell();
    
    	translate([-(CaseOverallWidth/2 + 10),50,CaseOverallHeight/2])
    	  rotate([0,-90,0])
    		color("Gold") PlugPlate();
    
    	translate([0,((Layout == "Show")?(ShellLength + ShowGap):ShellLength),0])
    	  rotate([-90,0,0])
    		color("BurlyWood") TopPlate();
    }
    }
    
    if (Layout == "Build1") {
    
    translate([5 + CaseOverallHeight,0,0])
    	rotate([0,0,90])
    	  Base();
    
    translate([-(5 + CaseOverallHeight),0,0])
    	rotate(90)
    	  TopPlate();
    
    }
    
    if (Layout == "Build2") {
    
    translate([0,-CaseOverallHeight/2,ShellLength])
    	rotate([-90,0,0])
    		CaseShell();
    
    }
    
    if (Layout == "Build3") {
    
    translate([0,0,(PlugBaseThick + PlugFillThick)])
    	rotate([180,0,0])
    	  PlugPlate();
    
    }
    
  • Wouxun KG-UV3D GPS Interface: PCB Component Envelopes

    This doodle just emerged from the heap:

    TT3 and Interface PCB keepout dimensions
    TT3 and Interface PCB keepout dimensions

    It gives the envelope dimensions for the two PCBs in the quasi-extruded GPS interface case:

    HT-GPS Case - Trial fit - rear view
    HT-GPS Case – Trial fit – rear view

    The overall idea was to subtract the envelopes from the interior of the solid case and print the shell:

    HT-GPS Adapter Case - end view
    HT-GPS Adapter Case – end view

    Surprisingly, after minimal cleanout, the PCBs pretty much just slid into place and the setscrews locked them down.

    Now I can throw out that scrap of paper…

    The OpenSCAD source has the gory details; the actual dimensions don’t quite match the doodle, but that’s in the nature of fine tuning.

  • KG-UV3D GPS+Voice: Quasi-Extruded Case

    Unlike the previous kludge, this GPS interface case resembles an extrusion with the PCBs sliding into place, held by setscrews along the edges of the slots:

    HT-GPS Adapter Case - end view
    HT-GPS Adapter Case – end view

    Those errant threads seem to arise from not quite bonding to the corner. The battery side of the case (bottom in this view) is one thread wide, which isn’t quite enough. Adding another thread makes it 1 mm wide, which seems excessive.

    The idea was to glue the battery interface plate on that side, but printing the case vertically puts various flaws along that surface:

    HT-GPS Adapter Case - bottom view
    HT-GPS Adapter Case – bottom view

    So the next iteration will merge the battery plate with the case and print the whole affair in one shot. This view shows all the parts separately:

    HT-GPS Adapter Case - exploded bottom view
    HT-GPS Adapter Case – exploded bottom view

    This shows the case joined with the battery plate, neatly aligned for printing:

    HT-GPS Adapter Case - combined battery interface
    HT-GPS Adapter Case – combined battery interface

    The battery plate has a 0.1 mm extension into the case to avoid problems from objects with coincident planes. Unfortunately, however, that means the intersection between the base plate and the shell forms a line with three planes extending from it: the two outside walls (which are co-planar) and the plate extension inside the case. Skeinforge sometimes complains mightily about that, despite my having applied a union() to fuse the plate with the case: obviously I don’t quite understand how union() works.

    I think the battery contact holes will come out close enough to being right; they all have points on the top edge to reduce the overhang problem.

    One gotcha: the actual metallic contact studs for the battery. The contacts for the ICOM IC-Z1A case came from carefully shaped brass screws secured by nuts above the PCB and that’s what I’ve been designing around for this case. Unfortunately, the PCB must slide in before installing the studs, which means reaching into the depths of the case, with all the wiring in the way, to turn those nuts. Fortunately, the PCB has plenty of clearance in that direction, but … it’ll be awkward at best.

    The studs also need a slot / socket / dingus to prevent rotation while tightening the nuts; right now the contact plate is circular-ish, but maybe I should rethink that.