A battery holder for AA alkaline cells descends directly from the NP-BX1 version:

The square recesses fit single contact pads on the left and a “positive-to-negative conversion” plate on the right, all secured with dabs of acrylic adhesive:

Although the OpenSCAD code contains an array of battery dimensions, it only works for AA cells.
The recess on the far left is where you solder the wires onto the contact tabs, with the wires leading outward through the holes in the lid. The case needs an indexing feature to hold the lid square while gluing it down.
Alkaline cells cells do not have current-limiting circuitry, so a low-current PTC fuse seems like a Good Idea. I initially thought of hiding it in the recess, but the Brutalist nature of the astables suggests open air.
The OpenSCAD source code as a GitHub Gist:
| // Astable Multivibrator | |
| // Holder for Alkaline cells | |
| // Ed Nisley KE4ZNU August 2020 | |
| /* [Layout options] */ | |
| CellName = "AA"; // [AA] — does not work with anything else | |
| NumCells = 2; | |
| Layout = "Case"; // [Build,Show,Lid] | |
| Struts = -1; // [0:None, -1:Dual, 1:Quad] | |
| // Extrusion parameters – must match reality! */ | |
| /* [Hidden] */ | |
| ThreadThick = 0.25; | |
| ThreadWidth = 0.40; | |
| HoleWindage = 0.2; | |
| function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit); | |
| function IntegerLessMultiple(Size,Unit) = Unit * floor(Size / Unit); | |
| Protrusion = 0.1; // make holes end cleanly | |
| inch = 25.4; | |
| //- Basic dimensions | |
| WallThick = IntegerMultiple(3.0,ThreadWidth); | |
| CornerRadius = WallThick/2; | |
| FloorThick = IntegerMultiple(3.0,ThreadThick); | |
| TopThick = IntegerMultiple(2.0,ThreadThick); | |
| WireOD = 1.7; // wiring from pins to circuitry | |
| Gap = 5.0; | |
| // Cylindrical cell sizes | |
| // https://en.wikipedia.org/wiki/List_of_battery_sizes#Cylindrical_batteries | |
| CELL_NAME = 0; | |
| CELL_OD = 1; | |
| CELL_OAL = 2; | |
| CellData = [ | |
| ["AAAA",8.3,42.5], | |
| ["AAA",10.5,44.5], | |
| ["AA",14.5,50.5], | |
| ["C",26.2,50], | |
| ["D",34.2,61.5], | |
| ["A23",10.3,28.5], | |
| ["CR123A",17.0,34.5], | |
| ["18650",18.8,65.2], // bare 18650 with button end | |
| ["18650Prot",19.0,70.0], // protected 18650 = 19670 plus a bit | |
| ]; | |
| CellIndex = search([CellName],CellData,1,0)[0]; | |
| echo(str("Cell index: ",CellIndex," = ",CellData[CellIndex][CELL_NAME])); | |
| //- Contact dimensions | |
| CONTACT_NAME = 0; | |
| CONTACT_WIDE = 1; | |
| CONTACT_HIGH = 2; | |
| CONTACT_THICK = 3; // plate thickness | |
| CONTACT_TIP = 4; // tip to rear face | |
| CONTACT_TAB = 5; // solder tab width | |
| ContactData = [ | |
| ["AA+",12.2,12.2,0.3,1.7,3.5], // pos bump | |
| ["AA-",12.2,12.2,0.3,5.0,3.5], // half-compressed neg spring | |
| ["AA+-",28.2,12.2,0.3,5.0,0], // pos-neg bridge | |
| ["Li+",18.5,16.0,0.3,2.8,5.5], | |
| ["Li-",18.5,16.0,0.3,6.0,5.5], | |
| ]; | |
| function ConDat(name,dim) = ContactData[search([name],ContactData,1,0)[0]][dim]; | |
| ContactRecess = 2*ConDat(str(CellName,"+"),CONTACT_THICK); | |
| ContactOC = CellData[CellIndex][CELL_OD]; | |
| WireBay = 6.0; // room for wiring to contacts | |
| //- Wire struts | |
| StrutDia = 1.6; // AWG 14 = 1.6 mm | |
| StrutSides = 3*4; | |
| ID = 0; | |
| OD = 1; | |
| LENGTH = 2; | |
| StrutBase = [StrutDia,StrutDia + 2*5*ThreadWidth, // ID = wire, OD = buildable | |
| FloorThick + CellData[CellIndex][CELL_OD]]; // base is flush with cell top | |
| //- Holder dimensions | |
| BatterySize = [CellData[CellIndex][CELL_OAL] + // cell | |
| ConDat(str(CellName,"+"),CONTACT_TIP) + // pos contact | |
| ConDat(str(CellName,"-"),CONTACT_TIP) – // neg contact | |
| 2*ContactRecess, // sink into wall | |
| NumCells*CellData[CellIndex][CELL_OD], | |
| CellData[CellIndex][CELL_OD] | |
| ]; | |
| echo(str("Battery space: ",BatterySize)); | |
| CaseSize = [3*WallThick + // end walls + wiring partition | |
| BatterySize.x + // cell | |
| WireBay, // wiring bay | |
| 2*WallThick + BatterySize.y, | |
| FloorThick + BatterySize.z | |
| ]; | |
| BatteryOffset = (CaseSize.x – (2*WallThick + | |
| CellData[CellIndex][CELL_OAL] + | |
| ConDat(str(CellName,"-"),CONTACT_TIP)) | |
| ) /2 ; | |
| ThumbRadius = 0.75 * CaseSize.z; | |
| StrutOC = [IntegerLessMultiple(CaseSize.x – 2*CornerRadius -2*StrutBase[OD],5.0), | |
| IntegerMultiple(CaseSize.y + StrutBase[OD],5.0)]; | |
| StrutAngle = atan(StrutOC.y/StrutOC.x); | |
| echo(str("Strut OC: ",StrutOC)); | |
| LidSize = [2*WallThick + WireBay + ConDat(str(CellName,"+"),CONTACT_THICK), CaseSize.y, FloorThick/2]; | |
| //———————- | |
| // Useful routines | |
| module PolyCyl(Dia,Height,ForceSides=0) { // based on nophead's polyholes | |
| Sides = (ForceSides != 0) ? ForceSides : (ceil(Dia) + 2); | |
| FixDia = Dia / cos(180/Sides); | |
| cylinder(r=(FixDia + HoleWindage)/2,h=Height,$fn=Sides); | |
| } | |
| //– Overall case with origin at battery center | |
| module Case() { | |
| difference() { | |
| union() { | |
| hull() | |
| for (i=[-1,1], j=[-1,1]) | |
| translate([i*(CaseSize.x/2 – CornerRadius), | |
| j*(CaseSize.y/2 – CornerRadius), | |
| 0]) | |
| cylinder(r=CornerRadius/cos(180/8),h=CaseSize.z,$fn=8); // cos() fixes undersize spheres! | |
| if (Struts) | |
| for (i = (Struts == 1) ? [-1,1] : -1) { // strut bases | |
| hull() | |
| for (j=[-1,1]) | |
| translate([i*StrutOC.x/2,j*StrutOC.y/2,0]) | |
| rotate(180/StrutSides) | |
| cylinder(d=StrutBase[OD],h=StrutBase[LENGTH],$fn=StrutSides); | |
| translate([i*StrutOC.x/2,0,StrutBase[LENGTH]/2]) | |
| cube([2*StrutBase[OD],StrutOC.y,StrutBase[LENGTH]],center=true); // blocks for fairing | |
| for (j=[-1,1]) // hemisphere caps | |
| translate([i*StrutOC.x/2, | |
| j*StrutOC.y/2, | |
| StrutBase[LENGTH]]) | |
| rotate(180/StrutSides) | |
| sphere(d=StrutBase[OD]/cos(180/StrutSides),$fn=StrutSides); | |
| } | |
| } | |
| translate([BatteryOffset,0,BatterySize.z/2 + FloorThick]) // cells | |
| cube(BatterySize + [0,0,Protrusion],center=true); | |
| translate([BatterySize.x/2 + BatteryOffset + ContactRecess/2 – Protrusion/2, // contacts | |
| 0, | |
| BatterySize.z/2 + FloorThick]) | |
| cube([ContactRecess + Protrusion, | |
| ConDat(str(CellName,"+-"),CONTACT_WIDE), | |
| ConDat(str(CellName,"+-"),CONTACT_HIGH) | |
| ],center=true); | |
| translate([-(BatterySize.x/2 – BatteryOffset + ContactRecess/2 – Protrusion/2), | |
| ContactOC/2, | |
| BatterySize.z/2 + FloorThick]) | |
| cube([ContactRecess + Protrusion, | |
| ConDat(str(CellName,"+"),CONTACT_WIDE), | |
| ConDat(str(CellName,"+"),CONTACT_HIGH) | |
| ],center=true); | |
| translate([-(BatterySize.x/2 – BatteryOffset + ContactRecess/2 – Protrusion/2), | |
| -ContactOC/2, | |
| BatterySize.z/2 + FloorThick]) | |
| cube([ContactRecess + Protrusion, | |
| ConDat(str(CellName,"-"),CONTACT_WIDE), | |
| ConDat(str(CellName,"-"),CONTACT_HIGH) | |
| ],center=true); | |
| translate([-CaseSize.x/2 + WireBay/2 + WallThick, // wire bay | |
| 0, | |
| BatterySize.z/2 + FloorThick + Protrusion/2]) | |
| cube([WireBay, | |
| BatterySize.y, | |
| BatterySize.z + Protrusion | |
| ],center=true); | |
| for (j=[-1,1]) | |
| translate([-(BatterySize.x/2 – BatteryOffset + WallThick/2), // contact tabs | |
| j*ContactOC/2, | |
| BatterySize.z + FloorThick – Protrusion]) | |
| cube([2*WallThick, | |
| ConDat(str(CellName,"+"),CONTACT_TAB), | |
| (BatterySize.z – ConDat(str(CellName,"+"),CONTACT_HIGH)) | |
| ],center=true); | |
| if (false) | |
| translate([0,0,CaseSize.z]) // finger cutout | |
| rotate([90,00,0]) | |
| cylinder(r=ThumbRadius,h=2*CaseSize.y,center=true,$fn=22); | |
| if (Struts) | |
| for (i2 = (Struts == 1) ? [-1,1] : -1) { // strut wire holes and fairing | |
| for (j=[-1,1]) | |
| translate([i2*StrutOC.x/2,j*StrutOC.y/2,FloorThick]) | |
| rotate(180/StrutSides) | |
| PolyCyl(StrutBase[ID],2*StrutBase[LENGTH],StrutSides); | |
| for (i=[-1,1], j=[-1,1]) // fairing cutaways | |
| translate([i*StrutBase[OD] + (i2*StrutOC.x/2), | |
| j*StrutOC.y/2, | |
| -Protrusion]) | |
| rotate(180/StrutSides) | |
| PolyCyl(StrutBase[OD],StrutBase[LENGTH] + 2*Protrusion,StrutSides); | |
| } | |
| } | |
| } | |
| module Lid() { | |
| difference() { | |
| hull() | |
| for (i=[-1,1], j=[-1,1], k=[-1,1]) | |
| translate([i*(LidSize.x/2 – CornerRadius), | |
| j*(LidSize.y/2 – CornerRadius), | |
| k*(LidSize.z – CornerRadius)]) // double thickness for flat bottom | |
| sphere(r=CornerRadius/cos(180/8),$fn=8); | |
| translate([0,0,-LidSize.z]) // remove bottom | |
| cube([(LidSize.x + 2*Protrusion),(LidSize.y + 2*Protrusion),2*LidSize.z],center=true); | |
| for (j=[-1,1]) // wire holes | |
| translate([0,j*LidSize.y/4,-Protrusion]) | |
| PolyCyl(WireOD,2*LidSize.z,6); | |
| } | |
| } | |
| //——————- | |
| // Build it! | |
| if (Layout == "Case") | |
| Case(); | |
| if (Layout == "Lid") | |
| Lid(); | |
| if (Layout == "Build") { | |
| rotate(-90) | |
| translate([CaseSize.x/2 + Gap,0,0]) | |
| Case(); | |
| rotate(-90) | |
| translate([-LidSize.x/2 – Gap,0,0]) | |
| Lid(); | |
| } | |
| if (Layout == "Show") { | |
| Case(); | |
| translate([-CaseSize.x/2 + LidSize.x/2,0,(CaseSize.z + Gap)]) | |
| Lid(); | |
| } | |


























