vail-adapter/case/paddles.scad

522 lines
20 KiB
OpenSCAD

/*
>>> COMPACT 3D-PRINTED PADDLES <<<
Torbjørn Skauli, LA4ZCA (tskauli@gmail.com)
v2.0, December 2018
Iambic paddles designed for 3D-printing. The design is simple, but provides precise movement with adjustable force and travel. Design features include a printed rocker hinge, force adjustment by a sliding spring, travel adjustment using a modified screw, ergonomic grip and general simplicity and precision.
Changes in v2.0: The paddles have been made narrower, and the base is thinner. There is an option to remove the bottom mounting holes. The base extends under the paddles to protect them from being pushed upwards. The cable exit is on the side, to fit mounting on the QCX transceiver.
TODO:
-Arms 1-2 mm higher, spring channel longer towards contacts?
Materials:
- 3 printed parts
- 2 screws M3x5mm, cylinder head, with washers for adjustment if needed
- 1 screw M4 x 18-20mm, cylinder head, with lockwasher and nut
- Compression spring, 6-8 mm in diameter
- Compression spring, 4-5 mm in diameter
- Cable with plug as required, up to 3.5 mm diameter
Note: Nickel plated brass screws have been found to give the most reliable contact operation. Dimensions of screws, springs and cable can be changed in the code.
Assembly:
- First, prepare the 3D-printed parts by removing support material in the arm spring well and in the ends of the cable holes. Also remove any protuding edges and bumps by gently filing the surfaces.
- Place the large spring so that it is held between the paddles approximately in the middle of the spring well. Also place the small spring in the holes at the hinge. Temporarily slide the two paddles in place. Check the spring force on the paddles and adjust as desired by either moving the spring along the well or by bending the spring to change its length. Make sure that the small spring keeps the arms in place at the hinges during use.
- Remove 6-8 cm of the outer isolation (if present) of the cable and 1 cm of the inner isolation of each wire. Insert the cable from the back through the diagonal hole, and temporarily pull it out from the side "window". Insert the cable back into the other hole and press the cable bend into the window so that the outer isolation ends in the interior wiring well. This forms a strain relief.
- Prepare a 18-20 mm M4 screw with cylindrical head by grinding the outer 5 mm to flatten two opposing sides. Preferably align the flattened screw end with the slot in the screw head.
- Enter two M3 screws with cylindrical heads into the paddle arms, with the heads facing inwards. Clamp the dot and dash wire ends under these screw heads.
- Enter the M4 screw from the bottom and clamp the ground wire underneath the lockwasher. Tighten the nut quite firmly, while allowing a small amount of adjustment of the screw angle to set the travel distance.
- Place the spring between the paddles and slide them in place. Adjust travel by rotating the M4 screw. If the travel is asymmetric, it may be necessary to correct the difference by placing a washer under the head of one of the M3 screws in the paddle arms.
*/
//*************** Rendering output control
mountholes=true;//Whether to have mounting holes in bottom
cacc=4; // accuracy of circles, multiplier for $fn. Use cacc=1 for dev/debug, =2-4 for final.
preview=0; // =0 for print layout,
// =1 or =2 for 1- or 2-arm assembly preview,
// =3 for base only
// =4 for arms print layout,
// =5 base+attachment preview
// =6 base modified for attachment to other cabinet
// =7 attachment for inclusion in other cabinet
// =8 rotation stopper for inclusion in other cabinet
//**************** main parameters of the design
wxbase=24; // overall width, sets arm thickness etc.
lybasemin=40; // length excl. knobs (normally 40, made longer below for QCX)
hzarm=20; // height of main part of arm
wallt=2; // wall thikckness
wallmin=2; // min wall thickness under cable holes
dfinger=30; // approx diameter of finger for curved knob. Also knob length.
lyknob=25; // total length of knob
rround=1; // radius of rounded edges on knob
txknobmin=2; // min. thickness of knob
yhinge=6.5; // y position of hinge relative to back
dxwedge=4; // height of hinge wedge
ahinginn=50; // angles for inner and outer part of hinges
ahingout=80;
dstopper=1.5; // diameter of stopper on top of hinge that keeps arm down
armsep=0.75; // arm separation from all walls
dyarmrests=2; // y width of resting and bounding surfaces for arms
minstroke=0.25; // minimum stroke length (at full dia of center screw)
//**************** parameters for non-printed parts
dscrew1=4; // screw dia, also scales screw head height
dscrew2=3; // screw dia, also scales screw head height
hscrhrel=2/3; // screw head height and diam rel to diameter
dscrhrel=7/4;
dzcontact=0.1; // extra height of all screws
dzsprwell=-1; // height adjustment of spring well
dcable=4; // cable diameter
dspring=9.5; // spring diameter
dhingespring=5; // diameter of spring keeping hinge in place
//*************** Parameters for cabinet attachment, needed to make integrated paddle
daxis=6; // Diameter of rotation axis tube
lyflange=22; // Length and height of flange on axis tube
hzflange=13;
atthick=1.5;
rotsnaph=1; // Height of snaps for paddle attachment rotation
rotstop=7; // Size of rotation stopper shelf
//**************** parameters for rendering
gap=0.2; // gap for loose fit
tol=0.025; // general tolerance
nil=0.001; // Negligible distance, to correct rendering
lybase=lybasemin; // length of basee ex. knobs
echo("Length of base (mm):",lybase);
hzwall=hzarm+armsep; // total height of walls
hzbase=2*wallmin+dcable; // height of base under arms
ycontact=lybase-1*dscrew1; // position of contact screw
wxarm=wxbase/2-wallt-armsep-hscrhrel*dscrew2-minstroke-dscrew1/2; // arm thickness is the remaining space after removing many contributions to total width
echo("Total height (mm):",hzwall+hzbase);
echo("Total width for QCX (mm):",wxbase+dcable);
echo("Max length of contact screws in arms (mm):",wxarm);
echo("Min length of contact screw in base (mm):",hzbase-hscrhrel*dscrew1+armsep+hzarm/2+dscrew2*dscrhrel/2);
echo("Min length to flatten contact screw in base (mm):",dscrew2*dscrhrel);
wxknob=wxarm+wallt+armsep+hscrhrel*dscrew2;
dxknob=hscrhrel*dscrew2; // x offset toward center rel to main arm
hzknob=hzarm; // height of knobs
lyarm=lybase-wallt-armsep/2; // knob spaced armsep/2 from base front
y0sprwell=yhinge-wallt-armsep+dxwedge*tan(ahingout/2); // starting pos of spring channel
sprfloort=wallt; // thickness of floor underneath spring
sprlen=wxbase-2*(wallt+armsep+sprfloort); // length of spring
dxtip=wxbase/4; // diameter of rounded tip with attachment hole
dytip=(wxbase-dxtip)/2; // length of tip ex rouned part
rtip=dxtip*sqrt(2)/2; // radius of tip
ycentip=lybase+dytip-rtip/sqrt(2); // center of rounded tip
module teardropHole(lh,rh){ // Hole with 45-degree teardrop shape
rotate([90,0,0])
rotate([0,0,45])
union(){
cylinder(h=lh,r1=rh,r2=rh,$fn=8*cacc);
cube([rh,rh,lh]);
};
};
module snap45(snaph,snapl){ // bumps to snap parts together, max angle 45 degrees
// snapl is length of bump, passed as parameter to allow tolerance
difference(){
rsnap=snaph/(1-1/sqrt(2));
translate([rsnap-snaph,-snapl/2,0])
rotate([-90,0,0])
cylinder(h=snapl,r=rsnap,$fn=5*cacc);
translate([nil,-snapl/2,-rsnap])
cube([2*rsnap,snapl,2*rsnap]);
};
}; // end snap
module wedge(a,wx,hz){ // Equilateral triangular block
//top angle a(deg), length hz and triangle height wx in x direction
linear_extrude(height=hz)
polygon([[0,0],[wx,wx*tan(a/2)],[wx,-wx*tan(a/2)]]);
};
module cone45(dc){ // cone for stopper that keeps arm down, 45-degree slope
rotate([180,0,0])
cylinder(d1=dc,d2=0,h=dc*1.0,$fn=8*cacc);
};
module wedge_hinge(){ //wedge on wall for hinge, with cutout so that arm rests on top and bottom
// cutout for stopper cone
translate([0,0,hzwall])
cone45(dstopper);
// wedge with cutout, centered on the edge between cone and armrest,
// to make arm better supported against up-down tilt
difference(){
wedge(a=ahinginn,wx=dxwedge,hz=hzwall);
translate([-hzarm/2/sqrt(2),hzarm/2,hzarm/2+armsep-dstopper*0.75/2])
rotate([90,0,0])
cylinder(h=hzarm,d=hzarm,$fn=12*cacc);
};
};
module bump45(bumph){ // Spherical bump with at most 45 degree angle
rotate([0,180,0])
difference(){
amax=60; // max overhang angle on bump, not necessarily 45 degerees
rsnap=bumph/(1-cos(amax));
translate([rsnap-bumph,0,0])
sphere(r=rsnap,$fn=5*cacc);
translate([nil,-rsnap,-rsnap])
cube([2*rsnap,2*rsnap,2*rsnap]);
};
};
module rotsnaps(snaph){ // Rotation snaps for paddle
rrot=(hzbase+hzwall)/2*sqrt(2)-snaph/(1-1/sqrt(2))-wallt;
for (a=[45:90:360])
rotate([a,0,0])
translate([0,rrot,0])
bump45(snaph);
};
module base_add(){ // parts of base that add to shape
// base plate
translate([-wxbase/2,0,0])
cube([wxbase,lybase+lyknob-rround,hzbase]);
//rounded front
translate([-wxbase/2+rround,lybase+lyknob-rround,0])
minkowski(){
cube([wxbase-2*rround,tol,hzbase]);
// rounding cy
cylinder(r=rround,h=tol,$fn=cacc*4);
};
// walls
translate([0,0,hzbase])
difference(){
translate([-wxbase/2,0,0])
cube([wxbase,lybase,hzwall]);
translate([-wxbase/2+wallt,wallt,0])
cube([wxbase-2*wallt,lybase,hzwall+tol]);
}
//wedge 1
translate([wxbase/2-wallt-dxwedge,yhinge,hzbase])
wedge_hinge();
//wedge 2
translate([-(wxbase/2-wallt-dxwedge),yhinge,hzbase])
rotate([0,0,180])
wedge_hinge();
// bottom arm resting surface, height armsep above base top
translate([0,yhinge,hzbase])
cube([wxbase-2*wallt-2*dxwedge-2,dyarmrests,2*armsep],center=true);
// front arm lower resting surface, normally with 2*gap airgap
translate([-wxbase/2,lybase-dyarmrests,hzbase])
cube([(wxbase-2*dscrew1)/2,dyarmrests,armsep-2*gap]);
translate([wxbase/2,lybase,hzbase])
rotate([0,0,180])
cube([(wxbase-2*dscrew1)/2,dyarmrests,armsep-2*gap]);
// outer end stops, 2mm wide
translate([-(wxbase/2-wallt),lybase-dyarmrests,hzbase])
cube([armsep,dyarmrests,hzwall]);
translate([(wxbase/2-wallt)-armsep,lybase-dyarmrests,hzbase])
cube([armsep,dyarmrests,hzwall]);
// extra column for center screw stability
translate([0,ycontact,hzbase])
cylinder(d=dscrew1*dscrhrel*1.5,h=dscrew1/2+dzcontact,$fn=8*cacc);
// QCX attachment: add 1x cable dia of wall thickness and rotation axis
if (preview>=5){
// Thicker wall
translate([wxbase/2,0,0])
cube([dcable,lybase,hzbase+hzwall]);
// Rotation stopper
translate([wxbase/2,lybase,0])
cube([dcable,rotstop,hzbase]);
// snaps for paddle rotation
translate([wxbase/2+dcable,lybase/2,(hzbase+hzwall)/2])
rotsnaps(rotsnaph);
};
};
module base_sub(){ // parts of base that cut away from shape
// center contact screw hole
translate([0,ycontact,0])
cylinder(d=dscrew1,h=hzbase*9,$fn=8*cacc);
// center contact screw head recess (20% enlarged) filled in by cylinder to avoid need for support
translate([0,ycontact,0])
cylinder(d=dscrew1*dscrhrel*1.2,h=dscrew1*hscrhrel*1.2,$fn=8*cacc);
// Front mounting screw hole with recess
if (mountholes && preview<5){
translate([0,ycentip,0])
cylinder(d=dscrew1+gap,h=hzbase,$fn=8*cacc);
translate([0,ycentip,wallt])
cylinder(d=dscrew1*dscrhrel*1.2,h=hzbase,$fn=8*cacc);
};
// wire well
wellw=wxbase/2; // wire well width and length
translate([wellw/2,ycontact-2*dscrew1,wallt])
rotate([0,0,180])
cube([wellw,wellw,hzbase]);
// Back mounting screw hole in well
if (mountholes && preview<5){
translate([0,ycontact-2*dscrew1-wellw/2,0])
cylinder(d=dscrew1+gap,h=hzbase,$fn=8*cacc);
};
//cable holes
if (preview<5) // Free standing paddle, no attachment
translate([-(wxbase/2-dcable/4),ycontact-2*dscrew1-wellw-dcable/2,dcable/2+wallmin]){
// cable hole 1
translate([0,-dcable/2,0])
rotate([0,0,90])
teardropHole(lh=wxbase*2,rh=dcable/2);
// cable hole 2
rotate([0,0,45+90])
teardropHole(lh=wxbase/sqrt(2)/2,rh=dcable/2);
//cable access opening
cube([dcable,2*dcable,dcable],center=true);
}
else{ // Cable routing for QCX
// cable hole from well
translate([0,lybase/2+dcable,dcable/2+wallmin])
rotate([0,0,90])
teardropHole(lh=wxbase,rh=dcable/2);
//cable access opening, printable without support, using breakaway wall
for (i=[-1,1])
translate([wxbase/2+0*wallt/2,lybase/2+i*(gap+dcable/2), wallmin]){
cube([dcable,dcable,dcable]);
translate([0,dcable/2,1.5*dcable])rotate([0,90,0])wedge(90,dcable/2,dcable);
};
//vertical cable hole
translate([wxbase/2+dcable-(wallt+dcable)/2,lybase/2,wallmin]){
hhole=hzbase+hzwall/2;
cylinder(d=dcable,h=hhole); // main hole
translate([0,0,hhole])
cylinder(d1=dcable,d2=0,h=dcable); // tapered top to avoid support
};
// rotation axis
translate([wxbase/2-dcable,lybase/2,(hzbase+hzwall)/2]){
// Axis hole
rotate([0,90,0])
cylinder(d=daxis+gap,h=99,$fn=8*cacc);
// Rotation snap
};
// Room for square flange soldered onto rotation axis
translate([wxbase/2-wallt,(lybase-lyflange)/2,(hzbase+hzwall-hzflange)/2]){
cube([dcable,lyflange,hzflange]);
translate([-tol,0,hzflange-tol])
rotate([90,-135,180])
wedge(90,dcable/sqrt(2),lyflange);
};
}; // End QCX attachment
};
module base(){ // complete base part
difference(){
base_add();
base_sub();
};
};
module attachment(){ // attachment that can be included in QCX (or other) cabinet
difference(){
union(){
// Attachment part of wall
translate([0,-2*rotstop,0])
cube([atthick,2*rotstop+lybase,hzbase+hzwall]);
}; // end union
// Axis hole
translate([0,lybase/2,(hzbase+hzwall)/2])
rotate([0,90,0])
cylinder(d=daxis+gap,h=99,$fn=8*cacc);
// snaps for paddle rotation, slightly tight to avoid play
translate([0,lybase/2,(hzbase+hzwall)/2])
rotsnaps(rotsnaph-gap/3);
}; // end difference
};
module rotlimit(){ // Rotation stopper for attachment, to be included in cabinet
limdim=wallt+dcable; // width of stopper cube, equal to stopper on paddle base
// stopper knob
translate([-dcable,lybase/2-limdim/2,(hzbase+hzwall)/2-limdim/2])// move to hole
rotate([0*30,0,0]) // rotate if desired
translate([0,-lybase/2-limdim/2-rotstop,(hzbase+hzwall)/2-limdim/2])
cube([dcable,limdim,limdim]);
};
module rotlimitEXPORT(){ // Rotation stopper for attachment, to be included in cabinet
translate([0,-lybase,-hzbase-hzwall])
rotlimit();
};
module attachmentEXPORT(){ // Rotation stopper for attachment, to be included in cabinet
translate([0,-lybase,-hzbase-hzwall])
attachment();
};
function attachHeightEXPORT()=hzbase+hzwall; // Export total height
module arm_add(){ // arm base shape, without knob
// main arm
cube([wxarm,lyarm,hzarm]);
// extra material for supporting hinge spring
translate([-dxknob,0,0])
cube([dxknob,y0sprwell,hzarm]);
// extra material near top of arm, for stiffness and appearance
hzarmextra=hzarm/2-dzcontact-(dscrew2*dscrhrel*1.3);
translate([-dxknob,0,hzarm-(hzarmextra-dxknob)]){
cube([dxknob,lyarm,hzarmextra-dxknob]);
// 45 deg underside of extra material, to avoid generation of support
translate([dxknob,0,0])
rotate([90,45,180])
wedge(90,dxknob/sqrt(2),lyarm);
}
};
module arm_sub(){ // arm shaping
// hinge groove
translate([wxarm-dxwedge+armsep,yhinge-wallt-armsep,0])
wedge(a=ahingout,wx=dxwedge,hz=hzarm);
// hinge stopper cutout
translate([wxarm-dxwedge+armsep,yhinge-wallt-armsep,hzarm])
cone45(dstopper+2*gap);
// tension spring channel
translate([0,y0sprwell,(hzarm-dspring)/2+dzsprwell]){
cube([wxarm-sprfloort,ycontact-y0sprwell-2.5*dscrew1,dspring]);
translate([-tol,0,dspring-tol])
rotate([90,-135,180])
wedge(90,(wxarm-sprfloort)/sqrt(2),ycontact-y0sprwell-2.5*dscrew1);
};
// contact screw hole
translate([-tol,ycontact-wallt-armsep,hzarm/2+dzcontact])
rotate([0,0,90])
teardropHole(lh=wxarm*2,rh=dscrew2/2-gap);
// hole for spring keeping hinge in place
translate([-dxknob-tol,yhinge-wallt-armsep,hzarm/2])
rotate([0,0,90])
teardropHole(lh=dxknob+wxarm-dxwedge+armsep-sprfloort+tol,rh=dhingespring/2);
// extra space for center screw column
translate([-(wxbase/2-wallt-armsep-wxarm),ycontact-wallt-armsep,0])
cylinder(d=dscrew1*dscrhrel*1.5+2*minstroke+dscrew1,h=dscrew1/2+dzcontact+dscrew1*hscrhrel*1.25,$fn=8*cacc);
};
module knob_curved(){ // finger-curved and rounded knob
intersection(){ // cutting to outer shape
minkowski(){ // rounding of edges
// un-rounded knob shrunk by rounding radius
difference(){ // shaping finger rest
// knob, to be shaped by subtraction
translate([-dxknob,lyarm,0])
cube([wxknob-rround,lyknob-rround,hzknob-rround]);
// shaping of knob
translate([-dxknob+txknobmin+dfinger/2,lyarm+0*wallt+dfinger/2,dfinger/2+0*wallt])
minkowski(){
cube([tol,wxbase,wxbase]);
sphere(r=dfinger/2,$fn=8*cacc);
};
};
// rounding sphere
sphere(r=rround,$fn=cacc*4);
};
// outer bound of knob
translate([-dxknob,lyarm,0])
cube([wxknob,dfinger,hzknob]);
};
};
module arm(){ // Generate final shape according to preview and cacc settings
difference(){
union(){
knob_curved();
arm_add();
};
arm_sub();
};
};
module build_all(){
if(preview==1){
base();
translate([wxbase/2-wallt-armsep-wxarm,wallt+armsep,hzbase+armsep])
arm();
}
else if (preview==2){
base();
translate([wxbase/2-wallt-armsep-wxarm,wallt+armsep,hzbase+armsep])
arm();
translate([-(wxbase/2-wallt-armsep-wxarm),wallt+armsep,hzbase+armsep])
scale([-1,1,1])
arm();
}
else if (preview==3){
base();
}
else if (preview==4){
translate([wxbase/2+wxknob+1,0,0])
scale([-1,1,1])
arm();
translate([wxbase/2+2*wxknob+2,0,0])
arm();
}
else if (preview==5){
base();
translate([wxbase/2+dcable+10,0,0]){ // spacing for rendering, for preview of fit
attachment();
rotlimit();
};
}
else if (preview==6){ // Base with additions for attachment
base();
}
else if (preview==7){ // Attachment for inclusion in cabinet
attachmentEXPORT();
}
else if (preview==8){ // Rotation stopper for inclusion in cabinet
rotlimitEXPORT();
}
else if (preview==9){ // Rotation stopper for inclusion in cabinet
attachmentEXPORT();
rotlimitEXPORT();
}
else{
base();
translate([wxbase/2+wxknob+1,0,0])
scale([-1,1,1])
arm();
translate([wxbase/2+2*wxknob+2,0,0])
arm();
};
};
build_all();