OpenFlexure Microscope OpenSCAD docs

libs/locking_dovetail.scad

Some sketches to work towards a nicer dovetail mechanism in OpenSCAD.

(c) Richard Bowman 2021, released under CERN-OHL-W v2

function dovetail_default_params()
[Source]
function dovetail_default_params() = [
    ["depth", 4],            // y distance between outer flat surface and tip
    ["angle", 60],           // angle of sloping part
    ["outer_flat", 6],       // width of outer flat parts
    ["overall_width", 30],   // width of whole structure
    ["overall_height", 16],  // height of whole structure
    ["block_depth", 12],     // y size of mounting block
    ["fillet_r", 0.5],       // fillet radius for rounded corners
    ["relief_r", 0.7],       // fillet radius for rounded corners
    ["lever", 8],            // distance from flat surface to pivot of clamp
    ["flex_l", 4],           // length of clamp flexure
    ["flex_t", 1.6],         // thickness of above
    ["clamp_t", 9],          // thickness of clamping flange, in the direction of the bolt
    ["top_t", 2],            // thickness of the top solid layer
    ["bottom_t", 2],         // thickness of the bottom solid layer
    ["vertical_gap", 1],     // gap between clamp and top/bottom layers
    ["clamp_support_t", 0.5],// thickness of internal bridge support for clamp
    ["clamp_angle", 7],      // angle through which we allow the clamp to bend
    ["pinch_bolt_inset", 2], // distance from centre of clamping bolt to female point
    ["taper_block", false],  // set this to true to taper the block parallel to the flanges
    ["nut_slot_slope", "up"] // set the nut slot to slope up from the nut. The other allowed value is "down"
];
function dovetail_params(
    overall_height=16,
    overall_width=30,
    block_depth=12,
    taper_block=false,
    nut_slot_slope="up",
    depth=4
)
[Source]
function dovetail_params(
    // This is an experiment in how to handle the commonly-changed parameters more nicely
    overall_height=16,
    overall_width=30,
    block_depth=12,
    taper_block=false,
    nut_slot_slope = "up",
    depth=4
) = replace_multiple_values(
    [
        ["overall_height", overall_height],
        ["overall_width", overall_width],
        ["block_depth", block_depth],
        ["taper_block", taper_block],
        ["nut_slot_slope", nut_slot_slope],
        ["depth", depth]
    ],
    dovetail_default_params()
);
function dovetail_back_width(p)
[Source]
function dovetail_back_width(p) = let(
    w = key_lookup("overall_width", p),
    depth = key_lookup("block_depth", p),
    angle = key_lookup("angle", p),
    taper_block = key_lookup("taper_block", p),
    tapered_width = w - 2*tan(90-angle)*depth
) taper_block ? tapered_width : w;
module block_sharp(p)
[Source]
module block_sharp(p){
    // the block to which we attach the male dovetail
    // or from which we cut the female one

    w = key_lookup("overall_width", p);
    depth = key_lookup("block_depth", p);
    back_w = dovetail_back_width(p);
    polygon([
        [     -w/2,      0],
        [      w/2,      0],
        [ back_w/2, -depth],
        [-back_w/2, -depth],
    ]);
}
module back_of_block_2d(p)
[Source]
module back_of_block_2d(p){
    // the back of the block to which we attach the male dovetail
    // or from which we cut the female one

    depth = key_lookup("block_depth", p);
    angle = key_lookup("angle", p);
    back_w = dovetail_back_width(p);
    fillet_r = key_lookup("fillet_r", p);

    hull(){
        reflect_x(){
            x_tr = back_w/2 - fillet_r*tan(angle/2);
            y_tr = -depth + fillet_r;
            translate([x_tr, y_tr]){
                circle(r=fillet_r);
            }
        }
    }
}
module flange_r(p, width=tiny())
[Source]
module flange_r(p, width=tiny()){
    // the angled part of a male dovetail

    shiftx = [-width, 0];
    // we extend the parallelogram into the block slightly,
    // at the same angle.
    shift_in = tiny()*[-cos(key_lookup("angle", p)), -sin(key_lookup("angle", p))];

    polygon([
        female_point(p) + shift_in,
        male_point(p),
        male_point(p) + shiftx,
        female_point(p) + shiftx + shift_in
    ]);
}
function male_point(p)
[Source]
function male_point(p) = let(
    w = key_lookup("overall_width", p),
    flat = key_lookup("outer_flat", p),
    depth = key_lookup("depth", p),
    angle = key_lookup("angle", p)
) [w/2 - flat + depth/tan(angle), depth];
function female_point(p)
[Source]
function female_point(p) = let(
    w = key_lookup("overall_width", p),
    flat = key_lookup("outer_flat", p)
) [w/2 - flat, 0];
module dovetail_section_m_sharp(p)
[Source]
module dovetail_section_m_sharp(p){
    // A male dovetail, before any filleting of the corners
    difference(){
        union(){
            block_sharp(p);

            hull(){
                reflect([1, 0]){
                    flange_r(p);
                }
            }
        }

        // relieve internal corners
        reflect([1, 0]){
            translate(female_point(p)){
                circle(key_lookup("relief_r", p));
            }
        }
    }
}
module solid_male_dovetail(p, height=undef)
[Source]
module solid_male_dovetail(p, height=undef){
    // Cut this shape out of a block with a face at y=0 to make
    // a dovetail
    h = is_undef(height) ? key_lookup("overall_height", p) : height;
    linear_extrude(h){
        // The male dovetail
        male_dovetail_2d(p);
    }
}
module male_dovetail_2d(p)
[Source]
module male_dovetail_2d(p){
    // Used to create the female cut out. Can also be used on its own
    // for a non-locking dovetail
    hull(){
        reflect([1, 0]){
            mirror([0,1]){
                flange_r(p);
            }
        }
    }
}
module dovetail_section_f_sharp_cutout(p)
[Source]
module dovetail_section_f_sharp_cutout(p){
    // We cut this shape out of a block to make the female cutout

    // The male dovetail
    male_dovetail_2d(p);

    // relieve internal corners
    hull(){
        reflect([1, 0]){
            translate(-male_point(p)){
                circle(key_lookup("relief_r", p));
            }
        }
    }
}
module dovetail_section_f_sharp(p)
[Source]
module dovetail_section_f_sharp(p){
    // A female dovetail, before any filleting of the corners
    difference(){
        block_sharp(p);
        dovetail_section_f_sharp_cutout(p);
    }
}
module rotate_repeat(angle)
[Source]
module rotate_repeat(angle){
    union(){
        children();
        rotate(angle){
            children();
        }
    }
}
module clamp_frame(p)
[Source]
module clamp_frame(p){
    // place the origin at the pivot point of the clamp
    // and align y axis with the dovetail angle
    translate(female_point(p)){
        rotate(key_lookup("angle", p) - 90){
            translate([0, -key_lookup("lever", p)]){
                children();
            }
        }
    }
}
module clamp_cutout_2d(p)
[Source]
module clamp_cutout_2d(p){
    // 2D cutout to make a male dovetail clamp
    fillet_r = key_lookup("fillet_r", p);
    lever = key_lookup("lever", p);
    flex_l = key_lookup("flex_l", p);
    flex_t = key_lookup("flex_t", p);
    clamp_t = key_lookup("clamp_t", p);
    clamp_angle = key_lookup("clamp_angle", p);
    $fn=16;
    clamp_frame(p){
        // between nut and screw
        hull(){
            translate([0, fillet_r + flex_t/2]){
                circle(fillet_r);
            }
            translate([0, lever]){
                circle(fillet_r);
            }
        }
        // next to flexure
        hull(){
            reflect([1,0]){
                translate([flex_l/2, fillet_r + flex_t/2]){
                    circle(fillet_r);
                }
            }
        }
        // behind clamp
        sequential_hull(){
            // start next to the flexure
            translate([flex_l/2, -fillet_r - flex_t/2]){
                circle(fillet_r);
            }
            // duplicate the corner point to allow it to bend
            rotate_repeat(clamp_angle){
                translate([-clamp_t, -fillet_r - flex_t/2]){
                    circle(fillet_r);
                }
            }
            // don't use the second corner point, to avoid shortening the clamping part
            // because of cosine error
            translate([-clamp_t, -fillet_r - flex_t/2]){
                circle(fillet_r);
            }
            // a far-away, wider point, so the opening is wedge-shaped.
            rotate_repeat(clamp_angle){
                translate([-clamp_t, 99]){
                    circle(fillet_r);
                }
            }
        }
    }
}
module clamp_cutout_empty_2d(p)
[Source]
module clamp_cutout_empty_2d(p){
    // 2D cutout to make a male dovetail clamp
    union(){
        clamp_cutout_2d(p);

