"Perpetual motion" / "Perpetuum mobile" laser cutting
I was able to put together -- rather surprisingly quickly -- a Java program that used all the computations in my previous post. The main design points were:
- use double floating point precision
- use variables for all things I choose (inner radius, "D" length) and all things that could vary physically.
- allow for kerf compensation, given the laser cutter could eat more material than I expect
- allow for variable material thickness
- generate SVG as the output format
- push the output to an output stream, which I later converted to dump data into a known file
Initially, just to check my computations, I'd drawn the pictures on graph paper and used a rule to measure the actual lengths. I then used Excel to compute everything, and compared against the physical measurements. Once I was confident in the equations, I transferred them to Java program form.
The main iteration cycle I'd go through would be
- edit the program
- run the program
- load the output SVG file into Firefox, where it would render the results
- iterate
Sometimes, I'd check the output in pure text format using a simple text editor like vim. That also would allow me to edit some entries to see if different forms of SVG would be better than others.
Initial SVG output
My initial output was very much brute force, generating <polyline> tags with (x,y) pairs in the attributes of the polyline, and I generated all data that way. Also initially I just wanted the D and qhyp lines.SVG is a very simple format to use for this. I had used SVG before, but found it useful to just read up on the info at
http://www.w3schools.com/svg/
From there, I jumped into examples of an SVG Polyline.
Roughly speaking, the initial output looked like this:
<svg>
<polyline points="x0,y0 x1,y1 x2,y2 x3,y3...." style="fill:none;stroke:black;stroke-width:3" />
...
</svg>
The code then would iterate so it was something like this:
... Set the main R, D, and theta variables
... Compute all the other things like A, H, XDL, XDR, Q, qhyp
print "<svg>"
for (int i=0; i<nSegments; i++) {
angle = theta * i;
convert angle to radians
rotate (R,0) through angle using a normal rotation matrix
rotate (XDL,H) through angle
print the polyline with points="R,0 XDL,H" as appropriate each time.
}
... Similarly, draw the points from XDL,H to XDL',H' to build the edge walls.
print "</svg>"
The need to transform and scale
After generating the initial output, I found a few things were problematic. First, the output wasn't visible unless I provide width and height attributes in the initial <svg> tag. Also, if those values weren't large enough, the drawing would be clipped and the overall window wouldn't let me scroll to see it all.Second, the output wasn't the right size for me to look at it properly.
I changed the code to generate output like this:
<svg width="800" height="600">
<g transform="translate(400,300)">
<g transform="scale(2.5,2.5)">
... do what I did earlier
</g>
</g>
</svg>
The origin in SVG rendered on screen would be at the top, left corner of the canvas, and Y increases as units move down the screen. For me, the inverted Y axis didn't make a difference, because mirroring a cut on the laser cutter could be achieved by flipping my material over and running the same cut. If it matters to you, then you could negate the y scale value, and translate further.
Note that the transformations are applied in inside-out order, so in the example above the scaling happens first, then the translation.
Simplifying the coding using transformations
Once I knew there were SVG native transformation operations, it meant that I wouldn't have to compute the point rotations myself. Before the code was of the formfor i in 0..(360/theta)-1
{
compute the x,y points at rotation angle "i*theta"
print the appropriate svg to draw a line
}
Now the code is
compute the x,y points once at rotation angle 0
for i in 0..(360/theta)-1
{
print ("<g transform=\"rotate(" + (i*theta) + ")\">");
draw the shape as if it were at rotation angle zero
print ("</g>")
}
The need not to scale
Fairly quickly, I found that the scaling operation I was doing would scale everything, including the width of lines. Anisomorphic scaling (i.e., x scale is not the same as y scale), coupled with the scaling of line widths, resulted in having a unit square turn into a rectangle, but that rectangle would have thick edges on the short sides, and thinner edges on the long sides.In laser cutting at the TechShop, a hairline edge width is used for cutting, but thick edge widths imply rastering (etching), so having non-uniform edges due to scaling was problematic.
Also, having scaling meant that I would have to reverse the scale computation for elements where I had a known, required final value, such as the width of a hairline.
Once I knew that out, I took out the <g transform="scale(x,y)"> operations. Instead, I'd have to generate the SVG output using full size coordinates.
File format versions
Once I had a basic drawing in place, I took it to the TechShop and loaded it up in CorelDraw.In the past, I've had problems loading SVG into CorelDraw. The primary problem appears to be around curves or splines that are closed, where earlier versions of CorelDraw would not handle them properly, and would send closing vectors off to Never Never Land.
For this SVG, however, I was drawing very simple primitives, and I could load it directly in.
I did find that some computers had different versions of CorelDraw installed, and if I would load .svg, convert to .cdr, and save, it might not be readable in an earlier software installation version (CorelDraw 5 can't read CorelDraw 7 output). To get around that, while I still hadn't ironed out my .svg output, I could export from CorelDraw 7 in .pdf, and load that into CorelDraw 5.
Pixels? Points? What are SVG units?
There were two things I wanted to check when loading my SVG file into CorelDraw. The first problem was units. Since my SVG file was output with simple commands like <polyline points="100,200 200,200">, it wasn't clear exactly how those would measure out to become physical values in real world units.A second problem that arose, still related to units, was that the concept of a "hairline" line width is what determines whether or not the Epilog laser cutter will cut as vectors, or etch as rastering. CorelDraw doesn't explicitly show the line width in millimeters when it's set to "hairline".
My initial path here was to take a value like "100" SVG units, and compare against what was being shown in CorelDraw in millimeters. I'd generate the SVG and load the file into Corel Draw. Then, I'd set the ruler units (Ruler Settings option menu, then set units to millimeters) and click on an object to see how big it was.
Since the code was generating lines at angles, I had to find a generated line or rectangle that was at a multiple of 90 degrees. Once I knew the SVG value and its corresponding CorelDraw millimeters value, I got what I thought was an SVG-to-mm ratio. As it turns out, though, that was a mistake. It seems that default units are "pixels", and the ratio might vary from one computer to the next. I'm not sure, but it's definitely better to use real units and not rely on "pixels".
I also looked up the definition of "hairline" online, and it ends up being 0.00762 cm or 0.0762mm. So, with my errant unit conversion ratio, I generated some SVG values for the "stroke-width" attribute, loaded in CorelDraw, saw "hairline" in the interface, and was happy but inaccurate.
D board, Q boards, tabs, intersections
The walls could be plain, rectangular boards, glued down to the base. But in laser cutting tradition, I chose to cut tabs and slots for them to plug into.Here's the first sketch I had for what I'd want the boards to look like. It's not really correct, because each D wall hits the next D wall. But it shows the kinds of tabs to think about.
The D boards would have one tab sticking into the base. I would choose where that tab would go, and opted to put it some percentage distance down the length of D. I arbitrarily chose the tab length to be 20% the size of D itself, and messed around with the placement percentage to get it to hit somewhere along the RestD section of D (not along nor intersecting Base).
At the top of D, where it hits the outer edge, the board actually intersects with two Q boards at the same time. The drawing under the Kerf heading below shows what they'd be shaped like. The thought was to create each board in this way:
- The tabs would be 1/6th of the wall height. Each board would have two tabs where needed.
- The D board outer tabs would be the "middle" tabs.
- One end of Q would have the "upper" tabs, and the other end would have the "lower" tabs.
This is a top-down sketch of the D boards hitting each other (labeled "Intersection point"), and at the outer edge, a D board hitting two Q boards.
Kerf
Since I'd be cutting tabs and slots, I might have to compensate for kerf, which basically meant I'd have to cut less from the slots, and more around the tabs. The laser cutter burns away a small amount of material, much like a table saw would cut about the width of a saw blade. While the laser is much more precise, the amount of material removed is not zero.This is a drawing of what the kerf compensation cut lines would look like.
The red lines show what the actual object would look like, and black lines indicate where the laser cuts would be. Material between black and red represent the kerf.
The top part really should just be "MThick" (material thickness), not "MThick + 1/2K". The idea here is that I can draw these parts with simple x,y changes, so the code would say something like this:
print the polyline tag;
print x + "," + y + " ";
y += 2*T;
print x + "," + y + " ";
x += materialThickness;
print x + "," + y + " ";
etc.
print close polyline tag;
The bearing
At the center of the base circle, I wanted to press in a regular inline skate bearing. I had several laying around still from the JGRO project that started, but stalled. The outer diameter of one measured at 22.01mm, quite accurate for what the internet declared to be 22mm OD.To add the circle at the middle of my drawing, I wanted to cut a 22mm circle, minus half kerf on both sides. That would mean having a circle whose diameter was 22mm minus 1 kerf (radius is 11mm - half kerf). In SVG, that would come out looking like this:
<g transform="translate(x,y)">
<circle x="somex" y="somey" r="someradius" style="fill:none;stroke:black;stroke-width:3" />
</g>
and, since I was treating the whole page origin as the center of the circle, I could use the default x,y values and just draw it like this:
<g transform="translate(x,y)">
<circle r="someradius" style="fill:none;stroke:black;stroke-width:3" />
</g>
Test cuts
Concerned about the fit of the tabs to each other, tab fit within slots, and bearing fit within circle, I did some test cuts of each.The first test cuts are the five figures on the left side of the board.
For my "D board" test cuts, since I was dealing with a smaller piece of scrap and didn't want to waste material, I used CorelDraw to trim off a few nodes, and made two of them.
The first problem, and pretty much a showstopper for everything, was that the bearing circle came out at 21mm, far too small for the bearing to press in. I wasn't sure if the error was caused by an incorrect kerf setting, or because of the SVG-to-mm conversion ratio I was using.
I also took the two D boards, and put their tabs against each other. If cut correctly, they would have a smooth surface going across, but they didn't. Instead, there was a bump up, suggesting the resulting tabs were ending up being too large. That meant my kerf compensation value was too large (I'm on the outer edge, so cutting too far away from where I want the resulting edge to be), or my units were wrong, or both.
I then ran a second test cut with a bunch of circles of varying diameter. Once I figured out the "right" size of a circle for my bearing, I could use that as my target for the generated SVG. Here, CorelDraw provided a nice accelerator.
To draw circles in CorelDraw, you can choose the ellipse tool, and hold Shift while dragging to ensure the result is circular. Then, you can select your circle (click on its edge) and hit ctrl+D to duplicate the circle. After that, drag the new circle to a new location, and CorelDraw will remember the offset. From there, you just hit ctrl+D again and again, and each newly duplicated circle will be offset the way you chose.
What I didn't know but learned in this exercise is that CorelDraw also will auto-adjust dimensions when you hit ctrl+D. So what I did was:
- create circle
- using the dimensions pane, change width to 22mm and height to 22mm.
- select the circle
- hit ctrl+D to duplicate
- move the new circle to a nearby, non-overlapping location to the right of the original
- using the dimensions pane, change size to 21.95mm
- hit ctrl+D again and again
In the end, I had six circles ranging from 22mm to 21.75mm. I just cut them all out, and tried each one with the bearing to see which woud fit best.
Because I put them too close to each other, I did a third test cut with 21.85mm and 21.8mm diameters, but with a lot of material between. I still came out with 21.85mm being a nice, snug fit.
For the bearing hole, I also added two additional washer-style circles to be cut out. Those were just in case the material was so thin that the bearing wouldn't have enough to grab onto. With thin material, I'd just stack the washers over and concentric with the bearing hole, and that would provide enough thickness for a good seating of the bearing.
Reconsidering kerf compensation
The bearing circle diameter value of 21.85mm for a 22mm physical object suggested that the kerf value was actually 0.15mm, and that would translate to a very small fraction of an inch (0.005905512 inches).. I had been assuming a 1/32" kerf, though, which would be 0.03125, about five times as large.Left confused by the 5x factor, I just gave up on the kerf computation completely. I dropped the kerf variable down to zero, and re-cut some D and Q boards. After doing that, they slotted into each other quite nicely, with no noticeable step created at the top or bottom when joining the three boards together.
I also tried pushing the zero-kerf boards into the rebuilt-with-zero-kerf slots, and they fit somewhat snugly, so I went with it.
That left me just trying to figure out how to make sure my circle would come out as 21.85mm, because I still didn't have real physical units expressed in my .svg output.
SVG real world coordinates, and style sheets
So how do you figure out how to tell CorelDraw how to draw things in real world coordinates?Turns out it's pretty easy. But here's how I backed into it.
I started by creating a brand new file in CorelDraw, and saving it as SVG. The file that was created showed me a few things.
First, the unit type is declared in the opening <svg> statement, where the width and height are stated. So at the opening of my file, I really want something like this:
<svg width="800mm" height="600mm">
From that point onward, all unit measurements are assumed to be in that unit type, and they are not declared with their own unit type.
So, to get the bearing circle, I would just have to say this after having set up the millimeter unit type at the start of the file in the <svg> tag:
<circle r="21.85" style="fill:none;stroke:black;stroke-width:3" />
Even the viewBox declaration within the same <svg> tag is stated without a unit type.
I also noticed that the CorelDraw SVG output file made its XML format clear, and made use of stylesheets. The starting portion looks something like this:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="610mm" height="457mm" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
viewBox="0 0 610 457"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style type="text/css">
<![CDATA[
.str0 {stroke:black;stroke-width:0.0762}
.str3 {stroke:black;stroke-width:1}
.strred {stroke:red;stroke-width:0.1}
.strnocutblue {stroke:blue;stroke-width:1}
.fil0 {fill:none}
]]>
</style>
</defs>
This made it so that I could declare my hairline stroke width once, and refer to it later using a declaration like this:
<circle r="21.85" class="fil0 str0" />
Some decorative swirls
To get a more interesting center, and also leave the base with less mass, I wanted some sun rays radiating out from the center. I tried some simple crescents at first, but they weren't very interesting, and plain sine curves would cut across each other at the 180 degree point.Fortunately, MY was home from college and able to whip up a quick formula.
<insert formula here>
Full rendering
I adjusted the code to use stylesheets and millimeter units, and re-ran the code. I left some areas of the code such that I'd have to change them before each run, particularly the choices of whether to render the base, and/or the D boards, and/or the Q boards. This is a drawing of the D boards and the base. (Apologies to anyone with contrast vision problems, but the hairline stroke-width makes things very faint.)I then took the output to Corel Draw, created a new Broad Sheet document, and copied the objects from SVG into the new page. The material width and height sometimes wasn't exactly 24"x18", depending on what scraps I had, so it was important to lay the objects out according to actual dimensions.
I also duplicated the bearing washers, and had to manually duplicate the inner circle, and used Corel Draw's alignment functions to keep things centered up for them.
The edited Broad Sheet in Corel Draw 5 looked like this:
I cut that on 5mm ply, and that's when I ran into the tab collision problem. More on that later.