OpenFlexure Microscope OpenSCAD docs

libs/main_body_structure.scad

function base_mounting_holes(params, type="all")
[Source]
function base_mounting_holes(params, type="all") = let
(
    back_lug_hole_x = back_lug_x_pos(params) + microscope_wall_t()/2 - lug_back_offset().x,
    back_pos = [[back_lug_hole_x,-8,0],
               [-back_lug_hole_x,-8,0]],
    actuator_offset = [-1, -1, 0] * actuator_housing_xy_size().x/2/sqrt(2),
    y_front_lug_pos = y_actuator_pos(params) + actuator_offset + [-6.5, .5, 0],
    front_pos =[y_front_lug_pos,
               [-y_front_lug_pos.x, y_front_lug_pos.y, 0]],
    back = (type == "back") || (type == "all"),
    front = (type == "front") || (type == "all"),
    //Set which holse to output
    holes = [back?back_pos:[], front?front_pos:[]]
    //Final list comprehension make a single list of holes
) [for (h = holes) each h];
module leg_flexures(params, brace)
[Source]
module leg_flexures(params, brace){
    // These are the flexures on the top and bottom of the leg that connect the
    // two vertical bars together.
    //
    // `brace` sets the distance to widen the block at the bottom of the leg
    //  two flexures are prodcued offset by brace. Therefore
    //  * if brace=0 there is one normal sized flexure.
    //  * if brace=flex_dims().x there is one double width fexure
    //  * if brace>flex_dims().x there are two seperare flexures
    leg_block_t = key_lookup("leg_block_t", params);
    block_size = [leg_middle_w(), leg_dims(params).y, leg_block_t];
    flex_size = [leg_outer_w(params), leg_dims(params).y, flex_dims().z];

    for (i = [0,1]){
        z_pos=[lower_xy_flex_z(), upper_xy_flex_z(params)][i];
        brace_pos= [brace, 0][i];
        translate_z(z_pos){
            //Hull two blocks to make a big one
            hull(){
                repeat([0,brace_pos,0],2){
                    translate_x(-block_size.x/2){
                        cube(block_size);
                    }
                }
            }
            //Repeat two flexures may be separate depending on brace.
            repeat([0,brace_pos,0],2){
                translate_x(-flex_size.x/2){
                    cube(flex_size);
                }
            }
        }
    }

}
module leg(params, brace=flex_dims().x)
[Source]
module leg(params, brace=flex_dims().x){
    // The legs support the stage - this is either used directly
    // or via "actuator" to make the legs with levers
    fw=flex_dims().x;

    union(){
           //leg
        reflect_x(){
            //vertical bars of the leg
            translate_x(leg_middle_w()/2+flex_dims().y){
                hull(){
                    cube(leg_dims(params));
                    //extend the base to make the bars triangular
                    cube([leg_dims(params).x, fw+brace ,tiny()]);
                }
            }
        }
        leg_flexures(params, brace);

        //thin links between legs
        flex_sep = upper_xy_flex_z(params)-lower_xy_flex_z();
        n = floor(flex_sep/leg_link_spacing());
        if(n > 2){
            // adjust spacing so it is even
            link_space_adj = flex_sep/n;
            translate([0, leg_dims(params).y/2, lower_xy_flex_z()+link_space_adj]){
                repeat([0, 0, link_space_adj], n-1){
                    cube([leg_outer_w(params), 2, 0.5],center=true);
                }
            }
        }
    }
}
module actuator_leg(params)
[Source]
module actuator_leg(params){
    // The wide leg that supports the stage on the actuator side,
    // plus the horizontal lever that meets the flexure at the bottom
    // of the actuator column. This does not include the flexure itself.

    brace=20;
    fw=flex_dims().x;
    w = actuator_dims(params).x;
    union(){
        leg(params, brace=brace);

