OpenFlexure Microscope OpenSCAD docs

libs/lib_optics.scad

function optics_wedge_bottom()
[Source]
function optics_wedge_bottom() = -2;
function lens_aperture(lens_r)
[Source]
function lens_aperture(lens_r) = lens_r - 1.5;
function is_c270_spacer(optics_config)
[Source]
function is_c270_spacer(optics_config) = (key_lookup("optics_type", optics_config) == "spacer")
                                            && (key_lookup("camera_type", optics_config) == "logitech_c270");
function is_b0196_spacer(optics_config)
[Source]
function is_b0196_spacer(optics_config) = (key_lookup("optics_type", optics_config) == "spacer")
                                            && (key_lookup("camera_type", optics_config) == "arducam_b0196") ;
function camera_platform_extra_lift()
[Source]
function camera_platform_extra_lift() = 1;
module optical_path(optics_config, lens_z, camera_mount_top_z)
[Source]
module optical_path(optics_config, lens_z, camera_mount_top_z){
    // The cut-out part of a camera mount, consisting of
    // a feathered cylindrical beam path.  Camera mount is now cut out
    // of the camera mount body already.

    rms = key_lookup("optics_type", optics_config) == "RMS";
    lens_r = rms ?
        key_lookup("tube_lens_r", optics_config):
        key_lookup("lens_r", optics_config);
    aperture_r = is_c270_spacer(optics_config)?
        lens_aperture(lens_r)-2:
        lens_aperture(lens_r);

    union(){
        translate_z(camera_mount_top_z-tiny()){
            //beam path
            lighttrap_cylinder(r1=5, r2=aperture_r, h=lens_z-camera_mount_top_z+2*tiny());
        }
        translate_z(lens_z){
            //lens
            cylinder(r=aperture_r,h=99);
        }
    }
}
module lens_gripper(lens_r=10, h=6, lens_h=3.5, base_r=-1, t=0.65, solid=false, flare=0.4)
[Source]
module lens_gripper(lens_r=10,h=6,lens_h=3.5,base_r=-1,t=0.65,solid=false, flare=0.4){
    // This creates a tapering, distorted hollow cylinder suitable for
    // gripping a small cylindrical (or spherical) object
    // The gripping occurs lens_h above the base, and it flares out
    // again both above and below this.
    trylinder_gripper(inner_r=lens_r, h=h, grip_h=lens_h, base_r=base_r, t=t, solid=solid, flare=flare);
}
module camera_mount_top_slice(optics_config)
[Source]
module camera_mount_top_slice(optics_config){
    // A thin slice of the top of the camera mount
    thick_section(h=tiny(), center=false, shift=false){
        camera_mount(optics_config);
    }
}
module optics_module_body_outer(
    params,
    optics_config,
    body_r,
    body_top,
    rms_mount_h,
    wedge_top,
    bottom_r,
    include_wedge
)
[Source]
module optics_module_body_outer(params, optics_config, body_r, body_top, rms_mount_h, wedge_top, bottom_r, include_wedge){
    // The outer shape of the optics module body. Including the camera mount.

    beamsplitter = key_lookup("beamsplitter", optics_config);
    camera_rotation = key_lookup("camera_rotation", optics_config);
    camera_mount_top_z = rms_camera_mount_top_z(params, optics_config);

    // The top of the camera mount
    module top_of_camera_mount_in_place(){
        rotate(camera_rotation){
            translate_z(camera_mount_top_z){
                camera_mount_top_slice(optics_config);
            }
        }
    }

    // The bottom of the cylindrical body of the mount, and the fitting wedge
    module bottom_of_body_and_wedge(){
        translate_z(optics_wedge_bottom()){
            cylinder(r=bottom_r,h=tiny());
        }
        //the bottom of the wedge
        if (include_wedge){
            translate_z(optics_wedge_bottom()){
                objective_fitting_wedge(h=tiny());
            }
        }
    }

    // The top of the cylindrical body, and the fitting wedge
    module top_of_body_and_wedge(){
        translate_z(body_top){
            cylinder(r=body_r, h=tiny());
        }
        if (include_wedge){
            translate_z(wedge_top){
                objective_fitting_wedge(h=tiny());
            }
        }
    }

