////////////////////////////////////////////////////////////////////// // LibFile: battery-holder.scad // Functions and modules for creating battery holders // Includes: // include // FileGroup: Batteries // FileSummary: This file generates battery holders for arbitrary cylindrical sizes.. ////////////////////////////////////////////////////////////////////// /* [Battery Holder specs] */ // Battery Type battery_type="AAA"; // [None,AA,AAA,18650*,18650P,C,D,CR123A] // Number of battery slots to generate battery_slots = 1; strip_holes = true; wire_channels = true; battery_symbol = true; poles_symbol = true; contact_bulges = true; // Section: Batteries ////////////////////////////////////////////////////////////////////////////////////// /// /// FLEXBATTER: Flexing battery holder with built-in spring /// /// This file generates battery holders for arbitrary cylindrical sizes. /// The number of batteries and their size is fully parametrized. /// /// The usual metallic spring at the minus pole is replaced by a /// flexible printed spring, which is pressing the contacts firmly to /// the battery. /// /// The contacts for the plus and minus pole can easily be made by /// a few windings of the uninsulated ends of the connecting wires. /// Each battery compartment contains are several holes to the outside /// as well ad to the neighboring compartments, through which connecting /// wires can be passed for easy configuring of parallel, serial or /// balanced-serial battery packs. /// /// The preconfigured battery sizes are: /// AA, AAA, C, D, 18650(Li-Ion), 18650P(protected Li-Ion), CR123A(16340) /// /// Given that the printed plastic spring needs to be flexible, ABS is the material /// of choice here. /// ////////////////////////////////////////////////////////////////////////////////////// /// /// 2014-09-09 Heinz Spiess, Switzerland /// /// released under Creative Commons - Attribution - Share Alike licence (CC BY-SA) ////////////////////////////////////////////////////////////////////////////////////// /** * Generates a flexible battery holder for 'n' cylindrical batteries with complex * features like wire channels, screw holes, and contact points. * * @param {number} [n=1] - Number of battery slots to generate. * @param {number} [l=65] - Length of each battery slot. * @param {number} [d=18] - Diameter of the battery. * @param {number} [hf=0.75] - Height factor for the battery holder relative to the battery diameter. * @param {number} [r=4] - Radius for the plastic spring (calculated inside). * @param {number} [shd=3] - Screw hole diameter. * @param {number} [eps=0.28] - Epsilon for additional cavity space around the battery. * @param {number} [el=0] - Extra length for the spring. * @param {array} [xchan=[1/4,3/4]] - Relative positions for traversal wire channels. * @param {number} [$fn=24] - Number of fragments for circles/cylinders for smoother curves. * * @description * This module constructs a battery holder with: * - A main body with a cavity for each battery. * - Plastic springs for negative pole contact using the `sline` module. * - Contact points (+ and -) for electrical connectivity. * - Wire channels for routing electrical connections. * - Screw holes for mounting. * - Engraving for battery orientation symbols. * * Key Features: * - **Chamfering** of edges for a more finished look (`chamfered_cube`). * - **Deepening** for better grip on batteries (optional). * - **Wire Channels**: Both longitudinal and transversal for wire routing. * - **Spring**: Utilizes `sline` to create a complex spring shape for battery contact. * - **Contact Points**: Bulges for ensuring good electrical contact. * - **Engraving**: Symbols to indicate battery polarity. * * Note: * - The module uses recursion implicitly through the `for` loop to create multiple slots. * - Adjustments for middle separators are commented out but could be uncommented for use. * - Some parameters like `r` are calculated within the module to adjust based on input. */ // Module: battery_case() // Usage: // battery_case(n); // Description: // Generates a flexible battery holder for 'n' cylindrical batteries with complex // features like wire channels, screw holes, and contact points. // // DefineHeader(BulletList): Parts // Parts: This module constructs a battery holder with // A main body with a cavity for each battery. // Plastic springs for negative pole contact using the `sline` module. // Contact points (+ and -) for electrical connectivity. // Wire channels for routing electrical connections. // Screw holes for mounting. // Engraving for battery orientation symbols. // // DefineHeader(BulletList): Key Features // Key Features: // **Chamfering** of edges for a more finished look (`chamfered_cube`). // **Deepening** for better grip on batteries (optional). // **Wire Channels**: Both longitudinal and transversal for wire routing. // **Spring**: Utilizes `sline` to create a complex spring shape for battery contact. // **Contact Points**: Bulges for ensuring good electrical contact. // **Engraving**: Symbols to indicate battery polarity. // // Arguments: // n = Number of battery slots to generate. (default: 1) // l = Length of each battery slot. (default: 65) // d = Diameter of the battery. (default: 18) // hf = Height factor for the battery holder relative to the battery diameter. (default: 0.75) // r = Radius for the plastic spring (calculated inside). (default: 4) // shd = Screw hole diameter. (default: 3) // eps = Epsilon for additional cavity space around the battery. (default: 0.28) // el = Extra length for the spring. (default: 0) // xchan = Relative positions for traversal wire channels. (default: [1/4, 3/4]) // $fn = Number of fragments for circles/cylinders for smoother curves. (default: 24) // // Example: Simple Case // battery_case(n=1,l=46.1,d=10.45,hf=0.84,shd=2,el=1,xchan=[0.5],eps=0); module battery_case(n=1,l=65,d=18,hf=0.75,r=4,shd=3,eps=0.28,el=0,xchan=[1/4,3/4],$fn=24){ ew=0.56; // extrusion width eh=0.25; // extrusion height w = 4*ew; // case wall thickness ws = 2*ew; // spring wall thickness ch = w-ws; // edge chamfering deepen=0; //deepening for side grip of batteries //el = 0; // extra length in spring //eps = 0.28; //shd = 3; // screw hole diameter //hf=0.75; //xchan=[1/4,3/4]; // relative position of traversal wire channels r = d/5+2*ws; // linear spring length (depends on sline() call!) for(i=[0:n-1])translate([0,i*(d+w+ws),0]){ // generate n battery cases difference(){ union(){ difference(){ // main body translate([0,-w-d/2,0]) chamfered_cube([l+w,d+2*w+(i0)translate([-1,0,d/2+w])rotate([0,90,0])cylinder(r=d/2+eps,h=l+1); } // plastic spring for minus pole for(sy=[-1,1])scale([1,sy,1]) //assign(D=d+2*w-2*ws-0.7) { D=d+2*w-2*ws-0.7; translate([ch,d/2+w-ws/2,0])rotate(-90) //sline([90,0,120,90,120,90,0],[d/8+2,d/6,d/8-1,d/8,-d/8,d/8,d/2],0,ws,hf*d+w); sline([0,180,0,180,0,-180,0,90,0],[r+ch+el,D/4,el,D/12,el/2,D/12,1+el/2,D/5,D/3],0,ws,hf*d+w); } } // lower and upper holes for contacts if (strip_holes) stripHoles(case_wall_thickness = w,spring_wall_thickness=ws,battery_diam = d,slot_length=l); /* for(z=[-2*ws,2*ws]) translate([-2*ws,-w,w-ws/2+d/2+z]) cube([l+2*w+2,2*w,ws]); */ // Wire channels if (wire_channels) wireChannels( case_wall_thickness = w, spring_wall_thickness = ws, slot_length = l, battery_diam = d, spring_radius = r, channel_pos = xchan, extrusion_height = eh ); // grip deepening if (deepen>0) translate([w+l/2-0.5,-d/2-w-0.01,w+d+l]) rotate([-90,0,0]){ cylinder(r=l+deepen*d,h=d+2*w+2*ws+2,$fn=72); if(i==0)cylinder(r1=l+deepen*d+ch,r2=l+deepen*d,h=ch+0.02,$fn=72); if(i==n-1)translate([0,0,d+2*w-ch])cylinder(r2=l+deepen*d+ch,r1=l+deepen*d,h=ch+0.02,$fn=72); } // conical screw holes in corners for(x=[7+shd,l-2*shd])for(y=[-d/2+shd,d/2-shd]) translate([x,y,-1]){ cylinder(r=shd/2,h=w+2); translate([0,0,w-shd/2+1])cylinder(r1=shd/2,r2=shd,h=shd/2+0.01); } // holes for wires passing inside for(sy=[-1,1])scale([1,sy,1]){ translate([l-1,-d/2,w])cube([w+2,2,2]); for(x=[3,l-7])translate([x,-d/2-w-ws-1,w])cube([3,w+ws+3,2]); translate([3,-d/2+w/2-0.75,-1])cube([3,1.5,w+2]); translate([-0.5,-d/2+w/2,0])rotate([0,90,0])cylinder(r=w/2,h=6.5,$fn=5); } if (battery_symbol) batterySymbol( case_wall_thickness = w, battery_diam = d, slot_length = l, extrusion_height = eh, ); // engrave plus symbol //assign(sy=(l>12*shd)?1:-1) if (poles_symbol) polesSymbol( case_wall_thickness = w, battery_diam = d, slot_length = l, screw_hole_diameter = shd, extrusion_height = eh, ); if (contact_bulges) contactBulges( case_wall_thickness = w, spring_wall_thickness = ws, battery_diam = d, slot_length = l, spring_extra_length = el, vertical_bulge = true ); } } module contactBulges( case_wall_thickness, spring_wall_thickness, battery_diam,slot_length, spring_extra_length, vertical_bulge ) { // horizontal contact bulges (+ and - pole) for( x = [ -0.3,slot_length ] ) hull() for(y=[ -3+spring_extra_length, +3-spring_extra_length ]) translate([ x, y, case_wall_thickness + battery_diam / 2 ]) sphere(r=spring_wall_thickness); // vertical contact bulge (+ pole only) if (vertical_bulge) hull() for(z=[-3+spring_extra_length,3-spring_extra_length]) for(x=[0,case_wall_thickness-spring_wall_thickness]) translate([ slot_length + x,0,case_wall_thickness + battery_diam / 2 + z]) sphere(r=spring_wall_thickness); } /** * Engraves a simple battery symbol on the side of a battery case. * * @param {number} case_wall_thickness - Thickness of the case walls. * @param {number} slot_length - Length of the battery slot. * @param {number} battery_diam - Diameter of the battery, used for symbol size and positioning. * @param {number} extrusion_height - Height of the extrusion for the symbol. * * @description * This module creates a visual representation of a battery by engraving: * - A rectangle representing the battery body. * - A smaller rectangle at one end to symbolize the positive terminal or battery cap. * * The positioning of the symbol is: * - Centered along the length of the slot with an offset for the case wall thickness. * - Placed slightly above the bottom of the battery diameter for visibility. * * Notes: * - Both rectangles are centered (`true` in the `cube` calls) for symmetrical engraving. * - The dimensions of the symbol parts are proportional to `slot_length` and `battery_diam`, ensuring the symbol scales appropriately with different battery sizes. */ module batterySymbol( case_wall_thickness, slot_length, battery_diam,extrusion_height ) { translate( [ case_wall_thickness+slot_length/2, battery_diam/4+1, case_wall_thickness ]) cube( [ slot_length/5, battery_diam/4.5, 4*extrusion_height ],true); translate( [ case_wall_thickness+slot_length/2+slot_length/10, battery_diam/4+1, case_wall_thickness ]) cube([battery_diam/7,battery_diam/10,4*extrusion_height],true); }; /** * Creates strip-like holes on both ends of the battery slot for contact purposes. * * @param {number} case_wall_thickness - Thickness of the case walls. * @param {number} spring_wall_thickness - Thickness of the spring or contact wall. * @param {number} battery_diam - Diameter of the battery, used for positioning. * @param {number} slot_length - Length of the battery slot. * * @description * This module generates two rectangular holes at each end of the battery compartment: * - These holes are designed to facilitate contact between the battery terminals and external connectors or springs. * - They are positioned at the top and bottom of the battery diameter area, relative to the case's interior. * * Key features: * - **Positioning**: Holes are placed at the ends of the battery slot, with one at `z = -2*spring_wall_thickness` and another at `z = 2*spring_wall_thickness` above and below the battery's center line. * - **Dimensions**: * - Width spans the length of the slot plus additional space for case walls. * - Height is twice the case wall thickness, ensuring enough room for contact. * - Depth (or height in OpenSCAD's coordinate system) is equal to the spring wall thickness. * * Note: * - The module uses a loop to create both holes symmetrically around the battery's center. * - The exact positioning ensures these holes do not interfere with the battery's body but are accessible for contacts. */ module stripHoles( case_wall_thickness,spring_wall_thickness,battery_diam,slot_length ) { for( z = [-2*spring_wall_thickness,2*spring_wall_thickness] ) translate([ -2*spring_wall_thickness, -case_wall_thickness, case_wall_thickness-spring_wall_thickness/2+battery_diam/2+z ]) cube([ slot_length+2*case_wall_thickness+2, 2*case_wall_thickness, spring_wall_thickness ]); } /** * Creates wire channels in the battery case to route electrical connections. * * @param {number} case_wall_thickness - Thickness of the case walls. * @param {number} spring_wall_thickness - Thickness of the spring or contact wall. * @param {number} slot_length - Length of the battery slot. * @param {number} battery_diam - Diameter of the battery. * @param {number} spring_radius - Radius of the spring or additional space for wiring. * @param {array} channel_pos - Array of relative positions for transversal channels along the slot's length. * @param {number} extrusion_height - Height of the extrusion for positioning the wire channel. * * @description * This module constructs wire channels for guiding wires: * - **Longitudinal Channel**: Runs along the bottom of the battery case for wires connecting multiple batteries or exiting the case. * - **Transversal Channels**: Provides paths for wires to cross the width of the case, positioned according to `channel_pos`. * * Key Features: * - **Longitudinal Channel**: * - Positioned at the base of the case, extending the length of the slot plus additional space. * - Rotated to lie horizontally under the battery for ease of wire routing. * - **Transversal Channels**: * - Created at specified positions along the slot's length (`channel_pos`). * - Run perpendicular to the longitudinal channel, allowing for cross-connection of wires or external connections. * * Notes: * - Cylinders are used to form these channels, with $fn determining the number of facets for a smoother or more angular appearance. * - The longitudinal channel's height includes the slot length plus extra space for routing flexibility. * - Transversal channels are placed just below the battery's diameter to avoid interfering with battery placement. */ module wireChannels( case_wall_thickness, spring_wall_thickness,slot_length, battery_diam, spring_radius, channel_pos,extrusion_height ) { // longitudinal bottom wire channel translate([-2*spring_wall_thickness,0,0]) rotate([0,90,0]) cylinder( r=case_wall_thickness/2, h=slot_length + case_wall_thickness + 2 +spring_radius, $fn=5 ); // traversal bottom wire channels for ( x = slot_length * channel_pos ) translate([x,-battery_diam/2-case_wall_thickness-1,extrusion_height]) rotate([-90,0,0]) cylinder( r = case_wall_thickness/2, h = battery_diam +2 * case_wall_thickness + spring_wall_thickness+2, $fn=6 ); } /** * Engraves symbols for battery polarity (+ and -) on the side of the battery case. * * @param {number} case_wall_thickness - Thickness of the case walls. * @param {number} slot_length - Length of the battery slot. * @param {number} screw_hole_diameter - Diameter of the screw holes, used to determine symbol orientation. * @param {number} battery_diam - Diameter of the battery, used for symbol size and positioning. * @param {number} extrusion_height - Height of the extrusion for the symbols. * * @description * This module adds visual indicators for battery polarity: * - For longer batteries, symbols are placed horizontally. * - For shorter batteries, symbols are adjusted vertically to fit within the available space. * * The symbols are: * - **Plus (+)**: Created by intersecting two cubes to form a cross. * - **Minus (-)**: A single cube representing a line. * * The placement and orientation of these symbols depend on: * - `slot_length` compared to `screw_hole_diameter` to determine if the battery is considered 'short' or 'long'. * - `sy` (scale y) determines whether symbols are placed on the top or side based on the comparison. * * Note: * - The `echo` statement is included for debugging or informational purposes, to verify the battery diameter. * - The commented-out code for middle separators suggests this module might have been part of a larger context where such adjustments were necessary but have since been removed or commented out. */ module polesSymbol( case_wall_thickness, slot_length,screw_hole_diameter, battery_diam, extrusion_height ) { sy=( slot_length > 12 * screw_hole_diameter)?1:-1; echo ("battery_diam",battery_diam); { // for short batteries +- on the side translate([ case_wall_thickness+slot_length/2+slot_length/(sy>0?5:10), sy*(battery_diam/4+1), case_wall_thickness ]){ cube([1,battery_diam/4,4*extrusion_height],true); cube([battery_diam/4,1,4*extrusion_height],true); } // engrave minus symbol translate([ case_wall_thickness+slot_length/2-slot_length/(sy>0?5:10), sy*(battery_diam/4+1), case_wall_thickness ]) cube([1,battery_diam/4,4*extrusion_height],true); } //correction for middle separators //if(i0) translate([-d,-d/2-w-0.1,-1])cube([d,ws/2+0.1,d+2]); //else translate([1,-d/2-w,-0.01])cylinder(r1=ch,r2=0,h=ch); } } //caseCR123A(n=2); if (battery_type == "AAA") { battery_case(n=battery_slots,l=46.1,d=10.45,hf=0.84,shd=2,el=1,xchan=[0.5],eps=0); } else if ( battery_type == "AA" ) { battery_case(n=battery_slots,l=51.6,d=14.4,hf=0.80,shd=2.5,el=0.5,eps=0.28); } else if ( battery_type == "C" ) { battery_case(n=battery_slots,l=51.6,d=26.4,hf=0.75,shd=3,eps=0.28); } else if ( battery_type == "D" ) { battery_case(n=battery_slots,l=61.5,d=34.0,hf=0.75,shd=3,eps=0.28); } else if ( battery_type == "18650*" ) { battery_case(n=battery_slots,l=65.5,d=18.4,hf=0.75,shd=3,eps=0.28); } else if ( battery_type == "18650P") { battery_case(n=battery_slots,l=67.5,d=18.4,hf=0.75,shd=3,eps=0.28); } else if ( battery_type == "CR123A") { battery_case(n=battery_slots,l=35.1,d=16.7,hf=0.75,shd=3,xchan=[0.5],eps=0.28); } else { echo (str("ERROR for battery type :'",battery_type,"'")); } /** * Creates an arc or a segment of a cylinder with inner and outer radii. * * @param {number} r1 - Inner radius of the arc. * @param {number} r2 - Outer radius of the arc. * @param {number} height - The height of the arc segment. * @param {number} [a1=0] - Starting angle of the arc in degrees. * 0 degrees is along the positive X-axis. * @param {number} [a2=0] - Ending angle of the arc in degrees. * Positive values are counter-clockwise from a1. * * @description * This module constructs a 3D arc segment between two radii (`r1` and `r2`) * from `a1` to `a2` degrees. The arc's height is determined by `height`. * If `a2 - a1` is less than or equal to 180 degrees, it uses a direct * difference operation to carve out the arc, otherwise, it uses intersection * for arcs greater than 180 degrees to ensure proper geometry. * * The function uses cylinders and cubes for creating and subtracting shapes: * - A larger cylinder (`r2`) forms the outer arc. * - A smaller cylinder (`r1`) is subtracted to make the inner void. * - Cubes are positioned and rotated to cut away the unwanted parts of the * cylinders, forming the arc or segment shape. * * Note: * - All rotations are in the XY plane (around the Z-axis). * - The height parameter includes an extra 2 units for the difference and * intersection operations to ensure clean cuts through the cylinders. */ module arc( r1, r2, height, a1=0, a2=0 ) { if (a2 - a1 <= 180) { // For arcs less than or equal to 180 degrees, we can directly cut the shape difference() { // Create the outer cylinder cylinder(r=r2, h=height); // Remove the inner part to make it hollow translate([0,0,-1]) cylinder(r=r1, h=height+2); // Cut off one side of the arc rotate(a2) translate([-r1-r2,0,-1]) cube([2*(r1+r2),2*(r1+r2),height+2]); // Cut off the other side of the arc rotate(a1+180) translate([-r1-r2,0,-1]) cube([2*(r1+r2),2*(r1+r2),height+2]); } } else { // For arcs greater than 180 degrees, we use intersection to ensure we keep the correct shape difference() { cylinder(r=r2, h=height); // Outer shape translate([0,0,-1]) cylinder(r=r1, h=height+2); // Inner void intersection() { // First cut to carve the arc shape rotate(a2) translate([-r1-r2,0,-1]) cube([2*(r1+r2),2*(r1+r2),height+2]); // Second cut for arcs greater than 180 degrees to ensure we keep only the arc rotate(a1+180) translate([-r1-r2,0,-1]) cube([2*(r1+r2),2*(r1+r2),height+2]); } } } } /** * Generates a "snake line" or a path with segments that can turn or go straight. * * @param {array} angle - An array of angles for each segment. * Positive values indicate a left/counter-clockwise turn, * negative for right/clockwise, and zero for straight segments. * @param {array} radius - An array of radii for each segment. * The absolute value of radius[i] is used for turning segments, * and directly for straight segments. * @param {number} i - Current index in the angle and radius arrays. * @param {number} w - Width of the snake line. * @param {number} h1 - Height of the snake line. * * @description * This module constructs a continuous line that can bend at specified angles * and lengths. It uses recursion to build the path segment by segment: * * - For each segment: * - If the angle is non-zero (`a != 0`), it creates an arc using the `arc` module, * where the radii of the arc are adjusted by `w/2` to account for the line's width. * - For zero angle (`a == 0`), it simply extends straight using a cube. * - The `scale` operation ensures the correct orientation of the arc for negative angles. * - The `translate` and `rotate` operations move and orient each new segment relative * to the last one. * * Notes: * - The module uses a tiny overlap (`-r-0.01`) when placing cubes to ensure a seamless join. * - Recursive calls to `sline` build the whole path until all angles in the array are processed. */ module sline(angle, radius, i, w, h1) { scale([angle[i] >= 0 ? 1 : -1, 1, 1]) { // Flip for negative angles // Declare variables for current segment r = abs(radius[i]); // Radius for current segment a = angle[i]; // Angle for current segment translate([a ? r : 0, 0, 0]) { // Move to start of segment translate([-w/2, -r - 0.01, 0]) // Position for overlap cube([w, 0.02, h1]); // Small cube for overlap if (a) { // Create an arc if there's an angle (turn) arc( r1 = r - w/2, // Inner radius of arc r2 = r + w/2, // Outer radius of arc a1 = a, // Start angle, adjusted to be from 0 to a height = h1 // Height of the arc ); } else if (r > 0) { // Create straight segment if no turn and radius is positive translate([-w/2, -r, 0]) cube([w, r, h1]); } if (i + 1 < len(angle)) { // Check if there's another segment to process rotate(angle[i]) // Rotate for the next segment translate([a ? -r : 0, a ? 0 : -r, 0]) // Translate to the end of current segment sline(angle, radius, i + 1, w, h1); // Recursively call for next segment } } } } // build a cube with chamfered edges module chamfered_cube(size,d=1){ hull(){ translate([d,d,0])cube(size-2*[d,d,0]); translate([0,d,d])cube(size-2*[0,d,d]); translate([d,0,d])cube(size-2*[d,0,d]); } }