diff --git a/libtest.png b/libtest.png index 4f082fd..d3ae1fe 100644 Binary files a/libtest.png and b/libtest.png differ diff --git a/libtest.scad b/libtest.scad index 060d1ac..3893322 100644 --- a/libtest.scad +++ b/libtest.scad @@ -47,6 +47,7 @@ use use use use +use use use use @@ -364,6 +365,9 @@ translate([x3 + 170, veroboard_y + 16]) translate([x3, d_connectors_y]) d_connectors(); +translate([x3 + 170, d_connectors_y - 10]) + camera_housings(); + translate([x3, iecs_y]) iecs(); diff --git a/printed/camera_housing.scad b/printed/camera_housing.scad new file mode 100644 index 0000000..366b8c2 --- /dev/null +++ b/printed/camera_housing.scad @@ -0,0 +1,392 @@ +// +// NopSCADlib Copyright Chris Palmer 2020 +// nop.head@gmail.com +// hydraraptor.blogspot.com +// +// This file is part of NopSCADlib. +// +// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the +// GNU General Public License as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along with NopSCADlib. +// If not, see . +// + +// +//! Housings for PCB cameras. +// +include <../core.scad> +include <../vitamins/cameras.scad> +use <../vitamins/pcb.scad> +use <../vitamins/insert.scad> + +wall = 1.75; +min_wall = 2 * extrusion_width; +clearance = 0.2; + +connector_size = [23, 6, 2.65]; // Worst case size of flat flex connector + +cam_back_clearance = round_to_layer(1.5); // Clearance for components on the back of the pcb +cam_back_overlap = 1; // How much the back overlaps the edge of the pcb +cam_back_wall = min_wall; + +function cam_front_clearance(cam) = round_to_layer(camera_connector_size(cam).z + clearance); + +function cam_back_size(cam) = let( + pcb = camera_pcb(cam), + pcb_size = pcb_size(pcb), + nut = screw_nut(pcb_screw(pcb)), + holes = [for(h = pcb_holes(pcb)) pcb_coord(pcb, h).x], + pitch = max(holes) - min(holes), + length = pitch + 2 * (nut_radius(nut) + min_wall), + width = pcb_size.y + (length - pcb_size.x) * cos(30) + ) [length, width, wall + max(connector_size.z, cam_back_clearance + nut_trap_depth(nut))]; + + +function cam_front_size(cam) = cam_back_size(cam) + [ //! Outside dimensions of the case + 2 * (wall + clearance), + 2 * (wall + clearance), + pcb_thickness(camera_pcb(cam)) + cam_front_clearance(cam) + wall +]; + +hinge_screw = M2_cap_screw; +hinge_nut = screw_nut(hinge_screw); +hinge_screw_length = 12; + +hinge_r = nut_trap_radius(hinge_nut) + 3 * extrusion_width; +hinge_h = wall + nut_trap_depth(hinge_nut); +hinge_offset = hinge_r + 1; + +bracket_screw = M3_dome_screw; + +function cam_screw_length(cam) = let( + front = cam_front_size(cam), + screw = pcb_screw(camera_pcb(cam)), + nut = screw_nut(screw) + ) screw_longer_than(front.z + washer_thickness(screw_washer(screw)) - nut_trap_depth(nut) + nut_thickness(nut, true)); + +function hinge_z(cam) = cam_screw_length(cam) - hinge_r; + +module cam_holes(cam) { + pcb = camera_pcb(cam); + lens_y = camera_lens_offset(cam).y; + two_holes = !!len([for (h = pcb_holes(pcb)) if(abs(pcb_coord(pcb, h).y - lens_y) < 1) true]); + pcb_screw_positions(pcb) // screw holes + if($i > 1 || !two_holes) + children(); +} + +module rpi_camera_focus_ring_stl() { //! Focus ring the glue onto RPI lens + stl("rpi_camera_focus_ring"); + + rad = 15 / 2; + hole_r1 = 2.5 / 2; + hole_r2 = 5 / 2; + thickness = 3; + flutes = 8; + angle = 180 / flutes; + x = rad / (sin(angle / 2) + cos(angle / 2)); + r = x * sin(angle / 2); + + difference() { + linear_extrude(height = thickness, convexity = 5) + difference() { + union() { + circle(x); + for(i = [0 : flutes - 1]) + rotate([0, 0, 2 * angle * i]) + translate([x, 0]) + circle(r); + } + for(i = [0 : flutes - 1]) + rotate([0, 0, 2 * angle * i + angle]) + translate([x, 0]) + circle(r); + } + hull() { + poly_cylinder(r = hole_r1, h = 0.1, center = true); + + translate([0, 0, thickness]) + poly_cylinder(r = hole_r2, h = 0.1, center = true); + } + } +} + +module camera_back(cam) { //! Make the STL for a camera case back + stl(str("camera_back_", cam[0])); + pcb = camera_pcb(cam); + back = cam_back_size(cam); + screw = pcb_screw(pcb); + nut = screw_nut(screw); + + translate_z(back.z) + hflip() + difference() { + translate_z(back.z / 2) + cube(back, center = true); + + translate([0, -cam_back_overlap]) + cube([pcb_length(pcb) - 2 * cam_back_overlap, pcb_width(pcb), 2 * cam_back_clearance], center = true); + + translate([0, -pcb_width(pcb) / 2]) + cube([connector_size.x + 2 * clearance, 2 * connector_size.y + 1, 2 * round_to_layer(connector_size.z + clearance)], center = true); + + translate_z(back.z) + cam_holes(cam) + hflip() + nut_trap(screw, nut, supported = true); + } +} + +module camera_front(cam, hinge = 0) { //! Make the STL for a camera case front + stl(str("camera_front_", cam[0])); + front = cam_front_size(cam); + back = cam_back_size(cam); + pcb = camera_pcb(cam); + pcb_size = pcb_size(pcb); + lens_offset = camera_lens_offset(cam); + screw = pcb_screw(pcb); + + shelf = front.z - back.z; + + connector_slot = connector_size + 2 * [clearance, 0, layer_height]; + rad = wall; + led_hole_r = 1; + led_clearance = [5, 2, 1 * 2]; + res_clearance = [3.5, 2, 1 * 2]; + + conn_pos = camera_connector_pos(cam); + conn = camera_connector_size(cam); + sensor_length = conn_pos.y + conn.y / 2 - lens_offset.y + clearance; + + module hinge_pos() + if(!is_undef(hinge)) + rotate(hinge * 90) + translate([0, (hinge ? front.x * hinge : front.y) / 2 + hinge_offset, hinge_r]) + children(); + + difference() { + union() { + hull() + for(x = [-1, 1], y = [-1, 1]) + translate([x * (front.x / 2 - rad), y * (front.y / 2 - rad)]) + hull() { // 3D truncated teardrop gives radiused edges without exceeding 45 degree overhang + translate_z(front.z - 1) + cylinder(r = rad, h = 1); + + translate_z(rad) + sphere(rad); + + cylinder(r = rad * (sqrt(2) - 1), h = eps); + } + + hinge_pos() + hull() { + rotate([-90, 0, -90]) + teardrop(r = hinge_r, h = hinge_h, center = false); + + translate([0, -10, -hinge_r]) + cube([hinge_h, eps, 2 * hinge_r]); + } + } + + hinge_pos() + rotate([90, 0, 90]) + teardrop_plus(r = screw_clearance_radius(hinge_screw), h = 100, center = true); + + translate_z(front.z / 2 + shelf - layer_height) // recess for the back + cube([back.x + 2 * clearance, back.y + 2 * clearance, front.z], center = true); + + translate_z(front.z / 2 + shelf - pcb_size.z) // recess for PCB + cube([pcb_size.x + 2 * clearance, pcb_size.y + 2 * clearance, front.z], center = true); + + translate_z(shelf) + hflip() { + pcb_component_position(pcb, "smd_led") // clearance for LED + cube(led_clearance, center = true); + + pcb_component_position(pcb, "smd_res") // clearance for resistor + cube(res_clearance, center = true); + } + + translate([conn_pos.x, lens_offset.y + sensor_length / 2, shelf - pcb_size.z]) // clearance for sensor connector + cube([conn.x + 2 * clearance, sensor_length, 2 * cam_front_clearance(cam)], center = true); + + translate([0, -front.y / 2, shelf + front.z / 2]) // slot for connector + cube([connector_slot.x, connector_slot.y, front.z], center = true); + + translate_z(cam_back_clearance + layer_height) + cam_holes(cam) + rotate(90) + poly_cylinder(r = screw_clearance_radius(screw), h = 100, center = true); + + translate_z(shelf - pcb_size.z) + hflip() + camera_lens(cam, clearance); + + hflip() + pcb_component_position(pcb, "smd_led") + rotate(45) + poly_cylinder(r = led_hole_r, h = 100, center = true); // hole for led + } +} + +function bracket_thickness(cam) = max(wall, min(3.5, hinge_z(cam) - hinge_r - 1)); + +module camera_bracket_screw_positions(cam) { //! Position children at the bracket screw positions + r = washer_radius(screw_washer(bracket_screw)) + 0.5; + wide = bracket_thickness(cam) == wall; + pitch = wide ? cam_front_size(cam).x / 2 - r : hinge_h + 1 + r; + + for(side = [-1, 1]) + translate([side * pitch, 0]) + children(); +} + +module camera_bracket_position(cam) //! Position children at the bracket position + translate([0, cam_front_size(cam).y / 2 + hinge_offset]) + children(); + +module camera_bracket(cam) { //! Make the STL for the camera bracket + stl(str("camera_bracket_", cam[0])); + + t = bracket_thickness(cam); + z = hinge_z(cam); + translate([hinge_h / 2, 0]) + difference() { + hull() { + translate_z(eps / 2) + cube([hinge_h, 2 * hinge_r, eps], center = true); + + translate_z(z) + rotate([0, 90, 0]) + cylinder(r = hinge_r, h = hinge_h, center = true); + } + translate([hinge_h / 2, 0, z]) + rotate([90, 0, 90]) + nut_trap(hinge_screw, screw_nut(hinge_screw), horizontal = true); + } + + linear_extrude(t) + difference() { + hull() + camera_bracket_screw_positions(cam) + circle(washer_radius(screw_washer(bracket_screw)) + 0.5); + + camera_bracket_screw_positions(cam) + poly_circle(screw_clearance_radius(bracket_screw)); + } +} + +module camera_assembly(cam, angle = 0) //! Camera case assembly +assembly(str("camera_", cam[0])) { + front = cam_front_size(cam); + screw = pcb_screw(camera_pcb(cam)); + nut = screw_nut(screw); + screw_length = cam_screw_length(cam); + hinge_z = hinge_z(cam); + hinge_pos = [0, front.y / 2 + hinge_offset, -hinge_r]; + + camera_bracket_position(cam) { + nut = screw_nut(hinge_screw); + + stl_colour(pp1_colour) render() + camera_bracket(cam); + + translate([-hinge_h, 0, hinge_z(cam)]) + rotate([-90, 0, 90]) { + vflip() + translate_z(2 * hinge_h - nut_trap_depth(nut)) + nut(nut, true); + + screw_and_washer(hinge_screw, screw_longer_than(2 * hinge_h)); + } + } + + translate_z(hinge_z(cam) + hinge_r) + translate(hinge_pos) + rotate([-angle, 0, 0]) + translate(-hinge_pos) { + translate_z(cam_back_size(cam).z - front.z) + camera(cam); + + stl_colour(pp1_colour) render() + translate_z(-front.z) + camera_back(cam); + + cam_holes(cam) { + screw_and_washer(screw, screw_length); + + translate_z(-front.z + nut_trap_depth(nut)) + vflip() + nut(nut, true); + } + + *translate(camera_lens_offset(cam)) + translate_z(1.5) + stl_colour(pp1_colour) render() + rpi_camera_focus_ring_stl(); + + stl_colour(pp2_colour) render() + hflip() + camera_front(cam, 0); + } +} + +module camera_fastened_assembly(cam, thickness, angle = 0) { + camera_assembly(cam, angle); + + camera_bracket_position(cam) + camera_bracket_screw_positions(cam) { + nut = screw_nut(bracket_screw); + washer = screw_washer(bracket_screw); + t = bracket_thickness(cam); + screw_length = screw_longer_than(thickness + t + nut_thickness(nut, true) + 2 * washer_thickness(washer)); + vflip() + translate_z(thickness) + screw_and_washer(bracket_screw, screw_length); + + translate_z(t) + nut_and_washer(nut, true); + } +} + +module camera_back_rpi_camera_stl() camera_back(rpi_camera); +module camera_back_rpi_camera_v1_stl() camera_back(rpi_camera_v1); +module camera_back_rpi_camera_v2_stl() camera_back(rpi_camera_v2); + +module camera_front_rpi_camera_stl() camera_front(rpi_camera); +module camera_front_rpi_camera_v1_stl() camera_front(rpi_camera_v1); +module camera_front_rpi_camera_v2_stl() camera_front(rpi_camera_v2); + +module camera_bracket_rpi_camera_stl() camera_bracket(rpi_camera); +module camera_bracket_rpi_camera_v1_stl() camera_bracket(rpi_camera_v1); +module camera_bracket_rpi_camera_v2_stl() camera_bracket(rpi_camera_v2); + +module camera_rpi_camera_assembly() camera_assembly(rpi_camera); +module camera_rpi_camera_v1_assembly() camera_assembly(rpi_camera_v1); +module camera_rpi_camera_v2_assembly() camera_assembly(rpi_camera_v2); + +module camera_housing(cam) { + front = cam_front_size(cam); + + camera_front(cam, 0); + + translate([front.x, 0]) + camera_back(cam); + + translate([-front.x / 2 - 2 - hinge_r, 0]) + rotate(90) + camera_bracket(cam); +} + +cam = rpi_camera_v2; +if($preview) + camera_fastened_assembly(cam, 3); +else + camera_housing(cam); diff --git a/readme.md b/readme.md index 5eaf17b..76c9332 100644 --- a/readme.md +++ b/readme.md @@ -23,24 +23,24 @@ See [usage](docs/usage.md) for requirements, installation instructions and a usa Axials Jack Rails Box Annotation BOM Ball_bearings KP_pillow_blocks Ring_terminals Butt_box Bezier Clip Batteries LDRs Rockers Cable_grommets Catenary Global - Belts LED_meters Rod Carriers Dogbones Polyholes - Blowers LEDs SCS_bearing_blocks Corner_block Fillet Rounded_rectangle - Bulldogs Leadnuts SK_brackets Door_hinge Gears Sphere - Buttons Light_strips SMDs Door_latch Hanging_hole Teardrops - Cable_strips Linear_bearings SSRs Fan_guard Horiholes - Cameras Magnets Screws Fixing_block Layout - Circlips Mains_sockets Sealing_strip Flat_hinge Maths - Components Microswitches Shaft_couplings Foot Offset - DIP Microview Sheets Handle Quadrant - D_connectors Modules Spades PCB_mount Round - Displays Nuts Spools PSU_shroud Rounded_cylinder - Extrusion_brackets O_ring Springs Printed_box Rounded_polygon - Extrusions Opengrab Stepper_motors Ribbon_clamp Sector - Fans PCB Swiss_clips SSR_shroud Sweep - Fuseholder PCBs Toggles Screw_knob Thread - Geared_steppers PSUs Transformers Socket_box Tube - Green_terminals Panel_meters Tubings Strap_handle - Hot_ends Pillars Variacs + Belts LED_meters Rod Camera_housing Dogbones Polyholes + Blowers LEDs SCS_bearing_blocks Carriers Fillet Rounded_rectangle + Bulldogs Leadnuts SK_brackets Corner_block Gears Sphere + Buttons Light_strips SMDs Door_hinge Hanging_hole Teardrops + Cable_strips Linear_bearings SSRs Door_latch Horiholes + Cameras Magnets Screws Fan_guard Layout + Circlips Mains_sockets Sealing_strip Fixing_block Maths + Components Microswitches Shaft_couplings Flat_hinge Offset + DIP Microview Sheets Foot Quadrant + D_connectors Modules Spades Handle Round + Displays Nuts Spools PCB_mount Rounded_cylinder + Extrusion_brackets O_ring Springs PSU_shroud Rounded_polygon + Extrusions Opengrab Stepper_motors Printed_box Sector + Fans PCB Swiss_clips Ribbon_clamp Sweep + Fuseholder PCBs Toggles SSR_shroud Thread + Geared_steppers PSUs Transformers Screw_knob Tube + Green_terminals Panel_meters Tubings Socket_box + Hot_ends Pillars Variacs Strap_handle Hygrometer Pin_headers Veroboard IECs Pulleys Washers Inserts Wire @@ -4261,6 +4261,72 @@ of conductive panels, an extra layer of insulation. | 1 | round_grommet_top_60_3.stl | +Top + +--- + +## Camera_housing +Housings for PCB cameras. + + +[printed/camera_housing.scad](printed/camera_housing.scad) Implementation. + +[tests/camera_housing.scad](tests/camera_housing.scad) Code for this example. + +### Functions +| Function | Description | +|:--- |:--- | +| ```cam_front_size(cam)``` | Outside dimensions of the case | + +### Modules +| Module | Description | +|:--- |:--- | +| ```camera_assembly(cam, angle = 0)``` | Camera case assembly | +| ```camera_back(cam)``` | Make the STL for a camera case back | +| ```camera_bracket(cam)``` | Make the STL for the camera bracket | +| ```camera_bracket_position(cam)``` | Position children at the bracket position | +| ```camera_bracket_screw_positions(cam)``` | Position children at the bracket screw positions | +| ```camera_front(cam, hinge = 0)``` | Make the STL for a camera case front | +| ```rpi_camera_focus_ring_stl()``` | Focus ring the glue onto RPI lens | + +![camera_housing](tests/png/camera_housing.png) + +### Vitamins +| Qty | Module call | BOM entry | +| ---:|:--- |:---| +| 7 | ```nut(M2_nut, nyloc = true)``` | Nut M2 x 1.6mm nyloc | +| 10 | ```nut(M3_nut, nyloc = true)``` | Nut M3 x 2.4mm nyloc | +| 1 | ```camera(rpi_camera_v1)``` | Raspberry Pi camera V1 | +| 1 | ```camera(rpi_camera_v2)``` | Raspberry Pi camera V2 | +| 1 | ```camera(rpi_camera)``` | Raspberry Pi focusable camera | +| 7 | ```screw(M2_cap_screw, 10)``` | Screw M2 cap x 10mm | +| 4 | ```screw(M3_cap_screw, 16)``` | Screw M3 cap x 16mm | +| 4 | ```screw(M3_dome_screw, 10)``` | Screw M3 dome x 10mm | +| 2 | ```screw(M3_dome_screw, 12)``` | Screw M3 dome x 12mm | +| 7 | ```washer(M2_washer)``` | Washer M2 x 5mm x 0.3mm | +| 16 | ```washer(M3_washer)``` | Washer M3 x 7mm x 0.5mm | + +### Printed +| Qty | Filename | +| ---:|:--- | +| 1 | camera_back_rpi_camera.stl | +| 1 | camera_back_rpi_camera_v1.stl | +| 1 | camera_back_rpi_camera_v2.stl | +| 1 | camera_bracket_rpi_camera.stl | +| 1 | camera_bracket_rpi_camera_v1.stl | +| 1 | camera_bracket_rpi_camera_v2.stl | +| 1 | camera_front_rpi_camera.stl | +| 1 | camera_front_rpi_camera_v1.stl | +| 1 | camera_front_rpi_camera_v2.stl | + +### Assemblies +| Qty | Name | +| ---:|:--- | +| 1 | camera_rpi_camera_assembly | +| 1 | camera_rpi_camera_v1_assembly | +| 1 | camera_rpi_camera_v2_assembly | + + Top --- diff --git a/tests/camera_housing.scad b/tests/camera_housing.scad new file mode 100644 index 0000000..16c7b8e --- /dev/null +++ b/tests/camera_housing.scad @@ -0,0 +1,33 @@ +// +// NopSCADlib Copyright Chris Palmer 2020 +// nop.head@gmail.com +// hydraraptor.blogspot.com +// +// This file is part of NopSCADlib. +// +// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the +// GNU General Public License as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along with NopSCADlib. +// If not, see . +// +include <../core.scad> +use <../utils/layout.scad> + +use <../printed/camera_housing.scad> + +include <../vitamins/cameras.scad> + +use <../vitamins/pcb.scad> + +module camera_housings() + layout([for(c = cameras) pcb_length(camera_pcb(c))], 15, false) let(c = cameras[$i]) + camera_fastened_assembly(c, 3); + +if($preview) + camera_housings(); diff --git a/tests/cameras.scad b/tests/cameras.scad index 631a7fb..09b99da 100644 --- a/tests/cameras.scad +++ b/tests/cameras.scad @@ -24,7 +24,7 @@ include <../vitamins/cameras.scad> use <../vitamins/pcb.scad> module cameras() - layout([for(c = cameras) pcb_length(camera_pcb(c))], 10, false) let(c = cameras[$i]) + layout([for(c = cameras) pcb_length(camera_pcb(c))], 15, false) let(c = cameras[$i]) camera(c); if($preview) diff --git a/tests/png/camera_housing.png b/tests/png/camera_housing.png new file mode 100644 index 0000000..30b7964 Binary files /dev/null and b/tests/png/camera_housing.png differ diff --git a/tests/png/cameras.png b/tests/png/cameras.png index 565546f..5ebbaa2 100644 Binary files a/tests/png/cameras.png and b/tests/png/cameras.png differ diff --git a/vitamins/cameras.scad b/vitamins/cameras.scad index 4efd536..cab5d46 100644 --- a/vitamins/cameras.scad +++ b/vitamins/cameras.scad @@ -68,6 +68,6 @@ rpi_camera = ["rpi_camera", "Raspberry Pi focusable camera", rpi_camera_pcb, [0, [0, 18 - 1.5 - 2.5], [8, 5, 1.6] ]; -cameras = [rpi_camera_v1, rpi_camera, rpi_camera_v2]; +cameras = [rpi_camera_v1, rpi_camera_v2, rpi_camera]; use