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.

Category: Machine Shop

Mechanical widgetry

  • Helmet Mirror Mount: Solid Model

    Helmet mirror mount - 3D model - Fit layout
    Helmet mirror mount – 3D model – Fit layout

    After a bit of OpenSCAD twiddling, those doodles turned into a printable model. This view shows what it looks like all neatly assembled:

    The tiny hole on the top of the Elevation Body accepts a 2-56 setscrew that grabs the arc protruding from the Elevation Plate and locks the up-and-down setting. The Azimuth Mount pivots on the 3-48 screw holding it to the Elevation Mount.

    Both of those pivots must be loose enough to move when you bump the mirror and tight enough to stay put in normal use. It’s a delicate balance and I’m not convinced this will work for the long term, but it’s a brassboard.

    The 2-56 stud on the end of the mirror shaft screws into a socket in the rear side of the Az Mount. Another 2-56 setscrew in the Az Mount (facing the El Body), grabs the side of the shaft and prevents it from rotating.

    All the parts lay out on their backs for printing, with a grid to show how they fit on the build platform:

    Helmet mirror mount - 3D model - Show layout
    Helmet mirror mount – 3D model – Show layout

    The mirror shaft shoulder on the Az Mount (front center) sticks out in mid air and requires a little bit of support.

    The El Mount (left rear) builds surprisingly well with its curved top surface downward. If it’s rotated 90 degrees with the curve facing to the left, Skeinforge grumps about not being able to do something or another and generates totally bogus G-Code.

    The Helmet Plate has a 3 mm deep depression that more-or-less corresponds to the helmet’s surface. It’s gouged out by a huge sphere sitting on the plate, with a radius calculated from the measured helmet curvature.

    The OpenSCAD source code has two useful parameters near the top:

    • Layout selects the overall appearance: Fit, Show, or Build
    • Examine selects a single part for inspection & tweakage

    You’ll need the MCAD and Visibone libraries to make this work. It’s the original code, without the tweaks to the grid mentioned in the comments there:

    // Helmet mirror mount
    // Ed Nisley KE4ZNU June 2011
    
    include </home/ed/Thing-O-Matic/lib/MCAD/units.scad>
    include </home/ed/Thing-O-Matic/lib/MCAD/boxes.scad>
    include </home/ed/Thing-O-Matic/lib/visibone_colors.scad>
    
    //-- Layout Control
    
    Layout = "Show";					// Build Fit Show None
    
    Examine = "None";				// AzMount ElMount ElBody ElPlate HelmetPlate None
    
    //-- Extrusion parameters
    
    ThreadThick = 0.33;
    ThreadWT = 2.0;
    ThreadWidth = ThreadThick * ThreadWT;
    
    HoleWindage = 0;			// enlarge hole dia by this amount
    
    //-- Useful sizes
    
    Tap2_56 = 0.070 * inch;
    Clear2_56 = 0.082 * inch;
    Head2_56 = 0.156 * inch;
    Head2_56Thick = 0.055 * inch;
    Nut2_56Dia = 0.204 * inch;
    Nut2_56Thick = 0.065 * inch;
    
    Tap3_48 = 0.079 * inch;
    Clear3_48 = 0.096 * inch;
    Head3_48 = 0.184 * inch;
    Head3_48Thick = 0.058 * inch;
    Nut3_48Dia = 0.201 * inch;
    Nut3_48Thick = 0.073 * inch;
    
    Tap4_40 = 0.089 * inch;
    Clear4_40 = 0.110 * inch;
    Head4_40 = 0.211 * inch;
    Head4_40Thick = 0.065 * inch;
    Nut4_40Dia = 0.228 * inch;
    Nut4_40Thick = 0.086 * inch;
    
    //-- Azimuth Mount
    
    AzMountDia = 12.0;
    AzMountLength = 14.0;
    
    AzFacets = 30;
    
    echo(str("Azmuth mount dia: ",AzMountDia," length: ",AzMountLength));
    
    //-- Mirror sizes
    
    MirrorShaftDia = 3.60;
    MirrorShaftOffset = -1.5;				// vertical offset from center of AzMountBody
    MirrorShoulderLen = 3*MirrorShaftDia;
    MirrorShoulderDia = min(AzMountDia,MirrorShaftDia + 6*ThreadWidth);
    
    MirrorStudDia = Tap3_48;
    MirrorStudLen = 2.0;
    
    //-- Elevation Mount / Body / Plate
    
    ElMountDia = AzMountDia;
    ElMountLength = 2.0 + ElMountDia;
    
    ElMountBase = 2.0;
    
    ElMountRounding = 2.0;
    
    ElMountFacets = AzFacets;
    
    ElBodyWidth = ElMountDia;
    ElBodyBlockLength = ElMountLength + AzMountLength/2 - MirrorShaftOffset;
    ElBodyThick = 8.0;
    
    echo(str("Elevation body overall: ",(ElBodyBlockLength + ElBodyWidth/2)," width: ",ElBodyWidth));
    
    ElPlateTall = ElBodyBlockLength + 0.70*ElBodyWidth;
    ElPlateWidth = 1.25 * ElPlateTall;
    ElPlateThick = ceil(4.0 / ThreadThick) * ThreadThick;
    
    ElPlatePlusX = ElPlateThick + (ElMountDia/2 + ElMountBase) + ElBodyThick;
    
    echo(str("Elevation plate tall: ",ElPlateTall," width: ",ElPlateWidth));
    
    ElArcRadius = (3/4) * ElBodyBlockLength;
    ElArcThick = 4*ThreadWidth;
    ElArcHeight = (1/2) * ElBodyThick;
    ElArcAngle = 35;
    ElArcFacets = 32;
    
    ElPlateFacets = 52;
    
    //-- Helmet Interface Plate
    
    HelmetCX = 60.0;
    HelmetMX = 4.0;
    HelmetRX = (pow(HelmetMX,2) + pow(HelmetCX,2)/4)/(2*HelmetMX);
    
    HelmetPlateC = max(ElPlateTall,ElPlateWidth);
    HelmetPlateTheta = atan(HelmetPlateC/HelmetRX);
    HelmetPlateM = 2*HelmetRX*pow(sin(HelmetPlateTheta/4),2);
    
    HelmetPlateThick = ThreadThick*(ceil(HelmetPlateM/ThreadThick) + 1);
    
    //-- Bearing Interfaces
    
    BearingWidth = 3*ThreadWidth;
    
    BearingOverlap = 3*ThreadThick;
    BearingClearance = 1*ThreadThick;
    
    BearingStudDia = min(AzMountDia,ElBodyWidth) - 2*BearingWidth;
    
    //-- Convenience values
    
    Protrusion = 0.1;		// make holes look good
    
    PegSize = 1.0;
    
    //----------------------
    // Useful routines
    
    module PolyCyl(Dia,Height) {			// based on nophead's polyholes
    
      Sides = ceil(Dia) + 2;
      FixDia = Dia / cos(180/Sides);
    
      cylinder(r=(FixDia + HoleWindage)/2,
               h=Height,
    	   $fn=Sides);
    }
    
    module ShowPegGrid(Size) {
    	for (x=[-5:5])
    	  for (y=[-5:5])
    		translate([x*10,y*10,Size/2])
    		  cube(Size,center=true);
    
    }
    
    //----------------------
    // Azimuth Mount
    
    module AzMount() {
    
      difference() {
    	union() {
    	  cylinder(r=AzMountDia/2,h=AzMountLength,$fn=AzFacets);		// body
    	  translate([0,0,AzMountLength/2 + MirrorShaftOffset])
    		rotate([-90,0,0])
    		  cylinder(r=MirrorShoulderDia/2,
    				   h=MirrorShoulderLen,$fn=AzFacets);				// mirror shaft shoulder
    
    	  if (Layout != "Fit")
    		for (y=[0:1])												// shoulder support
    		  translate([-AzMountDia/2,(4*y + AzMountDia/2 + ThreadWidth),0])
    			difference() {
    			  cube([AzMountDia,2*ThreadWidth,AzMountLength/6]);
    			  translate([AzMountDia/2,-Protrusion,AzMountLength/2 + MirrorShaftOffset])
    				rotate([-90,0,0])
    				  cylinder(r=MirrorShoulderDia/2,h=ThreadWidth + 2*Protrusion);
    			}
    	}
    
        translate([0,-Head3_48/2,AzMountLength/2 + MirrorShaftOffset])
          rotate([-90,0,0])
    		PolyCyl(MirrorShaftDia,(AzMountDia + MirrorShoulderLen));	// mirror shaft
    
        translate([0,-(Head3_48/2 - Protrusion),AzMountLength/2 + MirrorShaftOffset])
    	  rotate([90,0,0])
    		PolyCyl(MirrorStudDia,MirrorStudLen+Protrusion);			// mirror stud
    
        translate([0,0,
    	       Head3_48Thick - (AzMountLength - MirrorShaftDia)/2 + MirrorShaftOffset - Protrusion])
          PolyCyl(Head3_48,AzMountLength + Protrusion);					// mounting screw head
    
        translate([0,0,-Protrusion])
          cylinder(r=(Clear3_48 + HoleWindage)/2,
    	  h=(AzMountLength + 2*Protrusion),
    	  $fn=ceil(Clear3_48)+2);										// mounting screw clearance
    
    	translate([0,0,AzMountLength/2 + Head3_48Thick + MirrorShaftDia/2 + MirrorShaftOffset - Protrusion])
    	  cylinder(r1=(Head3_48/cos(180/7) + HoleWindage)/2,
    			   r2=Clear3_48/2,
    			   h=(3*ThreadThick + Protrusion),
    			   $fn=7);												// overhang support
    
        translate([0,0,AzMountLength/2 + MirrorShaftOffset])
          rotate([0,90,0])
    		PolyCyl(Tap2_56,AzMountDia/2 + Protrusion);					// setscrew hole
    
        translate([0,0,AzMountLength - (BearingOverlap + BearingClearance)])
    	  PolyCyl(BearingStudDia,
    			  BearingOverlap + BearingClearance + Protrusion);		// bearing surface
    
      }
    
    }
    
    //----------------------
    // Elevation Mount
    
    module ElMount() {
    
      difference() {
    
    	union() {
    
    	  translate([(ElMountDia/4 + ElMountBase/2),0,(ElMountLength/2 + BearingOverlap)])
    		rotate([0,90,0])
    		  cube([ElMountLength,ElMountDia,(ElMountDia/2 + ElMountBase)],
    			  center=true);											// mounting block
    
    	  translate([0,0,BearingOverlap]) {
    //		color([0.4,0.3,0.3,0.7])
    		cylinder(r=ElMountDia/2,
    				 h=ElMountLength - ElMountDia/2,
    				 $fn=ElMountFacets);								// cylinder to Az
    
    //		color([0.3,0.4,0.3,0.7])
    		translate([0,0,ElMountLength - ElMountDia/2]) {				// curved interface
    		  intersection() {
    			cylinder(r=ElMountDia/2,h=ElMountDia/2,$fn=ElMountFacets);
    			translate([0,ElMountDia/2,0])
    			  rotate([90,0,0])
    				cylinder(r=ElMountDia/2,h=ElMountDia,$fn=ElMountFacets);
    		  }
    		}
    
    	  }
    
    	  cylinder(r=(BearingStudDia - HoleWindage)/2,h=BearingOverlap);	// bearing stud
    	}
    
    	translate([0,0,-Protrusion])
    	  PolyCyl(Tap3_48,(3/4)*ElMountLength + BearingOverlap + Protrusion);	// AzMount screw
      }
    }
    
    //----------------------
    // Elevation Body
    
    module ElBody() {
    
      difference() {
    	union() {
    	  translate([-ElBodyBlockLength,-ElBodyWidth/2,0])
    		cube([ElBodyBlockLength,ElBodyWidth,ElBodyThick]);
    	  translate([0,0,ElBodyThick])
    		cylinder(r=(ElBodyWidth - 2*BearingWidth)/2,h=BearingOverlap);
    	  cylinder(r=ElBodyWidth/2,h=ElBodyThick,$fn=ElMountFacets);
    	}
    
    	PolyCyl(Clear3_48,ElBodyThick + BearingOverlap + Protrusion);
    
    	translate([0,0,-Protrusion])
    	  PolyCyl(Head3_48,Head3_48Thick);
    
    	translate([-ElArcRadius,0,ElBodyThick - ElArcHeight/2])
    	  rotate([0,-90,0])
    		PolyCyl(Tap2_56,ElBodyBlockLength - ElArcRadius + Protrusion);
    
    	translate([0,0,ElBodyThick - (ElArcHeight + BearingClearance)])
    	  difference() {
    		cylinder(r=ElArcRadius + (ElArcThick/2 + BearingClearance),
    				 h=ElArcHeight + BearingClearance + Protrusion,
    				 $fn=ElArcFacets);
    		cylinder(r=ElArcRadius - (ElArcThick/2 + BearingClearance),
    				 h=ElArcHeight + BearingClearance + Protrusion,
    				 $fn=ElArcFacets);
    	  }
    
      }
    
    }
    
    //----------------------
    // Elevation Plate
    
    module ElPlate() {
    
      union() {
    	difference() {
    	  translate([ElBodyWidth/2 - ElPlateTall/2,0,0])
    		scale([ElPlateTall,ElPlateWidth,1.0])
    		  cylinder(r=0.5,h=ElPlateThick,$fn=ElPlateFacets);
    	  translate([0,0,-Protrusion])
    		PolyCyl(Tap3_48,ElPlateThick + 2*Protrusion);
    	  translate([0,0,ElPlateThick - (BearingOverlap + BearingClearance)])
    		PolyCyl(BearingStudDia,(BearingOverlap + BearingClearance) + Protrusion);
    	  translate([0,0,-Protrusion])
    		cylinder(r=Nut3_48Dia/2,h=(1.1*Nut3_48Thick + Protrusion),$fn=6);
    	}
    
    	translate([0,0,ElPlateThick])
    	difference() {
    	  cylinder(r=ElArcRadius + ElArcThick/2,
    			   h=ElArcHeight,
    			   $fn=ElArcFacets);
    	  cylinder(r=ElArcRadius - ElArcThick/2,
    			   h=ElArcHeight + Protrusion,
    			   $fn=ElArcFacets);
    	  rotate([0,0,90 - ElArcAngle])
    	    translate([ElArcRadius + ElArcThick,0,ElArcHeight/2])
    		  cube([2*ElArcRadius + ElArcThick,
    				2*ElArcRadius + ElArcThick,
    				ElArcHeight + Protrusion],
    				center=true);
    	  rotate([0,0,-(90 - ElArcAngle)])
    	    translate([ElArcRadius + ElArcThick,0,ElArcHeight/2])
    		  cube([2*ElArcRadius + ElArcThick,
    				2*ElArcRadius + ElArcThick,
    				ElArcHeight + Protrusion],
    				center=true);
    	}
      }
    }
    
    //----------------------
    // Helmet Interface Plate
    
    module HelmetPlate() {
    
      difference() {
    	scale([ElPlateTall,ElPlateWidth,1.0])
    	  cylinder(r=0.5,h=HelmetPlateThick,$fn=ElPlateFacets);
    
    	translate([0,0,HelmetRX + HelmetPlateThick - HelmetPlateM])
    	  sphere(r=HelmetRX,$fn=256,$fs=0.1);
    
      }
    }
    
    //----------------------
    // Lash it together
    
    if (Examine == "AzMount")
      AzMount();
    
    if (Examine == "ElMount")
      ElMount();
    
    if (Examine == "ElBody")
      ElBody();
    
    if (Examine == "ElPlate")
      ElPlate();
    
    if (Examine == "HelmetPlate")
      HelmetPlate();
    
    if ((Layout == "Build" || Layout == "Show") && Examine == "None") {
      translate([-10,-20,0])
    	rotate([0,0,90])					// mis-align top fill from ElMount
    	  AzMount();
    
      translate([-10,20,ElMountLength + BearingOverlap])
    	rotate([0,180,-90])
    	  ElMount();
    
      translate([0,0,0])
    	rotate([0,0,0])
    	  ElBody();
    
      translate([10,15,0])
    	rotate([0,0,215])					// mis-align top fill from ElBody
    	  ElPlate();
    
      translate([20,-20,0])
    	rotate([0,0,-45])
    	  HelmetPlate();
    
      if (Layout == "Show")
    	ShowPegGrid(PegSize);
    
    }
    
    if ((Layout == "Fit") && Examine == "None") {
      translate([0,0,-(AzMountLength/2 + MirrorShaftOffset)])
    	color(MFG) AzMount();
    
      translate([0,0,AzMountLength/2 - MirrorShaftOffset - BearingOverlap])
    	color(DHC) ElMount();
    //	color([  0/255, 204/255, 204/255,0.5]) ElMount();
    
    	translate([ElMountDia/2 + ElMountBase,0,0])
    	  rotate([0,90,0])
    		color(DFC) ElBody();
    
    	translate([ElPlatePlusX,0,0])
    	  rotate([180,90,0])
    		color(LHC) ElPlate();
    
    	translate([ElPlatePlusX,0,ElPlateTall/2 - ElBodyWidth/2])
    	  rotate([0,90,0])
    		color(LWM) HelmetPlate();
    }
    
  • OpenSCAD Layout Grid

    OpenSCAD Build Surface Grid
    OpenSCAD Build Surface Grid

    This OpenSCAD module spreads an array of cubes across the otherwise featureless preview window, so I know whether the gizmo I’m building or the parts I’m arranging actually fit on the Thing-O-Matic’s build platform. This doesn’t get out to the very edge, but if it looks close, then I should pay more attention anyway.

    module ShowPegGrid(Size) {
    
     for (x=[-5:5])
      for (y=[-5:5])
       translate([x*10,y*10,Size/2])
        cube(Size,center=true);
    
    }
    
    ShowPegGrid(1.0);
    

    You obviously don’t want to extrude these things, so put the ShowPegGrid() statement inside an if, so you can turn it off for the final build layout.

  • Helmet Mirror: Mirror Modifications

    Mirror shaft - 2-56 stud
    Mirror shaft – 2-56 stud

    This 2-56 stud will hold the mirror shaft into whatever helmet mount I eventually decide on. It’s a pan-head screw that miraculously fits snugly inside the cut-down shaft section, held in with a delicate epoxy dribble around the edge.

    The head abuts the end of the smaller shaft section, so the two no longer slide. I think a length of heat-shrink tubing will stabilize them in rotation, although perhaps I should have just slobbered more epoxy into that joint.

    After the epoxy cured, I sliced off all but 2 mm of the screw thread with an abrasive wheel and cleaned up the wreckage with a file. I actually remembered to spin on a nut before cutting, which ensured I finished the threads properly.

    The business end of the mirror has far too many moving parts: two indented plates for the balls on the mirror and shaft, a screw, and a nut. That’s one too many ball joints, at least, and Wouldn’t It Be Nice If the mirror had a watertight seal around its perimeter?

    Mirror ball joint clamp
    Mirror ball joint clamp

    For now, I just epoxied the nut in place after scuffing up the plate and nut with some sandpaper to give the epoxy something to grip:

    Mirror ball joint - epoxied nut
    Mirror ball joint – epoxied nut

    You can’t see the new washer and rubber grommet under the screw head that provides a bit of compliance to hold the balls more securely, plus a dot of low-strength Loctite in the nut to discourage things from falling apart on the road.

  • Inspection Mirror Shaft Innards

    Inspection mirror shaft friction springs
    Inspection mirror shaft friction springs

    With those doodles in mind, I applied an abrasive cutoff wheel to the shaft of an inspection mirror (from the usual eBay supplier) about 15 mm behind the second joint. That puts a short section of the third tube inside the yet-to-be-built helmet mirror mount.

    The two copper-colored springs center the smaller tube inside the larger one and provide enough friction to make the whole thing work. The tubes seem to be chrome-plated brass and the springs  might be phosphor bronze. I suppose they’re Matryoshka-sized from one end to the other.

    I’d never taken one of those shafts apart before; now we both know.

  • Better Bike Mirror Doodles

    Mirror Mount - Unworkable Doodles
    Mirror Mount – Unworkable Doodles

    Having had many bike helmet mirrors disintegrate over the miles and years, I’ve had a background project bubbling along to build something more durable. Whether that’s feasible or not remains to be seen, but here’s another go at it.

    A full-up ball joint seems to be more trouble than it’s worth and, in any event, requires far too much precision to be easily duplicated. That renders those doodles, mmm, inoperative.

    These doodles aren’t workable, either, but they convert the ball joint into two orthogonal rotating joints that could be 3D printed with some attention to detail.

    The general idea:

    • An ordinary inspection mirror has most of the tricky bits
    • An azimuth-elevation mount aligns the shaft relative to the helmet
    •  The mirror shaft extends to put the mirror forward of your eye
    • The existing mirror ball joint aligns the mirror relative to your eye

    What’s not to like:

    • Exposed screw heads
    • Off-center, hard-to-grip adjustments
    • Probably not printable without support due to all the bearing surfaces and cutouts and suchlike
    Mirror Mount - Doodles
    Mirror Mount – Doodles

    A few more days of doodling produced something that seems better. The az-el joint axes and the mirror shaft axis now meet at a common point, so the mirror shaft moves as the radius of a sphere. The elevation screw hides behind the azimuth mount, out of the way, which makes it awkward to adjust the tension.

    The helmet mount plate must be concave to more-or-less match the helmet curvature. I’ve been securing mirrors using double-sided foam tape to good effect, but it requires a fairly large pad to provide enough adhesive force.

    Two glue joints make everything buildable and should have basically the same strength as the parts themselves. The helmet plate builds concave face up. The az and el mounts build with the bearings upward, as do the mating surfaces on the other parts. Maybe the screws need actual nuts embedded in the mating parts, in which case there may be problems.

    The setscrew holding the mirror shaft can crush the tube; I think they’re thin brass, at best. Putting a stud screw on the end will hold the shaft in place, leaving the setscrew to prevent rotation. Perhaps the stud can reinforce the tube.

    What’s not to like:

    • Many parts (but all buildable at once)
    • It sticks out too far from the side of the helmet
    • Ugly on a stick

    But maybe something will come of it.

  • Thing-O-Matic: Large Knots

    Printing tiny knots showed the need for support under the loop takeoff points, which xorxo’s Hi-Res 3D Knot provides:

    Large Knot - scaffolded
    Large Knot – scaffolded

    My Shop Assistant cleaned up a second version:

    Large Knot cleaned - top
    Large Knot cleaned – top

    As the scrawled notation says: printed at 50 mm/s with 100 mm/s moves. The only cleanup: remove the scaffolding and slice off the Reversal zittage.

    If the truth be known, that was actually the third knot. The first suffered a spectacular failure: one corner of the filament spool snagged on the wall behind the printer and jammed the filament:

    Large Knot - failed
    Large Knot – failed

    The filament drive pulled all the slack out of the bundle, broke off three of the six internal guide posts (admittedly, they’re just hot-melt glued in place), and dragged a nasty kink halfway down the feeder tube. Obviously the stepper was shedding steps during that whole process, but it came rather close to doing the Ouroboros thing.

    While that went down, I was puttering around in the far reaches of the Basement Laboratory, attempting to clean up a bit of the clutter, and checking in on the printer every now and again. Seemed like a good idea at the time, is all I can say.

    Perhaps the Lords of Cosmic Jest simply decided this was an appropriate object to mess with. The vertices of the hexagonal filament spool stick out perhaps 10 mm from the printer’s backside and every one has cleared the wall on countless previous rotations. I moved the entire affair a bit further from the wall and maybe it’ll be all good from now on.

  • Stepper Sync Wheel: Current Waveform First Light

    Eks loaned me a Tek AM503 Current Probe Amplifier, one of those gorgeous instruments that Just Works: a clamp-on DC to 50 MHz Hall Effect current meter. Because it’s electrically isolated from all the hideous electrical hash that surrounds any stepper motor driver circuit, it doesn’t see much of the garbage that pollutes any current sensor depending on a series resistance and a differential amplifier.

    Which lets you take pix like this:

    Stepper Test
    Stepper Test

    From top to bottom:

    The initial ramp occupying the first third of each step comes from the motor’s L/R time constant coupled with the 9 V supply I was using. Back of the envelope: 2 mH / 2 Ω = 1 ms. With 8 V (9 V less MOSFET drops &c) applied, the initial slope = 8 V / 2 mH = 2500 A/s, so in 75 ms it rises 187 mA: close enough.

    The small ripples show the A4988 chopping the current to maintain the proper value for each microstep.

    Looks just like the pretty pictures in the datasheet, doesn’t it?