Li-ion Cell Holder
A series battery pack holder for 18650 or 21700 lithium-ion cells in electronics projects. The cells lie side by side in open-top cradles and snap in from above past two small lips. Each end wall has a slot per cell for a flat spring or dimple contact plate or a nickel strip tab, with notches between neighbouring slots and a wire channel along each ridge for series wiring; + and - marks show the alternating cell direction. The default two-cell 18650 version measures 53.2 x 99 x 15.78 mm and has four M3 mounting holes in its end flanges. Print it flat on its base as exported; no supports are needed. You can change the cell type, the number of cells (1-4), the length allowance for springs or protected cells, the radial clearance and the snap lip overlap.
# Li-ion Cell Holder - 18650 / 21700 series pack for electronics
#
# N cylindrical cells lie side by side along Y, each in its own open-top cradle.
# The cradle wraps a little past the cell's widest point, so a cell is pressed
# in from above and snaps past the two lips (snap = lip overlap per side).
#
# Contacts: each end wall has a 1 mm slot per cell, open at the top. Drop a flat
# battery contact plate (spring on the minus end, dimple on the plus end, up to
# 12 mm wide) or the tab of a nickel strip down into the slot; its contact
# face reaches the cell through the window in the thin inner lip, and the
# solder tab sticks out of the top. Notches across the top of each end wall
# join neighbouring slots, so a double (bridge) plate, a nickel strip or a
# short jumper wire links the cells in series. Put the cells in alternating
# directions - the + and - marks on the end flanges show the orientation.
# A wire channel along the ridge between neighbouring cradles carries a lead
# from the far end of the pack back to the near end.
#
# Mounting: four M3 clearance holes in the end flanges.
# Print flat on the base as exported (Z up). No supports: the slots and windows
# open upward and the cradle lips overhang less than 45 deg.
#
# Cell sizes are typical values (assumptions): 18650 = 18.5 x 65 mm,
# 21700 = 21.3 x 70 mm. Protected cells are longer (about 69 / 76 mm) -
# raise the length allowance for them.
@param choices:["18650", "21700"] group:"Cells" desc:"Cell type"
cell = "18650"
@param 1..4 step:1 group:"Cells" desc:"Number of cells in series"
count = 2
@param 2..14 step:0.5 group:"Fit" desc:"Length allowance over the bare cell, for springs and protected cells (mm)"
allowance = 6
@param 0.1..0.8 step:0.05 group:"Fit" desc:"Radial clearance between cell and cradle (mm)"
clearance = 0.3
@param 0..1.2 step:0.1 group:"Fit" desc:"Snap lip overlap per side (0 = drop-in, mm)"
snap = 0.5
# --- Cell (assumed typical sizes) ----------------------------------
cell_d = if cell == "18650" then 18.5 else 21.3
cell_l = if cell == "18650" then 65 else 70
# --- Derived dimensions --------------------------------------------
r = cell_d / 2 + clearance # cradle bore radius
rib = 5 # web between cradles / outer side wall at the cell axis
floor_t = 2.4 # material under the cell
base_t = 3 # flange plate thickness
ew = 5 # end wall thickness (Y)
flange = 9 # flange beyond each end wall (Y)
pitch = 2 * r + rib
cav = cell_l + allowance # cavity length between the end-wall inner faces
axis_z = floor_t + r # cell axis height
opening = cell_d - 2 * snap # width of the top opening
lip_h = sqrt(r * r - (opening / 2) * (opening / 2)) # lip height above the axis
top_z = axis_z + lip_h
block_w = count * pitch + rib
block_l = cav + 2 * ew
total_l = block_l + 2 * flange
# contact slot in the end wall
lip = 1.0 # inner lip between cavity and slot
slot_t = 1.0 # slot thickness (plates are 0.3-0.5 mm)
slot_w = 12 # slot width
slot_z = axis_z - 7 # slot bottom
win_w = 10 # contact window in the inner lip
win_z = axis_z - 5 # window bottom (keeps the plate foot captured)
notch_d = 2.5 # bridge notch depth from the top
ch_w = 2.4 # wire channel width (about 22 AWG)
ch_d = 2.5 # wire channel depth
m3 = 3.4 / 2 # M3 clearance hole radius
mark_d = 0.6 # depth of the + / - marks
x0 = -(count - 1) * pitch / 2
slot_y = cav / 2 + lip + slot_t / 2
flange_y = block_l / 2 + flange / 2
# --- Body ------------------------------------------------------------
block = box block_w block_l top_z | floor | edges =Z | fillet 2
plate = box block_w total_l base_t | floor | edges =Z | fillet 3
body = block + plate
# cradle bores along Y
bores = union [cylinder r cav | rotate 90 0 0 | translate (x0 + i * pitch) 0 axis_z for i in range(count)]
# contact slots and windows, both ends
slots_f = union [box slot_w slot_t (top_z - slot_z + 1) | floor | translate (x0 + i * pitch) (-slot_y) slot_z for i in range(count)]
slots_b = union [box slot_w slot_t (top_z - slot_z + 1) | floor | translate (x0 + i * pitch) slot_y slot_z for i in range(count)]
wins_f = union [box win_w (lip + 0.4) (top_z - win_z + 1) | floor | translate (x0 + i * pitch) (-(cav / 2 + lip / 2)) win_z for i in range(count)]
wins_b = union [box win_w (lip + 0.4) (top_z - win_z + 1) | floor | translate (x0 + i * pitch) (cav / 2 + lip / 2) win_z for i in range(count)]
holder0 = body - bores - slots_f - slots_b - wins_f - wins_b
# mounting holes in the flanges
hx = block_w / 2 - 5
def m3hole(x, y) = cylinder m3 (base_t * 4) | translate x y 0
holes = union [m3hole(-hx, -flange_y), m3hole(hx, -flange_y), m3hole(-hx, flange_y), m3hole(hx, flange_y)]
holder1 = holder0 - holes
# + / - marks on the flange in front of each cell; cells alternate direction
def plus(x, y) = union [box 5 1.2 2, box 1.2 5 2] | translate x y base_t
def minus(x, y) = box 5 1.2 2 | translate x y base_t
marks_f = union [(if i % 2 == 0 then plus(x0 + i * pitch, -flange_y) else minus(x0 + i * pitch, -flange_y)) for i in range(count)]
marks_b = union [(if i % 2 == 0 then minus(x0 + i * pitch, flange_y) else plus(x0 + i * pitch, flange_y)) for i in range(count)]
holder2 = holder1 - (marks_f | translate 0 0 (1 - mark_d)) - (marks_b | translate 0 0 (1 - mark_d))
# series wiring: bridge notches between neighbouring slots, and wire channels on the ridges
nb = max(count - 1, 1)
notches_f = union [box pitch slot_t (notch_d + 1) | floor | translate (x0 + (i + 0.5) * pitch) (-slot_y) (top_z - notch_d) for i in range(nb)]
notches_b = union [box pitch slot_t (notch_d + 1) | floor | translate (x0 + (i + 0.5) * pitch) slot_y (top_z - notch_d) for i in range(nb)]
channels = union [box ch_w (block_l + 2) (ch_d + 1) | floor | translate (x0 + (i + 0.5) * pitch) 0 (top_z - ch_d) for i in range(nb)]
holder = if count > 1 then (holder2 - notches_f - notches_b - channels) else holder2
# --- Checks ------------------------------------------------------------
cells = union [cylinder (cell_d / 2) cell_l | rotate 90 0 0 | translate (x0 + i * pitch) 0 axis_z for i in range(count)]
assert holder.solids == 1
assert holder.valid
assert holder.bbox.x ~ block_w
assert holder.bbox.y ~ total_l
assert holder.bbox.z ~ top_z
assert opening <= cell_d # the lips hold the cell
assert asin(lip_h / r) < 45 # cradle lip overhang, measured from vertical
assert cav >= cell_l + 2 # room for the contacts
assert clear(holder, cells) # a seated cell touches nothing
print "cradle lip overhang (deg from vertical)" asin(lip_h / r)
print "cell protrudes above the holder (mm)" (axis_z + cell_d / 2 - top_z)
holder