Skip to content

Instantly share code, notes, and snippets.

@ednisley
Created November 18, 2025 20:47
Show Gist options
  • Select an option

  • Save ednisley/822efca70718eddd8eb644473580b0ca to your computer and use it in GitHub Desktop.

Select an option

Save ednisley/822efca70718eddd8eb644473580b0ca to your computer and use it in GitHub Desktop.
OpenSCAD source code: Bird Feeder Tray Mount
// Bird feeder tray mount
// Ed Nisley - KE4ZNU
// 2025-11-06
include <BOSL2/std.scad>
Layout = "Show"; // [Build,Show,Lid,Mount,Segment,Nut]
/* [Hidden] */
ID = 0;
OD = 1;
LENGTH = 2;
HoleWindage = [0.2,0.2,0.2];
Protrusion = 0.1;
NumRibs = 12; // stiffening ribs
NumSides = 8*NumRibs;
$fn=NumSides;
Gap = 5.0;
WallThick = 9.0; // robust walls
Kerf = 1.0; // clamp cut
TapeThick = 0.5; // wrap around pole
LidOD = 12; // main diameter in inches
PoleOD = 1*INCH;
PlateThick = 2.0; // backing plate for clamp
Screw = [6.0,12.0,6.0]; // thread OD, washerOD, head
ScrewLength = 75.0;
ScrewOC = 60.0; // chosen to clear stiffening ribs in lid
LidLayers = [ // bottom to top, ID = 0 means solid disk, LENGTH = exterior measurement
[0,(LidOD-2*(3/8))*INCH,Protrusion], // 0 - below zero to prevent Z fighting
[0,(LidOD-2*(3/8))*INCH,(3/8)*INCH], // 1 - base inside bucket
[0,(LidOD+2*(1/8))*INCH,(1/8)*INCH], // 2 - flange
[(LidOD-2*(1/2))*INCH,LidOD*INCH,(7/8)*INCH], // 3 - sealing ring
];
LidOAH = LidLayers[1][LENGTH] + LidLayers[2][LENGTH] + LidLayers[3][LENGTH];
LidTopDepth = (3/4)*INCH; // from highest part of interior
MountBlockWidth = ScrewOC + 2*WallThick;
BaseSagitta = LidLayers[1][OD]/2 - sqrt((LidLayers[1][OD]/2)^2 - (MountBlockWidth^2)/4);
echo(BaseSagitta=BaseSagitta);
PoleOffset = BaseSagitta + ((LidLayers[2][OD] - LidLayers[1][OD])/2) + WallThick + PoleOD/2;
MountBlock = [PoleOffset + PoleOD/2 + WallThick - PlateThick,MountBlockWidth,LidOAH];
echo(MountBlock=MountBlock);
SegBlockOffset = ScrewLength - MountBlock.x - PlateThick; // assumes recessed
SegmentBlock = [2*SegBlockOffset,MountBlock.y,LidTopDepth];
Rib = [2*6.0,5.0,LidTopDepth]; // lid stiffening ribs
RibAlign = 0 * 180/NumRibs; // position ribs wrt mount
EdgeRadius = 3.0;
//-----
// Rivnut
// The model collects all the magic numbers right here
/*
RivnutOAL = 15.0;
module Rivnut() {
union() {
cyl(1.6,d=13.0,circum=true,anchor=BOTTOM);
cyl(RivnutOAL,d=9.0,circum=true,anchor=BOTTOM);
}
}
*/
//-----
// Square nut
// The model collects all the magic numbers right here
NutOAL = 5.0;
module SquareNut() {
cuboid([10.0,10.0,5.0],anchor=BOTTOM);
}
//-----
// Bucket lid
// Centered at XY=0, Z=0 at top of exterior flange
module BucketLid(Interior=true,Expand=false) {
render()
union() {
down(LidLayers[2][LENGTH])
cyl(LidLayers[1][LENGTH],d=LidLayers[1][OD],anchor=TOP);
cyl(LidLayers[2][LENGTH],d=LidLayers[2][OD],anchor=TOP);
if (Interior) {
if (false)
down(Expand ? Protrusion : 0)
tube(LidLayers[3][LENGTH] + (Expand ? 2*Protrusion : 0),
id=LidLayers[3][ID],od=(Expand ? 2 : 1)*LidLayers[3][OD],anchor=BOTTOM);
else
difference() {
cyl(LidLayers[3][LENGTH] + (Expand ? 2*Protrusion : 0),
d=(Expand ? 2 : 1)*LidLayers[3][OD],anchor=BOTTOM);
up(LidLayers[3][LENGTH] - LidTopDepth)
cyl(LidTopDepth + (Expand ? 2*Protrusion : 0),
d=LidLayers[3][ID],anchor=BOTTOM);
}
up(LidLayers[3][LENGTH] - LidTopDepth)
for (i=[0:(NumRibs - 1)])
zrot(i*360/NumRibs + RibAlign)
right(LidLayers[3][ID]/2)
cuboid(Rib,anchor=BOTTOM,rounding=1,edges="Z");
}
else
down(Expand ? Protrusion : 0)
cyl(LidLayers[3][LENGTH] + (Expand ? 2*Protrusion : 0),
d=(Expand ? 2 : 1)*LidLayers[3][OD],anchor=BOTTOM);
}
}
// Mount clamp
module Mount() {
render()
difference() {
cuboid(MountBlock,anchor=BOTTOM+LEFT,rounding=EdgeRadius,edges="X");
left(LidLayers[1][OD]/2 - BaseSagitta)
up(LidLayers[1][LENGTH] + LidLayers[2][LENGTH])
BucketLid(Interior=false);
right(PoleOffset) {
cyl(3*MountBlock.z,d=(PoleOD + HoleWindage.x + 2*TapeThick),circum=true,anchor=CENTER);
cuboid([Kerf,2*MountBlock.y,3*MountBlock.z]);
}
if (false)
right(MountBlock.x - PlateThick)
cuboid(3*[PlateThick,MountBlock.y,MountBlock.z],anchor=LEFT);
up(LidOAH - LidLayers[3][LENGTH]/2)
for (j=[-1,1])
fwd(j*ScrewOC/2) {
cyl(ScrewLength,d=Screw[ID] + HoleWindage.x,circum=true,orient=RIGHT,anchor=BOTTOM,$fn=6,spin=180/6);
if (false)
right(MountBlock.x + Protrusion)
cyl(Screw[LENGTH] + Protrusion,d=Screw[OD] + HoleWindage.x,circum=true,
orient=LEFT,anchor=BOTTOM,$fn=12,spin=180/12);
}
}
}
// Nut block segment inside lid
module NutSegment() {
render()
difference() {
cuboid(SegmentBlock,anchor=BOTTOM,rounding=EdgeRadius,edges="X");
down(LidLayers[3][LENGTH] - LidTopDepth)
left(LidLayers[1][OD]/2 - BaseSagitta)
BucketLid(Interior=true,Expand=true);
up(LidTopDepth - LidLayers[3][LENGTH]/2)
for (j=[-1,1])
fwd(j*ScrewOC/2) {
left(SegmentBlock.x/2)
cyl(ScrewLength,d=Screw[ID],circum=true,anchor=BOTTOM,$fn=6,spin=180/6,orient=RIGHT);
left(SegmentBlock.x/2)
yrot(90)
SquareNut();
}
}
}
//-----
// Build things
if (Layout == "Lid")
BucketLid();
if (Layout == "Mount")
Mount();
if (Layout == "Segment")
NutSegment();
if (Layout == "Nut")
Rivnut();
if (Layout == "Show") {
down(LidLayers[1][LENGTH] + LidLayers[2][LENGTH]) {
Mount();
color("Orange",0.5)
up(LidOAH - LidLayers[3][LENGTH]/2)
right(MountBlock.x + PlateThick)
for (j=[-1,1])
fwd(j*ScrewOC/2)
cyl(ScrewLength,d=Screw[ID],circum=true,orient=LEFT,anchor=BOTTOM);
}
up(LidLayers[3][LENGTH] - LidTopDepth)
NutSegment();
color("Gray",0.4)
right(PoleOffset)
cylinder(3*MountBlock.z,d=(PoleOD),anchor=CENTER);
color("Gray",0.4)
left(LidLayers[1][OD]/2 - BaseSagitta)
BucketLid();
color("White",0.7)
down(LidLayers[1][LENGTH] + LidLayers[2][LENGTH])
right(MountBlock.x + 2*PlateThick)
difference() {
cuboid([PlateThick,MountBlock.y,MountBlock.z],anchor=BOTTOM+LEFT,rounding=EdgeRadius,edges="X");
up(LidOAH - LidLayers[3][LENGTH]/2)
for (j=[-1,1])
fwd(j*ScrewOC/2)
cyl(ScrewLength,d=Screw[ID],circum=true,orient=RIGHT,anchor=CENTER);
}
}
if (Layout == "Build") {
render()
union() {
difference() {
left(MountBlock.z + Gap/2)
up(PoleOffset - Kerf/2)
yrot(90)
Mount();
cuboid([3*MountBlock.z,2*MountBlock.y,3*MountBlock.x],anchor=TOP);
}
render()
right(Gap/2)
intersection() {
up(MountBlock.x)
yrot(90)
Mount();
up(MountBlock.x - PoleOffset)
right(MountBlock.z/2)
cuboid([2*MountBlock.z,2*MountBlock.y,MountBlock.x],anchor=TOP);
}
right(2*MountBlock.z - BaseSagitta)
up(SegmentBlock.x/2)
yrot(-90)
NutSegment();
}
}
@ednisley
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment