Friday, May 16, 2014

Day 263: Friday Fail: Off-by-one edition

Being off by one is probably the simplest, most common coding mistake you can make, but it got me this week while I was trying to make tessellating Cairo pentagon tiles for Frank Morgan and his students at Williams College. These Cairo tiles are supposed to have a mirror symmetry as shown in the drawing below, but the ones I printed didn't have this symmetry, so I knew something was wrong.


It took me a while to figure it out, but the culprit was a for loop in OpenSCAD that I had started at 1 instead of 0. By "a while" I mean I didn't figure it out until the next day! It's obvious now, but maybe knowing what kind of error this can cause in a 3D model will help someone else identify this mistake sooner than I did, so let me explain. The coordinates I was using for the pentagon were as follows:

/////////////////////////////////////////////////////////////
// point data ///////////////////////////////////////////////

points_cairo = [
[-1,0],[1,0],[2.3660,2.3660],[-2.3660,2.3660],[0,3.7321]
];

My simple pentagon module just forms the convex hull around five spheres at these points, and five spheres translated a bit up from these five points, to make a rounded pentagon tile:

/////////////////////////////////////////////////////////////
// render ///////////////////////////////////////////////////

pentagon(points_cairo,radius=.5,scale=5,height=2); 

/////////////////////////////////////////////////////////////
// pentagon module //////////////////////////////////////////

module pentagon(points,radius,scale) {
hull(){
for (i = [0:4]){
// the five points of the pentagon
translate(scale*points[i]) sphere(radius);
// repeat the pentagon points at a higher level
translate([0,0,height]) translate(scale*points[i]) sphere(radius);
}
}
}

The code above is the correct code for the Cairo pentagon; my original code erroneously ran the loop from 1 to 5 instead of from 0 to 4. OpenSCAD thought of the Cairo data set as indexed from i=0 to i=4, so my original loop from 1 to 5 was interpreted by OpenSCAD as taking i=1 through i=4 and then some mystery i=5 datapoint that was not defined. You can see in the picture of the printed tiles that OpenSCAD decided to make this i=5 datapoint the origin (0,0) instead of returning an error. 

This is one of the things I find most difficult about debugging OpenSCAD code: if I have an error in my code, sometimes OpenSCAD finds a way to compile anyway, either ignoring my code or substituting something it can handle, as it did here. 

Stay tuned for the correct print of these tessellating pentagons later this weekend...

2 comments:

  1. I've only recently seen that you can iterate over the points directly which avoids this issue:

    module pentagon(points,radius,scale,height) {
    hull(){
    for (p=points){
    // the five points of the pentagon
    translate(scale*p)
    sphere(radius);
    // repeat the pentagon points at a higher level
    translate([0,0,height])
    translate(scale*p)
    sphere(radius);
    }
    }
    }

    ps I added 'height' as a parameter as I think having a mixture of parameters and globals in the same module is a bit confusing.

    ReplyDelete
    Replies
    1. Oh this is nice, iterating over the points. One less thing for me to screw up! Thanks :)

      Delete