June 15, 2014

Emergent Properties of Meat

Tasmania Sites #1

The first half of our Tasmania visit. The first part is all at Cradle Mountain where we explored many trails. This was the only truly cold day of our trip, but we still saw many beautiful places on our hikes, and wombats, echidnas and wallabies in the wild. There's also pictures here from the beautiful Cataract Gorge in Launceston.

June 15, 2014 07:19 PM

June 13, 2014

MetaRepRap Soup

[wizard23] Our new self assembled Mendel90 at Metalab :) - YouTube

Our new self assembled Mendel90 at Metalab :) - YouTube

June 13, 2014 09:46 AM


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


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.

  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

  1 rpi_bracket.stl
When it comes to mounting the camera there are again lots of possibilities. I contemplated something like this: 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.

  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

  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.

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

  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.


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.



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.


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://
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://
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


To control the RPI's GPIO pins I followed the instructions here: This boiled down to:
git clone git://
cd wiringPi
Then I extended the systems actions section of ~/.octoprint/config.yaml with the following:
  - action: printer on
    command: 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.


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: and the parts lists here: I also generated the files for sturdy and mendel variants but I haven't tested those myself.

by (nop head) at June 13, 2014 12:42 AM

June 11, 2014

Iltarastit Solvalla

Last orienteering run before Jukola - maybe mistakes made now means there are none left in the bag for Jukola?


K-1-2 nothing special. #2 is best split.
3 more north out of #2 would have been less steep downhill, and would have found the road sooner.
4 crazy mistake although I was inside the control circle on 2 or 3 occasions..
5-6-7-8 nothing special
9 split is slow but GPS-trace follows line OKish. Maybe a little south would have been faster.
10-11-12 OKish, but there is no excuse for not running straight along the 11-12 line.
13 came up from the flat "Punjonsuo" a little more left than had planned, but was easily on the map again.

by admin at June 11, 2014 07:37 PM

June 09, 2014

MetaRepRap Soup

[alphabet] Mendel90 Assembly @ Metalab

0817 3f61 400

Mendel90 Assembly @ Metalab

[Reposted from wizard23]

June 09, 2014 03:59 PM


Why slicers get the dimensions wrong

People using Slic3r often complain that holes come out too small, see 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

by (nop head) at June 09, 2014 01:19 PM

June 08, 2014

Itärastit Paloheinä


S-#1 the paths directly north would have been faster
#5-#6 running on the roads, maybe even straight over the hill, would have been faster than pushing through the green area.
#6-#7 bad direction out of #6, straight north would have been OK
#7-#8 most time lost here, and worst split :( Ran along the line until almost inside the control-circle but then circled around loosing 3 minutes. The road north, maybe via the big stone before the control, would have been a safer option?
#11-#12 aiming for the same North-South paths that lead to #1 would maybe have been faster than running along the blue ditch in the green.

Maybe one more orienteering practice before Jukola next weekend!

by admin at June 08, 2014 09:28 AM

June 07, 2014

MetaRepRap Soup

June 03, 2014

Espoorastit Oittaa


Four troublesome controls out of 15 in total.

#4 would have deserved more respect - I just ran roughly out from #3 and started to look for #4 on the wrong hill. Should have used the big stone and the big path as intermediate checkpoints.

#9 was located quite low and close to the yellow field, so the approach from the powerline and cliffs above is completely wrong. Should have instead taken the small path all the way to the corner of the field and #9 from there.

Inexplicably lost track of the hills on the way towards #10 and looked for it already on hill nr 2. Then a poorly planned direction out of #10 towards #11 through slow terrain. Staying on the white area of the map would have been much faster.

by admin at June 03, 2014 09:42 PM

Emergent Properties of Meat

Zoos in Victoria

We started out the next part of our trip in Werribee, where friends live, and they took us to the Werribee Open Range Zoo, run by the Melbourne Zoo. There you can ride in a bus around the safari area, seeing plenty of mainly African animals along the way. You can also walk around other areas and explore other animals. They also took us to other sites in Werribee. The next day we wandered around the entire Melbourne Zoo, which is quite an amazing place.

June 03, 2014 02:59 AM

June 02, 2014

MetaRepRap Soup

May 29, 2014

Länsirastit Kiimasuo


A map where the straightest route was fastest. Many places had cut down trees which were best avoided.

2-3: Drifted too much right, thus my route is unnecessarily long at 137% of the straight-line distance.

5-6: Tried to avoid the slow green area by running on the road/rock, but didn't hit the control and ran past it a bit. Worst split :(

8-9: Not much to read/observe on the downslope towards #9. My direction out of #8 is off a bit towards the south and I should have used the big stone and open clearing (circled yellow) as an intermediate checkpoint. Crossed the ditch too much south but found #9 reasonably quickly.

by admin at May 29, 2014 07:12 PM

MetaRepRap Soup

May 27, 2014

Firmaliiga 2014/3 - Pirttimäki


About twenty degrees colder than on Saturday, with +6C and rain today!

Biggest problem directly at the start when I looked for #1 on the wrong hill  :(
Otherwise mostly OK with no major mistakes.

by admin at May 27, 2014 09:00 PM

May 25, 2014

Itärastit Ämmässuo


Really hot yesterday - more or less 50% walking from #3 onwards...

#1-#2 OK
Just before #3 should have stayed on the white area - it was slower to cross the yellow open area.
#4-#5 OK
#6 a bit surprised to see the control so soon - did not have a good idea of where I left the road.
#7-#8 OKish (down the hill from #7 perhaps a more direct line over the wet/slow blue area would have been faster)
#9 was going OK all the way into the control-circle, but then went for the wrong group of stones and spent about 3-4 minutes extra here.
#10-F easy but quite tired and dehydrated at this point. Should bring water for 60+ minutes of running in the heat!

Firmaliiga at Pirttimäki on Tuesday is next.

by admin at May 25, 2014 04:46 PM

May 21, 2014

Firmaliiga 2/2014, Kattilajärvi



The second firmaliiga event this spring was held yesterday at Kattilajärvi.

I was on the E-course, and had no major problems on 7 out of 8 legs.

On control #5 I managed to spend three times as much time as the best runner!? How is that possible..


  • First I took the wrong direction, by maybe 45 degrees to the right, out of #4 :(
  • Then I mistook the hill close to #7 for the one on the #4-#5 line, and took the path at almost 90 degrees to my correct direction :(
  • When finally getting a grip on things again, around the 90-degree bend in my path, I think I am located somewhere north of the #5-#6 line, and I start to search for the control on the wrong hill :(


by admin at May 21, 2014 04:41 AM

May 18, 2014

Itärastit Kauhala


#4 didn't cross the blue swamp - it looked quite wet...
#8 looked for the control too soon, in a difficult place on a steep slope
#11 again anticipated the control too soon, even turned back and ran north along the trail for a while. Should have stayed closer to the line, not sure why there is drift to the right just after #10. Why not use the yellow open area as a midway checkpoint?!?

by admin at May 18, 2014 08:10 PM

May 17, 2014

Emergent Properties of Meat

Healesville Sanctuary

After Wilsons Promontory, we visited Healesville Sanctuary, one of the Zoos Victoria sites. At Healesville, all the animals are Australian. The day was overcast and lightly rainy, but not enough to interfere with our enjoyment of the animals.

They had displays of wombats and Tasmanian devils, but the animals were not visible when we went by. They also had a platypus on display, but the lighting was too dim to be able to get a photograph. Too bad, as we never got a platypus photo on the whole trip, though we did see them in zoos twice.

May 17, 2014 08:10 PM

May 15, 2014

Emergent Properties of Meat

Samsung ARM Chromebook: the portable I've always wanted

As you might remember, I got a chromebook back in January. A few months later, I just want to say: this is the portable I've always wanted.

May 15, 2014 04:28 PM

May 14, 2014

Espoorastit Juvanmalmi


#2 should have crossed the blue wet area later, now ended up on the wrong hill
#6 passed the right stone on the wrong side, looped back and found the flag on the other side of the stone!
#8 drifted too much right

by admin at May 14, 2014 04:42 PM

May 12, 2014


2D Sideways Offset Using Weaves

Most CADCAM systems have about half a dozen 2D polygon offsetting algorithms somewhere about inside them. None of them work very well because the trickiness of the problem is notoriously easy to underestimate. The naive method is to offset each line segment perpendicularly sideways and then insert an arc where the angle between two adjacent lines is convex, or otherwise trim them back when the angle is concave, like so:

This seems attractive because it’s going to work whenever the offset distance is very small. Once you offset further than half the minimum distance between any two vertices, then you start to get multiple self-intersections, some of which involve intersections with the inserted arcs. A little bit of thought and you’ll see that this quickly enters a quagmire and you’ll be debugging it for the next 20 years. This is not an exaggeration.

An alternative method is to use Voronoi diagrams. These are great if you want to offset the same polygon by numerous different values, for example if you are producing some offset area clearing passes. The API into such algorithms (if you’ve got one) is daunting, and it’s going to be very time-consuming to add features such as allowing offset to be on only one side.

When all else fails, there’s my machining method, where we repurpose one of the waterline machining algorithms to give the answer that we want. Here we imagine converting the contour we would like to offset into a vertical wall of rectangles, and then we run a flat-bottomed cutter against it. My weave sub-sampling waterline algorithm delivers the structure shown in cyan with a contour going all the way round it on both sides.

Now the trick is to break the contour so it gives only the right hand side (the yellow curve). We have filters for tracing out partial waterline contours based on the contact angle with the tool-tip — for example producing z-constant toolpaths only where the tool is in contact with the surface at greater than 75degrees so they only appear on the walls.

So we could fake this by tilting the vertical wall to one side so that on the upper side the tool tip makes contact with a slope at an angle of less than 75degrees, and on the undercut side the tool shaft hits the top edge giving an effective slope of 90degrees.
But this gets messy. It’s better to add in a special toolpath filtering case that simply works for this application and sets the flags to filter out the toolpath when the contact direction either on the left hand side of the contour, or in contact with one of the end-points of the contour if the contour is not closed.

The disadvantage of this method is that your offset contour is made up of hundreds of little line segments that only approximate the offset with regards to the endpoints of straight sections and linearizations of the arcs. But does this matter? Graphically it looks exactly the same. It might be good enough — especially if we don’t have to debug it for the next 20 years.

If I was starting this fresh and not trying to get it done in a day, I could encode which line segment or vertex each point of the offset shape was offset from, and then try to rejoin sequences of segments into the same line or arc where they came from the same entity. Then, after that, for segments that jump the gap between a offset point from entity A to a offset point from entity B, I’d extend the two offset passes to the true intersection between A and B. This would be robust almost all the time. And it would be fail-safe because wherever there was a problem it would fall back to the approximate offset — which is almost always going to be better than crashing or giving a properly screwed up result.

by Julian at May 12, 2014 07:32 PM

May 10, 2014

Emergent Properties of Meat

More Sites Around Victoria

Here are some fun photos showing a wide variety of places we visited over the course of a week in Melbourne and cities around Victoria. These include the Halls Gap Zoo (a small rural zoo with quite a few animals), Horsham (small town where Ingrid's friend teaches), Grampians (beautiful national park), Geelong (city south of Melbourne with the National Wool Musem), CERES Environmental Park (with community gardens, small farms, delicious cafe, plant nursery, tons of educational displays, and much more) and some community gardens on public housing estates.

May 10, 2014 04:53 PM

May 07, 2014

Iltarastit Pirttibacka


#1 close to the control I didn't find the path up the hill. The bigger path left was probably the more popular (and faster?) route choice.
#27 the plan was to stay closer to the line, but probably did not loose that much time anyway.
#29 rounding the hill to the right might have been faster, and close to the control I ran 100m too far on the road.

by admin at May 07, 2014 07:29 PM

May 04, 2014

Itärastit Uutela


#2, bad (easy!) right-ish direction out of #1
#10 too high up the hill from #9 (which was also hard to find)
#11 slow walking up the hill
#12 down to the path and round the steep cliff - maybe straighter would have been better?
#14 OK "on the map" around the forbidden area, but then drifted left down the hill. worst split :(
#15 ok orienteering just too slow..
#19 well hidden control on steep downslope
#21 drifting left into the woods when there would have been a straight path

by admin at May 04, 2014 08:41 AM

April 30, 2014

Emergent Properties of Meat

Towards fast I/O in Arduino for AVRs

So I've been working on something. I've only looked at assembler output so far, but it looks right and promising.

Basically, I'd like to let you name I/O pins via typedef, then efficiently read and write the pins. And I'd like to make Arduino's digitalWrite just as efficient when the pin argument is a constant.

All instruction counts are in Debian Wheezy's avr-gcc version 4.7.2 with -mmcu=atmega328p -Os specified on the commandline.

// You can declare ports (they use zero RAM)
typedef IOPIN<PORT_B, 0> iob0;
typedef IOPIN<PORT_B, 1> iob1;
typedef OCPIN<PORT_B, 1, true> ocb1; // emulate an open collector port

// .. and use them efficiently (instruction counts never include ret)
int en() { iob0::output(); }                // 1 instruction
int fn() { iob0::toggle(); }                // 1 instruction
int gn() { ocb1::set(); }                   // 2 instructions
int hn() { ocb1::clear(); }                 // 2 instructions
int jn(bool b) { iob0::write(b); }          // 5 instructions
int kn() { iob0::write(1); }                // 1 instruction

// you can use fastDigitalWrite just like digitalWrite (but faster and inline)
int c(int i) { fastDigitalWrite(1, i); }    // 5 instructions
int d() { fastDigitalWrite(1, 1); }         // 1 instruction

// these have a variable port so they still turn into calls to digitalWrite
int a(int i, int j) { fastDigitalWrite(i, j); }
int b(int i) { fastDigitalWrite(i, 1); }

Files currently attached to this page:


April 30, 2014 09:14 PM

April 29, 2014 (MetaLab)

[alphabet] ZnSe Co2 Laser Focus Lens

[Reposted from lazzzor]

<p><a href=""><img alt="7935 6694 400" height="400" src="" width="400" /></a></p> <p><a href="">ZnSe Co2 Laser Focus Lens </a><br><small>Diameter 20mm - Focal Length 50.8mm</small><br><br>Die Ø 20mm El Cheapo Linse aus fernen Landen ist zu <b>breit</b>. <br>Doch damit kennt sich der <a href=""></a> aus und hat sie uns auf die für den Epilog <a href="">Lazzzor</a> benötigten max. 19.2mm gebracht. <br><br><b>Sie tut nun wieder gut</b>. <br><i>Bis auf dass der Russ uns scheidet! </i><small><br>(oder unsachgemäße Reinigung)</small><br><br>Danke Zwax und Overflo!</p>

April 29, 2014 03:30 PM

April 27, 2014


Open call for AMRI 2014 Summer Fellowships

We are thrilled to announce an open call for Summer 2014 Fellowships at Advanced Manufacturing Research Institute (AMRI), hosted at Rice University in the department of Bioengineering.

This year there is an open call for applications for the following projects:

Project 1: e-NABLE 3D Printed Prosthetic Devices
In collaboration with the worldwide e-NABLE group, and Gloria Gogola, M.D. at Shriners Hospital for Children, Fellows will aid in the design, 3D printing, testing, and refinement of open-source prosthetic hand and finger designs. This unique fellowship will bring 3D printing into the clinical setting, working closely with Dr. Gogola and her patients in need.

Project 2: Selective Laser Sintering (SLS)
Fellows will augment and refine the open SLS design pioneered by Andreas Bastian last year. SLS machines typically cost $50k or more, we built ours for under $15k. This year we will focus on powder manufacturing and powder handling, as well as characterization of SLS parts via scanning electron microscopy (SEM) and mechanical testing.

Project 3: OLED 3D Photolithography of Living Tissues
Related to Anderson Ta’s exciting digital light projection (DLP) photolithography last year, Fellows will investigate and program organic light emitting diode (OLED) screens as a light source for 3D photolithographic printing of living tissues. Chemical functionalization of glass surfaces will also be investigated to passivate the screen surface and aid in detachment and 3D printing from the light source surface.

Check out all the details, and be sure to apply by May 15th:

Questions can be directed to

by (jmil) at April 27, 2014 07:43 PM

Itärastit Salmenkallio


#1 lots of problems with mistaking one path for the other as well as not knowing which big stone on the map is which in reality
#2 looping close to the control circle - should have a good secure plan all the way to the control
#3 no real route plan and as a result a completely crazy path up&down a big hill. the paths to the left would have been much better probably.
#4, #5, #6, and #7 went OK with #7 being the best split, mostly running on compass-bearing on top of the hill - checking progress form stones/shapes on the map.
#8 drifted too much to the right and going down the steep hill was slow. a bit of hesitation close to the control.
#9, #10, #11 OKish.

by admin at April 27, 2014 10:58 AM

April 26, 2014


Arduino powered mini machine tool

A mere 9 years after we first tried to use our own mini-machine tool — which Martin wants to make sure everyone understands he did get working (shortly before it broke) — it’s been brought out of the cupboard and fitted with some proper electronics in the form of an Arduino controller by the DoESLiverpool Italian intern, and is showing signs of life.

The part of this kit that gave me the most surprise was the GRBL G-code parser code that ran on the Arduino. Look at the amount of software crammed onto that thing! Watch out Hurco and Heidenhain, your days are numbered. Soon all your industrial equipment will be deader than a Vax VMS.

My favourite function in the GRBL code is report_realtime_status() which prints a line of the form

<idle WPos:10.999,-0.5,9.1>

Yes, it’s the current position of the tool while it is running.

I immediately wrote a threading Python program to drip tool motions into the Arduino interspersed with the “?” command to invoke this realtime status to find out where the position currently had got to. Eventually this could become the principle behind a combined probing, cutting and dynamically feed-backing CAM system — something that cannot be implemented on industrial machines today due to their batch-work nature and one-way post-processing valve filter. For more, see earlier post on the feedback in the cnc drive configuration.

While writing an internal rant about why it’s okay to machine triangles, when some CAM systems claim to be “more accurate” because they locate their cutter positions against the analytic geometric surfaces themselves, I came to an important observation: the point nodes of the toolpath as defined in the G-code file are of no significance to the total cutting process, which continues along the straight lines between the nodes. All these cutting positions on the lines between are never on the accurate surface, so getting the zero dimensional finite set of node positions exactly right doesn’t make a difference.

by Julian at April 26, 2014 07:50 AM

April 22, 2014

Iltarastit, Svedängen

Mostly jogging all the way, and no major mistakes.
out of 11 could have maybe gone straighter.
not sure why the split for 8 is so bad/slow...
the blue area close to 28 was very wet - should have anticipated this and approached from the left, not straight.


by admin at April 22, 2014 03:03 PM

April 21, 2014

Itärastit, Latokartano

Slow but no major mistakes.
9->10 might have been faster/clearer to run higher up the hill, past the clearly visible big stones.
10->11 maybe straight down the hill to the path from #10 would have been better.
12->13 I wasn't using a compass, which resulted in the wrong direction out of #12.


by admin at April 21, 2014 09:31 AM

April 18, 2014

Emergent Properties of Meat

Victoria Sites

After returning from Queensland, we explored a variety of sites to the east of Melbourne. Wilsons Prom was a beautiful park on the southern coast, and we went on a couple of nice cruises on the Murray River.

April 18, 2014 08:27 PM

April 15, 2014

Emergent Properties of Meat

GemCraft: Chasing Shadows "Grey Trees" puzzle

So you may have noticed the achivement "Grey Trees" "11331791" in the list. I haven't seen anybody explain this code or the related codes yet, so here's my explanation. Non-plot spoilers follow.

April 15, 2014 01:36 AM

April 13, 2014

Emergent Properties of Meat

GemCraft: Chasing Shadows compass puzzle

Almost solved compass puzzle (increased contract)
In GemCraft: Chasing Shadows, there are seven fields which have a special compass item. The game never explains the purpose of the compasses, though the first thing you'll note is that on the loading screen of a compass level, there are seven gem outlines shown. I googled around but did not find anyone else who has explained the compass puzzle, so here's my explanation. Spoilers (but not plot spoilers) follow.

April 13, 2014 02:12 PM

April 08, 2014


Custom G-code Generation with Mecode

If you've ever wanted to hard-code gcode but still retain some scripting flexibility (for art, science, or engineering), Jack Minardi just posted a custom g-code generation package he's been working on... it looks great.

Checkout the RepRap wiki entryand
also the github repo with instructions

This could be a big win for 3d printing sacrificial inks like sugars and pluronics where each extruded filament position needs to be placed with precise (x,y,z) coordinates. And for arcs and meanders, there are built-in functions too! Very exciting. From the Github README:
To use, simply instantiate the G object and use its methods to trace your desired tool path.
from mecode import G
g = G()
g.move(10, 10) # move 10mm in x and 10mm in y
g.arc(x=10, y=5, radius=20, direction='CCW') # counterclockwise arc with a radius of 5
g.meander(5, 10, spacing=1) # trace a rectangle meander with 1mm spacing between passes
g.abs_move(x=1, y=1) # move the tool head to position (1, 1)
g.home() # move the tool head to the origin (0, 0)

We got a chance to meet Jack at MRRF and everyone had a great time. Jack Minardi is currently a Research Fellow at Lewis Lab at Harvard.

by (jmil) at April 08, 2014 03:43 PM

Emergent Properties of Meat

Hacking flash apps...

So I enjoy the GemCraft game series, so I was excited to play GemCraft Chasing Shadows. Unfortunately, the game refuses to run on the latest version of Flash Player available for Linux in Firefox. Happily, I've been able to fix that for my own personal use.

April 08, 2014 03:41 PM

April 02, 2014

Espoorastit, Vermo

Orienteering season is here again!

Mostly running and no orienteering on this 1:4000 scale map.


by admin at April 02, 2014 06:38 PM

April 01, 2014

MetaRepRap Soup

March 30, 2014

Emergent Properties of Meat

Australia: Granite Gorge, Fitzroy Island and Lake Barrine

On our way to Cairns, we stopped at Granite Gorge, a private park. You can buy food to feed the Rock Wallabies (supposedly it doesn't make them fat, but they sure like it), and then take a nice hike over many boulders. It was a fun and very scenic day, and then we swam for a bit in their pond to cool off (though Jeff sunburned his back here). There's also a few more photos here from Fitzroy Island, where we enjoyed the rainforest in addition to the reef snorkeling already posted. Finally, we have some photos from our walk around Lake Barrine, a beautiful lake inland from Cairns.

March 30, 2014 06:59 PM

March 27, 2014

BodgeItQuick Rep Strap Bertha Project

The rain from the east took its time today so I was able to pop up 7 Solar panels on to the Home Office roof. Lesson learnt today allowing only a +/- 1mm tolerance on a roof is far too tight a tolerance even when using laser cut templates instead of a ruler it was very tight but they are nice and tight on the roof. Just have to wait for another rain free day to add the rest.
The rain from the east took its time today so I was able to pop up 7 Solar panels on to the Home Office roof. Lesson learnt today allowing only a +/- 1mm tolerance on a roof is far too tight a tolerance even when using laser cut templates instead of a ruler it was very tight but they are nice and tight on the roof. Just have to wait for another rain free day to add the rest.

by (BodgeIt) at March 27, 2014 05:24 PM

March 26, 2014


More triangulation carpeting

Trying to get this surface triangulation distraction closed down today. The proposition was to see whether I could repurpose my toolpath strategy code and apply it to the problem of triangulating trimmed NURBS surfaces. After all, trimmed NURBS surfaces exist on a 2D parametric domain (like the 3-axis drive plane), and the trimming curve is very much like a machining boundary. You have to know that something like the constant scallop routine is actually modelling a surface that is repeatedly being eroded away, because the toolpaths you see are in fact the boundary outline of that surface.

One of the common triangulation results I kept getting looks like a nice Afghan carpet pattern, like this:


I have an explanation. This happens when maxHdiffFibre is too small.

The surface is divided up into cells, and the sides of each cell has at least length/maxHdiffFibre extra points inserted along them. The convex polygonal area of each cell is then triangulated by lopping off the sharpest corners repeatedly, until you have an 8-sided shape that still has points along the top and bottom sides because they were the longest to begin with, so these are joined up. The outer boundary is the trimming curve, so one side of over-sampled cell is trimmed away leaving three sides that have too many points which need to be joined up in the best way they can. Then, at the corners of the trimming curve, the cells are subdivided to get smaller and smaller complete rectangular cells which are small enough not to be over-sampled.

I don’t think anyone else in the world misuses a 3-axis toolpath algorithm to make a triangulated surface, so this type of buggy behavior is unique.

Meanwhile, a correspondent of the blog took up the challenge of making an excellent triangulation of a cone. He did this by starting with the triangular lattice with rotational symmetry of order 6, and then cutting a pie slice out of it in order to join it together to form a cone. Because he didn’t have the power of Twistcodewiki to draw 3D geometry direct from Python in your browser, he implemented the picture of all the edges by exporting Postscript commands, and didn’t have the capability of drawing the triangles themselves in order to see the folds:


Twistcodewiki is great. I use it all the time, even though its light/shading features are crude and terrible, and the interface is almost non-existent. But the one thing it does is so important and unique that I can ignore the fact that it doesn’t do it very well. The same probably goes for a lot of software out there.

by Julian at March 26, 2014 12:29 PM

March 22, 2014 (MetaLab)

[alphabet] Internal Memo

0686 bb70

Internal Memo

Due to a broken lens the Lazzzor is out of order until further notice.

Metalab Facility Management

March 22, 2014 05:44 PM

March 21, 2014

Emergent Properties of Meat

Port Douglas Wildlife Habitat

Wildlife Habitat at Port Douglas, a zoo of North Queensland animals, including a great variety of birds, mammals and reptiles. There was plenty to see and do, and plenty of pictures to be taken. We'd already seen many of the birds in the wild, but it was neat to see them up close.

March 21, 2014 07:10 PM

March 20, 2014

BodgeItQuick Rep Strap Bertha Project

Sorry for the long delay in post but I have been very busy.. By April I will be fully off grid solar powered for Electricity and  Today I will start to integrate the good bits of my my 20 year  old Fridge Freezer into my 1 year old Laser cutter ready for full scale Production in April if all goes well fingers crossed.. .. Reason the laser cutter needs to have its coolant kept below 20C Currently even with a CW3000 Industrial Laser chiller Im limited to 2-3 hours of Laser cutting this is ok for R&D work depending on my coolant start temperature which is about 8-10C. As I discovered last year Ambient temperate soon creeps up and reduces my lasering time considerably.  It took me 2-3 hours of careful destructive disassembly to recover the whole freezer refrigerant system in tact still full of its refrigerant gas.
You ask why dose a 60W laser need so much cooling!! Well the laser tube is only 15% efficient so there is about 300W of waste heat to get rid of. The American RF Lasers are built into a huge lump of Aluminium the 50W one I had on loan was set in 25+ kilos of Aluminium this is then cooled by a stack of cooling fans take a look at the FabLab 30W laser it has a full row of fans on the top and bottom and back of the machine. Pictures of my Laser cutter the mods to follow...

by (BodgeIt) at March 20, 2014 05:06 PM

March 18, 2014


The impossibility of a good triangulation of a cone

I hadn’t worked on triangulating surfaces since the first version of machining strategist in 1994, but always thought I could do it better by distributing the triangles in a nice enough manners. It is true that the people who write surface triangulators in engineering CAD packages don’t in general try very hard, but I don’t think many folks realize that there is no right answer.

Suppose we have a smooth surface S, defined by a function from a subset of the plane to real space

  f: D ------> R3, where D is a subset of R2 
                  representing the set of points within a boundary B defined by:
  b: [0,1] ------> R2 where b(0) = b(1)

… roughly speaking.

We want to approximate this surface to the tolerance e with a set of connected triangles defined by triplets of points in R3-space:
T = { (p0, p1, p2) }

It would be nice if it had the following properties:

1. For every point p in every triangle, there is a point q in S where |p-q| < e.

2. For every point q S, there is a point p in one of the triangles where |p-q| < e.

3. For every point p on a 1-sided triangle boundary, there is a point q in B where |p-q| < e.

4. For every point q B, there is a point p on a 1-sided triangle boundary where |p-q| < e.

5. There is a smooth continuous homeomorphism from S to the surface set of points in R3 that inside all the triangles that doesn’t move any point by more than e.

6. The orientation of the fold at each 2-sided edge triangle boundary is consistent with the curvature of S in that region.

7. The triangle approximation should depend only on the embedding of S in R3, and not have any relation to the 2D parameter map f.

8. The triangles should be as close to equilateral as possible; there should be no shards.

9. There should be as few triangles as possible to satisfy the tolerance value e.

Admittedly, these conditions aren’t very well specified, and line 5 implies the previous four, but I’m just being a lazy amateur. A professional whose job it was to produce high-quality triangle approximations of engineering surfaces would do it properly, I’m sure.

Who cares about the speed? I wrote a program that flipped and cracked triangles under the following three triangular manifold transforms to gradually change an initial triangulation into something that satisfied the requirements above.


The first snag I hit was that I wanted to that the second transform that moved a vertex into the centre of its surrounding polygon wasn’t going to change the standard rectangular triangulation into the more even hexagonal form.


No matter. The smallest perturbation would shake it away from this initial form so things would quickly spread and crystallize out.

But, here’s the real problem. What’s going to happen with a developable surface, like a cone, which is made up of straight lines?


Clearly, the majority of the triangle boundaries must run along these lines. But at the thin end we need n-sides to approximate the surface to tolerance, but at the fat end we need 2n-sides. At some point there is a transition. And at this transition you will need to make edges that go around the cone. And there is no way to do this without edges that fold outwards and produces a manifold that bounds a non-convex volume.

So, although the cone bounds a purely convex volume, we cannot respect its convexity with a triangulated model without over-triangulating at the thin end.

Usually a CAD model is made up of different surface patches that join together. By default, because no one tries to make the triangulations within the patches interesting, the nasty wrong-folding transitions tend to happen at the joins, and so are mis-characterized as a multi-surface problem, rather than an issue that runs deeper and within the impossibility of defining the perfect answer.

by Julian at March 18, 2014 01:54 PM

March 17, 2014

Simple Trajectory Generation


The world is full of PID-loops, thermostats, and PLLs. These are all feedback loops where we control a certain output variable through an input variable, with a more or less known physical process (sometimes called "plant") between input and output. The input or "set-point" is the desired output where we'd like the feedback system to keep our output variable.

Let's say we want to change the set-point. Now what's a reasonable way to do that? If our controller can act infinitely fast we could just jump to the new set-point and hope for the best. In real life the controller, plant, and feedback sensor all have limited bandwidth, and it's unreasonable to ask any feedback system to respond to a sudden change in set-point. We need a Trajectory - a smooth (more or less) time-series of set-point values that will take us from the current set-point to the new desired value.

Here are two simple trajectory planners. The first is called 1st order continuous, since the plot of position is continuous. But note that the velocity plot has sudden jumps, and the acceleration & jerk plots have sharp spikes. In a feedback system where acceleration or jerk corresponds to a physical variable with finite bandwidth this will not work well.


We get a smoother trajectory if we also limit the maximum allowable acceleration. This is a 2nd order trajectory since both position and velocity are continuous. The acceleration still has jumps, and the jerk plot shows (smaller) spikes as before.


Here is the python code that generates these plots:

# AW 2014-03-17
# GPLv2+ license
import math
import matplotlib.pyplot as plt
import numpy
# first order trajectory. bounded velocity.
class Trajectory1:
	def __init__(self, ts = 1.0, vmax = 1.2345):
		self.ts = ts		# sampling time
		self.vmax = vmax	# max velocity
		self.x = float(0)	# position = 0
		self.v = 0 			# velocity
		self.t = 0			# time
		self.v_suggest = 0
	def setTarget(self, T): = T
	def setX(self, x):
		self.x = x
	def run(self):
		self.t = self.t + self.ts	# advance time
		sig = numpy.sign( - self.x ) # direction of move
		if sig > 0:
			if self.x + self.ts*self.vmax >
				# done with move
				self.x =
				self.v = 0
				return False
				# move with max speed towards target
				self.v = self.vmax
				self.x = self.x + self.ts*self.v
				return True
			# negative direction move
			if self.x - self.ts*self.vmax <
				# done with move
				self.x =
				self.v = 0
				return False
				# move with max speed towards target
				self.v = -self.vmax
				self.x = self.x + self.ts*self.v
				return True
	def zeropad(self):
		self.t = self.t + self.ts
	def prnt(self):
		print "%.3f\t%.3f\t%.3f\t%.3f" % (self.t, self.x, self.v )
	def __str__(self):
		return "1st order Trajectory."
# second order trajectory. bounded velocity and acceleration.
class Trajectory2:
	def __init__(self, ts = 1.0, vmax = 1.2345 ,amax = 3.4566):
		self.ts = ts
		self.vmax = vmax
		self.amax = amax
		self.x = float(0) = 0
		self.v = 0 
		self.a = 0
		self.t = 0 = 0 # next velocity
	def setTarget(self, T): = T
	def setX(self, x):
		self.x = x
	def run(self):
		self.t = self.t + self.ts
		sig = numpy.sign( - self.x ) # direction of move
		tm = 0.5*self.ts + math.sqrt( pow(self.ts,2)/4 - (self.ts*sig*self.v-2*sig*( / self.amax )
		if tm >= self.ts: = sig*self.amax*(tm - self.ts)
			# constrain velocity
			if abs( > self.vmax: = sig*self.vmax
			# done (almost!) with move
			self.a = float(0.0-sig*self.v)/float(self.ts)
			if not (abs(self.a) <= self.amax):
				# cannot decelerate directly to zero. this branch required due to rounding-error (?)
				self.a = numpy.sign(self.a)*self.amax = self.v + self.a*self.ts
				self.x = self.x + (*0.5*self.ts
				self.v =
				assert( abs(self.a) <= self.amax )
				assert( abs(self.v) <= self.vmax )
				return True
				# end of move
				assert( abs(self.a) <= self.amax )
				self.v =
				self.x =
				return False
		# constrain acceleration
		self.a = (
		if abs(self.a) > self.amax:
			self.a = numpy.sign(self.a)*self.amax = self.v + self.a*self.ts
		# update position
		#if sig > 0:
		self.x = self.x + (*0.5*self.ts
		self.v =
		assert( abs(self.v) <= self.vmax )
		#	self.x = self.x + (-vn+self.v)*0.5*self.ts
		#	self.v = -vn
		return True
	def zeropad(self):
		self.t = self.t + self.ts
	def prnt(self):
		print "%.3f\t%.3f\t%.3f\t%.3f" % (self.t, self.x, self.v, self.a )
	def __str__(self):
		return "2nd order Trajectory."
vmax = 3 # max velocity
amax = 2 # max acceleration
ts = 0.001 # sampling time
# uncomment one of these:
#traj = Trajectory1( ts, vmax )
traj = Trajectory2( ts, vmax, amax )
traj.setX(0) # current position
traj.setTarget(8) # target position
# resulting (time, position) trajectory stored here:
# add zero motion at start and end, just for nicer plots
Nzeropad = 200
for n in range(Nzeropad):
	t.append( traj.t )
	x.append( traj.x ) 
# generate the trajectory
	t.append( traj.t )
	x.append( traj.x ) 
t.append( traj.t )
x.append( traj.x )
for n in range(Nzeropad):
	t.append( traj.t )
	x.append( traj.x ) 
# plot position, velocity, acceleration, jerk
plt.title( traj )
plt.plot( t , x , 'r')
plt.ylabel('Position, x')
plt.plot( t[:-1] , [d/ts for d in numpy.diff(x)] , 'g')
plt.plot( t , len(t)*[vmax] , 'g--')
plt.plot( t , len(t)*[-vmax] , 'g--')
plt.ylabel('Velocity, v')
plt.plot( t[:-2] , [d/pow(ts,2) for d in numpy.diff( numpy.diff(x) ) ] , 'b')
plt.plot( t , len(t)*[amax] , 'b--')
plt.plot( t , len(t)*[-amax] , 'b--')
plt.ylabel('Acceleration, a')
plt.plot( t[:-3] , [d/pow(ts,3) for d in numpy.diff( numpy.diff( numpy.diff(x) )) ] , 'm')
plt.ylabel('Jerk, j')

See also:

I'd like to extend this example, so if anyone has simple math+code for a third-order or fourth-order trajectory planner available, please publish it and comment below!

by admin at March 17, 2014 06:48 PM

Dan Heeks's Milling

HeeksCNC 1.0

I made a release of HeeksCNC 1.0, my CAD/CAM software, for Windows.

You can program drilling, profile and pocket operations.

You can then run a simulation of solid removal.

This amazing software is less than $20. Get it here

by (Dan Heeks) at March 17, 2014 05:42 PM

March 13, 2014


Factories and surfaces

The days and weeks are passing me by. I’ve got to stop doing this programming and get with something I’m interested in. I don’t think I’ve been outside of Liverpool since January.

Due to a surface triangulation crisis (the speed of machining operations being four times faster than the initial time it takes the CAD kernel to produce the triangles it needs to start work), I spent the last 10 days hacking up a trimmed NURBS surface triangulator and triangle flipper with Martin based on some of the machining algorithms. We pretend the trimming curves are machining boundaries and encode an XYZ point at each XY 3-axis sample point instead of just tool tip height.


The triangle flipping is a second component experiment I’d been meaning to try for a long time. Can you redistribute the triangles in a better way than simply along the UV parametric lines? Not sure I’ve got an answer yet, but it’s exhausting trying to find out. I’ll write it up later when I have the energy.

Meanwhile, in another part of the city, things are being built by real engineers. I’m looking forward to installing it in the office and making it work. Which is more than can be said about many of the programming projects I’ve put my hand to recently.

I’m also wasting time posting ideas onto the internal Autodesk idea database. Most are sinking like lead bricks into mud. It’s pearls before swine. The latest idea which everyone hates is to hold Simultaneous Satellite Tech Conferences in each region rather than wasting a shedload of carbon by flying all the techies to meet in a hotel in Canada for two days.

“Oh, but I find the in-person meetings are so important for building relationships,” they say. “This never happens with on-line meetings.”

No one seems to think that maybe it’s because on-line meetings generally last less than an hour, but when you travel half-way round the world to a meeting, the duration of the meeting is in practice like 20 to 40 hours long (with sleeping breaks).

Perhaps this extended time period is what’s important, eh?

I mean, look, if you teleported into your favourite tech conference for, let’s say, one hour and fifteen minutes before suddenly vanishing in a puff of smoke, you wouldn’t be able to build a lot of relationship experiences with the people there, would you? However, if you were trapped in an elevator for 10 hours, and all you had was your phone which you could use to call the person in stuck in the other shaft, you’d become friends for life with that individual.

It’s all in the mind. Use your imagination. A functioning virtual telepresence system should not involve booking the suite for an hour on a stupid meeting. Instead it should require being time-locked in a tank for 12 or 24 hours where the only communication line is routed via the office to which your business travel destination has been designated. You can phone home, but the phone call will literally need to be be directed into a physical acoustic coupler device in that office. You are there, with all the inconvenience of being stuck there, meaning that the place of least effort to communicate is there, and it will be rude and boring if the people in that office don’t pay attention and entertain you while you are stuck there. Maybe after the first six hours you will have dispensed with enough pleasantries and finally be talking about the things you need to be talking about, and building those bonds of friendship that take time to form. Glue and cement and gelatin all take time to set. Do you not think they same could be true for our frivolous minds?

by Julian at March 13, 2014 07:41 PM

Emergent Properties of Meat

Southern Florida Nature

Photos from various parts of Florida including the Everglades, Keys and Miami area.

March 13, 2014 01:27 AM

March 11, 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:

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
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
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
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() {
cylinder(r = or, h = 3 * layer_height, center = true, $fn = ofn);
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;

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.

by (nop head) at March 11, 2014 09:29 PM

ADF4350 PLL+VCO and AD9912 DDS power spectra

Here's the 1 GHz output of an ADF4350 PLL+VCO evaluation board, when used with a 25 MHz reference.

The datasheet shows a phase noise of around -100 dBc/Hz @ 1-100 kHz, so this measurement may in fact be dominated by the Rigol DSA1030A phase noise which is quoted as -88 dBc/Hz @ 10 kHz.


The 1 GHz output from the ADF4350 is used as a SYCLK input for an AD9912 DDS. The spectrum below shows a 100 MHz output signal from the DDS with either a 660 MHz or 1 GHz SYSCLK. The 660 MHz SYSCLK is from a 10 MHz reference multiplied 66x by the AD9912 on-board PLL. The 1 GHz SYSCLK is from the ADF4350, with the AD9912 PLL disabled.

The AD9912 output is clearly improved when using an external 1 GHz SYSCLK. The noise-floor drops from -80 dBm to below -90 dBm @ 250 kHz from the carrier. The spurious peaks at +/- 50 kHz disappear. However this result is still far from the datasheet result where all noise is below -95 dBm just a few kHz from the carrier. It shouldn't matter much that the datasheet shows a 200MHz output while I measured a 100 MHz output.

Again I suspect the Rigol DSA1030A's phase-noise performance of -88dBc/Hz @ 10 kHz may in fact significantly determine the shape of the peak. Maybe the real DDS output is a clean delta-peak, we just see it like this with the spectrum analyzer?


Martein/PA3AKE has similar but much nicer results over here: 1 GHz refclock and 14 MHz output from AD9910 DDS. Amazingly both these spectra show a noise-floor below -90 dBm @ 50 Hz! Maybe it's because the spectrum analyzer used (Wandel & Goltermann SNA-62) is much better?

by admin at March 11, 2014 05:11 PM

March 09, 2014

MetaRepRap Soup

Emergent Properties of Meat

Florida 2014 (mostly Everglades)

We recently travelled to Florida. I took a ton of photos, mostly in the Everglades. Here are some of the better examples. Warning: Contains spiders and animals eating other animals.

March 09, 2014 06:02 PM

March 07, 2014

DDS Front Panel

Two of these 1U 19" rack-enclosure front panels came in from Shaeffer today. Around 70 euros each, and the best thing is you get an instant quote on the front panel while you are designing it with their Front Panel Designer software. The box will house an AD9912 DDS.


From left to right: 10 MHz reference frequency input BNC, DDS output BNC, 40mm fan (1824257 and 1545841), 20x2 character LCD (73-1289-ND), rotary encoder with pushbutton (102-1767-ND), and rightmost an Arduino Due (1050-1049-ND) with Ethernet shield (1050-1039-ND). The panel is made to fit a Schroff 1U enclosure (1816030) with the inside PCBs mounted to a chassis plate (1370461).


Here's a view from the inside. I milled down the panel thickness from 4 mm to around 2.5 mm on the inside around the rotary encoder. Otherwise all components seem to fit as designed.


Next stop is coding an improved user-interface as well as remote-control of the DDS and PLL over Ethernet.

by admin at March 07, 2014 07:41 PM

February 26, 2014

Drop-Cutter toroid edge test

The basic CAM-algorithm called axial tool projection, or drop-cutter, works by dropping down a cutter along the z-axis, at a chosen (x,y) location, until we make contact with the CAD model. Since the CAD model consists of triangles, drop-cutter reduces to testing cutters against the triangle vertices, edges, and facets. The most advanced of the basic cutter shapes is the BullCutter, a.k.a. Toroidal cutter, a.k.a. Filleted endmill. On the other hand the most involved of the triangle-tests is the edge test. We thus conclude that the most complex code by far in drop-cutter is the torus edge test.

The opencamlib code that implements this is spread among a few files:

  • millingcutter.cpp translates/rotates the geometry into a "canonical" configuration with CL=(0,0), and the P1-P2 edge along the X-axis.
  • bullcutter.cpp creates the correct ellipse, calls the ellipse-solver, returns the result
  • ellipse.cpp ellipse geometry
  • ellipseposition.cpp represents a point along the perimeter of the ellipse
  • brent_zero.hpp has a general purpose root-finding algorithm

The special cases where we contact the flat bottom of the cutter, or the cylindrical shaft, are easy to deal with. So in the general case where we make contact with the toroid the geometry looks like this:


Here we've fixed the XY coordinates of the cutter location (CL) point, and we're using a filleted endmill of radius R1 with a corner radius of R2. In other words R1-R2 is the major radius and R2 is the minor radius of the Torus. The edge we are testing against is defined by two points P1 and P2 (not shown). The location of these points doesn't really matter, as we do the test against an infinite line through the points (at the end we check if CC is inside the P1-P2 edge). The z-coordinate of CL is the unknown we are after, and we also want the cutter contact point (CC).

There are many ways to solve this problem, but one is based on the offset ellipse. We first realize that dropping down an R2-minor-radius Torus against a zero-radius thin line is actually equivalent to dropping down a zero-minor-radius Torus (a circle or 'ring' or CylCutter) against a R2-radius edge (the edge expanded to an R2-radius cylinder). If we now move into the 2D XY plane of this circle and slice the R2-radius cylinder we get an ellipse:


The circle and ellipse share a common virtual cutter contact point (VCC). At this point the tangents of both curves match, and since the point lies on the R1-R2 circle its distance to CL is exactly R1-R2. In order to find VCC we choose a point along the perimeter of the ellipse (an ellipse-point or ePoint), find out the normal direction, and go a distance R1-R2 along the normal. We arrive at an offset-ellipse point (oePoint), and if we slide the ePoint around the ellipse, the oePoint traces out an offset ellipse.


Now for the shocking news: an offset-ellipse doesn't have any mathematically convenient representation that helps us solve this!
Instead we must numerically find the best ePoint such that the oePoint coincides with CL. Like so:


Once we've found the correct ePoint, this locates the ellipse along the edge and the Z-axis - and thus CL and the cutter . If we go back to looking at the Torus it is obvious that the real CC point is now the point on the P1-P2 line that is closest to VCC.

In order to run Brent's root finding algorithm we must first bracket the root. The error we are minimizing is the difference in Y-coordinate between the oePoint and the CL point. It turns out that oePoints calculated for diangles of 0 and 3 bracket the root. Diangle 0 corresponds to the offset normal aligned with the X-axis, and diangle 3 to the offset normal  aligned with the Y-axis:



Finally a clarifying drawing in 2D. The ellipse center is constrained to lie on the edge, which is aligned with the X-axis, and thus we know the Y-coordinate of the ellipse center. What we get out of the 2D computation is actually the X-coordinate of the ellipse center. In fact we get two solutions, because there are two ePoints that match our requirement that the oePoint should coincide with CL:



Once the ellipse center is known in the XY plane, we can project onto the Edge and find the Z-coordinate. In drop-cutter we obviously approach everything from above, so between the two potential solutions we choose the one with a higher z-coordinate.

The two ePoint solutions have a geometric meaning. One corresponds to a contact between the Torus and the Edge on the underside of the Torus, while the other solution corresponds to a contact point on the topside of the Torus:

I wonder how this is done in the recently released pycam++?

by admin at February 26, 2014 08:16 PM


Washing the progress bar

Last year we got a chance to see SolidCAM’s iMachining and laughed at the way its progress bar jumped all over the place, from -14% to 200% and back again.

Then we looked at our own Adaptive Clearing strategy which we had just spent the past year making multicore — and noticed it did the same stupid thing!

How embarrassing.

You never notice yourself picking your own nose, but when someone else does it in your face, you realize it’s ugly.

Progress bars don’t get as much love and attention from the programmers as they ought to, given how much time the users have to stare at them. The users think it’s so normal for the progress bar to be absolutely wrong that it’s considered a sign of extreme naivety to think about complaining. They probably believe that we’d going to laugh at them if they raised the issue.

It turns out that the progress bar on multiple CPU processing is not hard to get right, but you do it differently to how you do it on a single-threaded process.

Let’s first think about what a progress bar is for. There are two different options. It could report the time remaining for the process to complete, or it could report the percentage of the process job that has been completed.

The time remaining might be the most useful information for organizing your life (is there enough time to grab lunch while this completes?), but there’s no way you’re going to get that information — even though it’s what everyone wants to know.

You will hear: “How many days till it’s done?” more often than “Are we at the 75% complete stage yet?” for a project — and that’s even before it’s over-run by a factor of two.

In fact, the only practical implementation for the time remaining is to run the whole job first, time it, and then set a count-down timer from that value when you run it again. It’ll make everything run twice as slow, but what’s the big deal?

And just suppose we implemented a percentage-complete progress bar that was accurate according to the definition that it moved at a constant rate from 0 to 100%. This would be a scale-free number that didn’t depend on the speed of your processor, or the fact that you have decided to watch a video while you waited, or created all kinds of other factors it is impossible to predict.

If this percentage-complete value was accurate enough in time, you could measure how long it took for the first 5% to pass, and multiplied the number by 19 to estimate the time to completion. Good enough?

Washing dishes

There are 8 plates and 2 oven pans in the sink.

Single-threaded version 1: Take each item from the sink, wash it, dry it, put it in the cupboard, and set the progress bar to the value (number of items done)/(total number of items) = n/10.

nitems = 10; 
for (i = 0; i < nitems; i++) {

The item number i steps from item 0 to item 9, and you need to add 1 to this value to state the number of items that are complete after you have washed each one.

Progress is going to be a bit juddery and uneven, because the oven pans take longer than the plates to wash, and 10% is a big increment.

Single-threaded version 2: Oven pans are harder to wash than plates. Count 6 points for each pan, and 1 point for each plate. This adds up to 20 points. Do the pans first. Set the progress bar to the sum of the points for the items already done divided by 20.

npans = 2; nplates = 8 
pantoplateworkratio = 6; 
totalpoints = pantoplateworkratio*npans + nplates;
for (i = 0; i < npans; i++) {
for (j = 0; j < nplates; j++) {
    setpercentprogress(100*(npans*pantoplateworkratio + (j+1))/totalpoints); 

This implementation is getting a bit complicated, but it’s all based on in-line linear equations, and you can see how it evolves as part of the natural process of software development. The first loop moves us from 0% to 60%, and the second loop goes from 60% to 100%.

Suppose we wanted to break down each operation into proportions of 2/5 for washing, 2/5 for drying and 1/5 for putting in the cupboard components, then we’d expand the first loop to read:


The code evolves and spreads out line this. You simply keep inserting new terms into your setpercentprogress() function calls. It’s not how you would design it if you were starting fresh, but programming is a process of constantly repairing an old machine and patching up its pipe-work to handle new chemicals.

Multi-threaded version 1: How about we set one person to do the plates, and the other to do the pans? Now you’re going to see the progress bar jumping back and forth between a number in the 0 to 60 range and a number in the 60 to 100 range. If, furthermore, each person doesn’t know how many items are in the other person’s sink, like they’ve randomly picked 2 pans and 3 plates for one of the sinks, the conflicts are going to be greater and may even account for the jumpy -14% and +200% ranges.

Single-threaded version 3: Create a special progress object that keeps count of how much progress has been made. Using the points system we have allocated 5% to each plate and 30% to each pan. Also, subdivide these incremental progress percentages into the individual components, like so:

Pick a plate out of the sink, wash it (+2%), dry it (+2%), into the cupboard (+1%). Take a pan out of the sink, wash it (+12%), dry it (+12%), into the cupboard (+6%).

This is getting smoother. But you could break down the drying part into smaller operations of, say, each of three wipes of the tea towel. Also, it doesn’t take that much longer to put an oven pan into the cupboard than a plate, so you should reserve a larger proportion of the pan time for the scrubbing and less for the putting away. Take a pan out of the sink, wash it (+25%), dry it (+3%), into the cupboard (+2%).

Now you have a system where it doesn’t matter what order you do the items, how many people are doing it concurrently, or what jobs they do. You could have a stack of plates on the draining board and two people drying them. If one of the plate-washers finishes first, they could join in the job of putting them away.

Multi-threaded version 2: These percentages may look pretty simple, but there’s the minor issue of how to get the numbers allocated to each component of each item in a sensible way. Those messy calculations of the form setpercentprogress(100*(i+0.4)*pantoplateworkratio/totalpoints) have to happen somewhere, they don’t just go away.

We got into a tangle with this, until we stopped trying to do the calculations in-line, and instead allocated all the percentages at the very start of the operation. We stuck three numbers onto each item while they were still in the sink, one for the washing, another for the drying, and one for the putting away. This can be done in one thread that has the big picture and all the available information. Now, as each job gets done (a pan gets dried), the process peels the stamp displaying the incremental progress allocated to it for the work and sends it to the progress object. Easy.

One of the challenges is that the sink water is dirty so you can’t see exactly how many plates there are left to do. To handle this, we work with an estimate and keep allocating an absolute percentage for each item that comes out of the sink from a remaining stock of allocations. When we run out of plates to wash, we report a residual allocation to the progress object, and it tries to spread out this error across the remaining space on the progress bar, as I outlined in this post about the progress object.

In the Adaptive Clearing algorithm we have 7 stages in the process, instead of just 3 for washing up, and some of the components depend on adjacent pairs (like relinking) so cannot be isolated. There are also two independent hierarchies of allocations and residuals, for the layers and for the toolpaths within each layer.

Here is a diagram of the process with the progress bar value super-imposed. It’s not a very good result, as you can see, but we’ve been working on it.


We should put some effort into balancing the proportions between the operations, but we don’t have time for that work right now. Maybe we’d do it if the users started complaining more about it. We can now see some problems with the strategy by the way the progress bar stalls. As a rule, if you can’t see a problem, you can’t fix it.

The original outline for this story involved harvesting potatoes from a valley with a number of farms, each farm with a number of fields, each field harvested by a number tractor runs along its length producing truckloads of potatoes that were taken back to the barn to be individually washed, sorted and bagged into sacks all overseen by an impatient manager who just wanted to know how much longer it was all going to take to get done.

Washing dishes is so much simpler. I don’t know why I didn’t think of it until now.

by Julian at February 26, 2014 01:41 PM

February 24, 2014

Emergent Properties of Meat

How do you check a signature on a PGP/Mime message

…I'll answer in Python. First, put your whole message in a file in unix "mbox" format (this will be the problem if you use some brain-dead GUI mailer!), then in Python extract the signature and the signed message, and finally invoke gpg:

import email
import os
import sys

with open(sys.argv[1]) as mfile: message = email.message_from_file(mfile)

if message.get_content_type() != "multipart/signed":
    raise SystemExit, "message not signed"

# Really, you'd want safe temporary filenames, and you'd want to clean them up
with open("msg", "wb") as msg: msg.write(message.get_payload(0).as_string())
with open("sig", "wb") as sig: sig.write(message.get_payload(1).get_payload())

# Delegate the real business to gpg
os.execvp("gpg", ["gpg", "--verify", "sig", "msg"])

February 24, 2014 01:10 PM

February 23, 2014

OpenCAMlib and OpenVoronoi toolpath examples

There might be renewed hope for a FreeCAD CAM module. Here are some examples of what opencamlib and openvoronoi can do currently.

The drop-cutter algorithm is the oldest and most stable. This shows a parallel finish toolpath but many variations are possible.


The waterline algorithm has some bugs that show up now and then, but mostly it works. This is useful for finish milling of steep areas for the model. A smart algorithm would use paralllel finish on flat areas and waterline finish on steep areas. Another use for the waterline algorithm is "terrace roughing" where the waterline defines a pocket at a certain z-height of the model, and we run a pocketing path to rough mill the stock at this z-height. Not implemented yet but doable.


This shows offsets calculated with openvoronoi. The VD algorithm is fairly stable for input geometries with line-segments, but some corner cases still cause problems. It would be nice to have arc-segments as input also, but this requires additional work.


The VD is easily filtered down to a medial-axis, more well-known as a V-carving toolpath. I've demonstrated this for a 90-degree V-cutter, but toolpath is easy to adapt for other angles. Input geometry comes from truetype-tracer (which in turn uses FreeType).


Finally there is an experimental advanced pocketing algorithm which I call medial-axis-pocketing. This is just a demonstration for now, but could be developed into an "adaptive" pocketing algorithm. These are the norm now in modern CAM packages and the idea is not to overload the cutter in corners or elsewhere - take multiple light passes instead.


The real fun starts when we start composing new toolpath strategies consisting of many of these simple/basic ones. One can imagine first terrace-roughing a part using the medial-axis-pocketing strategy, then a semi-finish path using a smart shallow/steep waterline/parallel-finish algorithm, and finally a finish operation or even pencil-milling.

by admin at February 23, 2014 08:32 PM

Emergent Properties of Meat

Northern Georgia Waterfalls

We spent a few days exploring the waterfalls in Northern Georgia. Gorgeous area!

February 23, 2014 02:11 AM