Posts Tagged M2

Slic3r vs. Sequential 3D Printing

The tiny posts on the fencing helmet ear grommet produced a remarkable amount of PETG hair, because the nozzle had to skip between four separate pieces on the platform at each layer:

So I told Slic3r to build each part separately:

Fencing helmet grommet - separate builds - first attempt

Fencing helmet grommet – separate builds – first attempt

Due to absolutely no forethought or planning on my part, that actually worked. Slic3r defines a cylindrical keep-out zone around the nozzle that I set to 15 mm radius and 25 mm height, but those numbers are completely wrong for the M2, particularly with a V4 hot end.

To the rear, the nuts & bolts along the bottom of the X gantry sit 5 mm above the V4 nozzle, with the relaxed actuator on my re-relocated Z-axis home switch at Z=+1 mm:

V4 PETG - extruder priming

V4 PETG – extruder priming

To the front, the bed fan doesn’t sit much higher:

M2 V4 Extruder - 24 V fans

M2 V4 Extruder – 24 V fans

As it turned out, the front washers built first, sitting there in front of the gantry and behind the fan, the rear washers appeared last, and Everything Just Worked.

However, even though the M2’s layout won’t allow for automated layout, I figured I could do it manually by building the parts from front to rear:

Fencing Helmet Ear Grommet - Slic3r layout

Fencing Helmet Ear Grommet – Slic3r layout

That way, the already-built parts never pass under the gantry / switch. For particularly tall parts, I could remove / relocate the bed fan to clear the already-built parts as they appear.

Come to find out that Slic3r, for whatever reason, doesn’t build the parts in the order you’d expect from the nice list on the far right side of the screen:

Sequential Build Order - Slic3r vs Pronterface

Sequential Build Order – Slic3r vs Pronterface

Worse, the Slic3r 3D preview shows the threads by layer (which is what you’d expect), rather than by object for sequential builds:

Slic3r - sequential preview vs build order

Slic3r – sequential preview vs build order

I don’t know how you’d force-fit a four-dimensional preview into the UI, so I won’t complain at all.

There’s no way to tell which part will build first; selecting the part will highlight its entry in the list (and vice versa), but the order of appearance in that list doesn’t tell you where the G-Code will appear in the output file. That’s not a problem for extruders with a keep-out volume that looks like a cylinder, so there’s no reason for Slic3r to do it any differently: it will manage the extruder position to clear all the objects in any order.

The Pronterface preview creates the objects by reading the G-Code file and displaying the threads in order, so, if you’re quick and it’s slow, you can watch the parts appear in their to-be-built order. The detailed preview (in the small window on the right in the screenshot) does show the parts in the order they will be built as you scroll upward through the “layers”, which is the only way you can tell what will happen.

So doing sequential builds requires iterating through these steps until the right answer appears:

  •  Add all objects separately to get each one as a separate line in the list to the right
    • Using the More option to duplicate objects produces multiple objects per line = Bad Idea
  • Arrange objects in a line from front to back
  • Export G-Code file
  • Load G-Code file into Pronterface
  • Pop up the Pronterface layer preview, scroll upward to show build order, note carefully
  • Rearrange parts in Slic3r accordingly

That’s do-able (note the different order from the Slic3r preview):

Fencing helmet grommet - manual sequential build

Fencing helmet grommet – manual sequential build

But it’s tedious and enough of a pain that it probably makes no sense for anything other than parts that you absolutely can’t build any other way.

In this case, completing each of the bottom washers separately eliminated all of the PETG hair between the small pegs. The upper washers still had some hair inside the inner cylinder, but not much. If you were fussy, you could suppress that by selecting “Avoid crossing perimeters”, at the cost of more flailing around in the XY plane.

All those spare grommets will make a good show-n-tell exhibit…

,

Leave a comment

Blue Gauntlet Fencing Helmet Ear Grommet

Our Larval Engineer practiced fencing for several years, learning the fundamental truth that you should always bring a gun to a knife fight:

Fencing - taking a hit

Fencing – taking a hit

It’s time to pass the gear along to someone who can use it, but we discovered one of the ear grommets inside the helmet had broken:

Blue Gauntlet M003-BG Helmet - broken ear grommet

Blue Gauntlet M003-BG Helmet – broken ear grommet

The cylinder in the middle should be attached to the washer on the left, which goes inside the helmet padding. It’s a tight push fit inside the washer on the right, which goes on the outside of the padding. Ridges along the cylinder hold it in place.

Being an injection-molded polyethylene part, no earthly adhesive or solvent will bother it, soooo… the solid model pretty much reproduces the original design:

Fencing Helmet Ear Grommet - show

Fencing Helmet Ear Grommet – show