        //arm (horizontal bit)
        difference(){
            sequential_hull(){
                translate_x(-leg_middle_w()/2){
                    cube([leg_middle_w(),brace+fw,4]);
                }
                translate_x(-w/2){
                    cube([w,brace+fw+0,actuator_dims(params).z]);
                }
                translate_x(-w/2){
                    cube(actuator_dims(params));
                }
            }
            //don't foul the actuator column
            translate_y(actuating_nut_r(params)){
                actuator_end_cutout();
            }
        }

    }
}
module actuator_silhouette(params, h=999)
[Source]
module actuator_silhouette(params, h=999){
    // This defines the cut-out from the base structure for the XY
    // actuators.
    linear_extrude(2*h,center=true){
        minkowski(){
            circle(r=flex_dims().y,$fn=12);
            projection(){
                actuator_leg(params);
            }
        }
    }
}
module mounting_hole_lugs(params, holes=true)
[Source]
module mounting_hole_lugs(params, holes=true){
    // lugs either side of the XY table to bolt the microscope down
    // these are to mount onto the baseplate

    //Just get one lug hole and then reflect the lug.
    hole_pos = base_mounting_holes(params);
    for (n = [0:len(hole_pos)-1]){
        hole = hole_pos[n];
        angle = lug_angles(params)[n];
        m3_lug(hole, angle, holes=holes);
    }
}
module m3_lug(pos, angle, holes=true)
[Source]
module m3_lug(pos, angle, holes=true){
    // position in the hole poistion. Rotate about hole

    translate(pos){
        rotate(angle){
            difference(){
                //the lug
                hull(){
                    translate(lug_back_offset()){
                        cube([10,tiny(),10]);
                    }
                    cylinder(d=8.8,h=3);
                }
                //the lug hole
                if (holes) {
                    translate_z(3){
                        m3_cap_counterbore(10, 10);
                    }
                }
            }
        }
    }
}
module reflection_illuminator_cutout(extra_depth=0)
[Source]
module reflection_illuminator_cutout(extra_depth=0){
    top_cutout_w = 17.8;
    mid_cutout_w = illuminator_width() + 1;
    bottom_cutout_w = illuminator_width() + 4;
    cutout_below_zero = 22; // size of cut-out below the z = 0 datum

    // Create a trapezoidal shape with width=top_cutout_w at the top.
    // This is the widest cutout we can make at height 'reflection_cutout_height()'
    // without the bridge having a corner in it.
    // Use to extrude and then rotate about the x-axis
    reflecton_illuminator_cutout_points = [[-(bottom_cutout_w)/2, -cutout_below_zero-extra_depth],
                                        [-(bottom_cutout_w)/2, 0.5],
                                        [-(mid_cutout_w)/2, 11],
                                        [-top_cutout_w/2, reflection_cutout_height()],
                                        [top_cutout_w/2, reflection_cutout_height()],
                                        [(mid_cutout_w)/2, 11],
                                        [(bottom_cutout_w)/2, 0.5],
                                        [(bottom_cutout_w)/2, -cutout_below_zero-extra_depth]
                                        ];

    // gap between illuminator cut-out and break-out block
    gap = 1.5;

    difference(){
        rotate_x(90){
            linear_extrude(height=999, center=false, twist=0){
                polygon(reflecton_illuminator_cutout_points);
            }
        }
        union(){
            // Distance to wall is set as a fixed number here:
            // knock_out_displace_y makes the break-out part thinner than the main wall
            // mainly to make the webs easier to cut. 
            knock_out_displace_y = 26 ;
            knock_out_web_thickness = 0.6;
            translate_y(-knock_out_displace_y){
                rotate_x(90){
                    linear_extrude(height=50, center=false, twist=0){
                        offset(-gap){
                            polygon(reflecton_illuminator_cutout_points);
                        }
                    }
                }
            }
            // Lower supporting web in the microscope body. The base overlaps the body by 3mm 
            // which is set in lib_microscope_stand.scad as microscope_depth()
            // so this is set to be at exactly the top of the base. 
            translate([-40/2, (-50 - knock_out_displace_y), microscope_depth() - knock_out_web_thickness]){
                cube([40, 50, knock_out_web_thickness]);
            }
            // Supporting web towards the top of the cutout in the microscope body.
            translate([-40/2, (-50 - knock_out_displace_y), reflection_cutout_height() - 3]){
                cube([40, 50, knock_out_web_thickness]);
            }
            // Supporting web in the base, just below z-zero datum
            translate([-40/2, (-50 - knock_out_displace_y), -2]){
                cube([40, 50, knock_out_web_thickness]);
            }
            // Supporting web at the bottom of the knock-out block, also serves as bridge during printing
            translate([-40/2, (-50 - knock_out_displace_y), -(cutout_below_zero + extra_depth - gap)]){
                cube([40, 50, knock_out_web_thickness]);
            }
        }
    }
}
module xy_stage(params, h=10, on_buildplate=false)
[Source]
module xy_stage(params, h=10, on_buildplate=false){
    // This module is the outer shape of the XY stage.
    // A square without corners, and a hole through middle.
    // The size in XY is set by microscope_parameters.scad,
    // the thickness (z) is set by input h
    // The boolean value on_buildplate sets wether the stage is printed on the
    // buildplate. If true, the bottom is flat, if false the bottom is made from
    // bridges round the edge, that then work inwards.

