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: Recumbent Bicycling

Cruisin’ the streets

  • 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();
    }
    
  • 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.

  • Bike Mirror Re-Repair

    A gust of wind blew Mary’s bike helmet off the seat and, by the conservation of perversity, it landed on the mirror with predictable results:

    Broken helmet mirror mount
    Broken helmet mirror mount

    I affixed the two ends with solvent glue, then epoxied a brass tube around them to stiffen it up. While I had the epoxy and brass out, I added a splint over a previous repair near the mirror ball:

    Re-repaired mirror mount
    Re-repaired mirror mount

    After taking that picture, I heated and bent the remaining shaft just slightly to put the ball near the middle of its range. There’s no possible way this can survive this year’s cycling, so I must get cracking on building some durable mirrors. A 3-D printer should come in handy for something in that project!

  • Thing-O-Matic: Fairing Clamp Plates

    Although those pink clamp plates worked well enough, they did not provide, shall we say, a completely satisfactory user experience. I reprinted new sets in red while varying the extruder speed by 0.1 rev/min, with small tweaks to the overlap between the infill and the loop threads.

    First, the big pictures with details scrawled on the back of the lower plate…

    At 3.2 rpm, which is only slightly too fast:

    Fairing Plate - 3.2 rpm
    Fairing Plate – 3.2 rpm

    At 3.3 rpm, a bit overstuffed:

    Fairing Plate - 3.3 rpm
    Fairing Plate – 3.3 rpm

    At 3.4 rpm, there’s obviously too much plastic:

    Fairing Plate - 3.4 rpm
    Fairing Plate – 3.4 rpm

    Some closeups, in the same order…

    At 3.2 rpm with 0.20 overlap, it looks OK:

    Fairing Plate - 3.2 rpm detail
    Fairing Plate – 3.2 rpm detail

    At 3.3 rpm with 0.25 overlap, which pretty much devours the inner loop thread:

    Fairing Plate - 3.3 rpm detail
    Fairing Plate – 3.3 rpm detail

    At 3.4 rpm with 0.25 overlap there’s serious overfill:

    Fairing Plate - 3.4 rpm detail
    Fairing Plate – 3.4 rpm detail

    In all cases, the extruder left a track while exiting upward from near the middle of the images. Even at 3. 2 rpm there’s slightly too much plastic.

    My ladies don’t care about the fine details. They prefer red to pink and the clamps hold the fairings firmly in place…

  • Tour Easy: Zzipper Fairing Lower Mount Plates

    Those plates handle the upper mount points, but the fairing also attaches to each side of the front fork. A nice rounded oval mates the fairing to the bracket, with two foam pads adapting the flat plates to the curved fairing surface. This view shows the outside of the fairing:

    Lower mount - front
    Lower mount – front

    The hole position requires a mirror-image pair of mounts that, mercifully, all fit on the build platform at once. The solid models look about like you’d expect:

    Lower Bushings
    Lower Bushings

    Those little tabs on the inside edge of the bracket recess printed about as poorly as you’d expect, but they’re not really critical.

    I printed a set of white plates for my bike, installed the new filament tensioner, and went full frontal Barbie for my favorite ladies. This view shows the inside of the fairing:

    Lower mount - rear
    Lower mount – rear

    Turns out my ladies don’t like pink any more than I do.

    The OpenSCAD source:

    // Clamp plates for Zzipper fairing on Tour Easy recumbents
    // Ed Nisley - KE4ZNU - Mar 2011
    
    // Build with...
    //	extrusion parameters matching the values below
    //	4 outer shells
    //	4 solid surfaces at top + bottom
    
    include </home/ed/Thing-O-Matic/lib/MCAD/units.scad>
    
    // Extrusion parameters for successful building
    
    ThreadWidth = 0.55;					// should match extrusion width
    ThreadZ = 0.33;						// should match extrusion thickness
    
    HoleWindage = ThreadWidth;			// enlarge hole dia by extrusion width
    
    // Plate dimensions
    
    Layer1X = 35;						// against fairing surface
    Layer1Y = 30;
    Layer1Z = 2*ThreadZ;
    
    HoleOffsetX = 5.0;					// will be sign-flipped as needed
    HoleOffsetY = -(Layer1Y/2 - 10.0);
    
    Layer2Margin = 1.5;					// uncovered edge
    Layer2X = Layer1X - 2*Layer2Margin;
    Layer2Y = Layer1Y - 2*Layer2Margin;
    Layer2Z = 3*ThreadZ;
    
    MountX = 16.3 + HoleWindage;		// front fork mounting plate
    MountHoleOffset = 13.0;				// Y end to hole center
    MountY = Layer1Y;
    MountZ = 4*ThreadZ;					// recess depth
    MountCap = 3.0;						// endcap arc height
    MountR = (pow(MountCap,2) + 0.25*pow(MountX,2)) / (2*MountCap);	// ... radius
    
    Layer3Margin = 1.5;
    Layer3X = Layer2X - 2*Layer3Margin;
    Layer3Y = Layer2Y - 2*Layer3Margin;
    Layer3Z = 3*ThreadZ;
    
    PlateZ = Layer1Z + Layer2Z + Layer3Z;
    
    HoleDia = 0.25 * inch;				// these are 1/4-20 bolt holes
    
    // Convenience settings
    
    BuildOffsetX = 3.0 + Layer1X/2;		// build X spacing between top & bottom plates
    BuildOffsetY = 3.0 + Layer1Y/2;		//	... Y
    
    Protrusion = 0.1;					// extend holes beyond surfaces for visibility
    
    //---------------
    // Create plate
    
    module Plate() {
    
      union() {
    	  translate([0,0,Layer1Z/2])
    		scale([Layer1X,Layer1Y,1]) cylinder(r=0.5,h=Layer1Z,$fn=32,center=true);
    	  translate([0,0,Layer1Z + Layer2Z/2])
    		  scale([Layer2X,Layer2Y,1]) cylinder(r=0.5,h=Layer2Z,$fn=32,center=true);
    	  translate([0,0,Layer1Z + Layer2Z + Layer3Z/2])
    		  scale([Layer3X,Layer3Y,1]) cylinder(r=0.5,h=Layer3Z,$fn=32,center=true);
      }
    
    }
    
    //---------------
    // Create hole
    
    module Hole(OffsetX,OffsetY) {
    
      translate([OffsetX,OffsetY,PlateZ/2])
    	cylinder(r=(HoleDia + HoleWindage)/2,
    			h=(PlateZ + 2*Protrusion),
    			center=true,$fn=10);
    
    }
    
    //---------------
    //-- Build the things...
    
    // Right side
    
    translate([BuildOffsetX,BuildOffsetY,0])
      difference() {
    	Plate();
    	Hole(HoleOffsetX,HoleOffsetY);
      }
    
    translate([BuildOffsetX,-BuildOffsetY,0])
      difference() {
    	  Plate();
    	  Hole(-HoleOffsetX,HoleOffsetY);
    	  translate([-HoleOffsetX,(HoleOffsetY - MountY/2 + MountHoleOffset),(PlateZ - MountZ/2 + Protrusion/2)])
    		intersection() {
    		  cube([MountX,MountY,(MountZ + Protrusion)],center=true);
    		  translate([0,(MountY/2 - MountR),0]) cylinder(r=MountR,h=(MountZ + Protrusion),center=true);
    		}
      }
    
    // Left side
    
    translate([-BuildOffsetX,BuildOffsetY,0])
      difference() {
    	Plate();
    	Hole(-HoleOffsetX,HoleOffsetY);
      }
    
    translate([-BuildOffsetX,-BuildOffsetY,0])
      difference() {
    	  Plate();
    	  Hole(HoleOffsetX,HoleOffsetY);
    	  translate([HoleOffsetX,(HoleOffsetY - MountY/2 + MountHoleOffset),(PlateZ - MountZ/2 + Protrusion/2)])
    		intersection() {
    		  cube([MountX,MountY,(MountZ + Protrusion)],center=true);
    		  translate([0,(MountY/2 - MountR),0]) cylinder(r=MountR,h=(MountZ + Protrusion),center=true);
    		}
      }