Created
November 18, 2025 20:47
-
-
Save ednisley/822efca70718eddd8eb644473580b0ca to your computer and use it in GitHub Desktop.
OpenSCAD source code: Bird Feeder Tray Mount
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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(); | |
| } | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
More details on my blog at https://softsolder.com/2025/11/19/bird-feeder-tray-mount/