    side_length = leg_middle_w()+2*flex_dims().y;
    cut_out_side_length = leg_middle_w()-2*flex_dims().x;
    thickness = on_buildplate?h:h-1;
    z = on_buildplate?0:1;
    hole_r = key_lookup("stage_hole_r", params);

    difference(){
        hull(){
            each_leg(params){
                translate([0,-flex_dims().y-tiny(),z+thickness/2]){
                    cube([side_length,2*tiny(),thickness],center=true);
                }
            }
        }
        // Cuts out the hole in the stage, starting from a square.
        if (on_buildplate){
            //Normal hole if being printed on build plate
            cylinder(r=hole_r,h=999,center=true,$fn=32);
        }
        else{
            // If being printed in the in the air it needs a series of bridges to
            // be printed.
            intersection(){
                // hole_from_bottom() is used to create a cylinder which starts
                // as a square, then and octagon, doubling in number of side until
                // "circular"
                translate_z(1){
                    rotate(45){
                        hole_from_bottom(hole_r,h=999);
                    }
                }
                //The hole is intersected with this cube so the area above the
                //Top x-y flexures is not cut out
                hull(){
                    each_leg(params){
                        cube([cut_out_side_length,tiny(),999],center=true);
                    }
                }
            }
        }
    }
}
module xy_actuators(params, ties_only=false)
[Source]
module xy_actuators(params, ties_only=false){
    // Just the actuators for the xy.
    // If ties_only=true then only the ties to the casing are printed. This is useful for
    // rendering instructions

    ties = key_lookup("print_ties", params);
    actuator_h = key_lookup("actuator_h", params);
    each_actuator(params){
        //actuator is the leg bat to connect to the flexure at the bottom of the column
        if (! ties_only){
            actuator_leg(params);
        }
        translate_y(actuating_nut_r(params)){
            if (! ties_only){
                actuator_column(actuator_h, join_to_casing=ties);
            }
            else{
                actuator_ties();
            }
        }
    }
}
module xy_screw_seat(params, label="")
[Source]
module xy_screw_seat(params, label=""){

    h = key_lookup("actuator_h", params);
    include_motor_lugs = key_lookup("include_motor_lugs", params);
    screw_seat(params,
               h,
               travel=xy_actuator_travel(params),
               include_motor_lugs=include_motor_lugs,
               extra_entry_h=actuator_dims(params).z+2,
               label=label);
}
module xy_legs_and_actuators(params)
[Source]
module xy_legs_and_actuators(params){
    // This is the xy_actuators including the casing and all 4 legs

    // back legs
    reflect_x(){
        leg_frame(params, 135){
            leg(params);
        }
    }
    //front legs and actuator columns
    xy_actuators(params);

    for(i = [0,1]){
        label = ["X","Y"][i];
        angle = [-45,45][i];
        leg_frame(params, angle){
            translate_y(actuating_nut_r(params)){
                xy_screw_seat(params, label);
            }
        }
    }
}
module internal_xy_structure(params)
[Source]
module internal_xy_structure(params){

