Sundials - World's Oldest Clocks

# North American Sundial Society

The goal of our first program is to create the dial base.  We'll define the dial base parametrically using three parameters for size: dial_btm, dial_top and dial_hght.  This allows for the diameter of the base bottom and top to create tapered sides and base  height.

Normally we think of making a cylinder using an OpenSCAD function called cylinder(h, d) where h is the height and d is the diameter. There is an extension of parameters such that the cylinder can be tapered using cylinder(h,d1,d2) where d1 is the base diameter and d2 is the top diameter.

But suppose that we want to make the sundial base in the shape of a hexagon or octagon.  We rely on a special OpenSCAD variable $fa that sets the minimum angle of our solid. Two or three degrees on the edge makes a nice circle, 45 degrees for each side makes an octagon and 60 degrees for each side makes a hexagon. It makes sense that we could invoke the cylinder functionfor a tapered hexagon as: cylinder($fa =45,h,d1,d2);

We'd like to invoke "if" statements to decide which shape to make.  The code takes the form of

if(shape=="hexagon") { make our tapered cylinder look like a hexagon }

The "if" statements (as well as "for") statements in OpenSCAD are handled a bit different than normal code.  Whatever happens within the "if" or "for" statement stays within it.  Variables can't leak out. Therefore we must build the sundial base within each selection of the choices "circular", "hexagon", or "octagon".   Thinking of terms of modular procedures, we can invoke a module called "sundial_base" that takes the appropriate parameters to create one of these three base shapes.

In the attached exemplar code, the size variables of the base (dial_btm, dial_top, dial_hght) from the main program are used directly within the "if" statement as well as within the sundial_base module. The hierarchy of variable scope allows these mainl variables to "fall" into these subservient functions and procedures.  However, within each "if" statement we need to set local paramaters to properly create the desired shape.  So within each "if" statement we create two variables to (1) set minimum segment angle and (2) rotate the base such that the hexagon or octagon has a flat side parallel to the x-axis.

The necessary rotation of the sundial base shows another peculiarity of the OpenSCAD code.  In mathematics one writes the translation or rotation operator (a matrix) to the left of the object being changed.  OpenSCAD does this by writing the operator on the lines before the object.  And there is a subtle use of the ";" symbol to indicate the end of operation:

rotate([xturn,yturn,zturn])          //Note that there is no ";"
cylinder(\$fa,h,d1,d2);               //Specifically ending with ";"

 Sundials Part 1 - Sundial Base.scad [ ] 2 kB

In this tutorial we will draw the time line markers on the face of our sundial. These lines are called hour lines even if we divide the hours into smaller units of half or quarter hours.  We know a couple of things before we start: the hour lines radiate outward from the base of the gnomon (we'll discuss making the gnomon in the next tutorial).  The 6am-6pm hour lines are perpendicular to the 12-noon line, which is aligned north-south.

If we imagine the sun's travel across the sky, we can describe its motion as the number of hours before or after the local noon meridian (the north-south line in the sky that goes directly overhead). The sun's position is known as the hour angle (HA).  We need HA in our equations as degrees, not hours. Accepting that there are 360/24 = 15 degrees in an hour of time our equations for hour angle becomes:

Before noon    HA = 15*(hour-12)         e.g. hour = 10am   gives   HA = -30

After noon      HA = 15*hour              e.g. hour =  3pm   gives   HA = +45

The equation to translate hour angle (HA) into the sundial hour line angle (theta)is

tan(theta) = tan(HA)*sin(lat)        where lat = dial's latitude

or      theta  = arctan(tan(HA)*sin(lat))

To insure that we have no ambiguities in determining theta, we can use the computer function arctan2 to determine the arc tangent using the sine and cosine ratio:

theta  = arctan2(sin(HA)*sin(lat),cos(HA))

To implement this in OpenSCAD, we make a long, slender "cube" (its technical name is really rectangular cuboid, but "cube" sufficies even if the sides are unequal) that is oriented toward north (y-axis) and centered on the origin (0,0).   Lwidth, Llength, and Lhght will be the width, length and height of the hour line.  Once this cube is created, because it is centered on (0,0), we move it one half its length north and raising it one half its height.  That sets the base of our slender cube at the origin.   Finally we rotate it by the hour line angle theta.  All of this is put into a loop using a for statement to step from the first to the last hour line in 15 degree increments:

first = 15*(minHA - 12);
last  = 15*(maxHA);
// loop through the hour angles
for(HA=[first:15:last]){
//compute the hour line angle
theta = atan2(sin(lat)*sin(HA),cos(HA));
//instantiate the hour line at angle theta using a slender cube
rotate([0,0,theta])
//translate the hour line to have its base at (0,0)
translate([0,Llength/2,Lhght/2])
cube([Lwidth,Llength,Lhght],center=true);
}

Technically the hour lines are in the right position, but they are  a bit "raw".  So we need to trim them.  The easiest way is to create a donut mask and then intersect the mask with the hour lines.  Here is a simple module to construct the donut mask using a ring size of Din, Dout, and Dhght representing the ring's inner and outer diameter and height;

difference(){
cylinder(d=Dout,h=Dhght);
cylinder(d=Din,h=3*Dhght,center=true);
}
}

Note that the donut mask uses a "cut-out" inner cylinder that is taller than the outer cylinder.  This is to insure that no face pieces remain in the center. The intersection of the hour lines and the donut mask is straightforward.  As with procedures above, we use parametric variables such as dial_top (the diameter of the dial), and the hour line dimension Lwidth, Llength, and Lhght. The result is:

theta = atan2(sin(lat)*sin(HA),cos(HA));
intersection(){
rotate([0,0,theta])
translate([0,Llength/2,Lhght/2])
cube([Lwidth,Llength,Lhght],center=true);
}

Figure 1. (a) raw hour lines, (b) donut mask, (c) intersection of hour lines and mask

Are we done?  We've designed a dial with the gnomon foot in the middle of the dial.  This to me is artistically unbalanced as the southern part of the dial is totally vacant.  So let's offset the gnomon and the center of the hour lines by a distance called Loffset to the south of the dial, expanding the useable area of the dial.  For example I prefer Loffset to be about a quarter of the dial's diameter.  The above code needs only one additional line:

theta = atan2(sin(lat)*sin(HA),cos(HA));
intersection(){
translate([0,-Loffset,0])
rotate([0,0,theta])
translate([0,Llength/2,Lhght/2])
cube([Lwidth,Llength,Lhght],center=true);
}

Figure 2. (a) centered hour lines & gnomon foot, (b) offset hour lines & gnomon foot

Download the OpenSCAD code from the attachment below that includes both Part 1 and Part 2 tutorials.

 Sundials Part 2 - Sundial HourLines.scad [ ] 4 kB
Cookies make it easier for us to provide you with our services. With the usage of our services you permit us to use cookies.