Friday, 13 June 2014

Lights, camera, action ...

I have been using the excellent OctoPrint by Gina Häußge to control two of my Mendel90 printers with a Raspberry Pi for a while now. I prefer the convenience of using an Ethernet connection rather than USB. It means I can control a machine from any PC in the house rather than having to dedicate a laptop that had to be close to a machine. Here are the details of my set up: -

Mounting

There are several places a Raspberry PI can be mounted but I chose to place it on top of the PSU so that the wiring was kept short and didn't need to pass though any of the frame elements or need any new holes drilling. I.e. all the electronics are together in one bay.


I made a tray that supports the PCB all the way round and has a couple of pillars with M2.5 nut traps to screw it down. It shares the screws with the bottom of the Melzi, replacing the spacers, which are moved to the top.

raspberry_pi_assembly:
Vitamins:
  2 Nyloc nut M2.5
  2 M2.5 pan screw x 12mm
  1 Raspberry PI model B
  2 Washer M2.5 x 5.9mm x 0.5mm

Printed:
  1 rpi_bracket.stl
When it comes to mounting the camera there are again lots of possibilities. I contemplated something like this:http://www.shapedo.com/danielbull/raspberry_pi_camera_mount_for_the_nop_head_mendel90_3d_printer but I went for a straight on view from the back by printing a bar that spans the stays and clamps to the Dibond.


This gives a view orthogonal and centred with the bed so the only degree of freedom the camera needs is vertical tilt.


The bar can be clamped at any height. Lower gives a better view of the nozzle when it it close to the bed but tall objects soon go out of the field of view. Obviously a longer flat cable is needed to connect the camera to the RPI than the one supplied with it. They are readily available on eBay.

I also attached a 300 lumen LED light strip to the bar which gives enough light for the camera, even in a dark room. Having the light behind the camera avoids any glare from the glass. The particular strip I used is a SPS125 from Sanken Power Systems. I bought them several years ago and I don't think they are commonly available. However, the design is easily customisable by adding a new description to scad/vitamins/light_strips.scad. The clamps should then morph to suit.

The bar is printed in two unequal halves and the shorter one (red) slides into the longer one. The seam marks where the camera should be placed for alignment with the bed and the overlap length is such that both halves are the same height in total, so printing them together allows better cooling without needing excessive slowdown.

raspberry_pi_camera_assembly:
Vitamins:
  2 M2 cap screw x 12mm
  2 M3 cap screw x 10mm
  7 M3 cap screw x 16mm
  2 Nyloc nut M2
  9 Nyloc nut M3
  1 Raspberry PI camera
  1 Sanken SPS125 light strip
  2 Washer M2 x 5mm x 0.3mm
  9 Washer M3 x 7mm x 0.5mm

Printed:
  1 rpi_camera_bar_stl.stl
  1 rpi_camera_back.stl
  1 rpi_camera_focus_ring.stl
  1 rpi_camera_front.stl
  2 rpi_light_clamp.stl
By default the RPI camera is focused at infinity and the lens is locked in place. It is possible to break the seal though and focus it very close indeed giving a microscopic view. For this application it only needs tweaking a little to focus at the middle of the bed. I designed a little focus wheel that can be glued onto the end of the lens carrier to make it easier to turn.

When I ran the camera with the default settings in OctoPrint it streamed data at about 16Mbits / second and used 40% of the RPI's CPU time. It worked fine printing from SD card but slowed down the comms when printing over USB. On a suggestion by Gina I added the usestills option to scripts/webcamDeamon, i.e.
camera_raspi_options="-fps 10 -x 640 -y 480 -usestills"
That reduced the data rate to 3Mbps and the CPU load to 5%. It also had the side effect of vastly increasing the field of view. Here is a time-lapse captured by OctoPrint before I made the change. Notice the reduced field of view compared to the picture above.



Nothing on my WinXP machine would play the mpg files produced by OctoPrint, so I downloaded VLC Media Player. I found that is also able to record the OctoPrint video stream on the host PC. This video clip was recorded during the same build as the time-lapse above.



On another Mendel90 that I only use to print ABS parts, and hence don't fit the fan duct, I use a Logitech C270 USB webcam mounted in front of the machine, which gives a view like this: -


For this machine I mounted the same light strip just behind the gantry on brackets that hang over the top of the stays in the same way as the spool holders.


light_strip_assembly:
Vitamins:
  2 M3 cap screw x 10mm
  2 Nyloc nut M3
  1 Sanken SPS125 light strip
  2 Washer M3 x 7mm x 0.5mm

Printed:
  1 light_strip_bracket_left.stl
  1 light_strip_bracket_right.stl
I prefer the  RPI camera at the back solution but it does have the disadvantage that it looks out into the room rather than at the wall behind the machine.

Wiring

