-
Badge Lanyard Reel Mount
A certain young engineer of my acquaintance now carries an ID badge and, so I hear, works in a PCB design & test venue. Seeing as how her favorite color is purple, this seemed appropriate:

Badge Lanyard Reel – front – overall The guts came from Circuit Breaker Labs in the form of a recycled PCB trapped in acrylic resin atop a plastic housing with a spring-loaded reel inside.
It arrived with a plastic bullet at the end of the lanyard:

Badge Lanyard Reel – plastic bullet link Which I immediately replaced with brass, because Steampunk:

Badge Lanyard Reel – bullet cross-drill That made the plastic housing look weak, so, in a series of stepwise refinements, I conjured a much better case from the vasty digital deep:

Badge Lanyard Reel – iterations All of the many, many critical dimensions lie inside the case, where they can’t be measured accurately, so each of those iterations could improve only one or two features. The absolutely wonderful thing about OpenSCAD is having it regenerate the whole model after loosening, say, the carabiner slot by two thread thicknesses; you can do that with a full-on relational CAD drawing, but CAD drawings always seems like a lot of unnecessary work if I must figure out the equations anyway.
The back sports my favorite Hilbert Curve infill with a nicely textured finish:

Badge Lanyard Reel – rear – oblique It’d surely look better in solid brass with Hilbert curve etching.
Black PETG doesn’t photograph well, but at least you can see the M2 brass inserts:

Badge Lanyard Reel – lower interior The first prototype showed the inserts needed far more traction than the usual reamed holes could provide, so I added internal epoxy grooves in each hole:

Badge Lanyard Reel Mount – show Recessing the screw heads into the top plate made them more decorative and smoother to the touch. Button-head screws would be even smoother, but IMO didn’t look quite as bold.
After seeing how well the grooves worked, I must conjure a module tabulating all the inserts on hand and automagically generating the grooves.
The yellow star holds up the roof of the reel recess in the build layout:

Badge Lanyard Reel Mount – build layout – bottom Slic3r produced the rest of the support material for the carabiner exit slot:

Badge Lanyard Reel Mount – bottom – Slic3r support Those two support lumps on the right don’t actually support anything, but tweaking the support settings to disable them also killed the useful support on the left; come to find out Slic3r’s modifier meshes don’t let you disable support generation.
The top plate required support all the way around the inside of the bezel:

Badge Lanyard Reel Mount – top – Slic3r support I carved the original plastic housing in half, roughly along its midline, and discarded the bottom section with the belt clip (it’s on the far left of the scrap pile). The top section, with PCB firmly affixed, holds the lanyard reel and anchors the retracting spring in a central slotted peg. No pictures of that, as it’s either a loose assembly of parts or a spring-loaded bomb and I am not taking it apart again.
The lanyard passes through an eyelet that pays it out to the rotating reel. I’d definitely do that differently, were I building it from scratch, because mounting the eyelet in exactly the proper position to prevent the lanyard from stacking up on the reel and jamming against the inside of the housing turned out to be absolutely critical and nearly impossible.
The top plate presses the original housing against the carabiner, with the cut-off section inside the carabiner’s circular embrace, which just barely worked: the PCB bezel is a millimeter smaller than the shoulder of the housing.
All in all, I think it came out really well for a 3D printed object made by a guy who usually builds brackets:

