Given the vanishingly small depth of field provided by a cheap USB camera peering through the stereo zoom microscope, I’ve always wanted a better way of moving objects by small increments. The rehabilitated micropositioner didn’t have the right orientation or end effector:

So I rearranged the axis slides and added a small table:

That frees up the magnetic base and husky angle bracket, plus a few odds & ends, for future adventures.
The clear base is a random chunk of acrylic, bandsawed to the proper length, then tediously squared and drilled on the Sherline:

I briefly thought of printing the base, but came to my senses: there are better ways to make big flat surfaces.
The little aluminum table has a nubbly spray coating that came straight from the heap and looks surprisingly good after squaring & drilling. The X axis block puts it below the platform and one screw head above the desk when the Y axis arm sits flat on the acrylic base.
One solid model view arranges things in more-or-less the proper layout to check the alignment:

The build layout reduces the platform space:

You’re looking at four hours of PETG print time at 0.2 mm layer thickness with 15% infill and Hilbert Curve surfaces.
All of the screws have UNF fine-pitch threads (4-48, 6-40, 8-36, stuff like that), so the solid model includes the spacing required to reuse the original screws: those big holes in the Y axis arm end in little clearance holes for the tiny screws. Some of the screws bottom out with barely two millimeters of thread engagement in the slides, while others could jam against the racks. I didn’t want to cut that many screws from my Brownell’s gun screw assortment unless I absolutely had to. So far, so good.
I spent quite a while doodling the layout to convince myself that it would actually work:

Memo to self: Next time, use a larger scale!
Although the whole lashup works as intended, those metal hunks are way too heavy for the plastic block that fits between the Z axis drive pillar and the X axis slide: that long Y axis arm drooped toward the front by about 5 mm. A small shim raised the front of the Z axis footprint enough to level the arm, but I think the right answer is a metal upright with a bigger footprint that spreads the load.
All that mass hanging out in mid-air turns the plastic pieces into springs: you can’t keep your fingers on the knobs. Fortunately, everything returns to the same position after you release the knob, so it’s easy to move in precise increments if you close your eyes until the view settles down.
There’s a reason optical equipment uses cast iron, steel, and brass… but I’ll settle for plastic.
The OpenSCAD source code as a GitHub gist:
// Microscope Stage Positioner | |
// Ed Nisley KE4ZNU January 2016 | |
Layout = "Build"; // Show Build | |
// Base ZStand YMount XMount | |
Gap = 0.0; | |
//- 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 | |
SlipFit = 0.1; | |
ZDrive = [26.0,19.6,75.0]; // stationary part of Z drive | |
ZDriveOffset =[0,0,22.0]; // left front corner of stationary Z base | |
ZWall = 4.0; // thickness of edge wrapped around Z columns | |
YStageBlock = [25.0,61.0,17.0]; // Y stage mount + slide | |
YStageOffset = [-6.0,4.0,0.0]; // offset to inner corner of Y stage holder | |
YArm = [10.0,93.0,17.0]; // mount to stationary part of Y stage | |
ZStage = [24.0,9.7,85.0]; // moving part of Z drive | |
ZYArm = [(2*ZWall + ZStage[0]),10.0,YArm[2]]; // attaches to ZStage, same thickness as YArm | |
XStageBlock = [25.0,20.0,12.0]; // X stage mount + slide | |
XStageOffset = [-95.0,-15.0,-26]; // offset to rear left bottom corner of X stage slide | |
XTray = [25,25,5]; // X tray attached to bottom of X 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); | |
} | |
//-- Z Stand | |
module ZStand() { | |
Holes = [12.0,41.5,68.0]; | |
HoleOD = 3.5; | |
HolesOC = 15.0; | |
echo(str("Z Stand holes OC: ",HolesOC)); | |
ZPlate = 6.0; // thickness of Z plate = max screw grab distance | |
ZStandWrap = 2.0; // length of edge wrapped around Z column | |
MaxY = 9.0; | |
MinY = -14.0; | |
difference() { | |
union() { | |
linear_extrude(height=ZDriveOffset[2]) | |
polygon(points=[ | |
[-ZWall,MaxY], // limited by Z slide rack | |
[ZDrive[0] + ZWall,MaxY], | |
[ZDrive[0] + ZWall,MinY], // limited by X slide rack | |
[-ZWall,MinY] | |
]); | |
linear_extrude(height=(ZDrive[2] + ZDriveOffset[2]),convexity=4) | |
polygon(points=[ | |
[-SlipFit,0], | |
[ZDrive[0] + SlipFit,0.0], | |
[ZDrive[0] + SlipFit,ZStandWrap], | |
[ZDrive[0] + ZWall,ZStandWrap], | |
[ZDrive[0] + ZWall,-ZPlate], | |
[-ZWall,-ZPlate], | |
[-ZWall,ZStandWrap], | |
[-SlipFit,ZStandWrap] | |
]); | |
} | |
for (i = [0:len(Holes) - 1]) // holes along Z stand | |
translate([ZDrive[0]/2,ZDrive[1]/2,(Holes[i] + ZDriveOffset[2])]) | |
rotate([90,0,0]) | |
PolyCyl(HoleOD,ZDrive[1]); | |
for (i = [-1,1]) // mounting screw holes | |
translate([i*HolesOC/2 + ZDrive[0]/2, // center the holes from side to side | |
(MaxY + MinY)/2, // moby hack to put holes on midline | |
-Protrusion]) | |
PolyCyl(3.5,0.75*ZDriveOffset[2],6); | |
} | |
} | |
//-- Y Mounting arm | |
// Polygon origin at inner corner nearest the Z stand column | |
module YMount() { | |
YHoles = [12.0,48.0,84.0]; // mounting holes along Y stage arm, from outside in | |
YScrewLength = 4.0; // screw head to Y stage mount | |
ZStageBase = [(ZDrive[0] - ZStage[0])/2,(ZDrive[1] + ZStage[1]),0.0] - YStageOffset; // local coordinates of Z slide left rear corner | |
ZHoles = [26.5,55.0,71.0]; | |
ZStageWrap = 8.0; // length of edge wrapped around Z stage | |
Trim = ZStageBase[1] - ZStageWrap; | |
union() { | |
difference() { | |
linear_extrude(height=YArm[2],convexity=5) | |
polygon(points=[ | |
[-Trim,0.0], | |
[-YStageBlock[0],0.0], | |
[-YStageBlock[0],-(YArm[1] + SlipFit)], | |
[-(YStageBlock[0] + YArm[0]),-(YArm[1] + SlipFit)], | |
[-(YStageBlock[0] + YArm[0]),Trim], | |
[-Trim,(ZStageBase[1] + ZYArm[1])], | |
[(ZStageBase[0] + ZStage[0]/2),(ZStageBase[1] + ZYArm[1])], | |
[(ZStageBase[0] + ZStage[0] + ZWall),(ZStageBase[1] + 0*ZYArm[1])], | |
[(ZStageBase[0] + ZStage[0] + ZWall),(ZStageBase[1] - ZStageWrap)], | |
[(ZStageBase[0] + ZStage[0] + SlipFit),(ZStageBase[1] - ZStageWrap)], | |
[(ZStageBase[0] + ZStage[0] + SlipFit),ZStageBase[1]], | |
[(ZStageBase[0] - SlipFit),ZStageBase[1]], | |
[(ZStageBase[0] - SlipFit),(ZStageBase[1] - ZStageWrap)], | |
[0.0,(ZStageBase[1] - ZStageWrap)], | |
[0.0,Trim] | |
]); | |
for (j=[0:len(YHoles) - 1]) { // Y stage mounting screws | |
translate([-(YStageBlock[0] + YScrewLength), | |
(-YArm[1] + YHoles[j] - 2*SlipFit), | |
YArm[2]/2]) | |
rotate([0,-90,0]) rotate(180/6) | |
PolyCyl(5.5,YArm[0],6); | |
translate([-(YStageBlock[0] - Protrusion), | |
(-YArm[1] + YHoles[j] - 2*SlipFit), | |
YArm[2]/2]) | |
rotate([0,-90,0]) rotate(180/6) | |
PolyCyl(2.5,2*YArm[0],6); | |
} | |
} | |
if (true) | |
difference() { | |
linear_extrude(height=ZStage[2],convexity=5) | |
polygon(points=[ | |
[(ZStageBase[0] - ZWall),(ZStageBase[1] + 5.0)], | |
[(ZStageBase[0] + ZStage[0] + ZWall),(ZStageBase[1] + 5.0)], | |
[(ZStageBase[0] + ZStage[0] + ZWall),(ZStageBase[1] - ZStageWrap)], | |
[(ZStageBase[0] + ZStage[0] + SlipFit),(ZStageBase[1] - ZStageWrap)], | |
[(ZStageBase[0] + ZStage[0] + SlipFit),ZStageBase[1]], | |
[(ZStageBase[0] - SlipFit),ZStageBase[1]], | |
[(ZStageBase[0] - SlipFit),(ZStageBase[1] - ZStageWrap)], | |
[(ZStageBase[0] - ZWall),(ZStageBase[1] - ZStageWrap)], | |
]); | |
for (k=[0:len(ZHoles) - 1]) | |
translate([(ZStageBase[0] + ZStage[0]/2),0.0,ZHoles[k]]) | |
rotate([-90,0,0]) | |
PolyCyl(3.5,2*ZStageBase[1],6); | |
} | |
} | |
} | |
//-- X Slide attachment | |
// Origin at left rear bottom of mount | |
module XMount() { | |
XHoles = [6.0,18.0]; // from end of X slide | |
XHolesOffset = 7.0; // from bottom of X slide | |
TrayHolesOC = 10.0; | |
echo(str("Tray holes OC: ",TrayHolesOC)); | |
BlockOAH = XStageBlock[2] - XStageOffset[2] - XTray[2]; // overall height of mount | |
difference() { | |
translate([XStageBlock[0],0,BlockOAH]) | |
rotate([0,90,180]) | |
linear_extrude(height=XStageBlock[0],convexity=2) | |
polygon(points=[ | |
[0,0], | |
[0.0,7.0], | |
[(XStageBlock[2] + SlipFit),7.0], | |
[(XStageBlock[2] + SlipFit),XStageBlock[1]], | |
[BlockOAH,XStageBlock[1]], | |
[BlockOAH,0.0], | |
]); | |
for (i=[0:len(XHoles) - 1]) // holes for X stage screws | |
translate([XHoles[i],Protrusion,BlockOAH - XStageBlock[2] + XHolesOffset]) | |
rotate([90,0,0]) | |
PolyCyl(3.5,2*7.0,6); | |
for (i=[-1,1]) // holes for tray mount | |
translate([i*TrayHolesOC/2 + XStageBlock[0]/2,-XStageBlock[1]/2,-Protrusion]) | |
PolyCyl(2.5,0.75*(BlockOAH - XStageBlock[2]),6); | |
} | |
} | |
//---------------------- | |
// Build it | |
if (Layout == "ZStand") | |
ZStand(); | |
if (Layout == "YMount") | |
YMount(); | |
if (Layout == "XMount") | |
XMount(); | |
if (Layout == "Show") { | |
color("lightgreen") | |
ZStand(); | |
color("orange") | |
translate(YStageOffset) | |
YMount(); | |
color("lightblue") | |
translate(XStageOffset + [0,0,-XStageOffset[2]]) | |
XMount(); | |
} | |
if (Layout == "Build") { | |
translate([20,0,0]) | |
ZStand(); | |
translate([YStageBlock[0]/2,0,0]) | |
YMount(); | |
translate([20,-30,0]) | |
XMount(); | |
} |
I’ve been watching a neato harvard neurobiology course online. They had a setup for sticking a needle into a grasshopper’s descending motion detection neuron so you could map its visual fields with an oscilloscope, using a three-axis micropositioner that was almost entirely 3d printed by a FDM like yours. The screws were metal, as was the needle, and that was the critical part: the load this thing was holding weighed a fraction of a gram. But it was a sweet setup. It was impressive to see how well it worked. I’m tempted to build additional needle positioners for our manual wafer prober, since again those things weigh like a gram a piece and replacement prober positioners go for hundreds of dollars a piece.
Without all those axis slides in hand, I’d be tempted to build one!