I power the RPI with the 5V standby rail of the ATX PSU and use one of the GPIO lines to turn the rest of the PSU on and off and another to turn the light on and off.

One thing I don't like about the RPI is the use of a micro USB connector for the power. A lot of micro USB leads have two much resistance to have enough voltage left at the RPI end. When you buy them there no indication of resistance but I managed to get some that where about 1.6Ω! To get around that I simply cut the wire off close to the plug and soldered to what was left.

The 5V volt supply comes from the purple wire of the ATX power supply. It looks blue on the photo below, but that is the camera lying! For a solid ground referenced to the logic on the Melzi I run a stout wire to the top terminal of the X limit switch. This ensures any voltage drop or noise in the ground wire between the PSU and the Melzi does not affect the USB comms and I find them rock solid in this configuration.


The remaining connections are the green PS_ON of the power supply goes to the drain of a small SMT MOSFET mounted on the back of the vero board and the negative lead of the light strip goes to the drain of a larger MOSFET. The positive lead of the light strip goes to the FAN+ terminal on the Melzi.

To make the vero circuit I started by cutting the tracks in a few places and drilling out some holes to provide strain relief for the flying leads.


I wanted the board to mount vertically but I didn't have a right angle connector to hand. Since I only need a few pins from one row I surface mounted a through hole straight connector.

Here it is with the MOSFETs and wires added: -

The small MOSFET is a 2N7002L and the larger one is a PHT8N06LT, but almost any logic drive N-channel enhancement mode MOSFETs should work. Neither gets fully turned on at 3V but both easily pass the current required, which is only milliamps for the PS_ON signal and about half an Amp for the lights.

On another machine I just soldered the MOSFETS to the connector and used heatshrink sleeving for strain relief for the flying leads on the drains. Much quicker but a bit fragile.

Software

OctoPrint

There are two ways to get OctoPrint onto a Raspberry Pi. You can start with a Raspbian image such as this one and then add OctoPrint by following these instructions or you can get an image for the RPI with OctoPrint already installed (called OctoPi) from here. The first method needs more steps and gets you the latest version. The second method is simpler but it takes me several hours to download, unzip and copy the image onto the SD card, so the first method can be quicker if you already have Raspbian installed.

When the RPI is booted you should be able to connect to it with SSH client like Putty or Tera Term. The default host name is octopi, username: pi, password: raspberry. If your DHCP server does not register the host name with DNS then you can find its IP address with a free application called Advanced IP Scanner found here. A good tutorial can be found here.

The first thing I do when connected is run :-
sudo raspi-config
to expand the file system, set the time zone and change the host name, etc.

VNC

In order to be able to update the firmware I install the Arduino IDE and to run that I need a remote desktop so I installed TightVNC following the guide here.

Arduino IDE

I install Arduino 1.0.1with:
sudo apt-get install arduino
I then get a copy of Marlin from Github with:
git clone git://github.com/nophead/Marlin.git
I then move the Melzi board support package to the Arduino IDE installation with:
cd Marlin
sudo mv Marlin/Melzi /usr/share/arduino/hardware


The Arduino IDE is a bit slow and clunky running on a RPI over VNC but it does work. A lighter weight alternative is a package called ino that can compile and download Arduino applications from the command line. It still needs the Arduino IDE installation but avoids the need for VNC.

Installation should be as simple as:
sudo pip install ino
But that installs an out of date version that does not scan the hardware directory for additional board support packages, and so does not support Melzi. Instead I install it from source:
git clone git://github.com/amperka/ino.git
cd ino
sudo pip install -r requirements.txt
sudo make install
If make install complains python2 does not exist then do:
sudo ln -s /usr/bin/python2.7 /usr/bin/python2
Ino expects the source code to be in a directory called src whereas the IDE puts it in a directory with the same name as the sketch. A simple workaround is to make a symbolic link in the directory above with:
ln -s Marlin src
rm -rf src/Gen7 src/Sanguino
(The other BSP packages have to be removed from the src directory because otherwise ino tries to build them and fails).

The firmware can then be built and downloaded with:
ino build -m atmega1284 && ino upload -p /dev/ttyUSB0 -m atmega1284

GPIO

To control the RPI's GPIO pins I followed the instructions here: https://projects.drogon.net/raspberry-pi/wiringpi/download-and-install/. This boiled down to:
git clone git://git.drogon.net/wiringPi
cd wiringPi
./build
Then I extended the systems actions section of ~/.octoprint/config.yaml with the following:
  - action: printer on
    command: gpio mode 5 out; gpio mode 6 out; gpio write 6 1
    name: Printer On
  - action: printer off
    command: gpio write 6 0
    confirm: You are about to turn the printer off.
    name: Printer Off
  - action: light on
    command: gpio mode 5 out; gpio write 5 1
    name: Light On
  - action: light off
    command: gpio write 5 0
    name: Light Off