    // The optics module is built from the bottom upwards - each pair of
    // shapes in the list below is hulled together to form the shape
    union(){
        if(beamsplitter){
            sequential_hull(){
                top_of_camera_mount_in_place();
                union(){
                    bottom_of_body_and_wedge();
                    fl_cube_casing_bottom(params, optics_config);
                }
                union(){
                    top_of_body_and_wedge();
                    extra_optics_body_for_beamsplitter(params, optics_config);
                }
            }
        }
        else {
            sequential_hull(){
                top_of_camera_mount_in_place();
                bottom_of_body_and_wedge();
                top_of_body_and_wedge();
            }
        }
        // The actual camera mount
        rotate(camera_rotation){
            translate_z(camera_mount_top_z){
                camera_mount(optics_config);
            }
        }
        // The housing for the RMS thread and tube lens gripper
        translate_z(body_top){
            cylinder(r=body_r, h=rms_mount_h);
        }
    }
}
module extra_optics_body_for_beamsplitter(params, optics_config)
[Source]
module extra_optics_body_for_beamsplitter(params, optics_config){
    bs_rotation = key_lookup("beamsplitter_rotation", optics_config);
    rotate(bs_rotation){
        hull(){
            //the box to fit the fl cube in
            fl_cube_casing(params, optics_config);
            //the mounts for the fl cube screw holes
            fl_screw_holes(params, optics_config, d = 4, h =8);
        }
    }
}
module fl_cube_casing_bottom(params, optics_config)
[Source]
module fl_cube_casing_bottom(params, optics_config){
    bottom = fl_cube_bottom(params, optics_config);
    translate_z(bottom){
        thick_section(){
            translate_z(-bottom){
                extra_optics_body_for_beamsplitter(params, optics_config);
            }
        }
    }
}
module optics_module_beamsplitter_cutout(params, optics_config)
[Source]
module optics_module_beamsplitter_cutout(params, optics_config){
    bs_rotation = key_lookup("beamsplitter_rotation", optics_config);

    cube_dim = [1, 1, 1] * fl_cube_w();
    cube_centre_z = fl_cube_bottom(params, optics_config)+fl_cube_w()/2;

    rotate(bs_rotation){
        translate_y(-2.5){
            fl_screw_holes(params, optics_config, d = 2.5, h = 6);
        }
        hull(){
            translate([0, -fl_cube_w(), cube_centre_z+3.5]){
                cube(cube_dim + [15, 0, 7], center=true);
            }
            translate([0, -fl_cube_w()-6, cube_centre_z+9]){
                cube(cube_dim + [20, 0, 6], center=true);
            }
        }
    }
}
module optics_module_body(
    params,
    optics_config,
    body_r,
    body_top,
    rms_mount_h,
    wedge_top,
    bottom_r=8,
    include_wedge=true
)
[Source]
module optics_module_body(
    params,  //microscope parameter dictionary
    optics_config, //dictionary of optics configuration
    body_r, //radius of mount body
    body_top, //z_poistion of the top of the body
    rms_mount_h, // height of the rms mount
    wedge_top, //z position of the top of the fitting_wedge
    bottom_r=8, //radius of the bottom of the mount
    include_wedge=true //set this to false to remove the attachment point
){
    // Make the main body of the optics module: A camera mount, a cylindrical body and a wedge for mounting.
    // Just add a lens mount on top for a complete optics module!

    beamsplitter = key_lookup("beamsplitter", optics_config);

    //The tube + the camera mount
    difference(){
        optics_module_body_outer(params, optics_config, body_r, body_top, rms_mount_h, wedge_top, bottom_r, include_wedge);
        // Mount for the nut that holds it on
        if (include_wedge){
            translate_z(-1){
                objective_fitting_cutout(params);
            }
        }
        // screw holes  and faceplate for fl module
        if(beamsplitter){
            optics_module_beamsplitter_cutout(params, optics_config);
        }
    }

}
module rms_thread_and_cutout_for_tube_lens(mount_h)
[Source]
module rms_thread_and_cutout_for_tube_lens(mount_h){
    // cut the RMS thread for the objective
    translate_z(mount_h - 5.5){
        rms_thread_cutter(h=6, $fn=32, peak_points=2);
    }
    // add a smaller cylinder to provide space for the lens gripper
    // for the tube lens
    cylinder(r=rms_thread_nominal_d()/2-1.2, h=mount_h-1, $fn=60);
}
module tube_lens_gripper(optics_config, pedestal_h)
[Source]
module tube_lens_gripper(optics_config, pedestal_h){
    gripper_t = key_lookup("gripper_t", optics_config);
    tube_lens_r = key_lookup("tube_lens_r", optics_config);
    aperture_r = lens_aperture(tube_lens_r);