The top washer goes inside the padding against your (well, her) ear, so I chamfered the edges sorta-kinda like the original.

There are no deliberate ridges on the central cylinder, but printing the parts in the obvious orientation with no additional clearance makes them a very snug push fit and the usual 3D printing ridges work perfectly; you could apply adhesive if you like. The outside washer has a slight chamfer to orient the post and get it moving along.

The posts keep the whole affair from rotating, but I’m not sure they’re really necessary.

Printing a pair doesn’t take much longer than just one:

Fencing Helmet Ear Grommet - build

Fencing Helmet Ear Grommet – build

It doesn’t look like much inside the helmet:

Blue Gauntlet M003-BG - replacement ear grommet - installed

Blue Gauntlet M003-BG – replacement ear grommet – installed

The OpenSCAD source code as a gist from Github:

,

6 Comments

Kenmore Progressive Vacuum Cleaner vs. Classic Electrolux Dust Brush

Vacuum cleaner dust brushes, separated by millimeters and decades:

Kenmore vs adapted Electrolux dust brushes

Kenmore vs adapted Electrolux dust brushes

The bulky one on the left came with our new Kenmore Progressive vacuum cleaner. It’s fine for dust on a flat horizontal or vertical surface and totally useless for dust on actual objects. It’s supposed to snap around the handle at the end of the cleaner’s flexy hose, where it helps make the entire assembly too large and too clumsy, or on the end of the “wand”, where it’s at the wrong angle. The bonus outer shell slides around the stubby bristles in the unlikely event they’re too long for the flat surface at hand.

The brush on the right emerged from the Box o’ Electrolux Parts that Came With The House™, must be half a century old, and consists of a cast aluminum lump with various holes milled into it, adorned with luxuriously long and flexible horsehair. Suffice it to say they don’t make ’em like that any more. Heck, they probably don’t make horses with hair like that any more, either.

The blue plastic adapter atop the aluminum ball looks like you’d expect by now:

Electrolux Brush Adapter

Electrolux Brush Adapter

The short snout fits neatly into the space available inside the ball. The abrupt ledge at the top of the snout, of course, didn’t work well; I rushed the design for a show-n-tell.

The OpenSCAD source code (as a Github gist) bevels that ledge and tweaks the interior air channel a bit:

That’s  supposed to prevent the WordPress post editors from destroying the formatting…

,

2 Comments

Gorilla Glue: Cured in the Bottle

So the dishwasher ate another rack protector, which happens a few times a year. I’m getting low on spares, so maybe it’s time to run off a few in cyan PETG to see if the cute support structure will still be removable:

Dishwasher rack protector - support model

Dishwasher rack protector – support model

Anyhow, this time I used urethane glue, because the last of the acrylic caulk went into another project. I store the Gorilla Glue bottle upside-down so the entire top doesn’t cure solid, but:

Gorilla Glue - cured in bottle

Gorilla Glue – cured in bottle

Usually, it’s just cured in the snout. This time, the layer across the bottom was a few millimeters thick and the glue below seemed rather thick. I tossed the solid lump, slobbered a dab of thick goo on the dishwasher rack, jammed the new protector in place, replaced the cap, and declared victory.

That’s why I no longer buy that stuff in The Big Bottle…

 

,

12 Comments

Whirlpool Refrigerator Drawer Strut Tab Replacement

The tab supporting the strut with the center slides for the lower drawers in our Whirlpool refrigerator broke of its own accord. This is a problem of long standing, somewhat exacerbated by the fact that lifting the strut will break the tab without much effort at all, but this time the drawers pulled the strut downward hard enough to not only break the tab, but also tear the small tabs that align the bracket right out of the frame.

While pondering the problem, I glued the broken chunk back into the frame:

Refrigerator Drawer Strut - clamping front plate

Refrigerator Drawer Strut – clamping front plate

We agreed that, after nigh onto two decades, it would be OK to swap the position of the two drawers, so as to let the strut use the undamaged part of the frame seen below. Presumably, we’ll eventually get used to having the apples on the right and the veggies on the left.

But it was obvious Something Serious Had To Be Done about the tab.

The tab should align like this inside the frame:

Refrigerator Drawer Strut Tab - alignment

Refrigerator Drawer Strut Tab – alignment

The rightmost part of the tab rests atop a U-shaped metal bar that also supports and stiffens the entire front of the frame, but cantilevering the weight of both drawers on that extended tab overpowered my last attempt at making a glue joint. Soooo, I decided to build a (wait for it …) 3D printed part that screws firmly to the front of the strut.

The first step involved introducing the strut to Mr Belt Sander to strip the wreckage of the OEM tab from the front end (visible through the opening) and smooth things out, then measuring the remainder. The locating flange inside the frame almost defeated me, but eventually I found a tool that fit inside the strut opening and around the flange:

