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:

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.

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.

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.

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(); }
One thought on “KG-UV3D GPS+Voice: Box Model”
Comments are closed.