    //NB the RMS thread is now part of rms_thread_and_cutout_for_tube_lens

    translate_z(-tiny()){ // ensure these parts join properly to the floor at z=0
        // gripper for the tube lens
        lens_gripper(lens_r=tube_lens_r, lens_h=pedestal_h+1, h=pedestal_h+1+2.5+tiny(), t=gripper_t);
        // pedestal to raise the tube lens up within the gripper
        // NB this becomes a tube rather than a cylinder, but the inner part 
        // is cut out by `optical_path` or `optical_path_fl`
        cylinder(r=aperture_r+.8, h=pedestal_h+tiny());
    }
}

* * This is the mount for the tube lens. The objective threads into * the threaded hole, defined in rms_thread_and_cutout_for_tube_lens

module optics_module_rms(params, optics_config, include_wedge=true)
[Source]
module optics_module_rms(params, optics_config, include_wedge=true){
    assert(key_lookup("optics_type", optics_config)=="RMS",
    "Cannot create an RMS optics module for a non-RMS configuration.");

    beamsplitter = key_lookup("beamsplitter", optics_config);

    // height of pedestal for tube lens to sit on (to allow for flex)
    pedestal_h = 2;
    //height of the top of the wedge
    wedge_top = 27;

    // The optics (i.e. tube lens and objective) are mounted in a cylinder at
    // the top, with an RMS thread at the top and a gripper for the tube lens
    // inside.
    rms_optics_mount_z = tube_lens_face_z(params, optics_config) - pedestal_h;
    rms_optics_mount_base_r = rms_thread_nominal_d()/2+1;
    rms_optics_mount_h = objective_shoulder_z(params, optics_config)-rms_optics_mount_z;

    camera_mount_top_z = rms_camera_mount_top_z(params, optics_config);
    difference(){
        union(){
            // The bottom part is just a camera mount with a flat top
            difference(){
                // camera mount with a body that's shorter than the fitting wedge
                optics_module_body(params,
                                   optics_config,
                                   body_r=rms_optics_mount_base_r,
                                   bottom_r=10.5,
                                   body_top=rms_optics_mount_z,
                                   rms_mount_h=rms_optics_mount_h,
                                   wedge_top=wedge_top,
                                   include_wedge=include_wedge);
                // cut a hole for the rms thread and tube lens gripper
                translate_z(rms_optics_mount_z){
                    rms_thread_and_cutout_for_tube_lens(rms_optics_mount_h);
                }
            }
            translate_z(rms_optics_mount_z){
                tube_lens_gripper(
                    optics_config,
                    pedestal_h=pedestal_h
                );
            }
        }
        // camera cut-out and hole for the beam
        if(beamsplitter){
            optical_path_fl(params, optics_config, rms_optics_mount_z, camera_mount_top_z);
        }
        else{
            optical_path(optics_config, rms_optics_mount_z, camera_mount_top_z);
        }
    }
}

* * This optics module takes an RMS objective and a tube length correction lens

module lens_spacer_gripper(lens_r, lens_h, pedestal_h, lens_assembly_base_r, lens_assembly_z)
[Source]
module lens_spacer_gripper(lens_r, lens_h, pedestal_h, lens_assembly_base_r, lens_assembly_z){

    lens_assembly_h = lens_h + pedestal_h; //height of the lens assembly

    // A lens gripper to hold the objective
    translate_z(lens_assembly_z){
        // gripper
        trylinder_gripper(inner_r=lens_r,
                          grip_h=lens_assembly_h-1.5,
                          h=lens_assembly_h,
                          base_r=lens_assembly_base_r,
                          flare=0.4,
                          squeeze=lens_r*0.15);
        // pedestal to raise the tube lens up within the gripper
        aperture_r = lens_aperture(lens_r);
        tube(ri=aperture_r, ro=aperture_r+1, h=pedestal_h);
    }
}
function lens_spacer_z(params, optics_config)
[Source]
function lens_spacer_z(params, optics_config) = let(
    sample_z = key_lookup("sample_z", params),
    parfocal_distance = key_lookup("parfocal_distance", optics_config),
    lens_spacing = key_lookup("lens_spacing", optics_config)
) sample_z - (parfocal_distance + camera_sensor_height(optics_config) + lens_spacing);

