/* --><title>Weird gears in DHTML</title>
<meta charset="utf-8" />
<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>

<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”.

<img style="float:right"
     alt="" title="3-D printed gears" />

<img style="float:right; clear:right"
     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:

  <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:
         <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,
         <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>.
  <li> Convert the two raster gear shapes to DXF polygons:
         <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
         <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>.)
  <li> Extrude the polygons into a vertical prism STL file; for each
         <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>.)
  <li> Generate G-code from the STL file (<a 
       <a href="gears4-left.gcode.gz">left</a>). I use “Quick slice” in
  <li> Look at the G-code in Pronterface from Printrun to see if it
       looks good.
  <li> Send it to the RepRap to fabricate.

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

center { text-align: center }
body p, body ol { max-width: 32em; margin-left: auto; margin-right: auto }
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;

function resetGear1() {
  gear1 = makeArray(ww * hh, 1);

function exchange() {
  gear2 = gear1;
  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),
                         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() {
  bigΘ += 1;

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

Reply via email to