Thing-O-Matic: Small Hole Calibration

The macro lens & microscope adapters for the Canon SX230HX camera required a bunch of large and fairly precise circles. The first-pass prints of the main tube and snouts came out with diameters about 2% too small, so I changed the hole diameter compensation to include a first-order Fudge Factor as well as the simple zero-order HoleWindage Finagle Constant I’d been using. In the process, I cooked up a simple OpenSCAD function with new coefficient names reflecting their order:

HoleFinagle = 0.20;
HoleFudge = 1.02;
function HoleAdjust(Diameter) = HoleFudge*Diameter + HoleFinagle;

That solved the immediate issue, but I wondered whether I was working on the right problem.

In the past, nophead’s polyholes testpiece showed the need for the 0.2 mm HoleWindage adder to make small holes turn out correctly. I rewrote his code to:

  • Use my HoleAdjust function
  • Lay the two rows out nose-to-tail
  • Add a bit more clearance between the holes

Which came out like this:

Small Hole Calibration - solid model
Small Hole Calibration - solid model

To find out where I’m starting from, I printed it (0.33 mm x 0.66 mm, 30 mm/s, 200 °C / 110 °C) with both correction factors set to “no change” and got a nice-looking plate that didn’t require any cleanup at all:

Small Hole Calibration object - HoleFinagle 0.00
Small Hole Calibration object - HoleFinagle 0.00

Note that the similar-looking holes in the two rows aren’t the same size: the row with the tiny triangle has *.0 mm holes, the tiny square marks the *.5 mm holes.

The Skirt thread thickness was 0.31 to 0.38 mm, so this object’s size should be about as good as it gets.

The point of the game is to circumscribe polygonal holes around a cylinder of a given diameter. I don’t have a set of metric drills (or drill rods), so I bracketed the holes with the nearest sizes of hard-inch number and letter drills:

Nominal Free fit Snug fit
1.00 0.98 1.04
2.00 2.05 2.18
3.00 2.93 3.03
4.00 3.99 4.04
5.00 5.06 5.13
6.00 6.21 6.23 no-go
7.00 6.98 7.12
8.00 7.50 8.19
9.00 8.77 9.05
10.00 9.92 10.19 tight

The “snug fit” column means the holes are definitely smaller than that measurement, so the maximum hole size comes out just about spot on; an error of 0.1 mm or so seems too small to quibble over.

So, for whatever reason, my previous Finagle Constant of 0.20 seems no longer necessary and, for sure, the Fudge Factor doesn’t bring anything to the table at this scale.

It’s definitely true that the height of the first layer affects the hole size for the next few layers, even with the Z-minimum switch measuring the build plate height. The Skirt threads generally measure within ±0.05 mm of the nominal 0.33 mm and I think much of that variation comes from residual snot on the nozzle when it touches the switch. I have no idea what the firmware’s resolution might be.

Given that I’ve been adding 0.2 mm to small-hole diameters all along, I suspect all these errors are now of the same general size:

  • Print-to-print variation (layer thickness, ABS variations)
  • Hole tolerance (size vs plastic vs speed)
  • Measurement error (digital caliper vs drills vs objects)

All in all, it’s pretty good.

The OpenSCAD source code, with the hole adjustment factors set to neutral:

// Small circle diameter calibration
// Adapted from Nophead's polyholes testpiece
// Ed Nisley - KE4ZNU - Nov 2011

//- Extrusion parameters must match reality!
//  Print with +1 shells, 3 solid layers, 0.2 infill

ThreadThick = 0.33;
ThreadWidth = 2.0 * ThreadThick;

HoleFinagle = 0.00;
HoleFudge = 1.00;

function HoleAdjust(Diameter) = HoleFudge*Diameter + HoleFinagle;

Protrusion = 0.1;			// make holes end cleanly

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

// Dimensions

DiaStep = 1.0;

NumHoles = 10;

Border = 5*ThreadWidth;

AllHoleLength = DiaStep*(NumHoles*(NumHoles + 1)/2) +	// total hole dia
				(NumHoles + 1)*Border +					// total border size
				DiaStep*NumHoles/2;						// radius of largest hole

BlockLength = AllHoleLength + 2*Border;
BlockWidth = 2*NumHoles*DiaStep + 2*Border;
BlockThick = IntegerMultiple(1.0,ThreadThick);


module PolyCyl(Dia,Height,ForceSides=0) {			// based on nophead's polyholes

  Sides = (ForceSides != 0) ? ForceSides : (ceil(Dia) + 2);

  FixDia = Dia / cos(180/Sides);


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

  Range = floor(50 / Space);

	for (x=[-Range:Range])
	  for (y=[-Range:Range])



module HoleRow(DiaDelta) {

	for (Index = [1:NumHoles])
	  translate([(DiaStep*(Index*(Index + 1)/2) + Index*Border),0,-Protrusion])
		PolyCyl((Index*DiaStep + DiaDelta),(BlockThick + 2*Protrusion));



  difference() {
	for (Index = [0,1])
	  translate([0,((2*Index - 1)*DiaStep*NumHoles/2),0])