PET Bottle Cap Accessories
A screw cap for the standard soda-bottle neck (PCO 1881 thread, 2.7 mm pitch) with a choice of three heads. The funnel turns an upright bottle into a wide-mouthed jar for filling. The drip spike goes into a plant pot with the filled bottle upside down, and water seeps out of three small holes near the tip. The bird feeder hangs the filled bottle over a dish: seed runs out of four side ports until it buries them and tops itself up as birds eat. The cap top is a 45 degree cone, so every head prints without supports - funnel and spike with the cap mouth down, feeder with the dish on the bed. The funnel measures 93 x 93 x 48 mm, the spike 34 x 34 x 85 mm and the feeder 90 x 90 x 16 mm. Adjustable parameters: head, thread clearance, wall thickness, grip ridges, funnel radius and wall, spike length and drip hole size, dish radius, rim height and port diameter.
# PET Bottle Cap Accessories - funnel, drip spike or bird feeder on a PCO 1881 thread
#
# One screw cap with the standard soda-bottle thread (PCO 1881: 27.4 mm thread, 2.7 mm
# pitch) and a head chosen by `head`:
# funnel - the bottle stands upright and the cap turns it into a funnel-topped jar
# spike - the filled bottle is turned over and the spike goes into a plant pot;
# water drips out of the side holes near the tip
# feeder - the filled bottle hangs upside down over a dish; seed runs out of the side
# ports until it buries them, and tops up as birds eat
# The cap top is a 45 deg cone, so every head prints without supports: funnel and spike
# with the mouth down, feeder with the dish on the bed.
# Screw on clockwise until the bottle neck seats on the cone.
@param choices:["funnel", "spike", "feeder"] desc:"Head on the cap"
$head = "funnel"
@param 0.1..0.6 step:0.05 group:"Thread" desc:"Radial clearance on the bottle thread (mm)"
$clearance = 0.3
@param 1.6..3 step:0.2 group:"Cap" desc:"Wall thickness (mm)"
$wall = 2
@param 24..60 step:1 group:"Cap" desc:"Number of grip ridges"
$ridges = 36
@param 30..60 step:5 group:"Funnel" desc:"Funnel mouth radius (mm)"
$funnel_r = 45
@param 1.2..2.4 step:0.2 group:"Funnel" desc:"Funnel wall thickness (mm)"
$funnel_wall = 1.6
@param 40..100 step:5 group:"Spike" desc:"Spike length (mm)"
$spike_len = 70
@param 1..2.5 step:0.25 group:"Spike" desc:"Diameter of the drip holes (mm)"
$drip_d = 1.5
@param 30..60 step:5 group:"Feeder" desc:"Dish radius (mm)"
$dish_r = 45
@param 8..20 step:1 group:"Feeder" desc:"Dish rim height (mm)"
$dish_h = 14
@param 6..12 step:1 group:"Feeder" desc:"Diameter of the four seed ports (mm)"
$port_d = 9
# --- PCO 1881 neck finish -------------------------------------------------------
$thread_d = 27.4 # thread outer diameter (T)
$root_d = 24.9 # thread root diameter (E)
$pitch = 2.7
$bore_d = 21.7 # neck bore (I)
$depth = ($thread_d - $root_d) / 2
$land = 0.9 # crest flat of the bottle thread
# --- Cap body: mouth at Z = 0, threaded band, then a 45 deg cone to the opening ---
$r_in = $thread_d / 2 + $clearance # cap thread root = bottle crest + clearance
$r_out = $r_in + $wall
$band_h = 9 # about 3.3 turns
$hole_r = 10 # opening at the top of the cone
$cone_h = $r_in - $hole_r # 45 deg inner cone
$rim_r = $hole_r + $wall + 0.5 # outer radius at the top
$top_z = $band_h + $cone_h + 2 # top plate level
$body = polyline [($r_in, 0), ($r_out, 0), ($r_out, $band_h), ($rim_r, $band_h + $r_out - $rim_r),
($rim_r, $top_z), ($hole_r, $top_z), ($hole_r, $band_h + $cone_h), ($r_in, $band_h)]
| revolve Y | rotate 90 0 0 # profile is (radius, height); axis becomes Z
# internal thread: the bottle thread grown by the clearance, run out of the mouth
$cutter = thread $r_in $pitch ($band_h + 3 * $pitch) land:$land depth:$depth center:(true, true, false)
| translate 0 0 (-2 * $pitch)
$lead = cone ($r_in + 0.6) ($r_in - $depth) ($depth + 0.6) center:(true, true, false)
$grip = cylinder 1 ($band_h - 1) center:(true, true, false) | polar $ridges $r_out | translate 0 0 1
$cap = ($body + $grip) - $cutter - $lead
assert $cap.solids == 1
assert $cap.volume < ($body + $grip).volume # the thread was actually cut
# fit: a bottle thread grown by (clearance - 0.05) clears the cap, grown by (clearance + 0.1) does not
$probe_in = thread ($thread_d / 2 + $clearance - 0.05) $pitch $band_h land:$land depth:$depth center:(true, true, false)
$probe_out = thread ($thread_d / 2 + $clearance + 0.1) $pitch $band_h land:$land depth:$depth center:(true, true, false)
assert clear($probe_in, $cap)
assert clear($probe_out, $cap) == false
print "cap" $cap.bbox
# --- Heads ------------------------------------------------------------------------
# funnel: open cone on the cap top, mouth down when printed
$funnel_outer = cone $rim_r ($funnel_r + $funnel_wall) ($funnel_r - $rim_r) center:(true, true, false)
$funnel_inner = cone $hole_r $funnel_r ($funnel_r - $hole_r) center:(true, true, false)
$funnel = ($funnel_outer - $funnel_inner) | translate 0 0 $top_z
$funnel_part = ($cap + $funnel) - (cylinder $hole_r ($top_z + 2) center:(true, true, false))
# spike: solid cone with a 2 mm bore and three drip holes near the tip, printed tip up
$tip_r = 1.5
$spike_body = cone $rim_r $tip_r $spike_len center:(true, true, false) | translate 0 0 $top_z
$bore = cylinder 1.5 ($top_z + $spike_len - 6) center:(true, true, false)
$drip = cylinder ($drip_d / 2) 20 center:(true, true, false) | rotate 0 90 0 | polar 3 0
| translate 0 0 ($top_z + $spike_len - 12)
$spike_part = ($cap + $spike_body) - $bore - $drip
# feeder: dish on the cap top, four ports through the cone, printed dish down
$dish_t = 2
$dish_outer = cylinder $dish_r ($dish_h + $dish_t) center:(true, true, false)
$dish_inner = cylinder ($dish_r - $wall) $dish_h center:(true, true, false)
$dish = ($dish_outer - $dish_inner) | rotate 180 0 0 | translate 0 0 $top_z # floor at top_z, rim rising toward the mouth
$ports = cylinder ($port_d / 2) 40 center:(true, true, false) | rotate 0 90 0 | polar 4 0
| translate 0 0 ($band_h + $cone_h / 2 + 0.5)
$feeder_part = ($cap + $dish) - $ports
$part = if $head == "funnel" then $funnel_part else if $head == "spike" then $spike_part else $feeder_part
assert $part.solids == 1
$out = if $head == "feeder" then ($part | rotate 180 0 0 | floor) else $part
assert $out.bbox.min[2] ~ 0 tol:0.01
print "part" $out.bbox
$out