## Tuesday, 11 March 2014

### Buried nuts and hanging holes

I needed to make some M4 nuts that could be finger tightened but I didn't have room for a standard wing-nut, so I decided to embed nuts in a printed plastic knob. I knocked up a simple design in OpenScad :-

M4 nuts are nominally 3.2mm thick. I made the base and lid 2.4mm and sliced it with 0.4mm layers. That meant the top of the nut would be flush with a layer boundary at 5.6mm and I confirmed that the first covering layer was at 6.0mm in Skeinlayer. So I needed to pause the build before the start of the layer at Z=6.0 and insert the nuts.

I run my USB machines using Raspberry PIs and OctoPrint (so that all my machines are connected via Ethernet) and noticed a post by the author, Gina Häußge, that said OctoPrint interprets an M0 in the gcode as a pause command. The host stops sending gcode until you press the pause button to un-pause it again. I believe other hosts use @PAUSE to do the same thing.

So M0 is exactly what I needed. The only problem is that up until then I mistakenly thought M0 meant end of program and placed it at the end of the PLA profiles that I distribute. Fortunately the version of Marlin I use ignores it but if you want to use the latest version, or OctoPrint, then you need to remove it from end.gcode, otherwise either the host or the firmware will pause at the end of the print and wait for a button press. Harmless but a bit confusing.

So, armed with a new appreciation of what M0 is, I searched my gcode for the first instance of Z6.0 which looks like this:

F12000.0
G1 X-9.082 Y3.907 Z6.0 F12000.0
G1 X-5.457 Y-3.937 Z6.0 F12000.0
G1 X-7.05 Y-3.803 Z6.0 F12000.0
G1 X-11.486 Y-4.991 Z6.0 F12000.0
G1 X-13.721 Y-10.229 Z6.0 F12000.0
G1 F1800.0
G1 E1.0
G1 F12000.0
M101
G1 X-12.65 Y-10.848 Z6.0 F1837.1615 E0.036


What we have is a sequence of non-extruding moves followed by an un-retract and the first extrusion. The moves are the result of the comb module and not really relevant if we are restarting after a pause, so I removed all but the last move and inserted my pause code:

M104 S100
G1 Z6.0
G1 X-100 Y-100 F9000
M0
G1 X10.0 Y98.0 F9000
G1 Z0.05
M109 S250
G92 E0
G1 E3 F50
G1 E-1 F1200
G1 X40.0 F4000
G1 Z6.0 F9000

G1 X-13.721 Y-10.229 Z6.0 F12000.0
G1 F1800.0
G1 E1.0
G1 F12000.0
M101
G1 X-12.65 Y-10.848 Z6.0 F1837.1615 E0.036


I set the extruder temperature to 100°C to minimise ooze and stop it discolouring while waiting for me to insert the nuts. The bed is left on so the half printed objects don't detach. It then moves up to Z = 6.0 to clear the objects before going to X = -100, Y =-100. That moves the bed to the front and the extruder to the far right on a Mendel90, giving the best access to the partially printed objects. M0 then pauses the program.

I threaded the nuts onto a screw to insert them easily without touching the hot plastic.

After pressing the pause button to make OctoPrint resume, the print head moves to the front of the bed to do another ooze free warmup. The only difference from the start of the print is it parks the nozzle 10mm further left to avoid the blob it has already made and it moves to Z = 6.0 before resuming the print.

This all worked very well except for a slight snag. ABS does not stick to steel, so when it extruded the circular holes on top of the nuts it made a bit of a mess.

Normally I would use a one layer support diaphragm when printing suspended holes and drill it out afterwards. In this case it can't be drilled because the nut is in the way, so I developed a method of printing holes in mid air.

The last layer of the nut trap looks like this:

You can't print a smaller hole on the next layer as the outline would be printed in mid air. The infill is also only attached at one end. After a few layers it does sort itself out but leaves a mess. However, what you can do is print two bridges over the large hole with a gap between them equal to the diameter of the small hole:

This is done by cutting out a one layer rectangle clipped to the hexagon. It is rotated to match the layer's infill direction because Skeinforge fails to detect it as a bridge, probably because the bridged area is tiny.

On the next layer we can bridge in the opposite direction and close down the hole to a square:

Two sides are supported by the edges of the rectangle below and the other two span the gap.

On the next layer we can approximate the hole with an octagon. Four edges are coincident with the square and the other four span small gaps:

It is now a good enough approximation to a circle for such a small hole so it continues up to the top as an octagon. The resulting print is much neater:

The cavity for the nut is made by subtracting a shape like this:

Here is the OpenScad code. It needs various functions from the Mendel90 source tree.

//
// Smaller alternative to a wingnut
//

module hanging_hole(or, ir, ofn = 0) {
union() {
intersection() {
if(ofn)
cylinder(r = or, h = 3 * layer_height, center = true, $fn = ofn); else poly_cylinder(r = or, h = 3 * layer_height, center = true); rotate([0, 0, 90]) cube([2 * or + 1, 2 * ir, 2 * layer_height], center = true); } rotate([0, 0, 90]) cube([ir * 2, ir * 2, 4 * layer_height + 4 * eta], center = true); rotate([0, 0, 22.5]) translate([0, 0, 2 * layer_height]) cylinder(r = corrected_radius(ir, 8), h = 100,$fn = 8);
}
}

base_thickness = 2.4;
lid_thickness = 2.4;

function nut_knob_height(nut) = base_thickness + nut_thickness(nut) + lid_thickness;

module nut_knob_stl(screw = M4_hex_screw, d = 14) {
nut = screw_nut(screw);
h = nut_knob_height(nut);
flutes = 3;

stl("nut_knob");
rotate([0, 0, -45])
difference() {
cylinder(r = d / 2, h = h);                                                 // basic shape

for(i = [0 : flutes - 1])                                                   // flutes for finger grip
rotate([0, 0, i * 360 / flutes + 30])
translate([d * cos(90 / flutes), 0, base_thickness])
cylinder(r = d / 2, h = 100);

union() {                                                                   // nut cavity
difference() {
translate([0, 0, base_thickness + nut_thickness(nut)])

translate([0, 0, base_thickness + nut_thickness(nut)])              // remove top of nut trap
cylinder(r = 20, h = 110);
}

translate([0, 0, base_thickness + nut_thickness(nut)])