* * Calculate the z_position of the lens spacer. * z position of lens is parfocal_distance below the sample * To reach the bottom of the spacer also subtract camera_sensor_height * and the desired lens spacing

module lens_spacer(params, optics_config)
[Source]
module lens_spacer(params, optics_config){
    // Mount a lens some distance from the camera

    assert(key_lookup("optics_type", optics_config)=="spacer", "Use spacer optics configuration to create a lens spacer.");

    // unpack lens spacer parameters
    lens_r = key_lookup("lens_r", optics_config);
    lens_h = key_lookup("lens_h", optics_config);
    lens_spacing = key_lookup("lens_spacing", optics_config);

    // z_position of the lens for this piece.
    // This is the height of the camera_sensor above the circuit board plus the spacing between the lens and the sensor
    lens_z = camera_sensor_height(optics_config)+lens_spacing;

    pedestal_h = 4; // extra height on the gripper, to allow it to flex
    lens_assembly_z = lens_z - pedestal_h; //z position of the bottom of the lens assembly

    lens_assembly_base_r = lens_r+1; //outer size of the lens grippers

    // This is the height of the block the camera mounts into.
    camera_mount_height = camera_mount_height(optics_config);
    lens_spacer_rotate = is_c270_spacer(optics_config)? -135: 0;

    rotate_z(lens_spacer_rotate){
        translate_z(lens_spacer_z(params, optics_config)){
            difference(){
                union(){
                    // This is the main body of the mount
                    sequential_hull(){
                        translate_z(camera_mount_height){
                            camera_mount_top_slice(optics_config);
                        }
                        translate_z(camera_mount_height+5){
                            cylinder(r=6,h=tiny());
                        }
                        translate_z(lens_assembly_z){
                            cylinder(r=lens_assembly_base_r, h=tiny());
                        }
                    }

                    lens_spacer_gripper(lens_r, lens_h, pedestal_h, lens_assembly_base_r, lens_assembly_z);

                    // add the camera mount
                    translate_z(camera_mount_height){
                        camera_mount(optics_config, screwhole=false, counterbore=false);
                    }
                }
                union(){
                    // cut out the optical path
                    z_offset_lens_spacer_optical_path = is_c270_spacer(optics_config) ?
                                                                    2.8: // to match the light trap to the mount aperture, C270
                                                                    0; // to match the light trap to the mount aperture, Picam 2 and B0196
                    optical_path(optics_config, lens_assembly_z, camera_mount_top_z=z_offset_lens_spacer_optical_path);
                    // cut out counterbores
                    translate_z(camera_mount_height){
                        camera_mount_counterbore(optics_config);
                    }
                }
            }
        }
    }
}
function camera_mounting_post_height(optics_config)
[Source]
function camera_mounting_post_height(optics_config) = key_lookup("mounting_post_height", optics_config);
function camera_board_thickness(optics_config)
[Source]
function camera_board_thickness(optics_config) = key_lookup("board_thickness", optics_config);
module camera_platform(params, optics_config, base_r, camera_rotation=0)
[Source]
module camera_platform(params, optics_config, base_r, camera_rotation=0){

    assert(key_lookup("optics_type", optics_config)=="spacer", "Use spacer optics configuration to create a camera_platform.");

    // platform height is 5mm below the lens spacer (board is 1mm thick mounting posts are 4mm tall)
    platform_h = lens_spacer_z(params, optics_config) - camera_mounting_post_height(optics_config) - camera_board_thickness(optics_config);
    assert(platform_h > upper_z_flex_z(params), "Platform height too low for z-axis mounting");

    camera_extra_rotation = is_c270_spacer(optics_config)? -135: 0;
    camera_mounting_posts_rotate  = camera_rotation + camera_extra_rotation;

