Commit 4db2f349 authored by Richard Bowman's avatar Richard Bowman 🔬 Committed by GitHub

Merge pull request #7 from rwb27/long-travel

More accessories and a build script

Former-commit-id: ec83a0fe4a8fbe52c772efb89f4dff4a6a801253
Former-commit-id: c00effccf1f8e6daed4b8e9296906ada13d171f9
parents 3d1ed1d2 349b144a
......@@ -11,3 +11,6 @@ openscad/*.stl
# Ignore zip files in the documentation dir
docs/*.zip
# Ignore build files
/builds/
\ No newline at end of file
# Makefile for the openflexure block stage
# Generated by generate_makefile.py
.PHONY: all cleanstl
SOURCE = openscad
OUTPUT = builds
BODIES := main_body_micromanipulator_2mm main_body_fibre_stage_1mm
BASES := base_micromanipulator_2mm base_fibre_stage_1mm
TOOLS := actuator_assembly_tools
ACCESSORIES := accessories/LED_to_top_plate accessories/rms_to_top_plate accessories/CPS532_to_top_plate accessories/microscope_module
COMMONPARTS := gears small_gears moving_platform
ALLPARTS := $(COMMONPARTS) $(TOOLS) $(BODIES) $(BASES) $(ACCESSORIES)
ALLSTLFILES := $(ALLPARTS:%=$(OUTPUT)/%.stl)
parameters_file := $(SOURCE)/parameters.scad
utilities_file := $(SOURCE)/utilities.scad
all_deps := $(parameters_file) $(utilities_file) #All targets depend on these
all: $(ALLSTLFILES)
cleanstl:
rm $(STLFILES)
#parameter and utilities files affect everything
$(OUTPUT)/%.stl: $(all_deps)
main_body_dep_names := compact_nut_seat fibre_stage_three_legs
main_body_deps := $(main_body_dep_names:%=$(SOURCE)/%.scad)
$(OUTPUT)/main_body_micromanipulator_2mm.stl: $(SOURCE)/main_body.scad $(main_body_deps)
openscad -o [email protected] -D 'xy_lever=20' -D 'z_lever=20' -D 'beam_height=85' -D 'fixed_stage=false' -D 'breadboard_lugs="atsides"' $<
$(OUTPUT)/base_micromanipulator_2mm.stl: $(SOURCE)/base.scad $(main_body_deps)
openscad -o [email protected] -D 'xy_lever=20' -D 'z_lever=20' -D 'beam_height=85' -D 'fixed_stage=false' -D 'breadboard_lugs="atsides"' $<
$(OUTPUT)/main_body_fibre_stage_1mm.stl: $(SOURCE)/main_body.scad $(main_body_deps)
openscad -o [email protected] -D 'xy_lever=10' -D 'z_lever=10' -D 'beam_height=75' -D 'fixed_stage=true' -D 'breadboard_lugs="diagonal"' $<
$(OUTPUT)/base_fibre_stage_1mm.stl: $(SOURCE)/base.scad $(main_body_deps)
openscad -o [email protected] -D 'xy_lever=10' -D 'z_lever=10' -D 'beam_height=75' -D 'fixed_stage=true' -D 'breadboard_lugs="diagonal"' $<
$(OUTPUT)/actuator_assembly_tools.stl: $(SOURCE)/actuator_assembly_tools.scad
openscad -o [email protected] -D 'foot_height=26' $<
$(OUTPUT)/accessories/LED_to_top_plate.stl: $(SOURCE)/accessories/LED_to_top_plate.scad
openscad -o [email protected] $<
$(OUTPUT)/accessories/rms_to_top_plate.stl: $(SOURCE)/accessories/rms_to_top_plate.scad
openscad -o [email protected] $<
$(OUTPUT)/accessories/CPS532_to_top_plate.stl: $(SOURCE)/accessories/CPS532_to_top_plate.scad
openscad -o [email protected] $<
$(OUTPUT)/accessories/microscope_module.stl: $(SOURCE)/accessories/microscope_module.scad
openscad -o [email protected] $<
$(OUTPUT)/%.stl: $(SOURCE)/%.scad $(all_deps)
openscad -o [email protected] $<
#!python
import re
"""This script generates the Makefile for the Openflexure Microscope.
It is intended to be run whenever you need a new makefile. The makefile lives in
the repository and is versioned, so most people never need to run this script.
"""
def part_versions(part_name, versions):
"""Given a part name and a dictionary of versions, list all the possible part names.
For example::
>>> part_versions("body_{}", {"a":{}, "b":{}})
"body_a body_b"
"""
return " ".join(part_name.format(n) for n in versions.keys())
def openscad_recipe(**kwargs):
"""Create a makefile recipe to compile an openscad file with given options."""
output = "\t" + "openscad -o [email protected]"
for name, value in kwargs.iteritems():
try:
float(value)
except ValueError:
# numbers and booleans are OK, but strings need to be double-quoted on the command line.
if value not in ["true", "false"]:
value = '"{}"'.format(value)
if type(value) == type(True): #need to convert boolean values
value = "true" if value else "false"
output += " -D '{name}={value}'".format(name=name, value=str(value))
output += " $<\n"
return output
body_versions = {
"fibre_stage_1mm":{
"beam_height":75,
"xy_lever":10,
"z_lever":10,
"breadboard_lugs":"diagonal",
"fixed_stage":True,
},
"micromanipulator_2mm":{
"beam_height":85,
"xy_lever":20,
"z_lever":20,
"breadboard_lugs":"atsides",
"fixed_stage":False,
},
}
accessory_versions = {
"CPS532_to_top_plate":{},
"LED_to_top_plate":{},
"rms_to_top_plate":{},
"microscope_module":{},
}
if __name__ == "__main__":
with open("Makefile","w") as makefile:
def M(line):
makefile.write(line + "\n")
M("# Makefile for the openflexure block stage")
M("# Generated by generate_makefile.py")
M(".PHONY: all cleanstl")
M("")
M("SOURCE = openscad")
M("OUTPUT = builds")
M("")
M("BODIES := " + part_versions("main_body_{}", body_versions))
M("BASES := " + part_versions("base_{}", body_versions))
M("TOOLS := actuator_assembly_tools")
M("ACCESSORIES := " + part_versions("accessories/{}", accessory_versions))
M("COMMONPARTS := gears small_gears moving_platform")
M("ALLPARTS := $(COMMONPARTS) $(TOOLS) $(BODIES) $(BASES) $(ACCESSORIES)")
M("ALLSTLFILES := $(ALLPARTS:%=$(OUTPUT)/%.stl)")
M("")
M("parameters_file := $(SOURCE)/parameters.scad")
M("utilities_file := $(SOURCE)/utilities.scad")
M("all_deps := $(parameters_file) $(utilities_file) #All targets depend on these")
M("")
M("all: $(ALLSTLFILES)")
M("")
M("cleanstl:")
M("\t"+"rm $(STLFILES)")
M("")
M("#parameter and utilities files affect everything")
M("$(OUTPUT)/%.stl: $(all_deps)")
M("")
M("main_body_dep_names := compact_nut_seat fibre_stage_three_legs")
M("main_body_deps := $(main_body_dep_names:%=$(SOURCE)/%.scad)")
for version, parameters in body_versions.items():
M("$(OUTPUT)/main_body_" + version + ".stl: $(SOURCE)/main_body.scad $(main_body_deps)")
M(openscad_recipe(**parameters))
M(" ")
M("$(OUTPUT)/base_" + version + ".stl: $(SOURCE)/base.scad $(main_body_deps)")
M(openscad_recipe(**parameters))
M("")
M("$(OUTPUT)/actuator_assembly_tools.stl: $(SOURCE)/actuator_assembly_tools.scad")
M(openscad_recipe(foot_height=26))
M("")
for version, parameters in accessory_versions.items():
M("$(OUTPUT)/accessories/" + version + ".stl: $(SOURCE)/accessories/" + version + ".scad")
M(openscad_recipe(**parameters))
M("")
M("$(OUTPUT)/%.stl: $(SOURCE)/%.scad $(all_deps)")
M(openscad_recipe())
M("")
......@@ -6,12 +6,13 @@ An adapter to fit an 11mm diameter laser diode module to the stage
*/
use <utilities.scad>;
use <../utilities.scad>;
use <cylinder_clamp.scad>;
include <../parameters.scad>;
hole_sep = 40;
hole_d = 3.5;
beam_h = 12.7;
beam_h = stage_to_beam_h;
d = 0.05;
w = hole_sep + hole_d + 4;
......
......@@ -8,11 +8,12 @@ TODO: locate the SCAD source file for the vertical slide clips!
*/
use <utilities.scad>;
use <../utilities.scad>;
include <../parameters.scad>;
hole_sep = 40;
hole_d = 3.5;
beam_h = 12.7;
beam_h = stage_to_beam_height;
d = 0.05;
w = hole_sep + hole_d + 4;
......
/*
An adapter to fit the OpenFlexure Microscope optics module on the
fibre alignment stage
(c) 2016 Richard Bowman - released under CERN Open Hardware License
*/
use <optics_mounts.scad>;
rms_mount_l = 25;
openflexure_microscope_module(rms_mount_l);
use <dovetail.scad>;
use <utilities.scad>;
objective_clip_w = 10;
objective_clip_y = 6;
clip_outer_w = objective_clip_w + 4;
h = 11.5;
d = 0.05;
//Centre of mounting holes to centre of mounting holes on static part is
// stage[1]/2 + z_lever + zflex[1]+4 (in the +y direction)
// stage[1] = 20
// z_lever = 10
// zflex[1] = 1.5
// so dy = 10 + 10 + 1.5 + 4 = 25.5
// actually the mounting holes are +/-5mm from this (10mm centres) so add 5mm
dy = 25.5 + 5;
difference(){
union(){
translate([0,objective_clip_y,0]) dovetail_clip([clip_outer_w,10,h],solid_bottom=0.5,slope_front=1.5);
sequential_hull(){
translate([-7,objective_clip_y - d + 10, 0]) cube([14, d, h]);
translate([-8,dy-4,0]) cube([16,d,5]);
reflect([1,0,0]) translate([5,dy,0]) cylinder(r=3,h=5);
}
}
reflect([1,0,0]) translate([5,dy,0])
cylinder(d=3*1.2, h=999,center=true);
}
\ No newline at end of file
......@@ -6,32 +6,111 @@ fibre alignment stage
(c) 2016 Richard Bowman - released under CERN Open Hardware License
*/
use <utilities.scad>;
use <dovetail.scad>;
use <thorlabs_threads.scad>;
include <../parameters.scad>;
//beam_height = platform_z + 3 + 10 + 6; //for picamera lens optics
beam_height_from_platform = 12.7;
beam_height = platform_z + 12.7; //for compatibility with standard stuff
objective_clip_w = 10;
objective_clip_y = 6;
clip_outer_w = objective_clip_w + 4;
module plate_with_keel(t=3, l=12, in_situ=false){
// A plate that sits on top of the fixed/moving platforms of the stage.
// if in_situ is false, the plate is vertical, such that the Z axis is the
// optical axis. If in_situ is true, the keel starts at x=y=0 and the plate
// sits on z=stage_height
keel = [3-0.3,l,1];
translate(in_situ?[0,0,stage_height]:[0,-stage_to_beam_height,l])
rotate([in_situ?0:-90,0,0]) union(){
translate([-16,0,0]) cube([32,l,t]);
translate([-keel[0]/2,0,-keel[2]]) cube(keel+[0,0,d]);
}
}
module plate_with_keel_base(t=3, l=12, w=25, in_situ=false){
// If you're mounting to a plate_with_keel, you can convex hull to
// this part in order to join to it nicely.
// NB if w>28 you will struggle to attach the plate to the stage.
if(w>28) echo("WARNING: You've got to keep the width of stuff < 28mm to attach to the base");
keel = [3-0.3,l,1];
translate(in_situ?[0,0,stage_height]:[0,-stage_to_beam_height,l])
rotate([in_situ?0:-90,0,0]){
translate([-w/2,0,t-d]) cube([w,l,d]);
}
}
module optics_to_platform(){
h=25;
clip_l = 5;
keel = [3-0.3,1,h];
translate([0,beam_height_from_platform-clip_l,0]) mirror([0,1,0]) dovetail_clip([10+2*1,clip_l,h],solid_bottom=0.5,slope_front=3,t=1);
translate([-16,0,0]) cube([32,3+d,h]);
translate([-keel[0]/2,-keel[1],0]) cube(keel+[0,d,0]);
//supports for during the print (snap off later)
reflect([1,0,0]) translate([-16,0,0]) cube([8,10,0.5]);
reflect([1,0,0]) translate([-16,0,0]) hull(){
cube([1,8,0.5]);
cube([1,1,8]);
rms_mount_hole_spacing = 18;
module each_rms_mounting_hole(){
for(a=[0,90,180,270]) rotate(a) translate([1,1,0]*rms_mount_hole_spacing/2) children();
}
module rms_to_platform(mounting_holes=true, l=15){
// A mount for an RMS objective that screws to the fixed/moving plate.
// l is the length of the mount
rms_r = 25.4*0.8/2-0.25; //see the openflexure microscope module
pitch=0.7056;
hole_r = rms_r + 0.44;
outer_r = hole_r + 1.2;
difference(){
union(){
// First, the bit that fixes to the stage
plate_with_keel(l=l);
hull(){
// Then, a place to put the thread
cylinder(r=outer_r, h=l);
// Join it to the bottom of the mount
plate_with_keel_base(l=l, w=outer_r*2);
// And make sure there are places to bolt to (if needed)
if(mounting_holes) each_rms_mounting_hole() cylinder(r=3, h=l);
}
}
cylinder(r=hole_r, h=999, center=true);
if(mounting_holes) each_rms_mounting_hole() translate([0,0,-1]) cylinder(d=3, h=999, $fn=8);
}
translate([0,0,l-5]) inner_thread(radius=rms_r,threads_per_mm=pitch,thread_base_width = 0.60,thread_length=5);
}
rms_to_platform(l=25,mounting_holes=true);
module microscope_to_rms_mount_holes(top_z){
// Holes to bolt the microscope module to the RMS mount above
for(a=[0,45]) rotate(a) each_rms_mounting_hole(){
translate([0,0,top_z-3-d]) cylinder(d=3.3, h=999, $fn=8);
hull(){
translate([0,0,top_z-3-5-d]) cylinder(r=3, h=d+5, $fn=16);
translate([2.5,2.5,top_z-3-5-d-10]) cylinder(r=3, h=d+5, $fn=16);
}
}
}
module openflexure_microscope_module(rms_mount_l=15){
// a cut down optics module that bolts to the RMS mount above
// this is useful for observing the motion of the stage.
top_z = 65-35-rms_mount_l;
outer_r = rms_mount_hole_spacing/sqrt(2)+3;
inner_r = 18; // this should be small enough to fit inside the RMS thread but
// big enough to clear the inner lens mount.
difference(){
// Start with an optics module:
import("optics_module_rms_f50d13_nodovetail.stl");
// Chop off the built-in RMS objective mount
translate([0,0,top_z]) difference(){
cylinder(r=999,h=999,$fn=3);
cylinder(d=inner_r,h=999,center=true);
}
microscope_to_rms_mount_holes(top_z);
}
difference(){
// Add a mounting flange to the top
hull(){
translate([0,0,top_z-3]) cylinder(r=outer_r, h=3);
translate([0,0,top_z-3-10]) cylinder(r=outer_r-10, h=3);
}
cylinder(d=inner_r,h=999,center=true);
microscope_to_rms_mount_holes(top_z);
for(a=[0,45]) rotate(a+180) translate([-999,12.4,-999]) cube(9999);
}
}
//openflexure_microscope_module(25);
module disc_to_platform(){
h=12;
......@@ -106,4 +185,4 @@ module inch_disc_holder(){
// cylinder(d=23,h=8);
// cylinder(d=10,h=999,center=true);
//}
optics_to_platform();
//optics_to_platform();
/*
An adapter to fit the OpenFlexure Microscope optics module on the
fibre alignment stage
(c) 2016 Richard Bowman - released under CERN Open Hardware License
*/
use <optics_mounts.scad>;
rms_mount_l = 25;
rms_to_platform(l=rms_mount_l);
/*
This file was put together by Graham Gibson, based on a part
by Hazen Babcock https://github.com/ZhuangLab/3D-printing/tree/master/nikon_filter_cube
That, in turn, borrowed from what I believe became the MCAD threads library.
I think everything in here can be considered GPL, but if I've misunderstood, I would be
very happy to be corrected.
-- Richard Bowman, July 2018
*/
$fn = 200;
// reverse trapezoid
module reverse_trapezoid(p0, p1, p2, p3, p4, p5, p6, p7)
{
polyhedron(
points = [p0, p1, p2, p3, p4, p5, p6, p7],
faces = [[0,1,2,3],[0,4,5,1],[0,3,7,4],
[6,5,4,7],[6,2,1,5],[6,7,3,2]]);
}
// thread.
module inner_thread (radius = 12.9,
thread_height = 0.45,
thread_base_width = 0.6,
thread_top_width = 0.05,
thread_length = 6.5,
threads_per_mm = 0.635,
extra = -0.5,
overlap = 0.01)
{
cylinder_radius = radius + thread_height;
inner_diameter = 2.0 * 3.14159 * radius;
number_divisions = 60;//Originally 180 - changed to 60 to reduce compiler time
overshoot = extra * number_divisions;
angle_step = 360.0/number_divisions;
turns = thread_length/threads_per_mm;
z_step = threads_per_mm/number_divisions;
fudge = angle_step * overlap;
p0 = [cylinder_radius * cos(-0.5 * (angle_step + fudge)),
cylinder_radius * sin(-0.5 * (angle_step + fudge)),
-0.5 * thread_base_width];
p1 = [radius * cos(-0.5 * (angle_step + fudge)),
radius * sin(-0.5 * (angle_step + fudge)),
-0.5 * thread_top_width];
p2 = [radius * cos(-0.5 * (angle_step + fudge)),
radius * sin(-0.5 * (angle_step + fudge)),
0.5 * thread_top_width];
p3 = [cylinder_radius * cos(-0.5 * (angle_step + fudge)),
cylinder_radius * sin(-0.5 * (angle_step + fudge)),
0.5 * thread_base_width];
p4 = [cylinder_radius * cos(0.5 * (angle_step + fudge)),
cylinder_radius * sin(0.5 * (angle_step + fudge)),
-0.5 * thread_base_width + z_step];
p5 = [radius * cos(0.5 * (angle_step + fudge)),
radius * sin(0.5 * (angle_step + fudge)),
-0.5 * thread_top_width + z_step];
p6 = [radius * cos(0.5 * (angle_step + fudge)),
radius * sin(0.5 * (angle_step + fudge)),
0.5 * thread_top_width + z_step];
p7 = [cylinder_radius * cos(0.5 * (angle_step + fudge)),
cylinder_radius * sin(0.5 * (angle_step + fudge)),
0.5 * thread_base_width + z_step];
difference(){
union(){
for(i = [-overshoot:(turns*number_divisions+overshoot)]){
//for(i= [0:1]){
rotate([0,0,i*angle_step])
translate([0,0,i*z_step])
reverse_trapezoid(p0, p1, p2, p3, p4, p5, p6, p7);
}
}
translate([0,0,-2])
cylinder(r = cylinder_radius+0.1, h = 2);
translate([0,0,thread_length])
cylinder(r = cylinder_radius+0.1, h = 2);
}
}
module outer_thread (radius = 12.9,
thread_height = 0.45,
thread_base_width = 0.6,
thread_top_width = 0.05,
thread_length = 6.5,
threads_per_mm = 0.635,
extra = -0.5,
overlap = 0.01)
{
cylinder_radius = radius + thread_height;
inner_diameter = 2.0 * 3.14159 * radius;
number_divisions = 60;//Originally 180 - changed to 60 to reduce compiler time
overshoot = extra * number_divisions;
angle_step = 360.0/number_divisions;
turns = thread_length/threads_per_mm;
z_step = threads_per_mm/number_divisions;
fudge = angle_step * overlap;
p0 = [radius * cos(-0.5 * (angle_step + fudge)),
radius * sin(-0.5 * (angle_step + fudge)),
-0.5 * thread_base_width];
p1 = [cylinder_radius * cos(-0.5 * (angle_step + fudge)),
cylinder_radius * sin(-0.5 * (angle_step + fudge)),
-0.5 * thread_top_width];
p2 = [cylinder_radius * cos(-0.5 * (angle_step + fudge)),
cylinder_radius * sin(-0.5 * (angle_step + fudge)),
0.5 * thread_top_width];
p3 = [radius * cos(-0.5 * (angle_step + fudge)),
radius * sin(-0.5 * (angle_step + fudge)),
0.5 * thread_base_width];
p4 = [radius * cos(0.5 * (angle_step + fudge)),
radius * sin(0.5 * (angle_step + fudge)),
-0.5 * thread_base_width + z_step];
p5 = [cylinder_radius * cos(0.5 * (angle_step + fudge)),
cylinder_radius * sin(0.5 * (angle_step + fudge)),
-0.5 * thread_top_width + z_step];
p6 = [cylinder_radius * cos(0.5 * (angle_step + fudge)),
cylinder_radius * sin(0.5 * (angle_step + fudge)),
0.5 * thread_top_width + z_step];
p7 = [radius * cos(0.5 * (angle_step + fudge)),
radius * sin(0.5 * (angle_step + fudge)),
0.5 * thread_base_width + z_step];
difference(){
union(){
for(i = [-overshoot:(turns*number_divisions+overshoot)]){
//for(i= [0:1]){
rotate([0,0,i*angle_step])
translate([0,0,i*z_step])
reverse_trapezoid(p0, p1, p2, p3, p4, p5, p6, p7);
}
}
translate([0,0,-2])
cylinder(r = cylinder_radius+0.1, h = 2);
translate([0,0,thread_length])
cylinder(r = cylinder_radius+0.1, h = 2);
}
}
\ No newline at end of file
/*
Tools for assembling the OpenFlexure Microscope v5.16
(c) 2016 Richard Bowman - released under CERN Open Hardware License
*/
use <utilities.scad>;
use <compact_nut_seat.scad>;
include <parameters.scad>;
ns = nut_slot_size();
shaft_d = nut_size()*1.1;
gap = 9; //size of the gap between gear and screw seat
swing_a = 30; //angle through which the tool swings
sso = ss_outer(25); //outer size of screw seat
handle_w = shaft_d+4; //width of the "handle" part
handle_l = sso[0]/2+gap; //length of handle part
module tool_handle(){
w = handle_w; //width of the handle
a = swing_a; //angle through which the tool is moved to tighten the nut
difference(){
sequential_hull(){
rotate([-a,0,0]) hull(){
rc=1.5;
reflect([1,0,0]) translate([w/2 - rc, rc, rc * tan(45 + a/2)]) sphere(r=rc);
reflect([1,0,0]) translate([w/2 - rc, rc, gap - rc]) sphere(r=rc);
}
translate([-w/2,(gap*cos(a)-ns[2])/tan(a) + gap*sin(a),0]) cube([w,d,ns[2]]);
//translate([-w/2,ns[2],0]) cube([w,d,ns[2]]);
translate([-w/2,sso[0]/2*cos(swing_a)+gap*sin(swing_a),0]) cube([w,d,ns[2]]);
translate([-ns[0]/2,handle_l,0]) cube([ns[0],d,ns[2]]);
}
//ground (or bottom of gear)
mirror([0,0,1]) cylinder(r=999,h=999,$fn=4);
//screw seat (in swung-in position)
rotate([0,180-swing_a,-90]) translate([0,0,-(gap+sso[2]/2)]) screw_seat_shell(25);
//screw
//rotate([-swing_a,0,0]) cylinder(d=shaft_d,h=999,center=true, $fn=16);
}
}
module xz_slice(y=0){
//slice out just the part of something that sits in the XZ plane
intersection(){
translate([0,y,0]) cube([9999,2*d,9999],center=true);
children();
}
}
module nut_tool(){
w = ns[0]-0.6; //width of tool tip (needs to fit through the slot that's ns[0] wide
h = ns[2]-0.7; //height of tool tip (needs to fit through slot)
l = 5+sso[1]/2+3;
difference(){
union(){
translate([0,-handle_l,0]) tool_handle();
sequential_hull(){
xz_slice() translate([0,-handle_l,0]) tool_handle();
translate([-w/2, 5, 0]) cube([w, d, h]);
translate([-w/2, l, 0]) cube([w, d, h]);
}
}
//nut
translate([0,l,-d])rotate(30)cylinder(r=nut_size()*1.15, h=999, $fn=6);
translate([0,l-nut_size()*1.15+0.4,-d]) cylinder(r=1,h=999,$fn=12);
}
}
module band_tool(){
w = ns[0]-0.5; //width of tool tip
h = 4.5; //height of tool tip (needs to fit through slot)
l = sso[2]/2+foot_height+5;
// presently, the hook on the actuator is a 1mm radius cylinder, centred
// 3.5mm from the edge of the (elliptical) wall of the screw seat.
difference(){
union(){
translate([0,-handle_l,0]) tool_handle();
sequential_hull(){
xz_slice() translate([0,-handle_l,0]) tool_handle();
translate([0,l-20,0]) xz_slice() translate([0,-handle_l,0]) tool_handle();
union(){
translate([-3/2, l-12,0]) cube([3,12,h]);
translate([-7/2, l-12,h-1]) cube([7,12,1]);
}
}
}
// cut-out to clear the hook
hull(){
translate([0,l,1.5]) scale([1,1,0.66]) rotate([90,0,0]) cylinder(r=1.4,h=18,center=true);
translate([0,l,2+3]) rotate([90,0,0]) cylinder(r=2.3,h=40,center=true);
}
// V shaped end to grip elastic bands
translate([0,l,0]) hull(){
translate([0,0.3,1.5]) rotate([0,90,0]) cylinder(r=1,h=999,center=true);
translate([0,-0.5,h-1.5]) rotate([0,90,0]) cylinder(r=1,h=999,center=true);
}
translate([-99,l-0.5,h-1.5]) cube(999);
// squeeze the sides slightly (commented out for strength)
reflect([1,0,0]){
// translate([-7/2,l,h/2-0.3]) rotate([90,15,-3]) scale([1.1,1.5,1]) cylinder(r=1,h=999,center=true);
}
}
}
band_tool_l = sso[2]/2+foot_height;
band_tool_w = ns[0]-0.5;
band_tool_h = 4;
module prong_frame(){
//Move the prongs out and tilt them slightly
smatrix(xz=0.3, xt=1.9, yt=band_tool_l) children();
}
blade_anchor = [0,-12,0]; //position of the bottom of the slot
module blade_point(pos, d1=1.5, d2=1.5, h=d){
union(){
translate(blade_anchor + [0,0,pos[2]]) cylinder(d=d1, h=h);
translate(pos) cylinder(d=d2, h=h);
}
}
module band_tool_2(handle=true){
//forked tool to insert the elastic band
h = band_tool_h; //overall height of the band insertion tool
union(){
// the two "blades" that support the band either side of the hook
reflect([1,0,0]) prong_frame() sequential_hull(){
blade_point([0,1.5,0], h=0.5);
blade_point([0,0,h-1]);
blade_point([0.3,0.5,h-d],d2=2.1);
}
// the flat bottom that passes between the hook and the outside of the column
hull() reflect([1,0,0]) prong_frame(){ //bottom of the tip
translate([0,1.5,0]) cylinder(d=1.5,h=0.5);
translate(blade_anchor) cylinder(d=1.5,h=0.5);
}
// connect the business end of the tool to the handle
difference(){
hull(){ //join the blades and the handle
reflect([1,0,0]) prong_frame() translate(blade_anchor) repeat([0,10,0], 2) cylinder(d=1.5,h=h);
xz_slice() translate([0,-handle_l,0]) tool_handle();
}
//cut out to get nice rounded corners at the bottom of the slot for the hook
hull() reflect([1,0,0]) prong_frame(){
translate(blade_anchor + [-2.25,3,h]) sphere(r=1.5,h=99);
translate(blade_anchor + [-1.5,10,0.5]) cube([1.5/2,999,999]);
}
}
//the handle
if(handle){
translate([0,-handle_l,0]) tool_handle();
}
}
}
module double_ended_band_tool(bent=false){
roc=2;
middle_w = 2*column_base_radius()+1.5+2*(band_tool_h-roc)+0.5; //width of the band anchor on the foot
flex_l = roc*3.14/2; //length of the flexible linkers
// We make two tools, spaced out by a flexible joiner
reflect([0,1,0]) translate([0,middle_w/2+flex_l,0]) if(bent){
translate([0,roc-3,roc]) rotate([90,0,0]) band_tool_2(handle=false);
}else{
band_tool_2(handle=false);
}
//flexible links between the two tools and the middle part
if(bent){
reflect([0,1,0]) translate([0,middle_w/2,roc]) difference(){
rotate([0,90,0]) cylinder(r=roc,h=ns[0],center=true);
rotate([0,90,0]) cylinder(r=roc-0.5,h=99,center=true);
translate([-99,-99,0]) cube(999);
translate([-99,-999,-99]) cube(999);
}
translate([0,0,0.5/2]) cube([ns[0],middle_w+2*d,0.5],center=true);
}else{