<!-- /* --><title>Weird gears in DHTML</title> <meta charset="utf-8" /> <center> <canvas width=512 height=512 id=c></canvas><br /> <button onclick="exchange()">Exchange</button> <button onclick="setGearRatio(1)">1:1</button> <button onclick="setGearRatio(2)">2:1</button> <button onclick="setGearRatio(3)">3:1</button> <button onclick="setGearRatio(0.5)">1:2</button> <button onclick="setGearRatio(1/3)">1:3</button> <button onclick="setGearRatio(2/3)">2:3</button> <button onclick="setGearRatio(1.5)">3:2</button> </center>
<p> <a href="http://canonical.org/~kragen/sw/dev3/gears">This program</a> interactively makes irregular two-dimensional shapes that function together as gears. Click in the gray square to start. <p>In June of 2013, I saw <a href="http://youtu.be/3LdlSAN1yks" title="MP4 17732771 bytes, sha1 01d86ca79f6ad9f1e04bf045c62ff21fca8cde79">How To Make Organically-Shaped Gears</a>, a 2010 video by Clayton Boyer, a totally awesome maker of elaborate wooden clocks, which shows a fairly general method for constructing a gear to mesh with another gear whose form is nearly arbitrary. Boyer uses two existing spur gears with the desired gear ratio (which probably needs to be fairly simple, like 1:1, 1:2, 2:3, or something like that, not 7:8 or 23:29), tapes a paper disc on top of one, and affixes the arbitrary shape to the other one. Then he rotates the two gears together, marking out the places where the arbitrary shape overlaps the paper disc; the remaining places, where it does not overlap, form the shape of the other gear. <p>That’s the basic thing this program does. You draw a red “draft” gear by clicking in the gray area, and it cuts away the gray where the red gear has to be able to move freely during its movement. If you click the “Exchange” button, the roles reverse: the old gray becomes the new red (which you can add to), a new gray field is established, and fairly quickly, the gray that would interfere with your gear gets cut away. If you wait long enough, it becomes fairly smooth. After a click of “Exchange”, the gears remain in contact with one another throughout their revolution, although not necessarily good contact. <p>You can also change the gear ratio you’re going for by clicking the buttons labeled with ratios such as “1:3”. </p> <img style="float:right" src="http://canonical.org/~kragen/sw/dev3/gears/gears4-Y2gb1Ab-512.jpg" alt="" title="3-D printed gears" /> <img style="float:right; clear:right" src="http://canonical.org/~kragen/sw/dev3/gears/gears4-Y2gb1Ab-closeup.jpg" alt="" title="3-D printed gears closeup" /> <p>I used this program to 3-D print a couple of gears on a Prusa Mendel, and they seem to mesh pretty well when I try them by hand, although I haven’t mounted them to see if they really work. The process, which I clearly need to streamline because it took me like an hour in all, was as follows: <ol> <li> Design the gears in this program, in Iceweasel (Firefox). <li> Right-click on the canvas and “Save Image As” <a href="gears4.png">a PNG</a>. <li> Extract the gear shapes as raster PNGs: <ol> <li> open the PNG in the GIMP, <li> merge the existing layer down onto a new white background, convert it to Image → Mode → Grayscale, <li> use Colors → Curves... to turn only the red gear black, and <li> export it as <a href="gears4-right.png">a PNG</a>. <li> Undo the curves, <li> bucket-fill the big gray background with white to eliminate it, <li> turn only the gray gear black with curves, <li> and export it as <a href="gears4-left.png">another PNG</a>. </ol> <li> Convert the two raster gear shapes to DXF polygons: <ol> <li> open them in Inkscape, <li> trace to vector image (Path → Trace Bitmap...) with the default settings, <li> edit the node paths (F2) and add a whole shitload of nodes with ctrl-alt-leftclick in between the existing nodes, <li> turn the polyline into a polygon by selecting all the segments of the path (^A) and using “Make selected segments lines”, <li> resize the path to make it smaller (by 50% for one gear and 49% for the other, although I should have scaled down to 25% of the original size), <li> move it so it’s centered on the lower left-hand corner of the page (the origin), and <li> save as a “Desktop Cutting Plotter (Autocad DXF R14)” DXF file (<a href="gears4-right-lines.dxf">right</a>, <a href="gears4-left-lines.dxf">left</a>.) </ol> <li> Extrude the polygons into a vertical prism STL file; for each gear: <ol> <li> In a new file in OpenSCAD, put <code>linear_extrude(height=3) import("foo.dxf"); </code>, where <code>foo.dxf</code> is the name of the DXF file containing one of the polygons. Actually I think <code>height=6</code> would have been a better idea. With my pipeline, the units are, by default, millimeters, so in theory that’s 3mm high, but it ends up being a little higher in practice. Because they’re about 100mm wide, that’s a 30:1 aspect ratio, which makes the gears kind of prone to flop apart if they’re not sitting on a flat surface. <li> Hit F5 to see the shape to see if it’s okay. <li> Do a CGAL render and then export an STL file (<a href="gears4-right.stl.gz">right</a>, <a href="gears4-left.stl.gz">left</a>.) </ol> <li> Generate G-code from the STL file (<a href="gears4-right.gcode.gz">right</a>, <a href="gears4-left.gcode.gz">left</a>). I use “Quick slice” in Slic3r. <li> Look at the G-code in Pronterface from Printrun to see if it looks good. <li> Send it to the RepRap to fabricate. </ol> <p>Aside from the crudeness of the process above, this code could be greatly improved in its efficiency, among other things by using bounding boxes and strength reduction so that it doesn’t have to do ten million multiplications per second. <style> center { text-align: center } body p, body ol { max-width: 32em; margin-left: auto; margin-right: auto } </style> <script> /**/ var cvs = document.getElementById('c') , ctx = cvs.getContext('2d') , ww = cvs.width , hh = cvs.height , gear1 = makeArray(ww * hh, 1) , gear2 = makeArray(ww * hh, 0) , bigΘ = 0 , gearRatio = 3 , g2Cx = 5 * ww / 8 , g2Cy = hh / 2 , g1Cx = 3 * ww / 8 , g1Cy = hh / 2 ; window.updateInterval = setInterval(update, 100); cvs.addEventListener('click', onClick, false); function setGearRatio(newRatio) { gearRatio = newRatio; resetGear1(); } function resetGear1() { gear1 = makeArray(ww * hh, 1); } function exchange() { gear2 = gear1; resetGear1(); gearRatio = 1/gearRatio; var tmpX, tmpY; tmpX = g2Cx, tmpY = g2Cy; g2Cx = g1Cx; g2Cy = g2Cy; g1Cx = tmpX; g1Cy = tmpY; } // Returns a transform that does the same thing as applying xformG // followed by xformF. function compose(xformF, xformG) { var cos = Math.cos(xformF.θ) , sin = Math.sin(xformF.θ) ; return { θ: xformG.θ + xformF.θ , dx: xformF.dx + cos * xformG.dx - sin * xformG.dy , dy: xformF.dy + cos * xformG.dy + sin * xformG.dx }; } function translate(dx, dy) { return { θ: 0, dx: dx, dy: dy }; } function rotate(θ) { return { θ: θ, dx: 0, dy: 0 }; } function rotateAround(cx, cy, θ) { return compose(translate(cx, cy), compose(rotate(θ), translate(-cx, -cy))); } function makeArray(size, val) { var rv = new Array(size); for (var ii = 0; ii < size; ii++) { rv[ii] = val; } return rv; } function paint() { var pd = ctx.getImageData(0, 0, ww, hh) , pix = pd.data , color ; for (var ii = 0; ii < ww*hh; ii++) { if (gear2[ii]) { color = [255, 64, 128, 255]; } else if (gear1[ii]) { color = [0, 0, 0, 128]; } else { color = [255, 255, 255, 128]; } for (var jj = 0; jj < 4; jj++) { pix[ii * 4 + jj] = color[jj]; } } ctx.putImageData(pd, 0, 0); } function update() { paint(); bigΘ += 1; carve(bigΘ); } function onClick(ev) { var xx = ev.clientX - ev.target.offsetLeft , yy = ev.clientY - ev.target.offsetTop ; for (var ii = -5; ii < 6; ii++) { for (var jj = -5; jj < 6; jj++) { gear2[ww * (yy+ii) + xx + jj] = 1; } } } // XXX repeatedly double or triple gear2 in order to make this more // efficient. Optionally, because the current behavior is awesome. // Remove material from gear1 where gear2 would carve into it, in // its new rotated position. function carve(θ) { var xform = compose( rotateAround(g2Cx, g2Cy, θ * gearRatio), rotateAround(g1Cx, g1Cy, θ) ) ; for (var yy = 0; yy < hh; yy++) { for (var xx = 0; xx < ww; xx++) { var g2P = compose(xform, translate(xx, yy)) , g2X = Math.round(g2P.dx) , g2Y = Math.round(g2P.dy) ; if ( 0 <= g2X && g2X < ww && 0 <= g2Y && g2Y < hh && gear2[g2Y * ww + g2X]) { gear1[yy * ww + xx] = 0; } } } } // </script> -- To unsubscribe: http://lists.canonical.org/mailman/listinfo/kragen-hacks