After a reboot the printers PSU and the light can be turned on and off from the OctoPrint system menu. Obviously the light will only come on if the PSU is on.

Files

I added a new Python script that makes the STL files and BOM files for a list of accessory assemblies:
accessories dibond|sturdy|mendel
The STL files can be found here: github.com/nophead/Mendel90/tree/master/dibond/stls/accessories and the parts lists here: github.com/nophead/Mendel90/tree/master/dibond/bom/accessories. I also generated the files for sturdy and mendel variants but I haven't tested those myself.

Monday, 9 June 2014

Why slicers get the dimensions wrong

People using Slic3r often complain that holes come out too small, see forums.reprap.org. This blog post is in reply to the question here.

The issue is not just with round holes (which shrink for reasons I described here) but rectangular or hexagonal holes as well. I got my rectilinear dimensions correct with Skeinforge nearly three years ago and blogged the maths here. I now design exclusively in OpenScad using polyholes to get cylindrical holes the correct size as well.

I have tried Slic3r, Cura and Kisslicer but none of them print the Mendel90 calibration part the right size, so I have stuck with Skeinforge. It is looking a bit old now as Enrique seemed to stop developing it around the time people started saying "Slic3r is nicer", but at least it is stable and has very few bugs.

Looking at the G code produced by Slic3r and Cura, they both assume the extruded outlines have a rectangular cross section. I.e. if I work out the volume of plastic extruded from the E number and feedstock diameter and divide it by the length of the line then I get an area. This is equal to the layer height times the intended filament width and the outline is offset by half the intended filament width. The problem is the filament does not form a rectangle if the edges are not constrained. It forms a rectangle with semi circular ends due to surface tension. The actual width of an outline of a given area is therefore slightly more than it would be if it was a rectangle.
The cross sectional area of a filament with height h and width W is that of a circle with diameter h plus a rectangle of width (W - h) and height h. A = πh2/4 + h(W - h).

Solving for W gives W = A / h + h(1 - π/4).

But slicers output enough filament to fill the rectangle, so A = intended w × h. Substituting that value for A we get W = w + h(1 - π/4). So we have an additional width that is just a fixed multiple of the layer height.
I print a lot of things with 0.4mm layers, so the error is very significant. To fix it in Skeinforge I set the outline flow rate to be enough to fill the rounded rectangle, rather than the full rectangle. I achieve this by setting the "Perimeter Flow Rate Multiplier (ratio)" on the Speed tab. A value equal to the "Perimeter Feed Rate Multipler (ratio)" gives an area equal to the full rectangle. To correct for the fact that it is actually a rounded rectangle one has to multiply the feed rate by the ratio of the areas, which is 1 + (π/4 -1) h / w, a value which only depends on the w / h ratio.
As you can see, when the w / h ratio is high the reduction in flow rate necessary is only about 4%. I assume the reason a lot of people don't notice this is that they use a high w / h ratio and then calibrate a thin wall box, so that the flow rate is low enough to compensate without making the infill too sparse. I generally print with lower values of w / h and prefer to just put in numbers, rather than measure thin wall thickness.

An alternative way to compensate would be to keep the flow rate the same but offset the outline by W/2 instead of w/2. I.e. by an additional h(1 - π/4)/2.

If objects are printed with multiple outlines then the error appears much worse if the inner outlines are printed first working outwards (or the opposite for holes). Looking at the area of plastic this is not obvious mathematically because an additional area of plastic equal to w × h will extend the width by w, preserving the original error but not adding to it.

However, if you consider the practicalities of the process it is obvious that you can't squeeze viscous plastic into the infinitely sharp point under the overhang of the previous outline, particularly when the opposite edge is completely unconstrained. In practice I think you get something like this: -
The plastic that fails to fill the overhang causes the opposite edge to bulge a bit further and this effect then gives the cumulative error that I experience. I don't think the correction factor is easily calculated because it depends on the viscosity of the plastic was well as layer height, etc.

So if you want accurate dimensions and multiple outlines then do the outer perimeter first. For better seam hiding on objects where the dimensions don't matter do the inner perimeters first. I tend to print most things with one outline as you effectively get an extra one free with Skeinforge because it joins the ends of the infill, see hydraraptor.blogspot.co.uk/2008/04/python-beans-make-object.

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
//
include <conf config.scad>

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)])
                        nut_trap(screw_clearance_radius(screw), nut_radius(nut), 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)])
                    hanging_hole(nut_radius(nut), screw_clearance_radius(screw), 6);    // replace with hanging hole
            }

        }
}

So this seems to be a general solution to printing holes in mid air without any support material. The only downside is that it is a bit weaker than using a membrane and drilling it out. In this case no strength above the nut was required. In general you can just make it two layers thicker.