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:

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:

Which I immediately replaced with brass, because Steampunk:

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:

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:

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:

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:

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:

Slic3r produced the rest of the support material for the carabiner exit slot:

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:

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:

I hope she likes it …
The OpenSCAD source code as a GitHub Gist:
// 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"); |
One more little improvement. A brass exit bushing will keep the lanyard from fraying or cutting into the plastic housing, the first of which is the dominant failure mode for my lanyards (the second not because the housing mine come with have a small stainless bushing at the exit.)
Oh, and, very nice work!
I think the original eyelet served both purposes, although (IMO) did neither very well. An additional bushing / sleeve / whatever seems like a great idea: when this holder wears out, it’s on the list … [grin]
“Acquaintance”, hm? Is that all? :)
Obviously, your browser failed to render the metonymy tag. [grin]