Microscope Stage Positioner

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:

Microscope Stage Positioner
Microscope Stage Positioner

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:

Microscope Stage Positioner - base squaring
Microscope Stage Positioner – base squaring

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:

Microscope Stage Positioner - solid model - Show layout
Microscope Stage Positioner – solid model – Show layout

The build layout reduces the platform space:

Microscope Stage Positioner - Slic3r preview
Microscope Stage Positioner – Slic3r preview

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:

Microscope Stage Positioner - layout doodle
Microscope Stage Positioner – layout doodle

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,
//-- 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() {
[-ZWall,MaxY], // limited by Z slide rack
[ZDrive[0] + ZWall,MaxY],
[ZDrive[0] + ZWall,MinY], // limited by X slide rack
linear_extrude(height=(ZDrive[2] + ZDriveOffset[2]),convexity=4)
[ZDrive[0] + SlipFit,0.0],
[ZDrive[0] + SlipFit,ZStandWrap],
[ZDrive[0] + ZWall,ZStandWrap],
[ZDrive[0] + ZWall,-ZPlate],
for (i = [0:len(Holes) - 1]) // holes along Z stand
translate([ZDrive[0]/2,ZDrive[1]/2,(Holes[i] + ZDriveOffset[2])])
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
//-- 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() {
[-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)],
for (j=[0:len(YHoles) - 1]) { // Y stage mounting screws
translate([-(YStageBlock[0] + YScrewLength),
(-YArm[1] + YHoles[j] - 2*SlipFit),
rotate([0,-90,0]) rotate(180/6)
translate([-(YStageBlock[0] - Protrusion),
(-YArm[1] + YHoles[j] - 2*SlipFit),
rotate([0,-90,0]) rotate(180/6)
if (true)
difference() {
[(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]])
//-- 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() {
[(XStageBlock[2] + SlipFit),7.0],
[(XStageBlock[2] + SlipFit),XStageBlock[1]],
for (i=[0:len(XHoles) - 1]) // holes for X stage screws
translate([XHoles[i],Protrusion,BlockOAH - XStageBlock[2] + XHolesOffset])
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")
if (Layout == "YMount")
if (Layout == "XMount")
if (Layout == "Show") {
translate(XStageOffset + [0,0,-XStageOffset[2]])
if (Layout == "Build") {

2 thoughts on “Microscope Stage Positioner

  1. 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.

    1. almost entirely 3d printed

      Without all those axis slides in hand, I’d be tempted to build one!

Comments are closed.