    difference() {
        add_hull_base(microscope_base_t()){
            wall_inside_xy_stage(params);
        }
        central_optics_cut_out(params);
        // Cut-out for reflection optics
        reflection_illuminator_cutout();
    }
}
module xy_stage_with_nut_traps(params)
[Source]
module xy_stage_with_nut_traps(params)
{
    //This is the microscope xy-stage built at the correct height
    //and including the nut traps.
    stage_t = key_lookup("stage_t", params);
    difference(){
        translate_z(upper_xy_flex_z(params)){
            xy_stage(params, h=stage_t);
        }
        each_leg(params){
            translate([0, -stage_hole_inset(), leg_height(params)]){
                extra_bore = 3;
                m3_nut_trap_with_shaft(slot_angle=0,tilt=0,deep_shaft=extra_bore); //mounting holes
            }
        }
    }
}
module xy_flexures(params)
[Source]
module xy_flexures(params){

    //Bottom flexures: flexures between legs and inner walls
    w=flex_dims().x;
    //The flexure length, increased for some overlap
    flex_len = flex_dims().y + microscope_wall_t()/2;
    each_leg(params){
        reflect_x(){
            translate([leg_middle_w()/2-w, 0, lower_xy_flex_z()+0.5]){
                //Each flexure is the hull of two offset cuboids.
                hull(){
                    repeat([flex_len,-flex_len,0],2){
                        cube([w, tiny(), flex_dims().z]);
                    }
                }
            }
        }
    }

    // Top flexures: flexures between legs and stage
    // NOTE: these connect the legs together, and pass all the way under the stage.
    // This is important! If they get cut then the bridges will fail!
    difference(){
        //Make a truncated square with a truncated "corner" at each leg
        hull(){
            each_leg(params){
                translate_z(upper_xy_flex_z(params)+flex_dims().z/2+0.5){
                    cube([leg_middle_w(),tiny(),flex_dims().z],center=true);
                }
            }
        }
        //chop out a smaller truncated square
        hull(){
            each_leg(params){
                cube([leg_middle_w()-2*flex_dims().x,tiny(),999],center=true);
            }
        }
    }
}
module xy_leg_ties(params)
[Source]
module xy_leg_ties(params){
    // Small ties that connect the legs to the walls of the structure to stop the
    // legs moving during printing. These muse be cut after printing.

    z_tr = actuator_wall_h()*0.7;
    // Note that the walls slope in by 6 degrees so must compensate tie length
    tie_length = flex_dims().y + z_tr*tan(6) + 2;
    x_tr = leg_middle_w()/2+flex_dims().y+flex_dims().x/2;
    y_tr = 1-tie_length;

    reflect_x(){
        leg_frame(params, 135){
            reflect_x(){
                translate([x_tr, y_tr, z_tr]){
                    cube([1, tie_length, 0.5]);
                }
            }
        }
    }
}
module xy_positioning_system(params)
[Source]
module xy_positioning_system(params){
    // This module creates the main XY positioning mechanism. Including the actuator columns.
    ties = key_lookup("print_ties", params);
    xy_legs_and_actuators(params);
    internal_xy_structure(params);
    xy_stage_with_nut_traps(params);

    // Connect the legs to the stage and structure with flexures
    xy_flexures(params);


