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: Thing-O-Matic

Using and tweaking a Makerbot Thing-O-Matic 3D printer

  • 3D Printed Bike Helmet Mirror Mount: Two Years Later

    A bit over two years ago, I hoped my design for a bike helmet mirror mount would prove to be more durable than the fragile commercial mirrors I’d given up on:

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

    Having just tightened the teeny screws that hold the joints in place for the first time since I glued it to the helmet, I’d say it’s working fine. The 2-56 elevation setscrew has worn a slight dent in the arc and the 3-48 azimuth screw worked slightly loose; the mirror didn’t fall apart, but the position wasn’t as stable as it should be.

    If I ever re-do the design, I’ll try adding a recessed metal (brass?) strip along the top of that arc, as that’s the most finicky adjustment. Perhaps a shoe under the setscrew would be better?

    Two years of road grit show up clearly against the yellow plastic, though:

    Bike helmet mirror mount - two years
    Bike helmet mirror mount – two years

    For the record, those 2-56 setscrews require 35 mil hex keys; as Eks reminds me, any design requiring those screws is just crazy talk.

  • Practical Solid Modeling for 3D Printing with OpenSCAD

    I’m teaching an introduction to OpenSCAD for Squidwrench this evening in Highland NY…

    To quote from the course description:

    This intensive course will bootstrap you into designing solid models of useful objects suitable for production on a 3D printer.

    We won’t build anything like this, but it makes a nice showpiece:

    Solid Model - Oblique Exploded Top
    Solid Model – Oblique Exploded Top

    The presentation in PDF form: Practical Solid Modeling for 3D Printing with OpenSCAD – 2013-09-25

    I plan on a bunch of learning-by-doing, but, in the event the typing becomes burdensome, here are the OpenSCAD files:

    A simplified version of the Dishwasher Rack Protector, minus the support structure:

    Dishwasher rack protector - support model
    Dishwasher rack protector – support model
    // Dishwasher rack protector
    // Simplified version for OpenSCAD course
    // Ed Nisley KE4ZNU - July 2013
    
    ThreadThick = 0.25;
    ThreadWidth = 0.40;
    
    Protrusion = 0.1;							// make holes end cleanly
    
    //-------
    // Dimensions
    
    PinDia = 4.0 + 0.5;                 // upright pin diameter + clearance
    PinRadius = PinDia/2;
    
    PinOC = 3.4;                        // bar center to pin center
    
    PinTubeLength = 15.0;               // length of upright tube along pin
    
    BarDia = 4.7 + 0.2;                 // horizontal bar diameter + clearance
    BarRadius = BarDia/2;
    
    BarTubeLength = 30.0;               // length of horizontal half tube along bar
    
    TubeWall = 4*ThreadWidth;           // wall thickness -- allow for fill motion
    
    TubeSides = 4 * 4;                  // default side count for tubes (in quadrants)
    $fn = TubeSides;
    
    SupportClear = 0.85;                // support structure clearance fraction
    
    //-------
    
    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);
    }
    
    //-------
    // Put it together
    
    module Protector() {
    
      difference() {
        union() {
          translate([0,PinOC,0])
            rotate(180/TubeSides)
              cylinder(r=(PinDia + 2*TubeWall)/2,h=PinTubeLength);
          translate([-BarTubeLength/2,0,0])
            rotate([0,90,0])
              rotate(180/TubeSides)
                cylinder(r=(BarDia + 2*TubeWall)/2,h=BarTubeLength);
        }
    
        translate([0,PinOC,-Protrusion])
          rotate(180/TubeSides)
            cylinder(r=PinRadius,h=(PinTubeLength + 2*Protrusion),$fn=TubeSides);
    
        translate([-BarTubeLength/2,0,0])
          rotate([0,90,0])
            rotate(180/TubeSides)
              translate([0,0,-Protrusion])
                cylinder(r=BarRadius,h=(BarTubeLength + 2*Protrusion));
    
        translate([0,0,-(BarRadius + TubeWall + Protrusion)/2])
          cube([(BarTubeLength + 2*Protrusion),
              BarTubeLength,
              (BarRadius + TubeWall + Protrusion)],center=true);
      }
    
    }
    
    //-------
    // Build it!
    
    ShowPegGrid();
    
    Protector();
    

    And a bare-bones version:

    // Dishwasher rack protector
    // Trivial version for OpenSCAD course
    // Ed Nisley KE4ZNU - July 2013
    
    	difference() {
    		union() {
    			translate([0,3.4,0])
    				color("lightgreen")
    					cylinder(r=5,h=15);
    			translate([-15.0,0,0])
    				rotate([0,90,0])
    					color("lightyellow")
    						cylinder(r=6.0,h=30.0);
    		}
    
    		translate([0,3.4,-15.0])
    			cylinder(r=3.0,h=3*15.0);
    
    		translate([-30.0,0,0])
    			rotate([0,90,0])
    				cylinder(r=3.0,h=2*30.0);
    
    		translate([0,0,-5.0])
    			cube([50,50,10.0],center=true);
    	}
    

    A simplified version of the Sink Drain Strainer I wrote up for Digital Machinist:

    3D Printed Sink Strainer - overview
    3D Printed Sink Strainer – overview
    // Strainer Plate
    // Simplified version for OpenSCAD course
    // Ed Nisley KE4ZNU - July 2013
    
    Layout = "Build";				// Handle Plate Show Build
    
    Protrusion = 0.1;							// make holes end cleanly
    
    PlateOD = 150.0;							// strainer plate diameter
    PlateThick = 5.0;							//  .. thickness
    
    HoleOD = 6.0;								// hole diameter
    
    NumRings = 4;								// number of hole rings
    RingMinDia = 20.0;							// innermost ring diameter
    RingStep = 30.0;							// ring diameter increment
    
    HandleOD = 8.0;								// handle diameter
    HandleLength = 15.0;						//  .. length
    HandlePegOD = HandleOD/2;					//  .. mounting peg
    HandlePegLength = 1.5;
    
    //-- Create single handle
    
    module Handle() {
    
    	cylinder(r=HandleOD/2,h=HandleLength);
    	cylinder(r=HandlePegOD/2,h=(HandleLength + HandlePegLength));
    
    }//-- Create single ring of holes
    
    module RingHoles(RingDia,HoleDia,Thickness) {
    
    	Num = floor(90/asin(HoleDia/RingDia));	// how many holes fit in ring?
    	echo(str("Dia: ",RingDia," Holes: ",Num));
    
    	for(n=[0:(Num-1)]) {
    		rotate([0,0,n*360/Num])
    		translate([RingDia/2,0,-Protrusion])
    			cylinder(r=HoleDia/2,
    					h=(Thickness + 2*Protrusion));
    	}
    
    }
    
    //-- Create strainer plate with holes
    
    module StrainerPlate() {
    
    	difference() {
    		cylinder(r=PlateOD/2,h=PlateThick);
    		for (RingID = [0:NumRings-1]) {
    			RingHoles((RingMinDia + RingID*RingStep),
    						HoleOD,PlateThick);
    		}
    		cylinder(r=HandlePegOD/2,h=3*PlateThick,center=true);
    	}
    }
    
    //-- Build it!
    
    if (Layout == "Plate")
    	StrainerPlate();
    
    if (Layout == "Handle")
    	Handle();
    
    if (Layout == "Build") {
    	StrainerPlate();
    	translate([PlateOD/2,PlateOD/2,0])
    		Handle();
    	translate([(PlateOD/2 - 2*HandleOD),
    				PlateOD/2,0])
    		Handle();
    	}
    
    if (Layout == "Show") {
    	color("LightYellow")
    		StrainerPlate();
    	color("LightGreen") {
    		translate([0,0,-HandleLength])
    			Handle();
    		translate([0,0,(PlateThick + HandleLength)])
    			rotate([180,0,0])
    			Handle();
    	}
    
    }
    

    And a bare-bones version, minus the handles:

    Protrusion = 0.1;
    
    PlateOD = 150.0;
    PlateThick = 5.0;
    HoleOD = 6.0;
    
    NumRings = 4;
    RingMinDia = 20.0;
    RingStep = 30.0;
    
    module RingHoles(RingDia,HoleDia,Thickness) {
    
      Num = floor(90/asin(HoleDia/RingDia));
      echo("Dia: ",RingDia," holes: ",Num);
    
      for(n=[0:(Num-1)]) {
    			rotate([0,0,n*360/Num])
    	 	 translate([RingDia/2,0,-Protrusion])
    			  cylinder(r=HoleDia/2,
    														h=(Thickness + 2*Protrusion));
      }
    }
    
    difference() {
    	cylinder(r=PlateOD/2,h=PlateThick);
    	for (RingID = [0:NumRings-1]) {
    		RingHoles((RingMinDia + RingID*RingStep),
    					       HoleOD,PlateThick);
    	}
    }
    

    [Update: The talk went well and took a bit under three hours, although by mutual agreement we didn’t fire up the M2 at the end. I’ll work on a short talk about Design for Printability and we’ll run that with a separate printing session. A good time was had by all!]

  • Creating a Curvelicious Cookie Cutter

    So, for reasons I need not go into, I needed an OpenSCAD solid model of a custom cookie cutter produced on an Afinia 3D printer from a Trimble Sketchup model:

    Afinia Robot Cutter - on raft
    Afinia Robot Cutter – on raft

    The cutter is still attached to the raft that, it seems, is required for passable results on the Afinia’s platform.

    Having already figured out how to wrap a cutter around a shape, the most straightforward procedure starts by extracting the cutter’s shape. So, lay the cutter face down on the scanner and pull an image into GIMP:

    Afinia Robot - scan
    Afinia Robot – scan

    Blow out the contrast to eliminate the background clutter, then posterize to eliminate shadings:

    Afinia Robot - scan enhanced
    Afinia Robot – scan enhanced

    Select the black interior region, grow the selection by a pixel or two, then shrink it back to eliminate (most of) the edge granularity, plunk it into a new image, and fill with black:

    Afinia Robot - scan filled
    Afinia Robot – scan filled

    Now the magic happens…

    Import the bitmap image into Inkscape. In principle, you can auto-trace the bitmap outline and clean it up manually, but a few iterations of that convinced me that it wasn’t worth the effort. Instead, I used Inkscape’s Bézier Curve tool to drop nodes (a.k.a. control points) at all the inflection points around the image, then warped the curves to match the outline:

    Afinia Robot - Bezier spline fitting
    Afinia Robot – Bezier spline fitting

    If you’re doing that by hand, you could start with the original scanned image, but the auto-trace function works best with a high-contrast image and, after you give up on auto-tracing, you’ll find it’s easier to hand-trace a high-contrast image.

    Anyhow, the end result of all that is a smooth path around the outline of the shape, without all the gritty details of the pixelated version. Save it as an Inkscape SVG file for later reference.

    OpenSCAD can import a painfully limited subset of DXF files that, it seems, the most recent versions of Inkscape cannot produce (that formerly helpful tutorial being long out of date). Instead, I exported (using “Save as”) the path from Inkscape to an Encapsulated Postscript file (this is a PNG, as WordPress doesn’t show EPS files):

    Afinia Robot - Bezier Curves.eps
    Afinia Robot – Bezier Curves.eps

    It’s not clear what the EPS file contains; I think it’s just a list of points around the path that doesn’t include the smooth Bézier goodness. That may account for the grittiness of the next step, wherein the pstoedit utility converts the EPS file into a usable DXF file:

    pstoedit dxf:-polyaslines Afinia\ Robot\ -\ Bezier\ Curves.eps Afinia\ Robot\ -\ outline.dxf
    

    Unfortunately, either the EPS file doesn’t have enough points on each curve or pstoedit automatically sets the number of points and doesn’t provide an override: contrary to what you (well, I) might think, the -splineprecision option doesn’t apply to whatever is in the EPS file. In any event, the resulting DXF file has rather low-res curves, but they were good enough for my purposes and OpenSCAD inhaled the DXF and emitted a suitable STL file:

    Afinia Robot - shape slab
    Afinia Robot – shape slab

    To do that, you set the Layout variable to “Slab”, compile the model, and export the STL.

    Being interested only in the process and its results, not actually cutting and baking cookies, I tweaked the OpenSCAD parameters to produce stumpy “cutters”:

    Afinia Robot - solid model
    Afinia Robot – solid model

    You do that by setting the Layout variable to “Build”, compile the model, and export yet another STL. In the past, this seemed to be a less fragile route than directly importing and converting the DXF at each stage, but that may not be relevant these days. In any event, having an STL model of the cookie may be useful in other contexts, so it’s not entirely wasted effort.

    Run the STL through Slic3r to get the G-Code as usual.

    The resulting model printed in about 20 minutes apiece on the M2:

    Robot Cutter - stumpy version
    Robot Cutter – stumpy version

    As it turns out, the fact that the M2 can produce ready-to-use cutters, minus the raft, is a strong selling point.

    Given a workable model, the next step was to figure out the smallest possible two-thread-wide cutter blade, then run variations of the Extrusion Factor to see how that affected surface finish. More on that in a while.

    The OpenSCAD source isn’t much changed from the original Tux Cutter; the DXF import required different scale factors:

    // Robot cookie cutter using Minkowski sum
    // Ed Nisley KE4ZNU - Sept 2011
    // August 2013 adapted from the Tux Cutter
    
    Layout = "Build";				// Build Slab
    
    //- Extrusion parameters - must match reality!
    
    ThreadThick = 0.25;
    ThreadWidth = 0.40;
    
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    
    MaxSize = 150;				// larger than any possible dimension ...
    
    Protrusion = 0.1;
    
    //- Cookie cutter parameters
    
    Size = 95;
    
    TipHeight = IntegerMultiple(3.0,ThreadThick);
    TipThick = 1.5*ThreadWidth;			// 1.5* = thinnest 2-thread wall, 1.0* thread has gaps
    
    WallHeight = IntegerMultiple(1.0,ThreadThick);
    WallThick = 4.5*ThreadWidth;
    
    LipHeight = IntegerMultiple(1.0,ThreadWidth);
    LipThick = IntegerMultiple(5,ThreadWidth);
    
    //- Wrapper for the shape of your choice
    
    module Shape(Size) {
      Robot(Size);
    }
    
    //- A solid slab of Tux goodness in simple STL format
    // Choose magic values to:
    //		center it in XY
    //		reversed across Y axis (prints with handle on bottom)
    //		bottom on Z=0
    //		make it MaxSize from head to feet
    
    module Tux(Scale) {
      STLscale = 250;
      scale(Scale/STLscale)
    	translate([105,-145,0])
    	  scale([-1,1,24])
    		import(
    		  file = "/mnt/bulkdata/Project Files/Thing-O-Matic/Tux Cookie Cutter/Tux Plate.stl",
    		  convexity=5);
    }
    
    module Robot(Scale) {
        STLscale = 100.0;
        scale(Scale / STLscale)
    			scale([-1,1,10])
    				import("/mnt/bulkdata/Project Files/Thing-O-Matic/Pinkie/M2 Challenge/Afinia Robot.stl",
    					convexity=10);
    }
    
    //- Given a Shape(), return enlarged slab of given thickness
    
    module EnlargeSlab(Scale, WallThick, SlabThick) {
    
    	intersection() {
    	  translate([0,0,SlabThick/2])
    		cube([MaxSize,MaxSize,SlabThick],center=true);
    	  minkowski(convexity=5) {
    		Shape(Scale);
    		cylinder(r=WallThick,h=MaxSize,$fn=16);
    	  }
    	}
    
    }
    
    //- Put peg grid on build surface
    
    module ShowPegGrid(Space = 10.0,Size = 1.0) {
    
      RangeX = floor(100 / Space);
      RangeY = floor(125 / Space);
    
    	for (x=[-RangeX:RangeX])
    	  for (y=[-RangeY:RangeY])
    		translate([x*Space,y*Space,Size/2])
    		  %cube(Size,center=true);
    
    }
    
    //- Build it
    
    ShowPegGrid();
    
    if (Layout == "Slab")
    	Shape(Size);
    
    if (Layout == "Build")
    	difference() {
    	union() {
    		translate([0,0,(WallHeight + LipHeight - Protrusion)])
    		EnlargeSlab(Size,TipThick,TipHeight + Protrusion);
    		translate([0,0,(LipHeight - Protrusion)])
    		EnlargeSlab(Size,WallThick,(WallHeight + Protrusion));
    		EnlargeSlab(Size,LipThick,LipHeight);
    	}
    	Shape(Size);					// punch out cookie hole
    	}
    
  • Makergear M2: Mechanical Setup

    That Slic3r configuration presumes a somewhat nonstandard mechanical setup for my M2…

    I put the XY coordinate origin in the middle of the platform, so that laying objects out for printing doesn’t require knowing how large the platform will be: as long as the printer is Big Enough, you (well, I) can print without further attention.

    The RepRap world puts the XY coordinate origin in the front left corner of the platform, so that the platform size sets the maximum printable coordinates and all printing happens in Quadrant I.  This has the (major, to some folks) advantage of using only positive coordinates, while requiring an offset for each different platform.

    Yes, depending on which printer software you use, you can (automagically) center objects on your platform; this is often the only way to find objects created with Trimble (formerly Google) Sketchup. I am a huge fan of knowing exactly what’s going to happen before the printing starts, so I position my solid models exactly where I want them, right from the start. For example, this OpenSCAD model of the bike helmet mirror parts laid out for printing:

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

    … exactly matches the plastic on the Thing-O-Matic’s platform, with the XY origin right down the middle of the platform:

    Helmet mirror mount on build platform - smaller mirror shaft
    Helmet mirror mount on build platform – smaller mirror shaft

    It’d print exactly the same, albeit with more space around the edges, on the M2’s platform.

    Similarly, the Z axis origin sits exactly on the surface of the platform. That way, the Z axis coordinate equals the actual height of the current thread extrusion in a measurable way: when you set the Z axis to, say, 2.0 mm, you can measure that exact distance between the extruder nozzle and the platform:

    Taper gauge below nozzle
    Taper gauge below nozzle

    Now, admittedly, I fine-tune that distance by measuring the height of the skirt thread around the printed object, but the principle remains: a thread printed  on the platform with Z=0.25 should be exactly 0.25 mm thick.

    The start.gcode file handles all that:

    ;-- Slic3r Start G-Code for M2 starts --
    ;  Ed Nisley KE4NZU - 15 April 2013
    M140 S[first_layer_bed_temperature]	; start bed heating
    G90				; absolute coordinates
    G21				; millimeters
    M83				; relative extrusion distance
    M84				; disable stepper current
    G4 S3			; allow Z stage to freefall to the floor
    G28 X0			; home X
    G92 X-95			; set origin to 0 = center of plate
    G1 X0 F30000		; origin = clear clamps on Y
    G28 Y0			; home Y
    G92 Y-127 		; set origin to 0 = center of plate
    G1 Y-125 F30000	; set up for prime at front edge
    G28 Z0			; home Z
    G92 Z1.0			; set origin to measured z offset
    M190 S[first_layer_bed_temperature]	; wait for bed to finish heating
    M109 S[first_layer_temperature]	; set extruder temperature and wait
    G1 Z0.0 F2000		; plug extruder on plate
    G1 E10 F300		; prime to get pressure
    G1 Z5 F2000		; rise above blob
    G1 X5 Y-122 F30000	; move away from blob
    G1 Z0.0 F2000		; dab nozzle to remove outer snot
    G4 P1			; pause to clear
    G1 Z0.5 F2000		; clear bed for travel
    ;-- Slic3r Start G-Code ends --
    

    The wipe sequence, down near the bottom, positions the extruder at the front center edge of the glass plate, waits for it to reach the extrusion temperature, then extrudes 10 mm of filament to build up pressure behind the nozzle. The blob generally hangs over the edge of the platform and usually doesn’t follow the nozzle during the next short move and dab to clear the mess:

    M2 - Wipe blobs on glass platform
    M2 – Wipe blobs on glass platform

    I’ve also configured Slic3r to extrude at least 25 mm of filament in at least three passes around the object. After that, the extruder pressure has stabilized and the first layer of the object begins properly.

    Which brings up another difference: the first layer printed on the platform is exactly like all the others. It’s not smooshed to get better adhesion or overfilled to make the threads stick together:

    Robot cookie cutter - printing first layer
    Robot cookie cutter – printing first layer

    I print the first layer at 25 mm/s to give the plastic time to bond to the platform and use hairspray to make PLA stick to glass like it’s glued down.

    After that, it’s just ordinary 3D printing…

  • Broom Handle Screw Thread: Now With Dedendum

    Although I don’t need another threaded plug, the most recent OpenSCAD version can handle a model including the thread dedendum:

    Broom Handle Screw - full thread - solid model
    Broom Handle Screw – full thread – solid model

    This hyper-close view (as always, clicky for more dots) shows the problem: the region where the addendum and dedendum meet at the pitch cylinder consists of a bazillion tiny faces:

    Broom Handle Screw - full thread - detail
    Broom Handle Screw – full thread – detail

    The previous version simply couldn’t handle that many elements, but the new version has a parameter that I tweaked (to 100,000), allowing it to complete the rendering. Compiling to a solid model requires about 45 minutes, most of which probably involves those unprintably small facets.

    The thread elements now taper slightly in the downhill direction, so that each quasi-cylinder nests cleanly inside the next to avoid the tiny slivers that stuck out of the joints in the previous model.

    And the new Slic3r version (from GitHub) has better internal support for those indentations around the base, which means that AC vent plug might be build-able, too.

    The OpenSCAD source code, with a few tweaks to nest the thread cylinders and properly locate the dedendum:

    // Broom Handle Screw End Plug
    // Ed Nisley KE4ZNU June 2013
    
    //- Extrusion parameters must match reality!
    //  Print with 2 shells and 3 solid layers
    
    HoleWindage = 0.2;
    
    Protrusion = 0.1;			// make holes end cleanly
    
    //----------------------
    // Dimensions
    
    PostOD = 22.3;				// post inside metal handle
    PostLength = 25.0;
    
    FlangeOD = 24.0;			// stop flange
    FlangeLength = 3.0;
    
    PitchDia = 15.5;			// thread center diameter
    ScrewLength = 20.0;
    
    ThreadFormOD = 2.5;			// diameter of thread form
    ThreadPitch = 5.0;
    
    BoltOD = 7.0;				// clears 1/4-20 bolt
    BoltSquare = 6.5;			// across flats
    BoltHeadThick = 3.0;
    
    RecessDia = 6.0;			// recesss to secure post in handle
    
    OALength = PostLength + FlangeLength + ScrewLength;
    
    $fn=8*4;					// default cylinder sides
    
    echo("Pitch dia: ",PitchDia);
    echo("Root dia: ",PitchDia - ThreadFormOD);
    echo("Crest dia: ",PitchDia + ThreadFormOD);
    
    Pi = 3.14159265358979;
    
    //----------------------
    // Useful routines
    
    // Wrap cylindrical thread segments around larger plug cylinder
    
    module CylinderThread(Pitch,Length,PitchDia,ThreadOD,PerTurn=32) {
    
    CylFudge = 1.02;				// force overlap
    
        RotIncr = 1/PerTurn;
        PitchRad = PitchDia/2;
    
        Turns = Length/Pitch;
        NumCyls = Turns*PerTurn;
    
        ZStep = Pitch / PerTurn;
    
        HelixAngle = atan(Pitch/(Pi*PitchDia));
        CylLength = CylFudge * (Pi*(PitchDia + ThreadOD) / PerTurn) / cos(HelixAngle);
    
    	for (i = [0:NumCyls-1]) {
    		assign(Angle = 360*i/PerTurn)
    			translate([PitchRad*cos(Angle),PitchRad*sin(Angle),i*ZStep])
    				rotate([90+HelixAngle,0,Angle])
    					cylinder(r1=ThreadOD/2,
    							r2=ThreadOD/(2*CylFudge),
    							h=CylLength,
    							center=true,$fn=12);
    	}
    }
    
    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,
               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);
    
    }
    
    //-------------------
    // Build it...
    
    ShowPegGrid();
    
    difference() {
        union() {
            cylinder(r=PostOD/2,h=PostLength);
            cylinder(r=PitchDia/2,h=OALength);
            translate([0,0,PostLength])
                cylinder(r=FlangeOD/2,h=FlangeLength);
            color("Orange")
            translate([0,0,(PostLength + FlangeLength)])
                CylinderThread(ThreadPitch,(ScrewLength - ThreadFormOD/2),PitchDia,ThreadFormOD);
        }
    
        translate([0,0,-Protrusion])
            PolyCyl(BoltOD,(OALength + 2*Protrusion),6);
        translate([0,0,(OALength - BoltHeadThick)])
            PolyCyl(BoltSquare,(BoltHeadThick + Protrusion),4);
    
        translate([0,0,(PostLength + FlangeLength + ThreadFormOD/2)])
    		rotate(-90)
            CylinderThread(ThreadPitch,ScrewLength,PitchDia,ThreadFormOD);
    
    	for (i = [0:90:270]) {
    		rotate(i)
    			translate([PostOD/2,0,PostLength/2])
    				sphere(r=RecessDia/2,$fn=8);
    	}
    }
    
  • Search Engine Optimization: Which One Is Not Like The Other Ones?

    Now that Google encrypts your search terms (so they can sell the results to their customers), it’s harder to determine where folks come from. WordPress does report whatever search terms it can, though, and a recent search for plastic kitchen sink strainer caught my eye.

    Here’s what you get (or, at least, what I got on that day) by feeding those words into Google Image Search:

    Plastic Kitchen Sink Strainer - Image search results
    Plastic Kitchen Sink Strainer – Image search results

    Search engine optimization like that is to die for, eh?

    The related post described a cleanup operation that didn’t really achieve very much in the long run:

    Skimming the strainer
    Skimming the strainer

    Some years ago I machined a pair of smoke gray acrylic sink strainers (using LinuxCNC / EMC2 loops and trig functions) on the Sherline and wrote it up for my Digital Machinist column. They came out quite nicely:

    CNC Sink Strainer
    CNC Sink Strainer

    Then I did a 3D printed version on the Thing-O-Matic:

    Strainer plate fill
    Strainer plate fill

    Which produced a note about small features and another Digital Machinist column, of course.

    Subtractive machining is definitely the right hammer for some jobs…

  • Makergear M2 vs. LinuxCNC: Project Overview

    M2 - cushwa Owl - half scale
    M2 – cushwa Owl – half scale

    During the course of my Makerbot Thing-O-Matic experience, I concluded:

    • Enthusiasm may get a product out, but engineering makes it work
    • Plywood and plastic do not produce a stable 3D printer
    • Measurements matter
    • 8-bit microcontrollers belong in the dustbin of history

    With that in mind, I’ve long thought that LinuxCNC (formerly EMC2) would provide a much better basis for the control software required for a 3D printer than the current crop of Arduino-based microcontrollers. LinuxCNC provides:

    • Hard real time motion control with proven performance
    • A robust, well-defined hardware interface layer
    • Ladder-logic machine control
    • Isolated userspace programming
    • Access to a complete Linux distro’s wealth of programs / utilities
    • Access to an x86 PC’s wealth of hardware gadgetry

    Rather than (try to) force-fit new functions in an Arduino microcontroller, I decided it would be interesting to retrofit a DIY 3D printer with a LinuxCNC controller, improve the basic hardware control and sensing, instrument the extruder, then take measurements that might shed some light on DIY 3D printing’s current shortcomings.

    The overall plan looks like this:

    • Start with a Makergear M2
    • See what the stock hardware can do
    • Replace the RAMBo controller with LinuxCNC
    • See what the hardware can do with better drivers
    • Adapt the G-Code / M-Code processing to use more-or-less stock Marlin G-Code
    • Add useful controllers along the lines of the Joggy Thing
    • Improve the platform height / level sensing
    • Rebuild the extruder with temperature and force sensors
    • Start taking measurements!

    My reasons for choosing the Makergear M2 as the basis for this project should be obvious:

    • All metal: no plywood, no acrylic (albeit a plastic filament drive)
    • Decent stepper motors (with one notable exception)
    • Reasonable hot end design
    • Good reputation

    The first step of the overall plan included a meticulously documented M2 build that I figured would take a month or two, what with the usual snafus and gotchas that accompany building any complex mechanism. Quite by coincidence, a huge box arrived on my birthday (the Thing-O-Matic arrived on Christmas Eve, so perhaps this is a tradition), the day when I learned that Mad Phil had entered his final weeks of life.

    As the Yiddish proverb puts it: If you wish to hear G*d laugh, tell him of your plans.

    So I converted a box of parts into a functional M2 3D printer over the course of four intense days, alternating between our living room floor and a card table in Phil’s home office, showing him how things worked, getting his advice & suggestions, and swapping “Do you remember when?” stories. Another few days sufficed for software installation, configuration, and basic tuneup; I managed to show him some shiny plastic doodads just before he departed consensus reality; as nearly as I can tell, we both benefited from the distractions.

    Which means I don’t have many pictures or much documentation of the in-process tweakage that produced a functional printer. The next week or so of posts should cover the key points in enough detail to be useful.

    Not to spoil the plot or anything: a stock M2 works wonderfully well.

    Owl - half size - left
    Owl – half size – left

    For example, a half-scale cushwa owl printed in PLA at 165 °C with no bed cooling and these Slic3r parameters:

    • 500 mm/s move
    • 300 mm/s infill
    • 200 mm/s solid infill
    • 100 mm/s internal perimeter
    • 50 mm/s bottom layer
    • 30 mm/s external perimeter
    • 1 mm retract @ 300 mm/s

    The beak came out slightly droopy and each downward-pointing feather dangles a glittery drop. There’s room for improvement, but that’s pretty good a week after opening a box o’ parts…