        // take the hull of just the internal part
        hull(){
            intersection(){
                clamp_cutout_2d(p);
                hull(){
                    repeat([-99, 0], 2, center=false){
                        clamp_frame(p){
                            reflect([0, 1]){
                                translate([0, key_lookup("lever", p)]){
                                    circle(key_lookup("relief_r", p));
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
module clamp_cutout_base_2d(p)
[Source]
module clamp_cutout_base_2d(p){
    // 2D cutout to separate the point of the clamp from the base
    relief_r = key_lookup("relief_r", p);

    union(){
        // separate the flange from the block
        hull(){
            translate(female_point(p)){
                circle(relief_r);
                clamp_t = key_lookup("clamp_t", p);
                angle = key_lookup("angle", p);
                translate([-clamp_t/sin(angle), 0]){
                    circle(relief_r);
                }
            }
        }

        // take the hull of just the external part
        intersection(){
            clamp_cutout_2d(p);
            hull(){
                reflect([1, 0]){
                    translate(female_point(p)){
                        circle(key_lookup("relief_r", p));
                        translate([0, 99]){
                            circle(key_lookup("relief_r", p));
                        }
                    }
                }
            }
        }
    }
}
module clamp_back_2d(p, extra_left=0, extra_right=0, extra_top=0)
[Source]
module clamp_back_2d(p, extra_left=0, extra_right=0, extra_top=0){
    // back of the internal part of the clamp
    // extra_l and extra_r add additional length on
    // the left/right respectively.  By default (0),
    // the part matches the size of the external part
    // of the clamp (i.e. it's in line with female_point)
    length = (
        key_lookup("clamp_t", p) -
        key_lookup("fillet_r", p) +
        extra_left +
        extra_right
    );
    clamp_frame(p){
        translate([-length + extra_right, -key_lookup("flex_t", p)/2]){
            square([length, key_lookup("flex_t", p) + extra_top]);
        }
    }
}
module clamping_flange_2d(p)
[Source]
module clamping_flange_2d(p){
    // 2D shape of the part of the flange that moves
    dovetail_convex_fillet(p){
        difference(){
            union(){
                hull(){
                    // Note: this is defined in x, not in the clamp_frame.
                    clamp_t = key_lookup("clamp_t", p);
                    fillet_r = key_lookup("fillet_r", p);
                    angle = key_lookup("angle", p);
                    flange_width = (clamp_t - fillet_r)/sin(angle);
                    // external end
                    flange_r(p, width= flange_width);
                    // internal end
                    clamp_back_2d(p); // NB this gets cut by clamp_cutout_2d
                }

                extra_right=key_lookup("flex_l", p)/2 + key_lookup("fillet_r", p);
                // to avoid fouling the fillet
                extra_left=-key_lookup("fillet_r", p);
                // add the flexure to join to the block.
                clamp_back_2d(p, extra_right=extra_right, extra_left=extra_left);
            }

            clamp_cutout_2d(p);
        }
    }
}
module clamping_flange(p)
[Source]
module clamping_flange(p){
    // The moving part that makes the right hand flange
    // clamp the female dovetail
    gap = key_lookup("vertical_gap", p);
    bottom = key_lookup("bottom_t", p) + gap + key_lookup("clamp_support_t", p);
    top = key_lookup("overall_height", p) - gap - key_lookup("top_t", p);
    translate_z(bottom){
        linear_extrude(top - bottom){
            clamping_flange_2d(p);
        }
    }
}
module clamping_bolt_and_nut(p)
[Source]
module clamping_bolt_and_nut(p){
    // The counterbored screw and nut that clamp the dovetail
    h = key_lookup("overall_height", p);
    // Place the clamping bolt relative to the female point
    clamp_y = key_lookup("lever", p) - key_lookup("pinch_bolt_inset", p);
    fillet_r = key_lookup("fillet_r", p);
    clamp_t = key_lookup("clamp_t", p);
    // Boolean, true of block is tapered to have a face perpendicular to the screw.
    // if not it needs a counterbore.
    tapered = key_lookup("taper_block", p);
    // The nut slot should slope up from the nut in use, so that the nut does not 
    // slip out when the screw is removed, nut_slot_slope set to 'up'. If the 
    // dovetail is inverted after printing for use set nut_slot_slope to 'down' 
    nut_rotation = key_lookup("nut_slot_slope",p) == "down"?
                                        120:
                                        60;
    // We place everything relative to
    clamp_frame(p){
        translate([0, clamp_y, h/2]){
            $fn = 16;
            // Hole for screw (in solid block)
            rotate_y(90){
                cylinder(d=3*1.2, h=99);
                // If not tapered, add counterbore large enough for an M3 washer
                if (!tapered){
                    translate([0,0,fillet_r + 4]){
                        cylinder(d=7.5, h=99);
                    }
                }
            }
            // Nut trap, with angled entry (in the clamp)
            rotate_y(-90){
                cylinder(d=3*1.2, h=clamp_t); //shaft of the screw
                translate_z( fillet_r + 2){
                    // The rotation below means the nut slides in at an angle, rather 
                    // than horizontally.  This is important: it ensures that the nut
                    // is retained by a ring of plastic within one layer, rather than
                    // relying on inter-layer adhesion (which is weaker).
                    // The entry slot should not be made horizontal without testing
                    // carefully for strength.
                    rotate_z(nut_rotation){
                        sequential_hull(){
                            m3_nut_hole(h=3.2, shaft=false);
                            translate_x(99){
                                m3_nut_hole(h=3.2, shaft=false);
                            }
                        }
                    }
                }
            }
        }
    }
}
module clamp_support(p)
[Source]
module clamp_support(p){
    // a bridge to support the internal part of the clamp
    gap = key_lookup("vertical_gap", p);
    bottom = key_lookup("bottom_t", p) + gap;
    support_t = key_lookup("clamp_support_t", p);
    fillet_r = key_lookup("fillet_r", p);

    // bridge the bottom of the flexure right across the gap
    translate_z(bottom){
        linear_extrude(support_t){
            // a bridge to support the internal part of the clamp
            // this sits underneath the back of the clamp
            extra_left=3*key_lookup("fillet_r", p);
            extra_right=key_lookup("flex_l", p)/2 + tiny();
            clamp_back_2d(p, extra_left=extra_left, extra_right=extra_right);
        }
    }

    // bridge the bottom layer of the cut-out next to the flexure
    translate_z(bottom + support_t){
        linear_extrude(support_t){
            // a bridge to support the internal part of the clamp
            // this sits underneath the back of the clamp
            clamp_back_2d(p, extra_left=-fillet_r, extra_right=-fillet_r, extra_top=3*fillet_r);
        }
    }
}
module dovetail_convex_fillet(p)
[Source]
module dovetail_convex_fillet(p){
    // smooth the convex corners
    $fn=12;

    convex_fillet(key_lookup("fillet_r", p)){
        children();
    }
}
module dovetail_concave_fillet(p)
[Source]
module dovetail_concave_fillet(p){
    // smooth the concave corners
    $fn=12;
    concave_fillet(key_lookup("fillet_r", p)){
        children();
    }
}
module dovetail_section_m(p, relief=true)
[Source]
module dovetail_section_m(p, relief=true){
    dovetail_convex_fillet(p){
        dovetail_section_m_sharp(p, relief=relief);
    }
}
module undercut_male_dovetail(p)
[Source]
module undercut_male_dovetail(p){
    // Chamfer the bottom of the mating faces to avoid
    // wonkiness due to "elephant's foot" issues
    minkowski(){
        mirror([0,1,0]){
            linear_extrude(tiny()){
                dovetail_section_f_sharp(p);
            }
        }

        cylinder(r1=2, r2=tiny(), h=2, $fn=16, center=true);
    }
}
module dovetail_clamp_m(p)
[Source]
module dovetail_clamp_m(p){
    // male dovetail with clamping arm
    //
    // NOTE: The clamp is designed with internal bridging that 
    // only works when printed in the orientation
    // given here.
    // There is an undercut on the mating surfaces to make sure that 
    // overextrusion or remnants of brim do not foul the mating surface
    // If the clamp is used the other way up after printing, then 
    // in set nut_slot_slope to 'down' the parameter dictionary p. 
    h = key_lookup("overall_height", p);
    difference(){
        union(){
            difference(){
                linear_extrude(h){
                    dovetail_convex_fillet(p){
                        difference(){
                            dovetail_section_m_sharp(p);
                            clamp_cutout_base_2d(p);
                        }
                    }
                }

                // void for clamp
                translate_z(2){
                    linear_extrude(h-4){
                        dovetail_concave_fillet(p){
                            clamp_cutout_empty_2d(p);
                        }
                    }
                }
            }

            // clamping flange
            clamping_flange(p);
            clamp_support(p);
        }
        clamping_bolt_and_nut(p);

        // work around "elephant's foot"/brim on mating faces
        undercut_male_dovetail(p);
    }
}
module dovetail_f(p, height=undef)
[Source]
module dovetail_f(p, height=undef){
    // A female dovetail, existing in y<0 with mating face at y=0
    h = is_undef(height) ? key_lookup("overall_height", p) : height;
    linear_extrude(h){
        dovetail_convex_fillet(p){
            dovetail_section_f_sharp(p);
        }
    }
}
module dovetail_f_cutout(p, height=undef)
[Source]
module dovetail_f_cutout(p, height=undef){
    // Cut this shape out of a block with a face at y=0 to make
    // a dovetail
    h = is_undef(height) ? key_lookup("overall_height", p) : height;
    w = key_lookup("overall_width", p);
    linear_extrude(h){
        dovetail_concave_fillet(p){
            union(){
                dovetail_section_f_sharp_cutout(p);
                translate([-w/2, tiny()]){
                    square([w, 99]);
                }
            }
        }
    }
}
module dovetail_block(p, height=undef)
[Source]
module dovetail_block(p, height=undef){
    // A 3D block, filleted as the dovetail would be
    h = is_undef(height) ? key_lookup("overall_height", p) : height;
    linear_extrude(h){
        dovetail_convex_fillet(p){
            block_sharp(p);
        }
    }
}