Advertisements

Hard Drive Platter Mood Light: Improved Solid Model

An improved version of the 3D printed plastic bits going into the Hard Drive Platter Mood Light:

Hard Drive Mood Light - improved - solid model - Show view

Hard Drive Mood Light – improved – solid model – Show view

The central pillar now has cutouts behind the Neopixel strips so you (well, I) can solder directly to the larger half-pads on the back, plus a boss on the top for better wire management:

Hard Drive Mood Light - improved - Pillar - solid model

Hard Drive Mood Light – improved – Pillar – solid model

I’m not entirely satisfied with the little slots for the strip edges; the resolution limits of 3D printing call for larger openings, but there’s not much meat around those pins up the edge.

The base becomes much larger to hold the Arduino Pro Mini and gains an optional slot to let the programming cable reach the outside:

Hard Drive Mood Light - improved - Base - solid model

Hard Drive Mood Light – improved – Base – solid model

The cap has a boss matching the one atop the pillar:

Hard Drive Mood Light - improved - Cap - solid model

Hard Drive Mood Light – improved – Cap – solid model

Both the cap & base have center features recessed by two thread thicknesses to let their rims apply a slight clamping force on the platters.

Our Larval Engineer says it really needs an internal battery with maybe four hours of runtime, a charging base station (ideally with inductive power transfer), buttons (or, better, a tilt switch / accelerometer) for mode selection, and perhaps a microphone to synchronize lighting effects with music. To my horror, her co-op job seems to have exposed her to Marketeers…

We do, however, agree that the Cap would look better in lathe-turned brass with a non-tarnish clearcoat.

The OpenSCAD source code:

// Hard Drive Platter Mood Light
// Ed Nisley KE4ZNU November 2015

Layout = "Spacers";					// Build Show Pixel LEDString Platters Pillar Spacers TopCap Base

CablePort = true;

ShowDisks = 2;						// number of disks in Show layout

//- Extrusion parameters must match reality!

ThreadThick = 0.25;
ThreadWidth = 0.40;

HoleWindage = 0.2;

Protrusion = 0.1;			// make holes end cleanly

inch = 25.4;

function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);

//----------------------
// Dimensions

ID = 0;
OD = 1;
LENGTH = 2;

Platter = [25.0,95.0,1.27];						// hard drive platters - must match actual thickness!

LEDStringCount = 3;								// number of LEDs on each strip
LEDStripCount = 4;								// number of strips (verify locating pin holes & suchlike)

WireSpace = 1.0;								// allowance for wiring along strip ends

Pixel = [13.0, 1000 / 144, 0.6];				// smallest indivisible unit of LED strip
PixelMargin = [1.0, 1.0, 2.0];					// LED and circuitry atop the strip

BeamAngle = 120;								// LED viewing angle
BeamShape = [
	[0,0],
	[Platter[OD]*cos(BeamAngle/2),-Platter[OD]*sin(BeamAngle/2)],
	[Platter[OD]*cos(BeamAngle/2), Platter[OD]*sin(BeamAngle/2)]
];

PillarSides = 12*4;

PillarCore = Platter[ID] - 2*(Pixel[2] + PixelMargin[2] + 2.0);		// LED channel distance across pillar centerline
PillarLength = LEDStringCount*Pixel[1] + Platter[LENGTH];
echo(str("Pillar core size: ",PillarCore));
echo(str("      ... length:"),PillarLength);

PCB = [34.5,17.5,1.6];								// Arduino Pro Mini (or whatever) PCB size
PCBClearTop = 5.0;
PCBClearBot = 5.0;
PCBHeight = PCB[2] + PCBClearBot + PCBClearTop;

PCBRadius = sqrt(pow(Platter[ID]/2 + PCB[1],2) + pow(PCB[0]/2,2));
echo(str("PCB Corner radius: ",PCBRadius));

CoaxConn = [7.8,11.2,5.0];							// power connector 

Cap = [Platter[ID] + 4.0,Platter[ID] + 4.0 + 10*2*ThreadWidth,2*WireSpace + 6*ThreadThick];		// cap over top of pillar
CapSides = 8*4;

BaseClearHeight = max(PCBHeight,CoaxConn[OD]);

Base = [2.0 + 2*PCBRadius,2.0 + 2*PCBRadius + CoaxConn[LENGTH],BaseClearHeight + 6*ThreadThick];
BaseSides = 8*4;

