Skip to content

Instantly share code, notes, and snippets.

@palaniraja
Created September 9, 2026 20:34
Show Gist options
  • Select an option

  • Save palaniraja/2ca92bfb7cdba04a91dacdee92f8ac01 to your computer and use it in GitHub Desktop.

Select an option

Save palaniraja/2ca92bfb7cdba04a91dacdee92f8ac01 to your computer and use it in GitHub Desktop.
Feeder to use GPI 28-400 compatible bottle
/*
GPI 28-400 inverted bottle hummingbird feeder - revised thread interface
IMPORTANT DIFFERENCE FROM v1:
The female closure bore CLEARS the bottle's thread crest (T dimension),
and an inward helical rib engages between the bottle's external threads.
v1 incorrectly used a near-neck-diameter bore plus a spiral cut.
28-400 reference values (mm):
bottle T max/min : 27.64 / 27.12
bottle E max/min : 25.25 / 24.74
pitch : 25.4 / 6 = 4.2333
400 finish : about 1 turn; closure drawings commonly show 1-1/8 turns
S nominal : about 1.17
H nominal : about 10.1
This is an FDM-printable approximation of a packaging thread, not a GPI gauge.
Print `part="thread_test"` first and tune diametral_clearance.
*/
$fn = 96;
part = "assembly"; // assembly, bowl, lid, gasket, thread_test, socket_section
show_bottle_reference = true;
// ---------- 28-400 reference ----------
T_max = 27.64;
T_nom = 27.38;
E_max = 25.25;
E_nom = 24.99;
pitch = 25.4/6;
finish_h = 10.1;
S_nom = 1.17;
thread_turns = 1.125;
// FDM fit tuning (diametral clearance, not radial)
diametral_clearance = 0.60;
axial_clearance = 0.20;
// Female socket: open bore clears bottle thread crest.
female_bore_d = T_max + diametral_clearance;
// Internal helical rib approaches bottle root but leaves clearance.
female_crest_d = E_max + diametral_clearance;
// Rib cross section dimensions
female_root_width = 1.70 + axial_clearance;
female_crest_width = 0.70 + axial_clearance;
socket_outer_d = 35.0;
socket_h = 11.5;
seal_compressed_h = 1.0;
// Female rib centerline is offset about half a pitch from the external ridge.
female_thread_z0 = seal_compressed_h + S_nom + pitch/2;
female_thread_phase = 180; // tune orientation only; does not change fit class
// ---------- feeder ----------
bowl_od = 118;
bowl_wall = 2.4;
bowl_floor = 2.6;
bowl_total_h = 16.6;
lid_thickness = 2.6;
lid_overlap_h = 4.0;
lid_fit_clearance = 0.35;
outlet_bottom_z = 5.0;
outlet_opening_top_z = 7.0;
nominal_liquid_z = 9.0;
feed_port_count = 6;
feed_port_circle_d = 82;
feed_port_d = 3.2;
gasket_od = 29.0;
gasket_id = 19.0;
gasket_thickness = 1.5;
down_tube_id = 12.0;
down_tube_wall = 2.0;
down_tube_od = down_tube_id + 2*down_tube_wall;
outlet_slot_w = 8.0;
outlet_slot_h = outlet_opening_top_z - outlet_bottom_z;
outlet_slot_count = 4;
// ---------- helical polyhedron ----------
// Sweeps a trapezoid in the r-z plane around the Z axis.
// r_root is the outer/root radius; r_crest is the inward crest radius.
module female_helical_rib(r_root, r_crest, z0, p, turns, root_w, crest_w, phase=0, steps_per_turn=120) {
n = ceil(turns * steps_per_turn);
pts = [
for (i=[0:n])
let(
a = phase + 360*turns*i/n,
zc = z0 + p*turns*i/n,
ca = cos(a), sa = sin(a)
)
each [
[r_root*ca, r_root*sa, zc-root_w/2],
[r_crest*ca, r_crest*sa, zc-crest_w/2],
[r_crest*ca, r_crest*sa, zc+crest_w/2],
[r_root*ca, r_root*sa, zc+root_w/2]
]
];
side_faces = [
for (i=[0:n-1]) each [
[4*i+0,4*(i+1)+0,4*(i+1)+1,4*i+1],
[4*i+1,4*(i+1)+1,4*(i+1)+2,4*i+2],
[4*i+2,4*(i+1)+2,4*(i+1)+3,4*i+3],
[4*i+3,4*(i+1)+3,4*(i+1)+0,4*i+0]
]
];
cap0 = [[0,1,2,3]];
j = 4*n;
cap1 = [[j+3,j+2,j+1,j+0]];
polyhedron(points=pts, faces=concat(side_faces,cap0,cap1), convexity=20);
}
module female_28_400_socket() {
difference() {
union() {
difference() {
cylinder(d=socket_outer_d, h=socket_h);
// Critical: this bore is LARGER than the male thread crest T.
translate([0,0,-0.05]) cylinder(d=female_bore_d, h=socket_h+0.1);
}
// Inward internal thread rib.
female_helical_rib(
r_root=female_bore_d/2 + 0.35,
r_crest=female_crest_d/2,
z0=female_thread_z0,
p=pitch,
turns=thread_turns,
root_w=female_root_width,
crest_w=female_crest_width,
phase=female_thread_phase
);
}
// Open flow path at the sealing floor.
translate([0,0,-0.05]) cylinder(d=down_tube_id, h=seal_compressed_h+0.5);
}
}
// Reference bottle neck, for visual verification only.
module bottle_28_400_reference() {
male_thread_h = (T_nom-E_nom)/2;
color([0.3,0.65,0.95,0.45])
union() {
translate([0,0,seal_compressed_h])
cylinder(d=E_nom, h=finish_h);
// Simplified external ridge. It is intentionally shown only as reference.
female_helical_rib(
r_root=E_nom/2 - 0.20,
r_crest=T_nom/2,
z0=seal_compressed_h + S_nom,
p=pitch,
turns=1.0,
root_w=1.65,
crest_w=0.65,
phase=0
);
}
}
module gasket() {
difference() {
cylinder(d=gasket_od, h=gasket_thickness);
translate([0,0,-0.05]) cylinder(d=gasket_id, h=gasket_thickness+0.1);
}
}
module bowl() {
difference() {
cylinder(d=bowl_od, h=bowl_total_h);
translate([0,0,bowl_floor])
cylinder(d=bowl_od-2*bowl_wall, h=bowl_total_h-bowl_floor+0.1);
}
}
module lid_plate() {
difference() {
cylinder(d=bowl_od-0.8, h=lid_thickness);
for (a=[0:360/feed_port_count:359])
rotate([0,0,a]) translate([feed_port_circle_d/2,0,-0.05])
cylinder(d=feed_port_d, h=lid_thickness+0.1);
translate([0,0,-0.05]) cylinder(d=down_tube_id, h=lid_thickness+0.1);
}
difference() {
translate([0,0,-lid_overlap_h])
cylinder(d=bowl_od-2*bowl_wall-2*lid_fit_clearance, h=lid_overlap_h);
translate([0,0,-lid_overlap_h-0.05])
cylinder(d=bowl_od-2*bowl_wall-2*lid_fit_clearance-2.0, h=lid_overlap_h+0.1);
}
}
module down_tube() {
tube_len = bowl_total_h - outlet_bottom_z;
difference() {
translate([0,0,-tube_len]) cylinder(d=down_tube_od, h=tube_len+0.2);
translate([0,0,-tube_len-0.05]) cylinder(d=down_tube_id, h=tube_len+0.35);
for (a=[0:360/outlet_slot_count:359])
rotate([0,0,a])
translate([down_tube_od/2-down_tube_wall-0.3,-outlet_slot_w/2,-tube_len-0.05])
cube([down_tube_wall+1.2,outlet_slot_w,outlet_slot_h]);
}
}
module lid() {
union() {
lid_plate();
down_tube();
translate([0,0,lid_thickness]) female_28_400_socket();
for (a=[0:360/feed_port_count:359])
rotate([0,0,a]) translate([feed_port_circle_d/2,0,lid_thickness])
difference() {
cylinder(d=12,h=1.2);
translate([0,0,-0.05]) cylinder(d=feed_port_d,h=1.3);
}
}
}
module assembly() {
assert(female_bore_d > T_max, "Female bore must clear bottle thread crest");
assert(female_crest_d > E_max, "Female rib crest must clear bottle neck root");
assert(nominal_liquid_z > outlet_opening_top_z, "Outlet opening is not submerged");
color("firebrick") # bowl();
color("gold") translate([0,0,bowl_total_h]) lid();
color("black") translate([0,0,bowl_total_h+lid_thickness]) gasket();
color([1,0,0,0.22])
translate([0,0,bowl_floor])
cylinder(d=bowl_od-2*bowl_wall-0.8, h=nominal_liquid_z-bowl_floor);
if (show_bottle_reference)
translate([0,0,bowl_total_h+lid_thickness]) bottle_28_400_reference();
}
module thread_test() {
// Short standalone socket for fitting the real bottle before printing the feeder.
female_28_400_socket();
if (show_bottle_reference) bottle_28_400_reference();
}
module socket_section() {
intersection() {
union() {
female_28_400_socket();
if (show_bottle_reference) bottle_28_400_reference();
}
translate([-25,-0.01,-1]) cube([50,25,20]);
}
}
if (part=="bowl") bowl();
else if (part=="lid") lid();
else if (part=="gasket") gasket();
else if (part=="thread_test") thread_test();
else if (part=="socket_section") socket_section();
else assembly();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment