The Smell of Molten Projects in the Morning

Ed Nisley's Blog: Shop notes, electronics, firmware, machinery, 3D printing, laser cuttery, and curiosities. Contents: 100% human thinking, 0% AI slop.

Tag: Improvements

Making the world a better place, one piece at a time

  • Harbor Freight Digital Thickness Gauge: Lubrication Thereof

    Picked up a Harbor Freight thickness gauge to measure Thing-O-Matic filaments and suchlike; it has a plastic piston and anvil, so it’s not well-suited to measure anything other than plastic parts. In fact, it’s all plastic and the various sliding surfaces produced a remarkable amount of friction.

    Fortunately, the back cover pops off without too much of a struggle:

    Harbor Freight Digital Thickness Gauge - cover removed
    Harbor Freight Digital Thickness Gauge – cover removed

    Dabs of silicone lube at all the contact points considerably improved its disposition.

    The display offers 0.01 mm resolution, but I don’t believe that rightmost digit for an instant. The stated accuracy is ±0.1 mm, which is probably closer to the truth, and it agrees reasonably well with my considerably better quality digital caliper.

  • Thing-O-Matic: Large Hole Calibration

    Flushed with success on the small-hole front, I conjured up a large hole testpiece using the same HoleAdjust function that proved unnecessary with the little ones:

    Circle Calibration - solid model
    Circle Calibration – solid model

    The first version didn’t have the cross bars, which turned out to be a mistake, because the individual rings distorted even under minimal pressure from the calipers:

    Large circle cal - unlinked rings
    Large circle cal – unlinked rings

    However, measuring as delicately as I could, the holes seemed a scant 0.20 mm too small, more or less, kinda-sorta:

    Nominal Nom+0.0
    10 9.83
    20 19.75
    30 29.85
    40 39.84
    50 49.84
    60 59.72
    70 64.76
    80 79.28
    90 89.77

    So I fed in HoleFinagle = 0.20 and the second iteration looks like it’d make a great, albeit leaky, coaster:

    Large Circle Calibration object - HoleFinagle 0.20
    Large Circle Calibration object – HoleFinagle 0.20

    Measuring those holes across the center with the calipers on facets (rather than vertices), produced somewhat more stable results:

    Nominal Nom+0.20
    10 10.08
    20 20.17
    30 30.08
    40 40.08
    50 50.00
    60 60.02
    70 70.05
    80 79.98
    90 90.07

    Frankly, I don’t believe those two least-significant digits, either, because a different set of measurements across different facets looked like this:

    Nominal Nom+0.20
    10 10.13
    20 20.11
    30 29.84
    40 39.90
    50 49.88
    60 59.90
    70 69.84
    80 79.82
    90 89.66

    I also printed a testpiece with HoleFinagle = 0.25 that averaged, by in-the-head computation, about 0.05 larger than that, so the hole diameter compensation does exactly what it should.

    Applying the calipers to the 10.0 mm hole in the small-hole testpiece gives about the same result as in this one. The fact that HoleFinagle is different poses a bit of a mystery…

    The only thing I can conclude is that the measurement variation and the printing variation match up pretty closely: the actual diameter depends more on where it’s measured than anything else. The holes are pretty nearly the intended size and, should the exact size matter, you (well, I) must print at least one to throw away.

    All in all, a tenth of a millimeter is Good Enough. Selah.

    Oh. The ODs are marginally too small, even using PolyCyl.

    The OpenSCAD source, with both adjustments set to neutral:

    // Large circle diameter calibration
    // 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
    
    Width = 2.5;
    Thickness = IntegerMultiple(2.0,ThreadThick);
    
    DiaStep = 10.0;
    
    NumCircles = 9;
    
    echo(str("Width: ",Width));
    echo(str("Thickness: ",Thickness));
    
    BarLength = (NumCircles + 1)*DiaStep;
    
    //-------
    
    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=HoleAdjust(FixDia)/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);
    
    }
    
    //------
    
    module Ring(RingID,Width,Thick) {
    
      difference() {
    	PolyCyl((RingID + 2*Width),Thick);
    	translate([0,0,-Protrusion])
    	  PolyCyl(RingID,(Thick + 2*Protrusion));
      }
    }
    
    //------
    
    ShowPegGrid();
    
    union () {
      for (Index = [1:NumCircles])
    	Ring(Index*DiaStep,Width,Thickness);
      for (Index = [-1,1])
    	rotate(Index*45)
    	  translate([-BarLength/2,-Width/2,0])
    		cube([BarLength,Width,Thickness]);
    }
    
  • 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);
    
      cylinder(r=HoleAdjust(FixDia)/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);
    
    }
    
    //------
    
    module HoleRow(DiaDelta) {
    
      translate([-AllHoleLength/2,
    			  0,0])
    	for (Index = [1:NumHoles])
    	  translate([(DiaStep*(Index*(Index + 1)/2) + Index*Border),0,-Protrusion])
    		PolyCyl((Index*DiaStep + DiaDelta),(BlockThick + 2*Protrusion));
    }
    
    //------
    
    ShowPegGrid();
    
    rotate(90)
      difference() {
    	translate([-BlockLength/2,-BlockWidth/2,0])
    	  cube([BlockLength,BlockWidth,BlockThick]);
    	for (Index = [0,1])
    	  translate([0,((2*Index - 1)*DiaStep*NumHoles/2),0])
    		rotate(Index*180)
    		  HoleRow(Index*0.5);
      }
    
  • Vanilla Extract: Commercial Variations

    This look at the ingredients found in various commercial vanilla extracts (plus their prices) finally pushed me over the edge into brewing up that DIY vanilla extract.

    We’ve been using McCormick vanilla forever, mostly because it has the simplest and shortest list of ingredients:

    McCormick Vanilla
    McCormick Vanilla

    Nielson-Massey vanilla seemed about the same, although it’s not clear why it needs more sugar than those “vanilla bean extractives”:

    Nielsen-Massey Vanilla
    Nielsen-Massey Vanilla

    Wal-Mart vanilla doesn’t smell like vanilla, even though it has more “extractive” than corn syrup:

    Wal-Mart Vanilla
    Wal-Mart Vanilla

    All three extracts have “Pure” on the label, which (according to Wikipedia, anyway) means that they have at least 13.35 ounce of vanilla bean per gallon of extract. I didn’t weigh the three beans in my 8 ounces of hooch, but I suspect they weighed far less than the regulation 0.834 ounce. Next time, for sure, I’ll go for triple strength extract!

    Despite that, my DIY hooch has turned brown and smells pretty good…

    These full-frame pix used my new close-up lens gizmo; even with some vignetting the results seem perfectly usable. Normally I crop pix down to the central section, so this will be as bad as it gets.

  • Presta Valve: Proper Pump Attachment Thereto

    All our bikes have Presta valves, which seem better suited for bike rims than the larger and more common automotive Schraeder valves:

    Presta valve stem
    Presta valve stem

    For all these years, I’d been attaching the pump head so the obvious sealing ring near the nozzle opening lined up with the flat section adjacent to the valve core stem. The pump head never seemed stable on the stem, often leaked, and generally had a precarious hold:

    Incorrect Presta pump head attachment
    Incorrect Presta pump head attachment

    Come to find out, more by accident than intention, that the correct way to attach the pump head involves ramming it all the way down onto the stem so that it can seal along the entire length of the threads. That’s nice and secure, doesn’t leak, and even looks like it should work perfectly:

    Correct Presta pump head attachment
    Correct Presta pump head attachment

    I’d feel even more like a doof if I hadn’t learned to do it wrong by watching somebody else back in the day or if I haven’t observed many other people making exactly the same mistake. I think the fact that the short nozzles on the old-school Zéfal pumps I swore by back in my wedgie-bike days never got a good grip on Presta stems got me off to a bad start, but … dang do I feel stupid.

    FWIW, the little tab sticking out under the latch handle makes up for a bit of slop in the valve head. When I got the pump, the Schraeder nozzle didn’t seal very well, either, and taking up a few mils of slack helped immeasurably. We don’t need that nozzle very often, but our bicycle touring guests frequently do; they know that they can top off a Schraeder-valved tube at any gas station or with any pump anywhere around the world.

    [Update: I hate it when I misspell a word in the title…]

  • Canon SX230HS Microscope and Close-up Macro Adapters

    The deal was, if my Shop Assistant repaired my pocket camera, she could have it. She did, which meant I lost the ability to take pix through the microscope. While I was conjuring up a replacement, it occurred to me that I should also build a gadget to hold a close-up lens in front of the camera for tighter macro shots that don’t quite require a microscope’s magnification.

    The solid model of the microscope adapter:

    Microscope mount - solid model
    Microscope mount – solid model

    The close-up macro adapter, with an LED ring light around the snout:

    LED Ring mount - solid model
    LED Ring mount – solid model

    They have a common camera mounting plate, with a hex recess for a 1/4-20 nut that mates with a standard tripod screw and some support material sticking up through the hole for the screw that holds the camera to the plate:

    Mounting plate - solid model - top view
    Mounting plate – solid model – top view

    The main tube glues into the plate’s cutout and is long enough to accommodate the fully extended lens turret, with four shallow holes for filament snippet locating pins to align the snout:

    Main tube - solid model - bottom view
    Main tube – solid model – bottom view

    An exploded view shows how everything fits together, with the stud below the camera representing its tripod mounting screw:

    LED Ring mount - solid model - exploded view
    LED Ring mount – solid model – exploded view

    More details on the parts will appear over the next few days, but here’s the view through the macro adapter:

    Dahlia through macro adapter
    Dahlia through macro adapter

    Yeah, some slight vignetting, but overall it’s pretty good.

    The OpenSCAD source code that builds both adapters:

    // Close-up lens mount & Microscope adapter for Canon SX230HS camera
    // Ed Nisley KE4ZNU - Nov 2011
    
    Mount = "Eyepiece";			// End result: LEDRing Eyepiece
    
    Layout = "Show";			// Assembly: Show
    							// Parts: Plate Tube LEDRing Camera Eyepiece
    							// Build Plates: Build1..4
    
    Gap = 12;					// between "Show" objects
    
    include </home/ed/Thing-O-Matic/lib/MCAD/units.scad>
    include </home/ed/Thing-O-Matic/Useful Sizes.scad>
    include </home/ed/Thing-O-Matic/lib/visibone_colors.scad>
    
    //-------
    //- Extrusion parameters must match reality!
    //  Print with +1 shells, 3 solid layers, 0.2 infill
    
    ThreadThick = 0.33;
    ThreadWidth = 2.0 * ThreadThick;
    
    HoleFinagle = 0.2;
    HoleFudge = 1.02;
    
    function HoleAdjust(Diameter) = HoleFudge*Diameter + HoleFinagle;
    
    Protrusion = 0.1;			// make holes end cleanly
    
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    
    //-------
    // Dimensions
    
    // doublet lens
    
    LensDia = 25.0;
    LensRad = LensDia/2;
    LensClearance = 0.2;
    
    LensEdge = 6.7;
    LensThick = 8.6;
    LensRimThick = IntegerMultiple((2.0 + LensThick),ThreadThick);
    
    // LED ring light
    
    LEDRingOD = 50.0;
    LEDRingID = 36.0;
    LEDBoardThick = 1.5;
    LEDThick = 4.0;
    LEDRingClearance = 0.5;
    LEDWireHoleDia = 3.0;
    
    // microscope eyepiece
    
    EyepieceOD = 30.0;
    EyepieceID = 24.0;
    EyepieceLength = 25.0;
    
    // camera
    // Origin at base of [0] ring, Z+ along lens axis, X+ toward bottom, Y+ toward left
    
    CameraBodyWidth = 2*10.6;							// 2 x center-to-curve edge
    CameraBaseWidth = 15.5;								// flat part of bottom front to back
    CameraBaseRadius = (CameraBodyWidth - CameraBaseWidth)/2;	// edge rounding
    CameraBaseLength = 60.0;							// centered on lens axis
    CameraBaseHeight = 55.0;							// main body height
    CameraBaseThick = 0.9;								// downward from lens ring
    
    echo(str("Camera base radius: ",CameraBaseRadius));
    
    TripodHoleOffset = -19.0;							// mount screw wrt lens centerline
    TripodHoleDia = Clear025_20;						// clearance hole
    
    TripodScrewHeadDia = 14.5;							// recess for screw mounting camera
    TripodScrewHeadRad = TripodScrewHeadDia/2;
    TripodScrewHeadThick = 3.0;
    
    // main lens tube
    
    TubeDia = 		[53.0,	44.0,	40.0,	37.6];		// lens rings, [0] is fixed to body
    TubeLength = 	[8.1,	20.6,	17.6,	12.7];
    
    TubeEndClearance = 2.0;								// camera lens end to tube end
    TubeEndThickness = IntegerMultiple(1.5,ThreadThick);
    TubeInnerClearance = 0.5;
    
    TubeInnerLength = TubeLength[0] + TubeLength[1] + TubeLength[2] + TubeLength[3] +
    				  TubeEndClearance;
    TubeOuterLength = TubeInnerLength + TubeEndThickness;
    
    TubeID = TubeDia[0] + TubeInnerClearance;
    TubeOD = TubeID + 6*ThreadWidth;
    TubeWall = (TubeOD - TubeID)/2;
    TubeSides = 48;
    
    echo(str("Main tube outer length: ",TubeOuterLength));
    echo(str("          ID: ",TubeID," OD: ",TubeOD," wall: ",TubeWall));
    
    // camera mounting base
    
    BaseWidth = IntegerMultiple((CameraBaseWidth + 2*CameraBaseRadius),ThreadThick);
    BaseLength = 60.0;
    BaseThick = IntegerMultiple((1.0 + Nut025_20Thick + CameraBaseThick),ThreadThick);
    
    // LED ring mount
    
    LEDBaseThick = IntegerMultiple(2.0,ThreadThick);	// base under lens + LED ring
    LEDBaseRimWidth = IntegerMultiple(6.0,ThreadWidth);
    LEDBaseRimThick = IntegerMultiple(LensThick,ThreadThick);
    
    LEDBaseOD = max((LEDRingOD + LEDRingClearance + LEDBaseRimWidth),TubeOD);
    
    echo(str("LED Ring OD: ",LEDBaseOD));
    
    // alignment pins between tube and LED ring / microscope eyepiece
    
    AlignPins = 4;
    AlignPinOD = 2.9;
    AlignPinCircleDia = TubeOD - 2*TubeWall - 2*AlignPinOD;		// 2*PinOD -> more clearance
    
    //-------
    
    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=HoleAdjust(FixDia)/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);
    
    }
    
    //-------
    
    //- Camera body segment
    //	Including lens base and peg for tripod hole access
    //	Z=0 at edge of lens base ring, X=0 along lens axis
    
    module CameraBody() {
    
      translate([0,0,-CameraBaseThick])
    	rotate(90)
    	  union() {
    		translate([0,0,(CameraBaseHeight/2 + CameraBaseRadius)])
    		  minkowski() {
    			cube([CameraBaseWidth,
    				  (CameraBaseLength + 2*Protrusion),
    				  CameraBaseHeight],center=true);
    			rotate([90,0,0])
    			  cylinder(r=CameraBaseRadius,h=Protrusion,$fn=8);
    		  }
    
    		translate([0,0,(TubeDia[0]/2 + CameraBaseThick)])
    		  rotate([0,90,0])
    			rotate(180/TubeSides)
    			  cylinder(r=(TubeDia[0]/2 + CameraBaseThick),
    					  h=(CameraBodyWidth/2 + Protrusion),
    					  $fn=TubeSides);
    
    		translate([CameraBodyWidth/2,0,(TubeDia[0]/2 + CameraBaseThick)])
    		  rotate([0,90,0])
    			cylinder(r=TubeDia[0]/2,h=TubeLength[0]);
    
    		translate([(TubeLength[0] + CameraBodyWidth/2),
    				  0,(TubeDia[0]/2 + CameraBaseThick)])
    		  rotate([0,90,0])
    			cylinder(r=TubeDia[1]/2,h=TubeLength[1]);
    
    		translate([(TubeLength[0] + TubeLength[1] + CameraBodyWidth/2),
    				  0,(TubeDia[0]/2 + CameraBaseThick)])
    		  rotate([0,90,0])
    			cylinder(r=TubeDia[2]/2,h=TubeLength[2]);
    
    		translate([(TubeLength[0] + TubeLength[1] + TubeLength[2] + CameraBodyWidth/2),
    				  0,(TubeDia[0]/2 + CameraBaseThick)])
    		  rotate([0,90,0])
    			cylinder(r=TubeDia[3]/2,h=TubeLength[3]);
    
    		translate([0,TripodHoleOffset,-BaseThick])
    		  PolyCyl(TripodHoleDia,(BaseThick + 2*Protrusion));
    
    	  }
    }
    
    //- Main tube
    
    module Tube() {
    
      difference() {
    	cylinder(r=TubeOD/2,h=TubeOuterLength,$fn=TubeSides);
    
    	translate([0,0,TubeEndThickness])
    	  PolyCyl(TubeID,(TubeInnerLength + Protrusion),TubeSides);
    
    	translate([0,0,-Protrusion]) {
    	  if (Mount == "LEDRing")
    		cylinder(r=LensRad,h=(TubeEndThickness + 2*Protrusion));
    	  if (Mount == "Eyepiece")
    		cylinder(r=EyepieceID/2,h=(TubeEndThickness + 2*Protrusion));
    	}
    
    	for (Index = [0:AlignPins-1])
    	  rotate(Index*90)
    		translate([(AlignPinCircleDia/2),0,-ThreadThick])
    		  rotate(180)			// flat sides outward
    			PolyCyl(AlignPinOD,TubeEndThickness);
      }
    
    }
    
    //- Base plate
    
    module BasePlate() {
    
      union() {
    	difference() {
    		linear_extrude(height=BaseThick)
    		  hull() {
    			translate([-(BaseLength/2 - BaseWidth/2),0,0])
    			  circle(BaseWidth/2);
    			translate([ (BaseLength/2 - BaseWidth/2),0,0])
    			  circle(BaseWidth/2);
    			translate([0,(0.75*BaseLength),0])
    			  circle(BaseWidth/2);
    		  }
    
    		translate([0,0,BaseThick])
    		  CameraBody();
    
    		translate([0,(TubeOuterLength + CameraBodyWidth/2),
    				  (BaseThick + TubeDia[0]/2)])
    		  rotate([90,0,0])
    			PolyCyl(TubeOD,TubeOuterLength,$fn=TubeSides);
    
    		translate([0,0,3*ThreadThick])
    		  PolyCyl((Nut025_20Dia*sqrt(3)/2),2*Nut025_20Thick,6);	// dia across hex flats
    
    		translate([0,0,-Protrusion])
    		  PolyCyl(Clear025_20,(BaseThick + 2*Protrusion));
    
    		translate([TripodHoleOffset,0,3*ThreadThick])
    		  PolyCyl((Nut025_20Dia*sqrt(3)/2),2*Nut025_20Thick,6);	// dia across hex flats
    
    		translate([TripodHoleOffset,0,-Protrusion])
    		  PolyCyl(Clear025_20,(BaseThick + 2*Protrusion));
    
    		translate([-TripodHoleOffset,0,-Protrusion])
    		  PolyCyl(TripodScrewHeadDia,(TripodScrewHeadThick + Protrusion));
    
    	}
    
    	translate([-TripodHoleOffset,0,0]) {				// support for tripod screw hole
    	  for (Index=[0:3])
    		rotate(Index*45)
    		  translate([-ThreadWidth,-TripodScrewHeadRad,0])
    			cube([2*ThreadWidth,TripodScrewHeadDia,TripodScrewHeadThick]);
    
    	  cylinder(r=0.4*TripodScrewHeadRad,h=(BaseThick - CameraBaseThick),$fn=9);
    	}
      }
    }
    
    //- LED mounting ring
    
    module LEDRing() {
    
      difference() {
    	cylinder(r=LEDBaseOD/2,h=LensRimThick,$fn=48);
    
    	translate([0,0,-Protrusion])
    	  PolyCyl((LensDia + LensClearance),
    			  (LensRimThick + 2*Protrusion));
    
    	translate([0,0,LEDBaseRimThick])
    	  difference() {
    		PolyCyl(LEDBaseOD,LensThick);
    		PolyCyl(LEDRingID,LensThick);
    	  }
    
    	translate([0,0,LEDBaseThick])
    	  difference() {
    		PolyCyl((LEDRingOD + LEDRingClearance),LensThick);
    		cylinder(r1=HoleAdjust(LEDRingID - LEDRingClearance)/2,
    				 r2=HoleAdjust(LensDia + LensClearance)/2 + 2*ThreadWidth,
    				 h=LensThick);
    	  }
    
    	for (Index = [0:AlignPins-1])
    	  rotate(Index*90)
    		translate([(AlignPinCircleDia/2),0,-ThreadThick])
    		  rotate(180)			// flat sides outward
    			PolyCyl(AlignPinOD,LEDBaseThick);
    
    	rotate(45)
    	  translate([0,LEDRingID/2,(LEDBaseThick + 1.2*LEDWireHoleDia/2)])
    		rotate([0,-90,0])			// flat side down
    		  rotate([-90,0,0])
    			PolyCyl(LEDWireHoleDia,2*LEDBaseRimWidth);
      }
    
    }
    
    //- Microscope eyepiece adapter
    
    module EyepieceMount() {
    
      difference() {
    	cylinder(r1=TubeOD/2,
    			 r2=(EyepieceOD + 8*ThreadWidth)/2,
    			 h=EyepieceLength,
    			 $fn=TubeSides);
    
    	translate([0,0,-Protrusion])
    	  PolyCyl(EyepieceOD,(EyepieceLength + 2*Protrusion));
    
    	for (Index = [0:AlignPins-1])
    	  rotate(Index*90)
    		translate([(AlignPinCircleDia/2),0,-ThreadThick])
    		  rotate(180)			// flat sides outward
    			PolyCyl(AlignPinOD,6*ThreadThick);
      }
    
    }
    
    //-------
    // Build it!
    
    if (Layout != "Show")
      ShowPegGrid();
    
    if (Layout == "Tube")
      Tube();
    
    if (Layout == "LEDRing")
      LEDRing();
    
    if (Layout == "Plate")
      BasePlate();
    
    if (Layout == "Camera")
      CameraBody();
    
    if (Layout == "Eyepiece")
      EyepieceMount();
    
    if (Layout == "Build1")
      translate([0,-BaseLength/3,0])
    	BasePlate();
    
    if (Layout == "Build2")
      Tube();
    
    if (Layout == "Build3")
      LEDRing();
    
    if (Layout == "Build4")
      EyepieceMount();
    
    if (Layout == "Show") {
      translate([0,TubeOuterLength,TubeDia[0]/2]) {
    	rotate([90,0,0])
    	  color(LTC) Tube();
    	translate([0,Gap,0])
    	  rotate([-90,0,0]) {
    		if (Mount == "LEDRing")
    		  color(OOR) LEDRing();
    		if (Mount == "Eyepiece")
    		  color(OOR) EyepieceMount();
    	  }
      }
    
      translate([0,-CameraBodyWidth/2,0])
    	color(PG) CameraBody();
    
      color(PDA)
    	render()
    	translate([0,-CameraBodyWidth/2,-(BaseThick + Gap)])
    	  BasePlate();
    }
    

    The original doodles & dimensions:

    Close-up Lens Doodles
    Close-up Lens Doodles
  • Canon SX230HS Lens Cap

    The SX230HS camera lives in my pants pocket, where it gets pressed between my leg and anything I lean against. Turns out that the lens turret end cap isn’t quite thick enough to not bend inward against the leaves that cover the lens, which causes them to hang up. The solution boils down to a hideous external lens cap:

    Canon SX230HS with lens cap
    Canon SX230HS with lens cap

    It’s built from forget-me-not yellow filament for an obvious reason…

    The sheet-metal plate bears against the non-moving rim around the turret. I marked the plate’s diameter with a compass, extracted it from the sheet with left-cutting tin snips, filed off the slivers, rounded the edge, and it snapped right into the recess where a touch of acrylic caulk holds it firmly in place.

    A thin plastic cover would be too flexible and a thicker plastic cover would be too thick; this must fit into an already-snug cloth pouch where a few additional millimeters of girth actually matter. My previous camera taught me that pocket fuzz gets into everything, so a pouch isn’t optional.

    The interior isn’t too inspiring, but you can see what two layers of plastic look like across the bottom:

    SX230HS lens cap - interior
    SX230HS lens cap – interior

    The front has the shallow recess that captures the metal plate. Because the front builds against the aluminum build platform, I added a support structure inside the recess:

    SX230HS lens cap - support in place
    SX230HS lens cap – support in place

    The solid model gives a better view:

    Lens cap - solid model - bottom view
    Lens cap – solid model – bottom view

    It’s basically a ring with tabs under the recess. The ring OD matches the lens caps’s ID, with a height equal to the recess depth, so only the tabs contact the cap. I removed them by twisting each tab with a needle-nose pliers until the whole thing popped loose:

    SX230HS lens cap - support structure
    SX230HS lens cap – support structure

    A bit of scraper and scalpel cleanup and it’s all good. The detail pix show the first trial of the lens cap, which lacks the nice bevel around the front rim.

    The camera is smart enough to notice when something blocks the lens: it immediately shuts down and displays a lens failure error message. That’s probably not a Good Thing on a regular basis, but it doesn’t seem to do any harm.

    FWIW, my previous pocket camera, a Casio EX-Z850 , sported a recessed and somewhat thicker turret end cap that didn’t have this problem. Mary says she’ll make a case for this camera, too, but until then I’m using a pouch from a dinky VOIP phone that just barely holds the camera.

    The OpenSCAD source code:

    // Lens cap for Canon SX230HS
    // 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.20;
    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
    
    LensDia = 53.0;
    LensRad = LensDia/2;
    LensLength = 8.0;
    
    PlateThick = IntegerMultiple(0.75,ThreadThick);
    PlateDia = 48.0;
    
    Shell = 2*ThreadWidth;
    Spacer = 2*ThreadThick;
    
    CapOD = LensDia + 2*Shell;
    CapLength = LensLength + Spacer + PlateThick;
    CapSides = 48;
    
    CenterHoleDia = 44.0;
    
    BevelWidth = PlateThick;
    
    NumStruts = 16;
    SupportStrutLen = (PlateDia - ThreadWidth)/2;		// small gap to cap
    
    //-------
    
    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=HoleAdjust(FixDia)/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);
    
    }
    
    //-------
    
    ShowPegGrid();
    
    difference() {
      PolyCyl(CapOD,CapLength,CapSides);
    
      translate([0,0,(Spacer + PlateThick)])					// lens shell
    	PolyCyl(LensDia,(LensLength + Protrusion),CapSides);
    
      translate([0,0,-Protrusion])								// center hole
    	PolyCyl(CenterHoleDia,(CapLength + Protrusion));
    
      translate([0,0,-Protrusion])								// bevel
    	difference() {
    	  cylinder(r=(CapOD/2 + 2*(BevelWidth + Protrusion)),
    			   h=(2*BevelWidth + Protrusion),
    			   $fn=CapSides);
    	  cylinder(r1=(CapOD/2 - BevelWidth - Protrusion),
    			   r2=(CapOD/2 + BevelWidth),
    			   h=(2*BevelWidth + Protrusion),
    			   $fn=CapSides);
    	}
    
      difference() {
    	translate([0,0,-Protrusion])							// cover plate recess
    	  PolyCyl(PlateDia,(PlateThick + Protrusion));
    	for (Index=[0:(NumStruts - 1)])							// support struts
    	  rotate(Index*360/NumStruts)
    		translate([-ThreadWidth,-SupportStrutLen,0])
    		  cube([2*ThreadWidth,SupportStrutLen,PlateThick]);
      }
    }
    
    difference() {									// support ring
      PolyCyl(CenterHoleDia,PlateThick);
      translate([0,0,-Protrusion])
    	PolyCyl((CenterHoleDia - 4*ThreadWidth),(PlateThick + 2*Protrusion));
    }