Screw = [1.5,2.0,20.0];							// screws used to secure cap & pillar

Spacer = [Platter[ID],(Platter[ID] + 2*8),(Pixel[1] - Platter[LENGTH])];
echo(str("Spacer  OD: ",Spacer[OD]));
echo(str(" ... thick:",Spacer[LENGTH]));

LEDStripProfile = [
	[0,0],
	[Pixel[0]/2,0],
	[Pixel[0]/2,Pixel[2]],
	[(Pixel[0]/2 - PixelMargin[0]),Pixel[2]],
	[(Pixel[0]/2 - PixelMargin[0]),(Pixel[2] + PixelMargin[2])],
	[-(Pixel[0]/2 - PixelMargin[0]),(Pixel[2] + PixelMargin[2])],
	[-(Pixel[0]/2 - PixelMargin[0]),Pixel[2]],
	[-Pixel[0]/2,Pixel[2]],
	[-Pixel[0]/2,0]
];

//----------------------
// 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);
}

//- Locating pin hole with glue recess
//  Default length is two pin diameters on each side of the split

PinOD = 1.70;

module LocatingPin(Dia=PinOD,Len=0.0) {
	
	PinLen = (Len != 0.0) ? Len : (4*Dia);
	
	translate([0,0,-ThreadThick])
		PolyCyl((Dia + 2*ThreadWidth),2*ThreadThick,4);

	translate([0,0,-2*ThreadThick])
		PolyCyl((Dia + 1*ThreadWidth),4*ThreadThick,4);
		
	translate([0,0,-(PinLen/2 + ThreadThick)])
		PolyCyl(Dia,(PinLen + 2*ThreadThick),4);

}
//----------------------
// Pieces

//-- LED strips

module OnePixel() {
	
	render()
		rotate([-90,0,0]) rotate(180)				// align result the way you'd expect from the dimensions
			difference() {
				linear_extrude(height=Pixel[1],convexity=3)
					polygon(points=LEDStripProfile);
				translate([-Pixel[0]/2,Pixel[2],-PixelMargin[0]])
					cube([Pixel[0],2*PixelMargin[2],2*PixelMargin[0]]);
				translate([-Pixel[0]/2,Pixel[2],Pixel[1]-PixelMargin[0]])
					cube([Pixel[0],2*PixelMargin[2],2*PixelMargin[0]]);
			}
}

module LEDString(n = LEDStringCount) {
	
	for (i=[0:n-1])
		translate([0,i*Pixel[1]])
//			resize([0,Pixel[1] + 2*Protrusion,0])
				OnePixel();
}

//-- Stack of hard drive platters

module Platters(n = LEDStringCount + 1) {
	
	color("gold",0.4)
	for (i=[0:n-1]) {
		translate([0,0,i*Pixel[1]])
			difference() {
				cylinder(d=Platter[OD],h=Platter[LENGTH],center=false,$fn=PillarSides);
				cylinder(d=Platter[ID],h=3*Platter[LENGTH],center=true,$fn=PillarSides);
			}
	}
}

//-- Pillar holding the LED strips

module Pillar() {
	
