diff --git a/.gitignore b/.gitignore index ec06738..9a9bb8a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /gen *.dsn.autosave +temp-printables.scad \ No newline at end of file diff --git a/README.md b/README.md index 7eb0c10..8ca4af6 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,6 @@ -# Yet-Another HOTAS Throttle (YAHT) +# Yet-Another HOTAS Throttle (YA-HOTAS-THROTTLE) -This is mostly 3D printable HOTAS throttle game controller for USB. - -![Full Preview](img/full-preview.png) - -**A photo of an actual build (don't mind the colors - this is the first fully working build)** - -![Full Photo](img/full-photo.png) - -# Features - -This HOTAS throttle connects as a standard USB joystick to Windows without needing to install any software. +This 3D printable HOTAS throttle game controller. It connects as a standard USB joystick to Windows without needing to install any software. It has: @@ -23,6 +13,18 @@ It has: - adjustable throttle handle angle (twist) for better ergonomics at different table positioning - host additional USB serial connectivity for debugging and programming +**Complete Design Explosion View** + +![Full Preview](img/full-preview.png) + +**A photo of an actual build (don't mind the colors - this is a fully working prototype build)** + +![Full Photo](img/full-photo.jpg) + +**And when fully opened** + +![Fully Opened Photo](img/fully-opened.jpg) + # Design The design is based on these guidelines: @@ -48,6 +50,8 @@ Some parts require or benefit from post-print processing: - some screw holes may benefit from drilling for easier fit - throttle center detent hole could be drilled bigger for stronger detent or cone shaped with a bit larger drill head for smoother detent exit/entry +Below is an illustration of all printable parts. + ![Preview of all printed parts](img/printed-parts.png) Connect the wires like this to match the code: @@ -56,6 +60,17 @@ Connect the wires like this to match the code: *Note: The components within the dashed region in the right of the circuit diagram are not currently in the physical build. They only exist in the code and are for future extensions.* +Below are some photos of the assembly + +![Fully Opened Photo](img/fully-opened.jpg) + +![Fitting Everything In the Handle](img/handle-insides.jpg) + +The cable management is important to make the throttle handle move smoothly and not move on its own at any position due wires pushing it. + +![Handle Cable Management](img/handle-cable.jpg) + + ## Firmware Directory **firmware** contains the code for Arduino. I has been tested with Arduino 1.8.9 on Teensy LC with USB Type **Serial + Keyboard + Mouse + Joystick**. You also need to have/install **Bounce2** and **FastLED** libraries. Prebuilt hex-file for Teensy LC is also located in the directory. @@ -79,25 +94,25 @@ This build does not use a keyboard diode matrix as Teensy LC has enough pins for # Parts List -On top of the 3D printable components: +On top of the 3D printable components, you need + - 2 x linear motion rods 8mm x 168mm (get one longer and cut in two) + - 2 x linear bearings 8x15x45 (e.g. LM8LUU) - 1 x Teensy LC (or another Arduino board with enough pins) - 2 x 10kohm LIN pot (Bourns BI P160 with center detent e.g. KNOC-model) - - 1 x 100mm travel 10kohm lin slide pot (Bourns PS100 B 10K 0E) + - 1 x 100mm travel 10kohm LIN slide pot (Bourns PS100 B 10K 0E) - 1 x multidirectional switch 8 dir + press (Alps 688RKJXL 100401V) - 5 x switches (a tact switch with pins fitting the circuit board listed) - 1 x slide switch (any generic model you can fit the case) - - 2 x linear motion rods 8mm x 168mm (get one longer and cut in two) - - 2 x linear bearings 8x15x45 (e.g. LM8LUU) + - some generic circuit board (0.1" / 2.54mm pin pitch) + - N x screws (2.5mm and 3mm) for plastic - for case cover, handle thumb section and internal parts + - 2 x screws 3mm for attaching the rods from one of their ends to the case + - 2 x screws 2mm for attaching the slide potentiometer - 6 x zip ties for attaching the bearings and do cable management, small ones will do - - 1 x joystick case (Saitek X45 stick part - or print it yourself) + - wires - 1 x RGB LED (WS2812 compatible) - 1 x 9.5mm steel ball - 1 x steel spring of e.g. 4.5x22mm (one from a regular ball point pen will work well) - - some generic circuit board (0.1" / 2.54mm pin pitch) - - wires - - N x screws (2.5mm and 3mm) for plastic - - 2 x screws 3mm for attaching the rods from one of their ends to the case The Teensy LC can quite easily be replaced with any Arduino compatible device with USB connectivity and enough pins. With more coding effort any USB capable micro controller that fits the case could also do. @@ -116,14 +131,10 @@ For simple buttons, the following logic could be made. Press and hold Lock-butto **Document:** - Add button module circuit board picture and cutting instructions - - Add cable management picture **Fix:** - - Make printable bottom case (now reusing bottom part of Saitek X45 joystick case) - - Make case and rod supports to both ends with exact correct rod separation and height - Add Lock-switch place to handle print - - Cable insert cut into the handle shaft - Cleanup 3D code - Turn the LEDs off when computer is sleeping - Thicker handle case and slightly better interlocking edges between the main and thumb sections @@ -135,8 +146,6 @@ For simple buttons, the following logic could be made. Press and hold Lock-butto - Add two buttons to front and lower thumb - can be less accessible than the 5 current ones - handy for less frequently used functions in a game - Add one or two more axis - can be in less accessible locations than the X/Y pots - these come handy in flight simulators - Add a place for the lights so they can be seen - - Curvier case cover near to player for better ergonomics when the throttle is placed nearer to the table edge (in certain positions mid-arm may rub against the case cover) - - Lower design **Other ideas:** diff --git a/commercial-parts.scad b/commercial-parts.scad index 0f1750c..51ccc4e 100644 --- a/commercial-parts.scad +++ b/commercial-parts.scad @@ -9,29 +9,43 @@ module TeensyPlusPlus() cube ([9,8,4]); } -module SlidePotentiometer100() +module SlidePotentiometer(range=100, leverWidth=8.0, leverHeight=20, + sliderEndLength=10, sliderHeight=6.4, + value=0.5, alignTop=false, alignPin=false, alignLever=false, alignValueMax=false) { - lever_range = 100; - lever_width = 8.0; - lever_thickness = 1.0; - lever_height = 20; - slider_length = 128; + slider_length = range + leverWidth + 2 * sliderEndLength; slider_width = 8.3; - slider_height = 6.4; - lever_offset = (slider_length - lever_range + lever_width) / 2; - // Slider base - difference () - { - cube ([slider_length,slider_width,slider_height]); - translate ([lever_offset, (slider_width-lever_thickness)/2, 1]) - cube ([lever_range,lever_thickness,slider_height]); - // Screw places - translate ([slider_length - 4, slider_width/2, slider_height - 3]) cylinder (r1=1.5, r2=1.5, h=4); - translate ([4, slider_width/2, slider_height - 3]) cylinder (r1=1.5, r2=1.5, h=3); - } - // Lever - translate ([lever_offset + lever_range/2, (slider_width-lever_thickness)/2, 0]) - cube ([lever_width,lever_thickness,lever_height]); + lever_thickness = 1.0; + pin_length = 4.4; + lever_groove_offset = (slider_length - range - slider_width) / 2; + lever_offset = sliderEndLength + range * value; + + alignZ = alignTop ? -sliderHeight : (alignPin ? pin_length : 0); + alignX = alignValueMax ? -sliderEndLength : 0; + alignY = alignLever ? -slider_width/2 : 0; + translate([alignX, alignY, alignZ]) + { + // Slider base + difference () + { + cube ([slider_length,slider_width,sliderHeight]); + translate ([lever_groove_offset, (slider_width-lever_thickness*2)/2, 1]) + cube ([range + slider_width,lever_thickness*2,sliderHeight]); + // Screw places + translate ([slider_length - 4, slider_width/2, sliderHeight - 3]) cylinder (d=3, h=4); + translate ([4, slider_width/2, sliderHeight - 3]) cylinder (d=3, h=4); + } + // Lever + translate ([lever_offset, (slider_width-lever_thickness)/2, 0]) + cube ([leverWidth,lever_thickness,leverHeight]); + // Pins + color("white") translate ([0, 0, -pin_length]) + { + translate ([0, slider_width-1, 0]) cube ([0.5,1,pin_length]); // +V + translate ([slider_length-0.5, 0, 0]) cube ([0.5,1,pin_length]); // GND + translate ([slider_length-0.5, slider_width-1, 0]) cube ([0.5,1,pin_length]); // R + } + } } module PotentiometerBourns95A1() diff --git a/common.scad b/common.scad index 2866450..fe566a9 100644 --- a/common.scad +++ b/common.scad @@ -765,3 +765,29 @@ module spring(d,l) { %cylinder(d=d, h=l); } + +module cutRectRimZ(x, y, thickness, tolerance=0.0) +{ + difference() + { + translate([-thickness, -thickness, -0.1]) + cube([x + thickness * 2, y + thickness * 2, thickness + 0.2]); + + translate([tolerance, tolerance, -1]) + cube([x ,y, thickness + 2]); + } +} + +module roundCube(d=3, size=[1, 1, 1], offset=[0, 0, 0], extended=0) +{ + intersection() + { + children(); + size2 = [size.x - 2*d + offset.x, size.y - 2*d + offset.y, size.z - 2*d + offset.z]; + minkowski() + { + translate(-offset) translate([d, d, d]) cube(size2); + sphere(d + extended, $fn=50); + } + } +} diff --git a/img/full-photo.jpg b/img/full-photo.jpg new file mode 100644 index 0000000..32ad276 Binary files /dev/null and b/img/full-photo.jpg differ diff --git a/img/full-photo.png b/img/full-photo.png deleted file mode 100644 index 7f12c1c..0000000 Binary files a/img/full-photo.png and /dev/null differ diff --git a/img/full-preview.png b/img/full-preview.png index 573642c..d8ec26f 100644 Binary files a/img/full-preview.png and b/img/full-preview.png differ diff --git a/img/fully-opened.jpg b/img/fully-opened.jpg new file mode 100644 index 0000000..3abfd28 Binary files /dev/null and b/img/fully-opened.jpg differ diff --git a/img/handle-cable.jpg b/img/handle-cable.jpg new file mode 100644 index 0000000..08bbb85 Binary files /dev/null and b/img/handle-cable.jpg differ diff --git a/img/handle-insides.jpg b/img/handle-insides.jpg new file mode 100644 index 0000000..6677542 Binary files /dev/null and b/img/handle-insides.jpg differ diff --git a/img/printed-parts.png b/img/printed-parts.png index 0f3ae59..0c1f3c0 100644 Binary files a/img/printed-parts.png and b/img/printed-parts.png differ diff --git a/knobs.scad b/knobs.scad index cadfb05..c353a2d 100644 --- a/knobs.scad +++ b/knobs.scad @@ -1,6 +1,5 @@ include ; - -$fn = $preview ? 40 : 100; +include ; // Recommend 0.20mm layer on 0.4mm nozzle module hatKnob() diff --git a/printed-parts.scad b/printed-parts.scad index b97e1fd..0cae28c 100644 --- a/printed-parts.scad +++ b/printed-parts.scad @@ -3,88 +3,77 @@ include ; include ; include ; -drawOtherParts = false; -separateHandle = true; - -handleSeparation = separateHandle ? 15 : 0; - +throttle_slider_height = 6.4; +throttle_slider_end_length = 10; handle_radius = 30; -shaft_intrusion = caseThickness + 1; -shaft_length = 35; -// Lay out all printable parts on the same level +dist = 5; -// Parts inside -ShaftAndBase(shaft_length, shaft_intrusion); -moveX(60) -{ - moveZ(main_throttle_lever_height) flipY() MainThrottleLever(); - moveX(30) - { - moveZ(base_height) flipY() BasePrinted(); - moveX(45) - { - moveZ(ThrottleAxisHolderOffsetX) turnY(-90) ThrottleAxisHolder(); - moveX(20) - { - moveZ(ThrottleAxisHolderOffsetX) turnY(-90) ThrottleAxisHolder(); - moveX(15) - { - moveZ(backstop_offset_z) turnY(90) Backstop(); - moveX(30) moveZ(BallSpringPlungerBallPartOffsetZ) flipY() - BallSpringPlunger(springPart=false); - moveX(45) moveZ(BallSpringPlungerSpringPartOffsetZ) - BallSpringPlunger(ballPart=false); - } - } - } - } -} +// Unless otherwise mentioned, 0.30mm layer on 0.40mm nozzle is sufficient -// Case and handle -moveY(90) +module AllPrintableParts() { - moveX(120) + CaseBottom(); + + moveY(case_outside.y/2 + dist) + flipY() moveZ(-case_depth-case_cover_height) CaseCover(right=true, left=false); + + moveY(- case_outside.y/2 - dist) + flipY() moveZ(-case_depth-case_cover_height) CaseCover(right=false, left=true); + + moveX(case_outside.x + dist) { - moveZ(handle_case_print_offset_z) handleCase(); - moveY(0) moveX(-50) + moveY(-base_width - handle_radius) { - moveX(-45) - moveY(-20) - moveZ(thumb_print_offset_z) - turnX(thumbPlateAngle.x) turnY(180 - thumbPlateAngle.y) - handleThumbPart(); - moveX(80) + flipY() moveZ(-base_height) + BasePrinted(leverDistance=throttle_lever_distance); + + moveX(-base_length - dist) + ShaftAndBase(shaft_length, shaft_intrusion); + + moveY(base_width) + moveX(-base_length) + moveZ(-base_height + 2) { + ThrottleAxisHolder(sliderPart=true); + moveX(-2*dist) ThrottleAxisHolder(sliderPart=true); + + moveX(-25) moveZ(case_bottom + base_height + 1) flipY() - moveY(140) - moveZ(-2*case_cover_height - caseThickness/2) - { - CaseCover(left=true, right=false); - moveY(-130) moveX(150) - // Turn the right cover to print the correct way - your slicer may do this differently, so check it - turnZ(-45) CaseCover(left=false, right=true); - } + BallSpringPlunger2(ballPart=true, springPart=false); } } + + moveX(-70) moveY(60) + PrintableKnobsAndButtons(); + + moveZ(handle_case_print_offset_z) + handleCase(); + + moveY(handle_radius*2 + dist) + moveZ(thumb_print_offset_z) + turnX(thumbPlateAngle.x) turnY(180 - thumbPlateAngle.y) + handleThumbPart(); } } // Knobs and buttons (Recommend 0.20mm layer on 0.40mm nozzle) -moveY(-50) +module PrintableKnobsAndButtons() { moveZ(12) flipY() XPotKnob(); - moveX(30) + moveY(30) moveZ(12) flipY() YPotKnob(); - moveX(50) + moveX(20) { flipY() hatKnob(); moveX(15) for (i=[0:1:4]) { - moveX(15*i) + moveY(15*i) moveZ(SwitchButtonBallOffsetZ) SwitchButtonBall(); } } } + +AllPrintableParts(); diff --git a/special-parts.scad b/special-parts.scad index c3ed73f..5bfaea1 100644 --- a/special-parts.scad +++ b/special-parts.scad @@ -77,7 +77,8 @@ module HatSwitchX45Holes(screws=true) BallSpringPlungerBallPartOffsetZ = 9.5 - 1/2; BallSpringPlungerSpringPartOffsetZ = -BallSpringPlungerBallPartOffsetZ; -module BallSpringPlunger(ball_diameter = 9.5, spring_diameter = 4.5, spring_length = 19, ball_extrusion_ratio = 0.20, springPart=true, ballPart=true) +module BallSpringPlunger(ball_diameter = 9.5, spring_diameter = 4.5, spring_length = 19, ball_extrusion_ratio = 0.20, + springPart=true, ballPart=true) { thickness = 1; gap = 0.5; @@ -175,3 +176,88 @@ module BallSpringPlunger(ball_diameter = 9.5, spring_diameter = 4.5, spring_leng } } } + + + +module BallSpringPlunger2(ball_diameter = 9.5, spring_diameter = 4.5, spring_length = 19, ball_extrusion_ratio = 0.20, + springPart=true, ballPart=true) +{ + thickness = 1; + gap = 0.5; + ball_out = ball_extrusion_ratio * ball_diameter; + ball_high = ball_diameter*(1-ball_extrusion_ratio) + gap; + ball_case_inner_top = ball_diameter - thickness + gap; + ball_case_inner_d = ball_diameter + gap; + ball_case_outer_d = ball_case_inner_d + thickness; + spring_case_inner_d = spring_diameter + gap; + spring_case_outer_d = spring_case_inner_d + thickness; + screw_d = 2; + + if (draw_other_parts == true) + { + %translate([0, 0, ball_out]) + { + // spring + translate([0, 0, ball_diameter/2]) + spring(d=spring_diameter, l=spring_length); + // ball + color("silver") sphere(d=ball_diameter); + } + } + + if (springPart == true) + { + spring_tube_length = spring_length - ball_out - gap; + translate([0, 0, ball_case_inner_top]) + { + // spring tube and its bottom + tubeD(spring_case_outer_d, spring_case_inner_d, spring_tube_length); + translate([0, 0, spring_tube_length]) tubeD(spring_case_outer_d, 2*gap, thickness); + + // connector plate + difference() + { + connector_plate_thickness = 3; + translate([0, 0, connector_plate_thickness/2]) + cube([ball_case_outer_d, 2*ball_case_outer_d + 1, connector_plate_thickness], center = true); + translate([0,0,-0.1]) + { + cylinder(d=spring_case_inner_d,h=connector_plate_thickness+0.2); + + translate([0,ball_case_outer_d- screw_d,0]) + cylinder(d=screw_d,h=3+0.2); + translate([0,-(ball_case_outer_d- screw_d),0]) + cylinder(d=screw_d,h=3+0.2); + } + } + } + } + + if (ballPart == true) + { + // ball tube + tubeD(ball_case_outer_d, ball_case_inner_d, ball_case_inner_top); + difference() + { + tubeD(ball_case_outer_d, 0, thickness); + translate([0, 0, ball_out]) sphere(d=ball_diameter + gap); + } + + // connector plate + translate([0,0,ball_case_inner_top]) + difference() + { + translate([0,0,-thickness/2]) + cube([ball_case_outer_d, 2*ball_case_outer_d + 1, thickness], center = true); + translate([0,0,-thickness-0.1]) + { + cylinder(d=ball_case_inner_d,h=thickness+0.2); + + translate([0,ball_case_outer_d- screw_d,0]) + cylinder(d=screw_d,h=thickness+0.2); + translate([0,-(ball_case_outer_d- screw_d),0]) + cylinder(d=screw_d,h=thickness+0.2); + } + } + } +} diff --git a/throttle-assembly.scad b/throttle-assembly.scad new file mode 100644 index 0000000..f4eef5d --- /dev/null +++ b/throttle-assembly.scad @@ -0,0 +1,12 @@ +include ; + +Throttle(); + +/* +TODO: + +- kaapelireiän paikka koteloon (pieni reikä) +- kanteen detent-palikka ja sen kolo +- pohjan kannen pulttituet korkeammiksi + +*/ \ No newline at end of file diff --git a/throttle-base.scad b/throttle-base.scad index 8093052..41a6dd2 100644 --- a/throttle-base.scad +++ b/throttle-base.scad @@ -1,23 +1,27 @@ // Throttle handle base that connect the handle to the rails and case bottom +include + // Parameters bearing_margin_side = 3; bearing_margin_frontback = 2; -bearing_offset = 60.5 / 2 - 7.5; -bearing_height = 15; -bearing_length = 45; -bearing_radius = 7.5; +bearing_offset = 60.5 / 2 - 7.5; // 25? +bearing_radius = bearing_height / 2; bearing_case_margin = 0.17; bearing_case_radius = bearing_radius + bearing_case_margin; -bearing_case_length = 45 + 2 * bearing_case_margin; +bearing_case_length = bearing_length + 2 * bearing_case_margin; -base_top_height = 5; +base_top_height = 1; base_width = 60.5 + 2 * bearing_margin_side; base_length = 45 + 2 * bearing_margin_frontback; base_height = bearing_height + base_top_height; +base_cavity_width = 25; shaft_outer_radius = 10; +shaft_bolt_offsets = 20; +shaft_connector_width = 35; +shaft_connector_height = 4; // Bearing casing for housing standard linear bearings like LM8LUU @@ -39,15 +43,14 @@ module BearingCase() cylinder (r1=5, r2=5, h=base_length + 2); // Rod insert box - translate ([-1 - bearing_margin_frontback,-5 , 0]) + translate ([-1 - bearing_margin_frontback, -5, -0.1]) cube ([base_length + 2, 10, bearing_radius]); } // The printed part of the base -module BasePrinted() +module BasePrinted(leverDistance=18, leverWidth=8.0, shaftConnectorWidth, shaftConnectorThickness=3.0) { - // Base difference () { // Main base box @@ -56,37 +59,25 @@ module BasePrinted() base_width, base_height]); - // Left bearing case - translate ([-base_length/2 + bearing_margin_frontback, bearing_offset,0]) BearingCase(); - // Right bearing case - translate ([-base_length/2 + bearing_margin_frontback, -bearing_offset,0]) BearingCase(); + translate ([-base_length/2 + bearing_margin_frontback, bearing_offset,0]) + BearingCase(); + + // Left bearing case + translate ([-base_length/2 + bearing_margin_frontback, -bearing_offset,0]) + BearingCase(); // Center cavity - translate ([-1-base_length/2, -12.5,-1]) - cube([base_length + 2, - 25, - 16]); + translate ([-0.1-base_length/2, -12.5,-0.1]) + cube([base_length + 0.2, base_cavity_width, base_height - base_top_height - 2 + 0.1]); - // Cable hole and its edge softening + // Cable hole cylinder (r1=7.7, r2=7.7, h=26); - translate ([0,0,base_top_height + 3]) - cylinder(r1=10,r2=7, h=13); - translate ([0,0,base_top_height + 6]) - cylinder(r1=12,r2=7, h=7); - // Handle mounting holes (4 x 4mm bolts with washers) - translate ([18,8,0]) cylinder (r1=2, r2=2, h=bearing_height + base_top_height + 1); - translate ([18,-8,0]) cylinder (r1=2, r2=2, h=bearing_height + base_top_height + 1); - translate ([-18,8,0]) cylinder (r1=2, r2=2, h=bearing_height + base_top_height + 1); - translate ([-18,-8,0]) cylinder (r1=2, r2=2, h=bearing_height + base_top_height + 1); + // Handle mounting holes (2 x 4mm bolts with washers) + translate ([shaft_bolt_offsets,0,0]) cylinder (r1=2, r2=2, h=bearing_height + base_top_height + 1); + translate ([-shaft_bolt_offsets,0,0]) cylinder (r1=2, r2=2, h=bearing_height + base_top_height + 1); - // Lever nut and bolt grooves - translate ([-20,27,15]) cube ([15,7,3.2]); - translate ([0,27,15]) cube ([15,7,3.2]); - translate ([-20,29,13.8]) cube ([15,2.5,10]); - translate ([0,29,13.8]) cube ([15,2.5,10]); - // Zip tie holes ziptie_thickness = 1.7; ziptie_width = 3.2; @@ -95,53 +86,43 @@ module BasePrinted() translate ([18.5,-34,2.5]) cube ([ziptie_width, 22, ziptie_thickness]); translate ([-21.5,-34,2.5]) cube ([ziptie_width, 22, ziptie_thickness]); } -} -main_throttle_lever_height = 8; -module MainThrottleLever() -{ - // Lever base - difference () - { - translate ([-2,0,-2]) cube ([32, 2, 2 + main_throttle_lever_height]); - - // Screw holes - translate ([3,3,4.5]) - rotate (a=90, v=[1,0,0]) - cylinder (r1=1.6, r2=1.6, h=5); - - translate ([23,3,4.5]) - rotate (a=90, v=[1,0,0]) - cylinder (r1=1.6, r2=1.6, h=5); - } // Lever - lever_slot_distance = 18; - translate([-1.5,0,0]) - { - translate ([8,0,5]) cube ([6, lever_slot_distance - 3, 3]); - difference () - { - translate ([5,lever_slot_distance-3,5]) cube ([12, 6, 3]); - translate ([7.1,lever_slot_distance-1.4,3]) cube ([7.8, 2.5, 6]); - } + lever_rim_width = 2; + lever_outside_width = leverWidth + 2 * lever_rim_width; + lever_height = 3; + + translate([-lever_outside_width/2,-(base_width/2 + leverDistance + lever_rim_width),base_height-lever_height]) + difference() + { + cube([lever_outside_width,leverDistance + lever_rim_width,lever_height]); + translate([lever_rim_width, lever_rim_width - 1, -0.1]) + cube([leverWidth,2,lever_height+0.2]); } + + // Ball Spring Plunger spring case + // moveY(base_width/2 + 6) moveZ(33) flipY() turnZ(-90) BallSpringPlunger(); // center detent actuator + translate([0, 2+base_width/2 + detent_ball_offset + 0.75, 12 - case_bottom + base_height + case_bottom_gap]) + flipY() turnZ(-90) BallSpringPlunger2(ballPart=false); + *translate([0, 2+base_width/2 + 0.5, 12 - case_bottom + base_height + case_bottom_gap]) + flipY() turnZ(-90) BallSpringPlunger2(springPart=false); } // -------------------------- // Handle shaft and its base // -------------------------- -module ShaftAndBase(length, intrusion, handle_base_height = 5, handle_base_width = 40) +module ShaftAndBase(length, intrusion) { difference () { union () { // Base box - translate ([-base_length/2, -handle_base_width/2,0]) + translate ([-base_length/2, -shaft_connector_width/2,0]) cube([base_length, - handle_base_width, - handle_base_height]); + shaft_connector_width, + shaft_connector_height]); // ???? TODO: Round the cable exit side of the base box @@ -158,59 +139,29 @@ module ShaftAndBase(length, intrusion, handle_base_height = 5, handle_base_width cube ([3,length, 18]); // Shaft base support cone - translate([0,0,handle_base_height]) + translate([0,0,shaft_connector_height]) difference () { - cylinder (d1=30, d2=20, h=8); + cylinder (d1=30, d2=20, h=6); } // Handle connector cone - translate ([0,0,length-intrusion-7-7.3]) + translate ([0,0,length - intrusion - 10 + case_thickness]) cylinder (d1=20, d2=30, h=7); } - // Handle mounting holes (4 x 4mm bolts with washers) - translate ([18,8,0]) hole (4, handle_base_height); - translate ([18,-8,0]) hole (4, handle_base_height); - translate ([-18,8,0]) hole (4, handle_base_height); - translate ([-18,-8,0]) hole (4, handle_base_height); + // Handle mounting holes (2 x 4mm bolts with washers) + translate ([shaft_bolt_offsets,0,0]) hole (4, shaft_connector_height); + translate ([-shaft_bolt_offsets,0,0]) hole (4, shaft_connector_height); // Central cable hole hole(14, length); // Side cable exit hole - translate ([0,0,16]) rotate([115,0,0]) hole(6, 18); + turnZ(-25) translate ([0,0,7]) rotate([110,0,0]) + { + scale([1,1.4,1]) + hole(8, 28); + } } } - -// -------------------------- -// Throttle axis holders -// -------------------------- - -ThrottleAxisHolderOffsetX = 3; -module ThrottleAxisHolder() -{ - difference() - { - translate ([-ThrottleAxisHolderOffsetX,3.5,12]) - cube ([18,8,2]); - translate([13-2.5,3.5+4,1]) - cylinder(d=3.5,h=20); - } - difference(convexity=10) - { - translate([-ThrottleAxisHolderOffsetX,-1.5,0]) - sheet_cone(x=7, y1=18, y2=8, h=12); - - translate([0.5,14,3]) - cylinder(d=5,h=20); - translate([0.5,14,-3]) - cylinder(d=2.5,h=20); - - translate([0.5,1,3]) - cylinder(d=5,h=20); - translate([0.5,1,-3]) - cylinder(d=2.5,h=20); - } -} - diff --git a/throttle-case.scad b/throttle-case.scad index 4fff781..7ee83de 100644 --- a/throttle-case.scad +++ b/throttle-case.scad @@ -1,31 +1,30 @@ // Throttle case +include ; include ; // Case parameters -case_bottom = 9.5; -case_thickness = 3; -case_depth = 24; // outer measurement -case_outside = 175.5; -case_inside = case_outside - 2 * case_thickness; +case_bottom = case_bottom_gap + case_thickness; +case_inside = [case_outside.x - 2 * case_thickness, case_outside.y - 2 * case_thickness]; -case_cover_support_offset = [(case_outside/2 - 10.7 + 3),(case_outside/2 - 13.2 + 3)]; +case_cover_support_offset = [(case_outside.x/2 - 10.7 + 3),(case_outside.y/2 - 13.2 + 3)]; case_cover_supports = [ [-case_cover_support_offset.x, -case_cover_support_offset.y], [-case_cover_support_offset.x, case_cover_support_offset.y], [case_cover_support_offset.x, -case_cover_support_offset.y], [case_cover_support_offset.x, case_cover_support_offset.y] ]; -case_cover_support_height = 31.5; +case_cover_support_height = case_depth; // Case cover parameters -case_cover_height = 23; -grooveClearance = 2; -grooveDiameter = 20 + grooveClearance; -grooveLength = 100 + grooveClearance; -grooveOffset = -(36-14)/2; +grooveDiameter = 2 * shaft_outer_radius + grooveClearance; +grooveLength = throttle_travel + grooveClearance; +grooveOffset = throttle_offset + grooveClearance; + +case_fitting_cut_thickness = case_thickness / 2 + 0.2; +case_fitting_cut_height = case_thickness / 2; module CaseBottom() @@ -38,146 +37,179 @@ module CaseBottom() cylinder (r1=1.5, r2=1.5, h=7); } } + + moveX(case_inside.x/2 - 40) moveY(-case_inside.y/2 + 10) moveZ(case_thickness) CaseNut(); + moveX(case_inside.x/2 - 40 - 30) moveY(-case_inside.y/2 + 10) moveZ(case_thickness) CaseNut(); - translate ([-case_outside/2,-case_outside/2,0]) difference () + translate ([-case_outside.x/2,-case_outside.y/2,0]) + difference () { - cube ([case_outside, case_outside, case_depth]); - translate ([case_thickness, case_thickness, case_thickness]) cube ([case_outside-2*case_thickness, case_outside-2*case_thickness, case_depth]); + roundCube(d=3, size=[case_outside.x, case_outside.y, 2*case_depth], offset=[0,0,2]) + cube ([case_outside.x, case_outside.y, case_depth + case_fitting_cut_height]); + + case_gap = 0.00; + translate ([case_fitting_cut_thickness,case_fitting_cut_thickness,case_depth-case_gap]) + cutRectRimZ(case_outside.x - 2*case_fitting_cut_thickness - 0.2, case_outside.y - 2*case_fitting_cut_thickness - 0.2, case_fitting_cut_thickness + 1 + case_gap); + + translate ([case_thickness, case_thickness, case_thickness]) + cube ([case_outside.x-2*case_thickness, case_outside.y-2*case_thickness, case_depth]); + + // Rod screws holes + translate([-0.1, case_outside.y/2 - bearing_offset, bearing_height/2 + case_bottom]) turnY(90) cylinder(d=3 + 0.2,h=case_thickness + 0.2); + translate([-0.1, case_outside.y/2 + bearing_offset, bearing_height/2 + case_bottom]) turnY(90) cylinder(d=3 + 0.2,h=case_thickness + 0.2); } - // Main slider nuts - translate ([65.5/2,case_inside/2 - 15,case_thickness]) CaseNut(); - translate ([-65.5/2,case_inside/2 - 15,case_thickness]) CaseNut(); + // Backstop + translate ([-case_outside.x/2 + throttle_offset + throttle_travel + base_length/2, 0, case_thickness]) + Backstop(); - // Backstop nuts - translate ([case_inside/2 - 15,32,case_thickness]) CaseNut(); - translate ([case_inside/2 - 15,-32,case_thickness]) CaseNut(); - - // Legs - translate ([case_inside/2 - 30 + 11.5,case_inside/2-44+11.5,case_thickness]) cylinder (r1=11.5, r2=11.5, h=9); - translate ([-case_inside/2 + 30 - 11.5,case_inside/2-44+11.5,case_thickness]) cylinder (r1=11.5, r2=11.5, h=9); - translate ([case_inside/2 - 30 + 11.5,-case_inside/2+44-11.5,case_thickness]) cylinder (r1=11.5, r2=11.5, h=9); - translate ([-case_inside/2 + 30 - 11.5,-case_inside/2+44-11.5,case_thickness]) cylinder (r1=11.5, r2=11.5, h=9); + // Frontstop + translate ([-case_inside.x/2 + throttle_offset - base_length / 2 - 8, 0, case_thickness]) + translate ([0,-base_width/2,0]) cube ([8,base_width, 3 + case_bottom_gap]); // Cover supports for (s = case_cover_supports) - translate ([s.x, s.y, case_thickness]) cylinder (d=6, h=case_cover_support_height); + { + translate ([s.x, s.y, case_thickness]) + difference() + { + union() + { + tubeD (dout=7, din=2.8, h=case_cover_support_height); + moveY(-8) moveX(-0.5) cube ([1, 16, case_depth - 3]); + moveX(-5) moveY(-0.5) cube ([10, 1, case_depth - 3]); + } + cylinder(d=2.8, h=case_cover_support_height); + } + } + + // Throttle slider supports + moveX(throttle_offset - case_inside.x/2 - throttle_slider_end_length) + translate ([0, -base_width/2 - throttle_lever_distance, case_thickness]) + { + ThrottleAxisHolder(basePart=true); + moveX(throttle_travel + 2*throttle_slider_end_length) + ThrottleAxisHolder(basePart=true); + } } -backstop_offset_z = 12 + 2; module Backstop() { difference () { union () { - // Back stopper - translate ([12,0,-7.5]) cube ([2,80,23]); - translate ([3,0,0]) cube ([11,80,2]); - translate ([3,51.5,2]) cube ([4,6,2]); - translate ([3,22,2]) cube ([4,6,2]); + // Back stopper + translate ([4,-40,0]) cube ([2,80,case_bottom + 12]); + translate ([3,-40,0]) cube ([8,80,3 + case_bottom_gap]); + /*translate ([3,base_cavity_width/2,2]) cube ([4,5,2]); + translate ([3,-base_cavity_width/2-5,2]) cube ([4,5,2]);*/ } - - // Nut holes - translate ([8,40 + 32,-0.5]) cylinder(r1=1.7,r2=1.7,h=3); - translate ([8,40 - 32,-0.5]) cylinder(r1=1.7,r2=1.7,h=3); // Rod holes - translate ([11.5,40 + bearing_offset,12-3.5]) - rotate (a=90, v=[0,1,0]) + translate ([3.9,bearing_offset, bearing_height/2 + case_bottom_gap]) + turnY(90) cylinder(r1=4.5,r2=4.0,h=1); - translate ([11.5,40 + bearing_offset,12-3.5]) - rotate (a=90, v=[0,1,0]) + translate ([3.9,bearing_offset, bearing_height/2 + case_bottom_gap]) + turnY(90) cylinder(r1=4.0,r2=4.0,h=3); - translate ([11.5,40 - bearing_offset,12-3.5]) - rotate (a=90, v=[0,1,0]) + translate ([3.9,-bearing_offset, bearing_height/2 + case_bottom_gap]) + turnY(90) cylinder(r1=4.5,r2=4.0,h=1); - translate ([11.5,40 - bearing_offset,12-3.5]) - rotate (a=90, v=[0,1,0]) + translate ([3.9,-bearing_offset, bearing_height/2 + case_bottom_gap]) + turnY(90) cylinder(r1=4.0,r2=4.0,h=3); } } -module Tracks() +module Tracks(bearingPosition=0) { module Bearing() { color("Snow") difference() { - translate ([0,0,7.5]) rotate (a=90, v=[0,1,0]) - cylinder (r1=7.5, r2=7.5, h=45); - translate ([-1,0,7.5]) rotate (a=90, v=[0,1,0]) - cylinder (r1=4.2, r2=4.2, h=47); + translate ([0,0,bearing_height/2]) turnY(90) + cylinder (d=bearing_height, h=bearing_length); + translate ([-0.1,0,bearing_height/2]) turnY(90) + cylinder (r=4.2, h=bearing_length + 0.2); } } module Rod() { - color("Snow") translate ([0,0,4]) rotate (a=90, v=[0,1,0]) - cylinder (r1=4, r2=4, h=168); + color("Snow") turnY(90) cylinder (r1=4, r2=4, h=168); } // Bearings and rods - translate ([-base_length/2 + bearing_margin_frontback, bearing_offset,0]) Bearing(); - - translate ([-base_length/2 + bearing_margin_frontback, -bearing_offset,0]) Bearing(); + moveX(bearingPosition - bearing_length/2) + { + translate ([0, bearing_offset,0]) Bearing(); + translate ([0, -bearing_offset,0]) Bearing(); + } - translate ([-168/2, bearing_offset,3.5]) Rod(); - translate ([-168/2, -bearing_offset,3.5]) Rod(); + translate ([-case_inside.x/2, bearing_offset, bearing_height/2]) Rod(); + translate ([-case_inside.x/2, -bearing_offset, bearing_height/2]) Rod(); } module CaseCoverFull() { difference() { - translate ([-case_outside/2,-case_outside/2,case_depth]) + translate ([-case_outside.x/2,-case_outside.y/2,case_depth]) difference () { union() { + // Main case body + roundCube(d=3, + size=[case_outside.x, case_outside.y, case_cover_height + 0.15], + offset=[0,0,3]) difference() { - cube ([case_outside, case_outside, case_cover_height]); - translate ([case_thickness, case_thickness, -case_thickness]) - cube ([case_inside, case_inside, case_cover_height]); + cube ([case_outside.x, case_outside.y, case_cover_height]); + translate ([case_thickness, case_thickness, 0]) + cube ([case_inside.x, case_inside.y, case_cover_height - case_thickness]); } + // Screw supports for (s = case_cover_supports) { - h = case_cover_support_height-case_cover_height; - translate([case_outside/2 + s.x, case_outside/2 + s.y, 0]) - { - translate ([0, 0, h+case_thickness]) cylinder (d=8.5+case_thickness, h=h); - } + h = case_cover_height - case_thickness; + translate([case_outside.x/2 + s.x, case_outside.y/2 + s.y, case_thickness]) + cylinder (d=8.5+case_thickness, h=h); } } // Shaft groove - translate ([case_outside/2 + grooveOffset, case_outside/2, case_cover_height - case_thickness - 1]) + translate ([0 + grooveOffset, case_outside.y/2 - grooveDiameter/2, case_cover_height - case_thickness - 0.1]) { - cube ([grooveLength, grooveDiameter, case_thickness + 6], center = true); - translate ([grooveLength/2,0,0]) - cylinder(d=grooveDiameter, h=case_thickness + 2); - translate ([-grooveLength/2,0,0]) - cylinder(d=grooveDiameter, h=case_thickness + 2); - - translate ([-(60),-28,1]) - cube([120,28, 1]); + cube ([grooveLength, grooveDiameter, case_thickness + 0.2]); + translate ([grooveLength,grooveDiameter/2,0]) + cylinder(d=grooveDiameter, h=case_thickness + 0.2); + translate ([0,grooveDiameter/2,0]) + cylinder(d=grooveDiameter, h=case_thickness + 0.2); } // Fitting cuts - case_fitting_cut_thickness = 1; - case_fitting_cut_height = 1.5; translate ([case_thickness - case_fitting_cut_thickness, case_thickness - case_fitting_cut_thickness, - 0.01]) - cube ([case_inside + case_fitting_cut_thickness*2, case_inside + 2*case_fitting_cut_thickness, case_fitting_cut_height]); + cube ([case_inside.x + case_fitting_cut_thickness*2, case_inside.y + 2*case_fitting_cut_thickness, case_fitting_cut_height]); } + + // Detents + for (s = detent_values) + { + translate([-case_inside.x/2 + throttle_offset + (1 - s.x) * throttle_travel, 36 + detent_ball_offset, case_depth + case_cover_height - case_thickness -0.1]) + cylinder(d=s.y, h=case_thickness + 0.2); + } + + // Support cavities for (s = case_cover_supports) { translate([s.x, s.y, case_cover_support_height + case_thickness]) { translate ([0, 0, 0]) cylinder (d=3.8, h=case_cover_height+case_thickness); - translate ([0, 0, case_thickness]) cylinder (d=8.5, h=case_cover_height); + translate ([0, 0, case_thickness + 1]) cylinder (d=8.5, h=case_cover_height); } } } @@ -186,74 +218,54 @@ module CaseCoverFull() module CaseCover(left = true, right = true) { // Left half - translate([0,-2,0]) if (left == true) { difference() { - intersection() - { - CaseCoverFull(); - scale([0.965, 0.965, 0.96]) - minkowski() - { - translate ([-case_outside/2,-case_outside/2,case_depth]) - cube ([case_outside, case_outside, case_cover_height]); - sphere(2.8, $fn=50); - } - } + CaseCoverFull(); + translate([-100,0,0]) cube([200, 200, 200]); } // Connector supports - translate ([-case_inside/2,-3, case_depth + case_cover_height - 2*case_thickness]) + translate ([-case_inside.x/2,-3, case_depth + case_cover_height - 2*case_thickness]) { translate ([0, 0, 1.5]) cube ([10, 5, 1.5]); - translate ([case_inside-10, 0, 1.5]) + translate ([case_inside.x - 10, 0, 1.5]) cube ([10, 5, 1.5]); } - translate ([case_inside/2-1.5, -10, case_cover_height-1]) - cube ([1.5, 10, 7]); - translate ([-case_inside/2, -5-35, case_cover_height-3]) - cube ([1.5, 5, 9]); + + // Edge supports + translate([case_inside.x/2 - 1, -7, case_depth]) cube([1, 7, 6]); + translate([-case_inside.x/2, -7, case_depth]) cube([1, 7, 6]); + translate([0, -case_inside.y/2, case_depth]) cube([7, 1, 6]); // side wall } // Right half - translate([0,2,0]) if (right == true) { difference() { - intersection() - { - CaseCoverFull(); - scale([0.965, 0.965, 0.965]) - minkowski() - { - translate ([-case_outside/2,-case_outside/2,case_depth]) - cube ([case_outside, case_outside, case_cover_height]); - sphere(3, $fn=50); - } - } + CaseCoverFull(); + translate([-100,-200,0]) cube([200, 200, 200]); - - // Center detent - translate([grooveOffset,grooveDiameter/2 + 28,case_depth + case_cover_height - case_thickness -0.1]) - cylinder(d=4.5, h=case_thickness + 0.2); } // Connector supports - translate ([-case_inside/2,0, case_depth + case_cover_height - 2*case_thickness]) + translate ([-case_inside.x/2,0, case_depth + case_cover_height - 2*case_thickness]) { - translate ([case_inside-30, -2, 1.5]) + translate ([case_inside.x - 21, -2, 1.5]) + cube ([10, 5, 1.5]); + translate ([11, -2, 1.5]) cube ([10, 5, 1.5]); } - translate ([case_inside/2-1.5, 0, case_cover_height-1]) - cube ([1.5, 10, 7]); - translate ([-case_inside/2, 35, case_cover_height-3]) - cube ([1.5, 5, 9]); + + // Edge supports + translate([case_inside.x/2 - 1, 0, case_depth]) cube([1, 7, 6]); // front wall + translate([-case_inside.x/2, 0, case_depth]) cube([1, 7, 6]); // back wall + translate([0, case_inside.y/2 - 1, case_depth]) cube([7, 1, 6]); // side wall } } @@ -307,3 +319,38 @@ module CaseCircuitSupport() rotate([0,45,0]) cube([3.5, 20, 2]); } + + +module ThrottleAxisHolder(center = true, basePart=false, sliderPart=false) +{ + l = 16; + difference() + { + union() + { + if (sliderPart == true) + { + difference() + { + translate([-4, -4 - l + 8, 8 + throttle_slider_height]) cube([8, l, 2]); + moveZ(8 + throttle_slider_height - 0.1) + cylinder(d=3.5, h=2.2); + } + } + + if (basePart == true) + { + moveY(-4 - l + 8) + difference() + { + moveX(-6) cube([12, 7, case_thickness + throttle_slider_height + 5 + 2]); + translate([-4, -0.1, 8 + throttle_slider_height]) cube([8, l, 2.1]); + } + } + } + + moveY(-l + 8) + moveZ(case_thickness + throttle_slider_height) + cylinder(d=2.5, h=10); + } +} diff --git a/throttle-handle.scad b/throttle-handle.scad index f24d8e2..e21ff09 100644 --- a/throttle-handle.scad +++ b/throttle-handle.scad @@ -2,6 +2,7 @@ include ; include ; include ; include ; +include ; // -------------------------------------------------- // Some part definitions for easier switch'n'replace @@ -21,14 +22,10 @@ module YPotKnob() { PotKnobBristles(22.5, 17.5, 12, 3); } solidOnly = false; // set true for faster preview rendering showHandle = true; showThumb = true; -drawOtherParts = true; // draw also non-printable parts like pots, switches etc. -showKnobs = true; -showHandleInsides = true; // Effective values based on the above settings and rendering mode -thumbSeparation = showHandleInsides ? 10 : -4; -$fn = $preview ? 30 : 100; +thumbSeparation = separation; fnForMinkowskiHull = $preview ? 10 : 15; draw_other_parts = $preview ? drawOtherParts : false; diff --git a/throttle-parameters.scad b/throttle-parameters.scad new file mode 100644 index 0000000..d4c0e08 --- /dev/null +++ b/throttle-parameters.scad @@ -0,0 +1,37 @@ +// Rendering + +throttle_value = 0.5; // Position the throttle when drawing the whole throttle unit +separation = 10; // Separation for making exploded view +drawOtherParts = true; // Whether to render non-printable parts as well in preview mode +showKnobs = true; // Whether to render all knobs as well (heavy calculation warning) +$fn = $preview ? 30 : 100; + +draw_other_parts = $preview ? drawOtherParts : false; + +// Throttle axis + +throttle_travel = 100; // Potentiometer travel length +throttle_offset = 14 + 49/2; // Offset of the base from the center-zero +throttle_lever_distance = 7; // Distance of the potentiometer lever from the side of the base +throttle_lever_width = 8.0; // Width of the potentiometer lever +bearing_height = 15; +bearing_length = 45; + +// Throtte handle shape + +throttle_handle_length = 60; // The length of the center section of the handle + +// Case + +case_thickness = 3; +case_bottom_gap = 3; // Gap between inside case bottom and throttle base +case_depth = 17; // outer height of the bottom of the case +case_outside = [2 * throttle_offset + throttle_travel + 5, 140]; +case_cover_height = 18; // outer height of the top of the case +grooveClearance = 2; + +shaft_length = 31; // Length of the shaft of the handle +shaft_intrusion = 11; + +detent_ball_offset = 0; +detent_values = [[0.75, 0.5], [0.5, 4]]; //[[0.7, 1.7], [0.5, 4], [0.25, 1.7]]; diff --git a/throttle.scad b/throttle.scad index 21db13b..274f7be 100644 --- a/throttle.scad +++ b/throttle.scad @@ -3,46 +3,53 @@ include ; include ; include ; -separateHandle = true; - -handleSeparation = separateHandle ? 15 : 0; - -handle_radius = 30; -shaft_intrusion = caseThickness + 1; -shaft_length = 35; +throttle_slider_height = 6.4; +throttle_slider_end_length = 10; // Put it all together for a grand view of it all -module All() -{ - rotate([0,0,180]) - translate ([0,0,case_bottom]) BasePrinted(); - rotate([0,0,180]) - translate ([-17.5,base_width/2,12+case_bottom]) MainThrottleLever(); - translate ([0,0,case_bottom]) Tracks(); - +module Throttle() +{ color("DimGray") CaseBottom(); - %CaseCover(); - translate ([-(case_inside/2)+case_bottom+12, -case_inside/2 + 29, 9.5 + case_thickness]) + + moveY(3*separation) moveZ(separation) CaseCover(right=true, left=false); + moveY(-3*separation) moveZ(separation) CaseCover(right=false, left=true); + + if (draw_other_parts == true) { - translate([0,0,5.6]) - SlidePotentiometer100(); - rotate([0,0,180]) - translate([-134.5,-11.6,0]) - ThrottleAxisHolder(); - translate([-6.5,-3.4,0]) - ThrottleAxisHolder(); + translate ([0,0,case_bottom]) + Tracks(-case_inside.x/2 + throttle_offset + throttle_value * throttle_travel); + + color("Snow") + moveX(throttle_offset) + translate ([-case_inside.x/2 - throttle_lever_width/2, -base_width/2 - throttle_lever_distance, case_bottom]) + { + SlidePotentiometer(sliderEndLength=throttle_slider_end_length, sliderHeight=throttle_slider_height, leverHeight=20, + value=throttle_value, alignPin=true, alignLever=true, alignValueMax=true); + } } - translate ([0,0,base_height + case_bottom]) ShaftAndBase(shaft_length, shaft_intrusion); + color("Silver") + moveX(throttle_offset + throttle_value * throttle_travel - case_inside.x/2) + { + translate ([0,0,case_bottom]) + BasePrinted(leverDistance=throttle_lever_distance); - translate ([0,-25,base_height + case_bottom + shaft_length + 18 - shaft_intrusion + handleSeparation]) - rotate([handleAngle-90,0,0]) - handle(); - - translate ([case_inside/2 - 23,-40,6 + case_thickness]) Backstop(); + translate ([0,0,case_bottom + base_height + separation]) + ShaftAndBase(shaft_length, shaft_intrusion); + moveY(base_width/2 + 3) moveZ(case_bottom + base_height + 9) flipY() turnZ(-90) BallSpringPlunger2(ballPart=true, springPart=false); + } - moveY(base_width/2 + 6) moveZ(35) flipY() turnZ(-90) BallSpringPlunger(); // center detent actuator -} + moveX(throttle_offset - case_inside.x/2 - throttle_slider_end_length) + translate ([0, -base_width/2 - throttle_lever_distance, case_thickness]) + { + ThrottleAxisHolder(sliderPart=true); + moveX(throttle_travel + 2*throttle_slider_end_length) + ThrottleAxisHolder(sliderPart=true); + } -All(); + moveZ(shaft_length + case_bottom + base_height + 4*separation + shaft_connector_height) + moveY(-25) + turnX(handleAngle-90) + handle(); +}