The tray holder on Mary’s bike worked well:

Except for having the bungee cord run across the middle of the tray where it blocks access for larger trays and tends to bend the taller leaves.
Well, I can fix that:

The front tiedown is similar:

They’re printed from TPU: rectangular blocks and chains, ending in wire hooks bashed from a coat hanger. The M4 button-head screws thread into (uncrushed) rivnuts, which seemed easier to manage than square nuts in this situation.
The chains are just thick circles, with half of the top links sunk into the blocks:

You’d (well, I’d) want to build them one at a time, because sometimes this happens:

Based on those measurements, I raised the extruder by 0.1 mm, but apparently did a poor job of cleaning / flattening the cold TPU on the nozzle and got it wrong. As a result, the first layer didn’t get squooshed properly onto the BuildTak, came unstuck, and produced art . The track down the middle of the photo shows traces of a previous, badly over-squooshed test chain.
The stretched TPU relaxes enough to leave very little tension after a day, as shown by the unhooked right chain:

However, that make the chains exactly the right length, so they require even more force to get the hooks off the rack. After relaxing for another day, the stretched chains return to roughly their original lengths, so it’s all good.
The OpenSCAD source code as a GitHub Gist:
| // TPU Tiedown Straps for bike rack tray holder | |
| // Ed Nisley – KE4ZNU | |
| // 2026-05-14 | |
| include <BOSL2/std.scad> | |
| Layout = "Build"; // [Show,Build,Chain,Blocks,Front,Rear] | |
| /* [Hidden] */ | |
| HoleWindage = 0.2; | |
| Protrusion = 0.01; | |
| NumSides = 4*3*2*4; | |
| Gap = 5.0; | |
| $fn=NumSides; | |
| LinkID = 7.0; | |
| LinkOD = 10.0; | |
| LinkOC = 14.0; | |
| LinkHeight = 4.0; | |
| JointWidth = 2.0; | |
| FrontChainAngle = 30; // from vertical | |
| FrontChainLength = 80.0; // nominal length | |
| RearChainAngle = 20; // from vertical | |
| RearChainLength = 100.0; // nominal length | |
| BlockOA = [80.0,12.0,15.0]; | |
| InsertOC = 30.0; | |
| //—– | |
| // Define things | |
| module Chain(n=2) { | |
| render() | |
| difference() { | |
| union() { | |
| hull() { | |
| cyl(LinkHeight,d=JointWidth,anchor=BOTTOM,rounding=0.0); | |
| back((n – 1)*LinkOC) | |
| cyl(LinkHeight,d=JointWidth,anchor=BOTTOM,rounding=0.0); | |
| } | |
| for (i = [0:n-1]) | |
| back(i*LinkOC) | |
| cyl(LinkHeight,d=LinkOD,anchor=BOTTOM,rounding=0.0); | |
| } | |
| for (i = [0:n-1]) | |
| back(i*LinkOC) | |
| down(Protrusion) | |
| cyl(LinkHeight + 2*Protrusion,d=(LinkID + HoleWindage),anchor=BOTTOM,rounding=-1.0); | |
| } | |
| } | |
| module FrontBlock() { | |
| difference() { | |
| cuboid(BlockOA,anchor=BOTTOM,chamfer=1.0,except=BACK); | |
| for (i = [-1:1]) | |
| right(i*InsertOC) down(Protrusion) { | |
| cyl(BlockOA.z + 2*Protrusion,d=4.0 + HoleWindage,anchor=BOTTOM); // screw clearance | |
| cyl(1.5,d=9.0,anchor=BOTTOM); // insert head | |
| cyl(11.0,d=6.0,anchor=BOTTOM); // insert body | |
| } | |
| } | |
| } | |
| module RearBlock() { | |
| up(BlockOA.z/2) fwd(BlockOA.y/2) | |
| difference() { | |
| cuboid(BlockOA,anchor=FRONT,chamfer=1.0,except=BACK); | |
| for (i = [-1:1]) | |
| right(i*InsertOC) fwd(Protrusion) { | |
| ycyl(BlockOA.z + 2*Protrusion,d=4.0 + HoleWindage,anchor=FRONT); // screw clearance | |
| ycyl(1.5,d=9.0,anchor=FRONT); // insert head | |
| ycyl(11.0,d=6.0,anchor=FRONT); // insert body | |
| } | |
| } | |
| } | |
| module FrontAssembly(cl=FrontChainLength,ca=FrontChainAngle) { | |
| Links = ceil(cl / LinkOC); | |
| union() { | |
| up(cl*cos(ca)) { | |
| FrontBlock(); | |
| back(BlockOA.y/2) | |
| xrot(90) | |
| for (i = [-1,1]) | |
| left(i*InsertOC/2) | |
| zrot(-i*ca + 180) | |
| Chain(Links); | |
| } | |
| } | |
| } | |
| module RearAssembly(cl=RearChainLength,ca=RearChainAngle) { | |
| Links = ceil(cl / LinkOC); | |
| union() { | |
| up(cl*cos(ca)) { | |
| RearBlock(); | |
| back(BlockOA.y/2) | |
| xrot(90) | |
| for (i = [-1,1]) | |
| left(i*InsertOC/2) | |
| zrot(-i*ca + 180) | |
| Chain(Links); | |
| } | |
| } | |
| } | |
| //—– | |
| // Build things | |
| if (Layout == "Chain") | |
| Chain(); | |
| if (Layout == "Blocks") { | |
| fwd(BlockOA.y) | |
| FrontBlock(); | |
| back(BlockOA.y) | |
| RearBlock(); | |
| } | |
| if (Layout == "Front") | |
| FrontAssembly(); | |
| if (Layout == "Rear") | |
| RearAssembly(); | |
| if (Layout == "Show") { | |
| fwd(BlockOA.y) | |
| FrontAssembly(); | |
| back(BlockOA.y) | |
| zrot(180) | |
| RearAssembly(); | |
| } | |
| if (Layout == "Build") { | |
| fwd(BlockOA.z + Gap/2) | |
| up(BlockOA.y/2) | |
| xrot(-90) | |
| down(FrontChainLength*cos(FrontChainAngle)) | |
| FrontAssembly(); | |
| back(BlockOA.z + Gap/2) | |
| zrot(180) | |
| up(BlockOA.y/2) | |
| xrot(-90) | |
| down(RearChainLength*cos(RearChainAngle)) | |
| RearAssembly(); | |
| } | |
Spam comments get trashed, so don’t bother. Comment moderation may cause a delay.