	difflen = PillarLength + 2*Protrusion;
	
//	render(convexity=5)
	difference() {
		linear_extrude(height=PillarLength,convexity=4)
			difference() {
				rotate(180/(12*4))
					circle(d=Platter[ID] - 1*ThreadWidth,$fn=PillarSides);
				
				for (i=[0:LEDStripCount-1]) 					// clearance for LED beamwidth, may not actually cut surface
					rotate(i*360/LEDStripCount)
						translate([PillarCore/2,0,0])
							polygon(points=BeamShape);
							
				for (i=[0:LEDStripCount-1])						// LED front clearance
					rotate(i*360/LEDStripCount)
						translate([(PillarCore/2 + Pixel[2]),(Pixel[0] - 2*PixelMargin[0])/2])
							rotate(-90)
								square([Pixel[0] - 2*PixelMargin[0],Platter[ID]]);

			}
			
		for (i=[0:LEDStripCount-1])								// LED strip slots
			rotate(i*360/LEDStripCount)
				translate([PillarCore/2,0,-Protrusion])
					linear_extrude(height=difflen,convexity=2)
						rotate(-90)
							polygon(points=LEDStripProfile);
		
		difference() {											// wiring recess on top surface, minus boss
			for (i=[0,90])
				rotate(i)
					translate([0,0,(PillarLength - (WireSpace/2 - Protrusion))])
						cube([(PillarCore + 2*Protrusion),Pixel[0] - 2*PixelMargin[0],WireSpace],center=true);
			cylinder(d=3*Screw[OD],h=PillarLength + Protrusion,$fn=CapSides);
		}
							
		for (i=[0:LEDStripCount-1])								// wiring recess on bottom surface
			rotate(i*90)
				translate([PillarCore/2 - (WireSpace - Protrusion)/2,0,WireSpace/2 - Protrusion])
					cube([WireSpace + Protrusion,Pixel[0] - 2*PixelMargin[0],WireSpace],center=true);
							
		for (j=[0:LEDStringCount-1])							// platter spacer alignment pins
			for (i=[0:LEDStripCount-1])
				rotate(i*360/LEDStripCount + 180/LEDStripCount)
					translate([(Platter[ID] - 1*ThreadWidth)/2,0,(j*Pixel[1] + Pixel[1]/2 + Platter[LENGTH]/2)])
						rotate([0,90,0])
							rotate(45)
								LocatingPin();
								
		translate([0,0,-Protrusion])							// central screw hole
			rotate(180/4)
				PolyCyl(Screw[ID],difflen,4);
		
		if (false)
		for (i=[-1,1])											// vertical wire channels
			rotate(i*360/LEDStripCount + 180/LEDStripCount)
				translate([PillarCore/2 - 2.0,0,-Protrusion])
					PolyCyl(2.0,difflen,4);
					
		for (i=[-1,1])											// locating pins
			rotate(i*360/LEDStripCount - 180/LEDStripCount)
				translate([PillarCore/2 - 2.0,0,0])
					LocatingPin();
	}
}

//-- Spacers to separate platters

module Spacers() {

	difference() {
		linear_extrude(height=Spacer[LENGTH],convexity=4)
			difference() {
				rotate(180/PillarSides)
					circle(d=Spacer[OD],$fn=PillarSides);
				
				for (i=[0:LEDStripCount-1]) 					// clearance for LED beamwidth, may not actually cut surface
					rotate(i*360/LEDStripCount)
						translate([PillarCore/2,0,0])
							polygon(points=BeamShape);
							
				for (i=[0:LEDStripCount-1])						// LED front clearance
					rotate(i*360/LEDStripCount)
						translate([(PillarCore/2 + Pixel[2]),(Pixel[0] - 2*PixelMargin[0])/2])
							rotate(-90)
								square([Pixel[0] - 2*PixelMargin[0],Platter[ID]]);

							
				rotate(180/PillarSides)
					circle(d=Spacer[ID],$fn=PillarSides);		// central pillar fits in the hole
			}
			
		for (i=[0:LEDStripCount-1])
			rotate(i*360/LEDStripCount + 180/LEDStripCount)
				translate([Platter[ID]/2,0,(Pixel[1] - Platter[LENGTH])/2])
					rotate([0,90,0])
						rotate(45)
							LocatingPin();

	}
}

//-- Cap over top of pillar

module TopCap() {
	
	difference() {
		cylinder(d1=(Cap[OD] + Cap[ID])/2,d2=Cap[OD],h=Cap[LENGTH],$fn=CapSides);		// outer lid
		
		translate([0,0,-Protrusion])
			PolyCyl(Screw[ID],Cap[LENGTH] + WireSpace + Protrusion,4);					// screw hole
		
		translate([0,0,Cap[LENGTH] - 2*WireSpace])
			difference() {
				cylinder(d=Cap[ID],h=2*Cap[LENGTH],$fn=CapSides);						// cutout
				cylinder(d=3*Screw[OD],h=Cap[LENGTH],$fn=CapSides);						// boss
			}
		
		translate([0,0,Cap[LENGTH] - 2*ThreadThick])
			cylinder(d=Cap[ID]/2,h=2*ThreadThick + Protrusion,$fn=CapSides);			// recess boss
	}
}

//-- Base below pillar

module Base() {
	
	SideWidth = 0.5*Base[OD]*sin(180/BaseSides);						// close enough
	