    //tie the legs to the wall to stop movement during printing
    if (ties){
        xy_leg_ties(params);
    }
}
module inner_wall_hash(params, hash)
[Source]
module inner_wall_hash(params, hash){
    // Using parameters that define the inner wall 
    // from inner_wall_vertex() and wall_inside_xy_stage()
    y_disp = 0.5-microscope_wall_t()-flex_dims().y;
    z_disp = inner_wall_h(params)/2;
    leg_frame(params, 45){
        rotate_x(6){
            translate([0,y_disp,z_disp]){
                rotate_x(90){
                    linear_extrude(20){
                        text(hash, size=3.8, halign="center", valign="center");
                    }
                }
            }
        }
    }
}
module central_optics_cut_out(params, h=10, center=true)
[Source]
module central_optics_cut_out(params, h=10, center=true) {
    // Central cut-out for optics of main body
    linear_extrude(h, center=center){
        central_optics_cut_out_projection(params);
    }
}
module xy_actuator_cut_outs(params)
[Source]
module xy_actuator_cut_outs(params){
    each_actuator(params){
        actuator_silhouette(params, xy_actuator_travel(params)+actuator_dims(params).z);
        translate_y(actuating_nut_r(params)){
            screw_seat_outline(h=999,adjustment=-tiny(),center=true);
        }
    }
}
module actuator_walls_and_z_casing(params, z_axis=true)
[Source]
module actuator_walls_and_z_casing(params, z_axis=true){
    // These are the wall that link the actuators. And the casing for the
    // z-axis. This casing includes the mount for the illumination dovetail.
    xy_cable_tidies = key_lookup("include_motor_lugs",params);
    difference(){
        union(){
            add_hull_base(microscope_base_t()) {
                //link the XY actuators to the wall
                if (z_axis){
                    reflect_x(){
                        wall_inside_xy_actuators(params);
                    }
                }
                reflect_x(){
                    wall_outside_xy_actuators(params);
                }
                reflect_x(){
                    wall_between_actuators(params);
                }
                // outer profile of casing and anchor for the z axis
                if (z_axis){
                    cable_housing = xy_cable_tidies; // for the main body there are no z-cable tidies if there are no xy cable tidies
                    z_axis_casing(params, condenser_mount=true, cable_housing=cable_housing);
                }
            }
            reflect_x(){
                if (xy_cable_tidies){
                    side_housing(params, screw_hole_type="pilot");
                }
            }
            //lugs to bolt the microscope down to base
            mounting_hole_lugs(params);
        }
        //This also cuts the walls hence why it is two objects
        if (z_axis){
            z_axis_casing_cutouts(params);
            if (xy_cable_tidies){
                z_cable_housing_cutout(params);
            }
            else {
                z_housing_frame(params, y_actuator=true){
                    translate([1,-5,-2]){
                        cube([7,5,7], center=true);
                    }
                }
            }
        }
        xy_actuator_cut_outs(params);
        central_optics_cut_out(params);
    }
}
module body_logos(params, message)
[Source]
module body_logos(params, message){
    // The openflexure and opehardware logos. Plus a customisable message.
    xy_cable_tidies = key_lookup("include_motor_lugs",params);
    size = xy_cable_tidies? 0.25 : 0.24;
    ofm_logo_position = xy_cable_tidies?
                        [9,actuator_wall_h()-2-15*size,-0.5] :
                        [9.5,actuator_wall_h()-0-15*size,-0.5] ;
    oshwa_logo_position = xy_cable_tidies?
                        [-24.5, actuator_wall_h()-15*size, -0.5] :
                        [-27, actuator_wall_h()-3-15*size, -0.5] ;

    place_on_wall(params, is_y=false, housing=xy_cable_tidies){
        translate(ofm_logo_position){
            scale([size,size,10]){
                if (xy_cable_tidies){
                    openflexure_logo_above();
                }
                else {
                    openflexure_logo();
                }
            }
        }
    }

    place_on_wall(params, housing=xy_cable_tidies){
        translate(oshwa_logo_position){
            mirror([1,0,0]){
                scale([size,size,10]){
                    if (xy_cable_tidies){
                        oshw_logo_and_text(message);
                    }
                    else {
                        oshw_logo_and_text_beside(message);
                    }
                }
            }
        }
    }
}
module xy_only_body(params)
[Source]
module xy_only_body(params){
    // This is a version of the body with only xy actuators. It is not used in the microscope
    // but can be useful for other positioning systems.
    xy_positioning_system(params);
    difference(){
        actuator_walls_and_z_casing(params, z_axis=false);
        body_logos(params, "xy-only");
    }
}
module main_body(params, version_string, hash)
[Source]
module main_body(params, version_string, hash){
    // This module represents the main body of the microscope, including the positioning mechanism.

    difference(){
        xy_positioning_system(params);
        z_axis_casing_cutouts(params);
        inner_wall_hash(params, hash);
    }

    //z axis - Only the actuator column is housed at this point
    //No lug brackets, they are in the z-casing already. Adding brackets
    //over the top would make them protrude into the access hole for the optics
    //fitting screw.
    complete_z_actuator(params, lug_brackets=false);

    difference(){
        actuator_walls_and_z_casing(params, z_axis=true);
        body_logos(params, version_string);
    }
}