    // Make a camera platform with a fitting wedge on the side and a platform on the top
    difference(){
        union(){
            // This is the main body of the mount
            sequential_hull(){
                hull(){
                    cylinder(r=base_r,h=tiny());
                    objective_fitting_wedge(h=tiny());
                }
                translate_z(platform_h){
                    hull(){
                        cylinder(r=base_r,h=tiny());
                        objective_fitting_wedge(h=tiny());
                        rotate_z(camera_mounting_posts_rotate){
                            camera_bottom_mounting_posts(optics_config, bottom_slice=true);
                        }
                    }
                }
            }

            // add the camera mount posts
            translate_z(platform_h){
                rotate_z(camera_mounting_posts_rotate){
                    camera_bottom_mounting_posts(optics_config, cutouts=false);
                }
            }
        }

        // Mount for the nut that holds it on 
        // The hole translated in -z from the nominal position lifts the platform up.
        translate_z(-camera_platform_extra_lift()){
            objective_fitting_cutout(params, y_stop=true, face_stops=true);
        }

        // Undercut on build plate
        undercut_objective_fitting_wedge(undercut_height = 1.5);
        // add the camera mount holes
        translate_z(platform_h){
            rotate_z(camera_mounting_posts_rotate){
                camera_bottom_mounting_posts(optics_config, outers=false, cutouts=true);
            }
        }
        // mark the optic axis
        translate_z(platform_h){
            cylinder(r=1, h=2, center = true);
        }
        // cut-out for Arducam b0196 cable
        if(is_b0196_spacer(optics_config)){
            rotate_z(45 + camera_rotation){
                translate([9,-11.5,10]){
                    cube([7,12,99]);
                }
            }
        }
        // cut ledge for the backshell, with a little clearance
        if(is_c270_spacer(optics_config)){
            rotate_z(camera_rotation){
                translate_z(lens_spacer_z(params, optics_config)-0.1){
                    rotate([180,0,0]){
                        c270_backshell();
                    }
                }
            }
        }
    }
}

* * camera_platform(params, base_r, h) * * * params - the microscope parameter dictionary * * optics_config - optics configuration dictionary * * base_r - radius of mount body

module undercut_objective_fitting_wedge(wedge_width_plus=20, undercut_height=1.5)
[Source]
module undercut_objective_fitting_wedge(wedge_width_plus=20, undercut_height = 1.5){
    difference(){
        translate([0,13,-(10/2 - undercut_height + tiny())]){
            rotate_x(-45){
                cube([wedge_width_plus,10,20], center = true);
            }
        }
        sequential_hull(){
            translate_z(-10 + undercut_height){
                hull(){
                    translate_x(-wedge_width_plus/2){
                        cube([wedge_width_plus,tiny(),tiny()]);
                    }
                    translate_y(-10){
                        objective_fitting_wedge(h=tiny());
                    }
                }
            }
            translate_z(10 + undercut_height){
                hull(){
                    translate_x(-wedge_width_plus/2){
                        cube([wedge_width_plus,tiny(),tiny()]);
                    }
                    translate_y(10){
                        objective_fitting_wedge(h=tiny());
                    }
                }
            }
        }
    }
}
module configurable_optics_module(
    optics,
    camera_type,
    beamsplitter,
    parfocal_distance,
    camera_rotation,
    beamsplitter_rotation
)
[Source]
module configurable_optics_module(optics, camera_type, beamsplitter, parfocal_distance, camera_rotation, beamsplitter_rotation){
    params = default_params();
    // 45mm is the default parfocal distance.
    // If this setting is changed, it would normally be to 35mm.
    // The code below will print a warning if it is changed.
    if (parfocal_distance!=45){
        if (parfocal_distance==35){
            echo("Generating an optics module for older, 35mm parfocal, objectives.  Please check carefully, this option is not tested.");
        }
        else {
            echo("WARNING: parfocal_distance is neither 35mm nor 45mm, this may be an error.");
        }
    }

    // Note calling the optics module rms inside each if statment
    // to avoid nested ternaries
    if (optics=="rms_f50d13"){
        optics_config = rms_f50d13_config(
            camera_type=camera_type,
            beamsplitter=beamsplitter,
            parfocal_distance=parfocal_distance,
            camera_rotation=camera_rotation,
            beamsplitter_rotation=beamsplitter_rotation
        );
        optics_module_rms(params, optics_config);
    }
    else if(optics=="rms_infinity_f50d13"){
        optics_config = rms_infinity_f50d13_config(
            camera_type=camera_type,
            beamsplitter=beamsplitter,
            parfocal_distance=parfocal_distance,
            camera_rotation=camera_rotation,
            beamsplitter_rotation=beamsplitter_rotation
        );
        optics_module_rms(params, optics_config);
    }
    else{
        assert(false, "Unknown optics configuration specified");
    }

}