	difference() {
		union() {
			difference() {
				cylinder(d=Base[OD],h=Base[LENGTH],$fn=BaseSides);			// outer base

				translate([0,0,6*ThreadThick])								// main cutout
					cylinder(d=Base[ID],h=Base[LENGTH],$fn=BaseSides);
					
				rotate(180/BaseSides)
					translate([0,0,Base[LENGTH] - BaseClearHeight/2]) 					// power connector hole
						rotate([90,0,0]) rotate(180/8)
							PolyCyl(CoaxConn[ID],Base[OD],8);
			}
			
			translate([0,0,Base[LENGTH]/2])									// recess pillar support below rim
				cube([PillarCore,PillarCore,Base[LENGTH] - 2*ThreadThick],center=true);
		}

		for (i=[0:LEDStripCount-1])											// wiring recesses
			rotate(i*90)
				translate([PillarCore/2 - (WireSpace - Protrusion)/2,0,Base[LENGTH] - 4*WireSpace/2])
					cube([WireSpace + Protrusion,PillarCore - 4*WireSpace,4*WireSpace],center=true);
		
		translate([0,0,-Protrusion])
			PolyCyl(Screw[ID],2*Base[LENGTH],4);						// screw hole
			
		translate([0,0,-Protrusion])									// screw head recess
			rotate(180/8)
				PolyCyl(8.5,Base[LENGTH] - 3.0 + Protrusion,8);
			
		for (i=[-1,1])													// locating pins
			rotate(i*360/LEDStripCount - 180/LEDStripCount)
				translate([PillarCore/2 - 2.0,0,Base[LENGTH] - ThreadThick])
					LocatingPin();
					
		if (CablePort)
			translate([0,Platter[ID]/2 + PCB[1],Base[LENGTH] - 3.0 + Protrusion])
				rotate(-90)
					cube([PCB[1],Base[OD],3.0]);

	}
		
}

//----------------------
// Build it

if (Layout == "Pixel")
	OnePixel();
	
if (Layout == "LEDString")
	LEDString(LEDStringCount);
	
if (Layout == "Platters")
	Platters(LEDStringCount + 1);
	
if (Layout == "Pillar")
	Pillar(LEDStringCount);
	
if (Layout == "TopCap")
	TopCap();
		
if (Layout == "Base")
	Base();

if (Layout == "Spacers")
	Spacers();
	
if (Layout == "Show") {
	Pillar();

	for (i=[0:LEDStripCount-1])											// LED strips
		rotate(i*360/LEDStripCount)
			translate([PillarCore/2,0,Platter[LENGTH]/2])
				rotate([90,0,90])
					color("lightblue") LEDString();
	if (true)	
	for (j=[0:max(1,ShowDisks - 2)])									// spacers
		translate([0,0,(j*Pixel[1] + Platter[LENGTH])])
			color("cyan") Spacers();
							
	for (j=[0:max(2,ShowDisks - 2)])										// spacer alignment pins
		for (i=[0:LEDStripCount-1])
			rotate(i*360/LEDStripCount + 180/LEDStripCount)
				translate([(Platter[ID] - 1*ThreadWidth)/2,0,(j*Pixel[1] + Pixel[1]/2 + Platter[LENGTH]/2)])
					rotate([0,90,0])
						rotate(45)
							 color("Yellow",0.25) LocatingPin(Len=4);
	translate([0,0,PillarLength + 3*Cap[LENGTH]])
		rotate([180,0,0])
			TopCap();
	
	translate([0,0,-2*Base[LENGTH]])
		Base();
		
	if (ShowDisks > 0)	
		Platters(ShowDisks);
	
}

// Ad-hoc build layout

if (Layout == "Build") {
	if (true)
		Pillar();
	
	if (true)
		translate([0,(Platter[ID] + Cap[OD])/2,0])
			TopCap();
			
	if (true)
		translate([0,-(Platter[ID] + Base[OD])/2,0])
			Base();
				
	Ybase = Spacer[OD] * (LEDStringCount%2 ? (LEDStringCount - 1) : (LEDStringCount - 2)) / 4;
	if (true)
		for (i=[0:LEDStringCount])										// build one extra set of spacers!
			translate([(i%2 ? 1 : -1)*(Spacer[OD] + Base[OD])/2,		// alternate X sides to shrink Y space
					(i%2 ? i-1 : i)*Spacer[OD]/2 - Ybase,				// same Y for even-odd pairs in X
					0])
				Spacers();
}
Advertisements

, ,