Badge Lanyard Reel – front – oblique I hope she likes it …
The OpenSCAD source code as a GitHub Gist:
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// Badge Lanyard Reel Mount // Ed Nisley KE4ZNU April 2017 // Reel center at origin, lanyard exit toward +X Layout = "Show"; Support = true; //- Extrusion parameters must match reality! ThreadThick = 0.20; ThreadWidth = 0.40; HoleWindage = 0.2; Protrusion = 0.05; // make holes end cleanly inch = 25.4; function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit); //———————- // Dimensions ID = 0; // for round things OD = 1; LENGTH = 2; Carabiner = [30.7,35.3,3.5]; // metal carabiner around original reel Latch = [6.0,-15,8.0]; // wire spring latch: offset from OD + thickness LatchAngle = 60; // max deflection angle to center from -X direction LatchPoints = [[0,0], [Latch[1]/tan(LatchAngle),0], [Latch[1]/tan(LatchAngle),-Latch[1]]]; // polygon in as-cut orientation echo(str("Latch polygon: ",LatchPoints)); Screw = [2.0,3.8 + 0*ThreadWidth,10.0]; // M2 screw: ID = clear, OD = head ScrewHeadLength = 2.0; ScrewSides = 8; ScrewRecess = 5*ThreadThick; MountSides = ScrewSides; // suitably gritty corners MountThick = Screw[LENGTH] / cos(180/MountSides) + ScrewRecess + 2.0; Insert = [Screw[ID],3.4,4.0]; // brass insert for screws BCD = Carabiner[OD] + 2.5*Insert[OD]; BoltAngles = [20,110]; // ± angles to bolt holes Reel = [5.3,25.5 + 2*ThreadWidth,6.0 + 2*ThreadThick]; // lanyard cord reel ShimThick = 2*ThreadThick; // covers open side of reel for better sliding Bezel = [31.0,32.0,7.5]; // PCB holder + shell, LENGTH = post + shell BezelSides = 6*4; BezelBlock = [5.5,7.5,3.6] + [ThreadWidth,ThreadWidth,ThreadThick]; // block around lanyard eyelet Eyelet = [3.5,4.5,3.0]; Bullet = [2.0,6.5,2.0]; // brass badge holder, LENGTH = recess into mount //———————- // Useful routines module PolyCyl(Dia,Height,ForceSides=0) { // based on nophead's polyholes Sides = (ForceSides != 0) ? ForceSides : (ceil(Dia) + 2); FixDia = Dia / cos(180/Sides); cylinder(r=(FixDia + HoleWindage)/2, h=Height, $fn=Sides); } //– Lanyard reel mockup module Reel() { cylinder(d=Reel[OD],h=Reel[LENGTH],center=true,$fn=6*4); } // Carabiner metal mockup // Some magic numbers lie in wait module Beener() { difference() { hull() { cylinder(d=Carabiner[OD], h=Carabiner[LENGTH] + 2*ThreadThick, center=true,$fn=BezelSides); translate([-Carabiner[OD]/2,0,0]) cylinder(d=Carabiner[OD] – 2.0, h=Carabiner[LENGTH] + 2*ThreadThick, center=true,$fn=6*4); } cylinder(d=Carabiner[ID], h=2*Carabiner[LENGTH], center=true,$fn=BezelSides); translate([Carabiner[ID]/4,0,0]) cube([Carabiner[ID],7.0,2*Carabiner[LENGTH]],center=true); } } // mockup of PCB holder atop remains of old mount with reel post // Z = 0 at midline of case module BezelMount() { rotate(180/BezelSides) { PolyCyl(Bezel[ID] + HoleWindage,MountThick,BezelSides); // PCB punches through mount PolyCyl(Bezel[OD] + HoleWindage,Bezel[LENGTH] – Reel[LENGTH]/2,BezelSides); } translate([Reel[OD]/2,0,BezelBlock[2]/2]) scale([2,1,1]) cube(BezelBlock,center=true); } // Main mount around holder & carabiner module Mount(Section="All") { render() difference() { hull() { for (a = BoltAngles) // spheres defining corners for (i=[-1,1]) rotate(i*a) translate([BCD/2,0,0]) sphere(d=MountThick,$fn=MountSides); cylinder(d=Carabiner[OD] + 4*ThreadWidth, h=MountThick,center=true); // capture carabiner ring } for (a = BoltAngles) // screw & insert holes, head recess for (i=[-1,1]) rotate(i*a) translate([BCD/2,0,0]) rotate(0*i*180/ScrewSides) { translate([0,0,-(Insert[LENGTH] + 2*ThreadThick)]) PolyCyl(Insert[OD], Insert[LENGTH] + 2*ThreadThick + Protrusion,ScrewSides); for (k = [-2:2]) // epoxy retaining grooves translate([0,0,-(k*3*ThreadThick + Insert[LENGTH]/2)]) PolyCyl(Insert[OD] + 1*ThreadWidth, 2*ThreadThick,ScrewSides); PolyCyl(Screw[ID],Screw[LENGTH],ScrewSides); translate([0,0,MountThick/2 – ScrewRecess]) // recess screw heads PolyCyl(Screw[OD],Screw[LENGTH],ScrewSides); } translate([0,0,-1*ThreadThick]) // Minkowski Z extends only top surface! minkowski() { // space for metal carabiner Beener(); // cube([ThreadWidth,ThreadWidth,2*ThreadThick]); cylinder(d=ThreadWidth,h=2*ThreadThick,$fn=6); } rotate([0,90,0]) rotate(180/6) // cord channel = brass tube clearance PolyCyl(Bullet[ID],Carabiner[ID],6); translate([Eyelet[LENGTH] + 2.0,0,0]) // eyelet, large end inward rotate([0,90,0]) rotate(180/6) PolyCyl(Eyelet[OD] + HoleWindage, Reel[OD]/2,6); if (false) translate([Reel[OD]/2 + Eyelet[LENGTH]/2,0,0]) // eyelet, small end outward rotate([0,90,0]) rotate(180/6) PolyCyl(Eyelet[ID],Eyelet[LENGTH],6); translate([(BCD/2 + MountThick/2)*cos(BoltAngles[0]) – Bullet[LENGTH],0,0]) // bullet recess rotate([0,90,0]) rotate(180/6) PolyCyl(Bullet[OD],Carabiner[ID],6); BezelMount(); // PCB holder clearance Reel(); // reel clearance translate([0,0,-(Reel[LENGTH] + ShimThick)/2]) // sliding plate on open side of reel cylinder(d=Reel[OD],h=ShimThick,center=true,$fn=6*4); translate([-Carabiner[OD]/2 + Latch[0],Latch[1],0]) linear_extrude(height=Latch[2],center=true) polygon(LatchPoints); if (Section == "Upper") // display & build section cutting translate([0,0,-2*Carabiner[LENGTH]]) cube(4*Carabiner,center=true); else if (Section == "Lower") translate([0,0,2*Carabiner[LENGTH]]) cube(4*Carabiner,center=true); } if (Support) { // Completely ad-hoc support structures color("Yellow", Layout == "Show" ? 0.3 : 1.0) { if (false && Section == "Upper") { Spokes = BezelSides; Offset = 6*ThreadWidth; for (i = [2:Spokes – 2]) rotate(i * 360/Spokes) translate([Offset,-ThreadWidth,0*(Carabiner[LENGTH]/2)/2]) cube([Carabiner[OD]/2 – Offset – 0*ThreadWidth, 2*ThreadWidth, Carabiner[LENGTH]/2],center=false); for (i = [0:Spokes – 1]) rotate(i * 360/Spokes) translate([Offset,-ThreadWidth,0]) cube([Bezel[OD]/2 – Offset, 2*ThreadWidth, Bezel[LENGTH] – Reel[LENGTH]/2 – 2*ThreadThick],center=false); Bars = 7; render() difference() { union() { for (i = [-floor(Bars/2) : floor(Bars/2)]) translate([-Carabiner[ID]/2,i*Carabiner[OD]/Bars,Carabiner[LENGTH]/4]) cube([Carabiner[ID]/3,2*ThreadWidth,Carabiner[LENGTH]/2],center=true); translate([-Carabiner[ID]/2,0,ThreadThick/2]) cube([Carabiner[ID]/3,Carabiner[ID],ThreadThick],center=true); } cylinder(d=Carabiner[ID] + 2*ThreadWidth,h=Carabiner[LENGTH]); } } if (Section == "Lower") { translate([0,0,-(Reel[LENGTH]/4 + ShimThick/2 – ThreadThick/2)]) for (i = [0:8]) rotate(i * 360/8) cube([Reel[OD] – 2*ThreadWidth, 2*ThreadWidth, Reel[LENGTH]/2 + ShimThick – ThreadThick],center=true); if (false) { Bars = 7; render() difference() { union() { for (i = [-floor(Bars/2) : floor(Bars/2)]) translate([-Carabiner[ID]/2,i*Carabiner[OD]/Bars,-Carabiner[LENGTH]/4]) cube([Carabiner[ID]/3,2*ThreadWidth,Carabiner[LENGTH]/2],center=true); translate([-Carabiner[ID]/2,0,-ThreadThick/2]) cube([Carabiner[ID]/3,Carabiner[ID],ThreadThick],center=true); } translate([0,0,-Carabiner[LENGTH]]) cylinder(d=Carabiner[ID] + 0*ThreadWidth,h=Carabiner[LENGTH]); } } } } } } //———————- // Build it if (Layout == "Beener") Beener(); if (Layout == "Mount") Mount(); if (Layout == "Reel") Reel(); if (Layout == "BezelMount") BezelMount(); Gap = 25; if (Layout == "Show") { translate([0,0,Gap/2]) Mount("Upper"); translate([0,0,-Gap/2]) Mount("Lower"); color("Green",0.3) Beener(); color("Brown",0.3) Reel(); color("Red",0.3) translate([0,0,-(Reel[LENGTH] + ShimThick)/2]) cylinder(d=Reel[OD],h=ShimThick,center=true,$fn=6*4); } if (Layout == "Build") { translate([(BCD + MountThick)/2,0,0]) rotate(180) Mount("Upper"); rotate([180,0,0]) translate([-(BCD + MountThick)/2,0,0]) Mount("Lower"); } if (Layout == "BuildUpper") Mount("Upper"); if (Layout == "BuildLower") rotate([180,0,0]) Mount("Lower");