Refrigerator Drawer - measuring flange

Refrigerator Drawer – measuring flange

Which produced a sketch of the key dimensions:

Refrigerator Drawer Strut - Dimension Doodles

Refrigerator Drawer Strut – Dimension Doodles

Which became an extruded polygon with a few holes punched in the side:

Refrigerator Shelf Strut Tab - solid model

Refrigerator Shelf Strut Tab – solid model

Building it standing up wraps the plastic threads around the entire tab and stacks the layers along the length of the tab. Doing it lying down in the obvious hump-up orientation would put the layers parallel to the bottom surface, where they can pull apart under load.

The key innovation here involves being willing to assemble the tab to the strut in situ, without insisting it fit through the frame opening and be more-or-less easily removable. That let me bulk up the tab to match the end of the strut, fill the entire frame opening with plastic, and get enough bulk for a pair of 4-40 screws that, being loaded in shear, should withstand the weight of all those fruits & veggies in the drawers.

The screws simply thread into the holes in the tab, without benefit of tapping. The OpenSCAD code now includes a pair of nut traps, but I’m hoping they won’t be needed.

The new tab really does fill the space available:

Refrigerator Drawer Strut - new tab in place

Refrigerator Drawer Strut – new tab in place

The OpenSCAD code now moves the notch half a millimeter further away from the strut to center it over the ridge. What’s not obvious is how the frame slants toward the tab over the U-bar: the tab just barely clears and probably should have a tapered nose. You may add that if you like.

The U-shaped bar constrains the tab pretty firmly and supports the end, which should now be plump enough to withstand the forces involved. The screws sit horizontally with the frame installed and can’t pull out, which is why I think they can get along without nut traps.

It’s built in cyan PETG with three perimeter threads and 40% 3D Honeycomb fill, making it essentially a solid block of plastic; it’ll be interesting to see what fails next.

The OpenSCAD source code, which I hammered out in a white-hot fury:

// Refrigerator Shelf Strut Tab
// Ed Nisley KE4ZNU December 2015

//- 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

TabSize = [20.0,12.0,35.0];							// length from bracket, height, width along front

SlotSize = [3.0,7.0];
SlotX = 7.0;

TabProfile = [
	[0,0],
	[12,0],	[12,7.0],
	[TabSize[0],7.0], [TabSize[0],TabSize[1]],
	[SlotX + SlotSize[0]/2,TabSize[1]],
	[SlotX + SlotSize[0]/2,5.0], [SlotX - SlotSize[0]/2,5.0],
	[SlotX - SlotSize[0]/2,TabSize[1]],
	[0,TabSize[1]]
];

ScrewY = 7.0;
ScrewOC = 25.0;
ScrewOD = 2.5;

NutOD = 6.6;					// across flats
NutThick = 2.5;

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

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

difference() {
	linear_extrude(height=TabSize[2],convexity=4)
		polygon(points=TabProfile);
	
	for (i=[-1,1]) {
		translate([-Protrusion,ScrewY,i*ScrewOC/2 + TabSize[2]/2])
			rotate([0,90,0])
				rotate(180/6)
					PolyCyl(ScrewOD,SlotX,6);
		translate([SlotX - SlotSize[0]/2 - NutThick - Protrusion,ScrewY,i*ScrewOC/2 + TabSize[2]/2])
			rotate([0,90,0])
				rotate(180/6)
					PolyCyl(NutOD,NutThick + SlotSize[0],6);
	}
}

Maybe that’ll last until we finally scrap out the refrigerator…

,

1 Comment

Kenmore 158: Presser Foot Screw Shrink

Mary started doing “ruler quilting” that involves sewing seams aligned with templates, only to find that the thumbscrew holding the (modified) presser foot obscures the view to the left of the needle:

Kenmore Model 158 - OEM Presser Foot Screw

Kenmore Model 158 – OEM Presser Foot Screw

The screw looked to be 6-32 and I wanted to use a socket head cap screw, but thread turns out to be 6-40. Having previously bought the Brownell’s Fillister Head Screw Assortment specifically to solve that problem, all I had to do was cut the screw to length:

Kenmore Model 158 - Small Presser Foot Screw

Kenmore Model 158 – Small Presser Foot Screw

The washer epoxied to the screw provides a bit more bearing surface.

Rather than putz with a screwdriver, this handle locates itself around the screw head; turn until the blade clicks into the screw slot, then tighten or loosen as needed:

Kenmore Model 158 - Presser Foot - Driver and Screw

Kenmore Model 158 – Presser Foot – Driver and Screw

The chubby driver handle descends directly from the Sherline tommy bar handles and four-jaw chuck speed keys:

Presser Foot Screw Driver - solid model

Presser Foot Screw Driver – solid model

The slot holds a chunk of spring steel (barely visible in the driver’s snout in group photo above) that accounts for the fat shaft around the screw head:

Presser Foot Screw Driver - top - Slic3r

Presser Foot Screw Driver – top – Slic3r

I think the shaft could be a few millimeters narrower, but a bit of meat around the ends of the blade will support it against the torque.

The screw head slot is about 1 mm and the blade is 0.75 mm. I chopped the blade to fit by whacking the spring with a poorly tempered cold chisel, then flexing across the impact line until it broke. That chisel needed sharpening anyhow.

A dab of epoxy along the slot edges holds the blade in place. I inserted it flush with the top of the socket, then lined up the screw and pushed, with the steel bottomed out in the screw head and riding down for a perfect fit.

Then it’s all good!

The OpenSCAD source code:

// Presser Foot Screw Driver for Kenmore Model 158
// Ed Nisley - KE4ZNU - December 2015

use <knurledFinishLib_v2.scad>

//- Extrusion parameters must match reality!
//  Print with 2 shells and 3 solid layers

ThreadThick = 0.20;
ThreadWidth = 0.40;

HoleWindage = 0.3;			// extra clearance to improve hex socket fit

Protrusion = 0.1;			// make holes end cleanly

inch = 25.4;

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

SocketDia = 5.75;				// generous fit on 6-40 fillister screw head
SocketDepth = 3.2;

Blade = [9.0,1.0,ceil(SocketDepth + 5)];		// inserted metal driver blade
echo(str("Blade: ",Blade));

ShaftDia = 1.5*Blade[0];		// un-knurled section diameter
ShaftLength = 10.0;				//  ... length

KnurlLen = 10.0;				// length of knurled section
KnurlDia = 18.0;				//   ... diameter at midline of knurl diamonds
KnurlDPNom = 30;				// Nominal diametral pitch = (# diamonds) / (OD inches)

DiamondDepth = 1.0;				//   ... depth of diamonds
DiamondAspect = 2;				// length to width ratio

KnurlID = KnurlDia - DiamondDepth;		// dia at bottom of knurl

NumDiamonds = ceil(KnurlDPNom * KnurlID / inch);
echo(str("Num diamonds: ",NumDiamonds));

NumSides = 4*NumDiamonds;		// 4 facets per diamond

KnurlDP = NumDiamonds / (KnurlID / inch);				// actual DP
echo(str("DP Nom: ",KnurlDPNom," actual: ",KnurlDP));

DiamondWidth = (KnurlID * PI) / NumDiamonds;

DiamondLenNom = DiamondAspect * DiamondWidth;					// nominal diamond length
DiamondLength = KnurlLen / round(KnurlLen/DiamondLenNom);		//  ... actual 

TaperLength = 0.50*DiamondLength;

KnobOAL = 2*TaperLength + KnurlLen + ShaftLength;

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

module ShowPegGrid(Space = 10.0,Size = 1.0) {

  Range = floor(50 / Space);

	for (x=[-Range:Range])
	  for (y=[-Range:Range])
		translate([x*Space,y*Space,Size/2])
		  %cube(Size,center=true);
}


//- Build it

ShowPegGrid();

difference() {
	union() {
		render(convexity=10)
		translate([0,0,TaperLength])			// knurled cylinder
			knurl(k_cyl_hg=KnurlLen,
				  k_cyl_od=KnurlDia,
				  knurl_wd=DiamondWidth,
				  knurl_hg=DiamondLength,
				  knurl_dp=DiamondDepth,
				  e_smooth=DiamondLength/2);
		color("Orange")							// lower tapered cap
		cylinder(r1=ShaftDia/2,
					r2=(KnurlDia - DiamondDepth)/2,
					h=(TaperLength + Protrusion),
					$fn=NumSides);
		color("Orange")							// upper tapered cap
		translate([0,0,(TaperLength + KnurlLen - Protrusion)])
			cylinder(r2=ShaftDia/2,
					r1=(KnurlDia - DiamondDepth)/2,
					h=(TaperLength + Protrusion),
					$fn=NumSides);
		color("Moccasin")						// cylindrical extension
		translate([0,0,(2*TaperLength + KnurlLen - Protrusion)])
			cylinder(r=ShaftDia/2,h=(ShaftLength + Protrusion),$fn=NumSides);

	}
	
	translate([0,0,(KnobOAL - SocketDepth + Protrusion)])
		PolyCyl(SocketDia,(SocketDepth + Protrusion),8);	// screw head socket
		
	translate([0,0,KnobOAL - (Blade[2] - Protrusion)/2])
		cube(Blade + [0,0,Protrusion],center=true);
}

, ,

Leave a comment

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

, ,

Leave a comment