歯車について勉強する の履歴(No.11)

更新


公開メモ

ちょっと思い立って歯車について勉強してみました

成果物

一般的な歯車の歯形を計算して描画できるようになりました。

スライダーを動かすこといろいろな歯車を描くことができます。

LANG: p5js_live
(()=>{
  const sk = sketch;

	// ui コントロールの一覧
	const tools = [];

	// スライダーコントロールを作成するユーティリティ
	// ラベルと値表示を追加する
	function myCreateSlider(label, i, option) {
	  let x = 20,
	    y = i * 30 + 20;
	  // ラベル
	  const span = sk.createSpan(label).position(x, y);
	  // 値表示
	  const value = sk.createSpan(option[2]);
	  value.position(x + 80, y);
	  // スライダー
	  const slider = sk.createSlider(...option);
	  slider.position(x + 110, y);
	  slider.size(300); // 値が変更されれば表示を更新
	  slider.input(() => value.html(slider.value()));
	  // ツールリストに登録
	  tools.push(span, value, slider);
	  // スライダーを返す
	  return slider;
	}

	// チェックボックスを作成するユーティリティ
	// ラベルをクリックしてもチェックできる
	function myCreateCheckbox(label, i, j = 0, checked = false) {
	  let x = j * 100 + 20,
	    y = i * 30 + 20;
	  // チェックボックス
	  const checkbox = sk.createCheckbox();
	  checkbox.position(x, y);
	  checkbox.checked(checked);
	  // ラベル (クリックでチェックを変更)
	  const span = sk.createSpan(label).position(x + 20, y);
	  span.mouseClicked(() => {
	    checkbox.checked(!checkbox.checked());
	    if (checkbox.elt.onchange) {
	      checkbox.elt.onchange(); // 自動で呼ばれない
	    }
	  });
	  span.style("cursor", "pointer ");
	  // ツールリストに登録
	  tools.push(span, checkbox);
	  // チェックボックスを返す
	  return checkbox;
	}

	let d = 0; // 時刻的な変数
	const wsize = 640; // ウィンドウサイズ

	function initializeTools() {
	  // ツールの初期化
	  checkHide = myCreateCheckbox("ツールを非表示", 19.5, 0);
	  sliderM = myCreateSlider("スケール", 0, [5, 300, 45, 1]);
	  sliderZA = myCreateSlider("歯数 A", 1, [6, 100, 6, 1]);
	  sliderZB = myCreateSlider("歯数 B", 2, [6, 100,20, 1]);
	  sliderA = myCreateSlider("圧力角", 3, [10, 40, 20, 1]);
	  sliderBL = myCreateSlider("バックラッシュ", 4, [0, 2, 0, 0.01]);
	  sliderS = myCreateSlider("回転速度", 5, [-10, 100, 30, 1]);
	  checkDk = myCreateCheckbox("歯先円", 6, 0);
	  checkDp = myCreateCheckbox("基準円", 6, 1, true);
	  checkDb = myCreateCheckbox("基礎円", 6, 2);
	  checkDf = myCreateCheckbox("歯底円", 6, 3);
	  checkFl = myCreateCheckbox("フィレット", 7, 0, true);
	  checkPt = myCreateCheckbox("接続点", 7, 1);
	  checkIn = myCreateCheckbox("内歯車", 7, 2);

	  // マウスオーバーで透過率を変化させる
	  tools.forEach((tool, i) => {
	    tool.style("opacity", "0.2");
	    tool.mouseOver(() => tools.forEach((t) => t.style("opacity", "1")));
	    tool.mouseOut(() => tools.forEach((t) => t.style("opacity", "0.2")));
	  });

	  // ツールの非表示
	  checkHide.elt.onchange = () => {
	    tools.forEach((tool, i) => {
	      if (i < 2) return; // 自分を消さない
	      if (checkHide.checked()) {
	        tool.hide();
	      } else {
	        tool.show();
	      }
	    });
	  };
	}

	// 初期化処理
	sk.setup = () => {
	  sk.createCanvas(wsize, wsize);
	  initializeTools();
	}

	// 描画
	sk.draw = () => {
	  sk.background(220);
	  sk.strokeWeight(1);
	  sk.noFill();

	  const m = sliderM.value();
	  const za = sliderZA.value();
	  const zb = sliderZB.value();
	  const a = (sliderA.value() / 360) * 2 * Math.PI;
	  const blash = sliderBL.value();
	  const fillet_r = checkFl.checked() ? undefined : 0;

	  // 歯車A
	  draw_gear(wsize / 2 - (m * za) / 2, wsize / 2, d / za, m, za, {
	    a,
	    blash,
	    fillet_r,
	  });

	  // 歯車B
	  if (checkIn.checked()) {
	    draw_gear(wsize / 2 - (m * zb) / 2, wsize / 2, d / zb, m, zb, {
	      a,
	      blash: -blash,
	      fillet_r,
	      rkm: 2.5,
	      rfm: 2.0,
	    });
	  } else {
	    draw_gear(
	      wsize / 2 + (m * zb) / 2,
	      wsize / 2, // 歯数の偶奇で位相を調整する
	      -d / zb + Math.PI * (1 + 1 / zb),
	      m,
	      zb,
	      { a, blash, fillet_r }
	    );
	  }

	  // 回転させる
	  d += 0.001 * sliderS.value();
	}

	// 歯車を描く(d は回転角度)
	function draw_gear(ox, oy, d, m, z, options) {
	  let { a, blash, rkm, rfm, fillet_r } = {
	    a: (20 / 360) * 2 * Math.PI,
	    blash: 0,
	    rkm: 2.0,
	    rfm: 2.5,
	    fillet_r: 0.37995 * m, // (rfm/2-1)/(1-Math.sin(a))
	    ...options,
	  };
	  
	  // https://involutegearsoft.hatenablog.com/entry/2023/08/20/131301
	  if(typeof(options.fillet_r)=='undefined') {
	    const c = Math.abs(rfm-rkm)/2;
	    // fillet_r = 0.37995 * m;
	    fillet_r = m * c/(1-Math.sin(a));
	    // こちらだと半径に垂直な直線が現れる場合に
	    // 残念なことになる
	    // fillet_r = m * Math.min(
	    //   c/(1-Math.sin(a)),
	    //   (Math.PI/4-(1+c)*Math.tan(a))/
	    //   Math.tan((Math.PI/2-a)/2)
	    // );
	  }
	
	  // 4 つの直径
	  let rp = (m * z) / 2;
	  let rk = rp + (rkm * m) / 2;
	  let rf = rp - (rfm * m) / 2;
	  let rb = rp * Math.cos(a);
	  let blash_offset = ((blash / 100) * (Math.PI * m)) / (2 * rp) / 2;

	  sk.stroke("#00c");
	  sk.drawingContext.setLineDash([5, 2, 5]);
	  if (checkDp.checked()) sk.circle(ox, oy, rp * 2);
	  if (checkDk.checked()) sk.circle(ox, oy, rk * 2);
	  if (checkDf.checked()) sk.circle(ox, oy, rf * 2);
	  if (checkDb.checked()) sk.circle(ox, oy, rb * 2);
	  sk.drawingContext.setLineDash([1, 1]);

	  // r からインボリュート角 a を求める関数
	  // 角度 0 で rp と交わる
	  //   → 歯先の中心の角度 + Math.PI / z / 2
	  //   → 歯元の中心の角度 - Math.PI / z / 2
	  const involute_theta0 =
	    Math.tan(Math.acos(rb / rp)) - Math.acos(rb / rp) + Math.PI / z / 2;
	  const involute_r2theta = (r) =>
	        - (Math.tan(Math.acos(rb / r)) - Math.acos(rb / r) - involute_theta0)
	        - blash_offset 
	  // トロコイド曲線
	  // t = 0 から増やすと歯元から歯先へ描けるが
	  // t が負の領域が必要になることもある
	  const trochoid = (t) => {
	    let y = (Math.PI * m) / 4 - m * Math.tan(a) + rp * t;
	    let x = rp - m;
	    let r = Math.sqrt(x * x + y * y);
	    let theta = Math.atan2(y, x) - t;
	    return [r, -(theta - Math.PI / z / 2) + Math.PI / z / 2 - blash_offset];
	  };

	  // 点を繋いで表示する
	  const drawCurve = (points, c = "#00c") => {
	    sk.stroke(c);
	    for (let j = 0; j < z; j++) {
	      let dd = ((2 * Math.PI) / z) * j;
	      for (let i = 0; i < points.length - 1; i++) {
	        sk.line(
	          ox + points[i][0] * Math.cos(d + points[i][1] + dd),
	          oy + points[i][0] * Math.sin(d + points[i][1] + dd),
	          ox + points[i + 1][0] * Math.cos(d + points[i + 1][1] + dd),
	          oy + points[i + 1][0] * Math.sin(d + points[i + 1][1] + dd)
	        );
	        sk.line(
	          ox + points[i][0] * Math.cos(d - points[i][1] + dd),
	          oy + points[i][0] * Math.sin(d - points[i][1] + dd),
	          ox + points[i + 1][0] * Math.cos(d - points[i + 1][1] + dd),
	          oy + points[i + 1][0] * Math.sin(d - points[i + 1][1] + dd)
	        );
	      }
	    }
	  };

	  const drawPoint = (p, c = "#00c") => {
	    if (!checkPt.checked()) return;
	    sk.stroke(c);
	    for (let j = 0; j < z; j++) {
	      let dd = ((2 * Math.PI) / z) * j;
	      sk.circle(
	        ox + p[0] * Math.cos(d + p[1] + dd),
	        oy + p[0] * Math.sin(d + p[1] + dd),
	        3
	      );
	      sk.circle(
	        ox + p[0] * Math.cos(d - p[1] + dd),
	        oy + p[0] * Math.sin(d - p[1] + dd),
	        3
	      );
	    }
	  };

	  let rbottom = Math.max(rb, rf);

	  // インボリュート曲線
	  const involute_n = 30; // 何分割するか
	  let involute_rs = rk; // 書き始めの r
	  let involute_re = rbottom; // 書き終わりの r
	  let bottom_less = false;
	  let top_less = false;
	  // 圧力角が大きいときは rk まで書くと書きすぎになるので rs を調整する
	  if (involute_r2theta(involute_rs) < 0) {
	    // involute_r2theta(r) = 0 となる点を求める
	    involute_rs = bisect_root(involute_rs, involute_re, involute_r2theta);
	    top_less = true;
	  }

	  // 圧力角が大きいときは rbottom まで書くと書きすぎになるので re を調整する
	  if (involute_r2theta(involute_re) > Math.PI / z) {
	    // involute_r2theta(r) = Math.pi/z となる点を求める
	    involute_re = bisect_root(
	      involute_rs,
	      involute_re,
	      (r) => involute_r2theta(r) - Math.PI / z
	    );
	    bottom_less = true;
	  }

	  // 歯元への垂線
	  let vertical_rs = rbottom;
	  let vertical_re = rf;
	  const vertical_theta = involute_r2theta(rb);

	  // 逃げを作成する領域を探索
	  let trochoid_ts = 0;
	  let trochoid_te = 0;
	  if (!bottom_less) {
	    // t の位置での逃げの量を求める
	    // 負なら逃げの必要なし
	    const involute_rb = involute_r2theta(rb);
	    const escape = (t) => {
	      let p = trochoid(t);
	      let theta = p[0] > rb ? involute_r2theta(p[0]) : involute_rb;
	      return theta - p[1];
	    };

	    // 逃げを作る範囲を求める
	    let t = 0,
	      dt = 0.02;
	    let escape_prev = escape(t);
	    if (escape_prev < 0) {
	      // escape の最大値を求める
	      for (t += dt; ; t += dt) {
	        const e = escape(t);
	        if (e < escape_prev) break;
	        escape_prev = e;
	      }
	      [t, escape_prev] = bisect_maximize(t, t - dt, escape);
	    }

	    // 最大値が正でなければ逃げは必要ない
	    if (escape_prev > 0) {
	      let t0 = t;
	      for (; escape(t) > 0; t -= dt) {}
	      trochoid_ts = bisect_root(t + dt, t, escape);
	      for (t = t0; escape(t) > 0; t += dt) {}
	      trochoid_te = bisect_root(t - dt, t, escape);

	      involute_re = Math.max(involute_re, trochoid(trochoid_te)[0]);
	      vertical_rs = Math.min(vertical_rs, trochoid(trochoid_ts)[0]);
	    }
	  }

	  // 逃げを描く
	  if (trochoid_ts < trochoid_te) {
	    const c_trochoid = [];
	    //if (rb > rf) {
	    for (let n = 10, i = 0; i <= n; i++) {
	      let t = trochoid_ts + (i * (trochoid_te - trochoid_ts)) / n;
	      c_trochoid.unshift(trochoid(t));
	    }
	    drawCurve(c_trochoid);
	  }

	  // 歯元
	  let bottom_s =
	    vertical_rs > vertical_re ? vertical_theta :
	    !bottom_less ? involute_r2theta(involute_re) : Math.PI / z;
	  const bottom_e = Math.PI / z;

	  const lawOfCosineA = (a, b, c) =>
	    Math.acos((b ** 2 + c ** 2 - a ** 2) / (2 * b * c));

	  const c_fillet = [];
	  let fillet_or = rf + fillet_r;
	  let fillet_sr;
	  let fillet_st = vertical_theta;
	  let fillet_sw = checkFl.checked();

	  if (fillet_r > 0) {
	    const fillet_slope = (r) => {
	      if (r <= rf) return 0;
	      const a = rf + fillet_r - r;
	      return (r * Math.sqrt(fillet_r ** 2 - a ** 2)) / a;
	    };
	    const dr = m / 100;
	    const involute_slope = (r) =>
	      dr / (involute_r2theta(involute_re) - involute_r2theta(involute_re + dr));
	    if (!bottom_less && rf + fillet_r < vertical_rs) {
	      // 縦線がフィレット半径より大きい
	      fillet_sr = fillet_or;
	      vertical_re = fillet_sr;
	    } else {
	      if (involute_re < rf + fillet_r) {
	        fillet_sr = involute_re;
	        if (involute_slope(involute_re) > fillet_slope(involute_re)) {
	          fillet_sr = bisect_root(
	            involute_re,
	            rf + fillet_r,
	            (r) => involute_slope(r) - fillet_slope(r)
	          );
	        }
	        fillet_st = involute_r2theta(fillet_sr);
	        involute_re = fillet_sr;
	      } else if (!bottom_less) {
	        fillet_sr = vertical_rs;
	        vertical_re = fillet_sr;
	      }
	    }
	  }

	  let fillet_ot = fillet_st + lawOfCosineA(fillet_r, fillet_sr, rf + fillet_r);
	  if (fillet_r > 0 && !isNaN(fillet_ot)) {
	    let fillet_s =
	      fillet_ot - Math.PI + lawOfCosineA(fillet_sr, fillet_r, rf + fillet_r);
	    let fillet_e = fillet_ot - Math.PI;
	    let fillet_ox = fillet_or * Math.cos(fillet_ot);
	    let fillet_oy = fillet_or * Math.sin(fillet_ot);
	    const fillet = (t) => {
	      let x = fillet_ox + fillet_r * Math.cos(t);
	      let y = fillet_oy + fillet_r * Math.sin(t);
	      return [Math.sqrt(x ** 2 + y ** 2), Math.atan2(y, x)];
	    };
	    drawPoint([fillet_sr, fillet_st]);

	    vertical_re = fillet(fillet_s)[0];
	    bottom_s = fillet(fillet_e)[1];

	    // フィレットがクロスしないようにする
	    if (fillet(fillet_e)[1] > Math.PI / z) {
	      fillet_e = bisect_root(
	        fillet_s,
	        fillet_e,
	        (t) => fillet(t)[1] - Math.PI / z
	      );
	      bottom_s = Math.PI / z;
	    }
	    for (let n = 10, i = 0; i <= n; i++) {
	      let t = fillet_s + ((fillet_e - fillet_s) / n) * i;
	      c_fillet.push(fillet(t));
	    }
	  }

	  // インボリュート曲線を作成
	  const c_involute = [];
	  if (involute_rs != rk) {
	    c_involute.push([involute_rs, 0]); // 最初の点
	  }
	  for (let i = c_involute.length; i <= involute_n; i++) {
	    let r = involute_rs + ((involute_re - involute_rs) / involute_n) * i;
	    c_involute.push([r, involute_r2theta(r)]);
	  }
	  drawCurve(c_involute);
	  drawPoint(c_involute[0]);
	  drawPoint(c_involute[c_involute.length - 1]);

	  // 歯先
	  const c_top = [];
	  if (!top_less) {
	    for (let n = 10, i = 0; i <= n; i++) {
	      c_top.push([rk, i * c_involute[0][1]/n]);
	    }
	  }
	  drawCurve(c_top);

	  const c_vertical = [];
	  if (vertical_rs > vertical_re) {
	    c_vertical.push([vertical_rs, vertical_theta]);
	    c_vertical.push([vertical_re, vertical_theta]);
	    drawPoint([vertical_rs, vertical_theta]);
	  }
	  drawCurve(c_vertical);

	  const c_bottom = [];
	  if (bottom_s < bottom_e) {
	    drawPoint([rf, bottom_s]);
	    for (let n = 10, i = 0; i <= n; i++) {
	      let t = bottom_s + ((bottom_e - bottom_s) / n) * i;
	      c_bottom.push([rf, t]);
	    }
	  }
	  drawCurve(c_bottom);

	  drawCurve(c_fillet);
	}

	// a と b の間で f(x) = 0 の点を探す
	function bisect_root(a, b, f, epsilon = 1e-5) {
	  let fa = f(a),
	    fb = f(b);
	  if (fa * fb > 0) return Math.NaN;

	  const flag = fa < 0;
	  for (; Math.abs(a - b) > epsilon; ) {
	    let m = (a + b) / 2;
	    let fm = f(m);
	    // let m2 = (a*Math.abs(fb) + b*Math.abs(fa)) / (Math.abs(fa)+Math.abs(fb));
	    // let fm2 = f(m2);
	    // if(Math.abs(fm2)<Math.abs(fm)) {
	    //   m = m2;
	    //   fm = fm2;
	    // }
	    if (flag ? fm < 0 : fm > 0) {
	      a = m;
	      fa = fm;
	    } else {
	      b = m;
	      fb = fm;
	    }
	  }
	  return a;
	}

	// a と b の間で単峰関数 f(x) を最大化する
	function bisect_maximize(a, b, f, epsilon = 1e-5) {
	  let [fa, fb] = [f(a), f(b)];
	  for (; Math.abs(a - b) > epsilon; ) {
	    const s = (2 * a) / 3 + b / 3;
	    const fs = f(s);
	    const t = a / 3 + (2 * b) / 3;
	    const ft = f(t);
	    if (fs > ft) {
	      b = t;
	      fb = ft;
	    } else {
	      a = s;
	      fa = fs;
	    }
	  }
	  return [a, fa];
	} 
})();

歯車の形状

参考: https://qiita.com/chromia/items/629311346c80dfd0eac7

gear.png

形状を指定するパラメータ

歯車は歯のピッチと歯の枚数、そして、歯の肉厚によって形が決まる。

  • モジュール (歯のピッチを $\pi$ で割った値) $m$
  • 歯の数 $z$
  • 圧力角(歯の肉厚を決める) $\alpha$

歯のピッチは円周の $1/z$ だから歯車の直径 $d$ として $\pi d/z$

ここから $mz=d$ の関係が出る

圧力角

  • 圧力角が小さいと歯と歯の滑りが小さくなり摩擦が小さくなるが、
    歯車が小さいときに接触時間が短くなる。
  • 圧力角が大きいと歯と歯の滑りが大きくなり摩擦が大きくなるが、
    歯車が小さいときも接触時間は長く取れる。

圧力角は 20°とするのが一般的だそう。

基準円とその他の円

上の $d$ は「基準円」の直径であり、他の直径と区別するため以下では $d_p$ と書く。

  • かみ合う2つの歯車は基準円同士がちょうど接する
  • 基準円の上で歯の厚さはピッチの半分になり、残りの半分が隙間になる
  • 基準円の上で歯の外形線と半径とがなす角が圧力角 $\alpha$ である
gear2.png

その他に重要な直径として以下がある。

歯先円(歯の先端)$d_k=d_p+2m$
基礎円(歯の外形と半径とが接する)$d_b=d_p\cos \alpha$
歯底円(歯間の底)$d_f=d_p-2.5m$


  • 基礎円は歯の一番厚い部分
  • 歯先円は、基準円から外側の歯先の長さ(歯末のたけ) $(d_k-d_p)/2=m$ を決める
  • 歯底円は、相手の歯先から歯底までの間の間隙の深さ(頂げき) $(d_p-d_k)/2=0.25m$ を決める

基礎円の外側(歯末)の歯形

基礎円より外側の歯の形状はインボリュート曲線とすることが多いらしい。

この曲線は外形線が基礎円と交わる点を基準とした極座標 $r,\theta$ を用いて、 $$ \theta=\tan a-a\ \ \text{ただし}\ \ a(r)=\cos^{-1}\frac{r_b}r $$ と表される。ここに現れる $$ \mathrm{inv}\,a=\tan a-a $$ はインボリュート関数と呼ばれる。

$\theta$ の基準を歯先の中心に取るなら、

$$ \begin{aligned} \theta(r)=\mathrm{inv}\, a(r) - \mathrm{inv}\, a(r_p) - \frac14\frac{2\pi} z \end{aligned} $$

とする。

LANG: js
  const involute_theta0 =
    Math.tan(Math.acos(rb / rp)) - Math.acos(rb / rp) + Math.PI / z / 2;
  const involute_r2theta = (r) => {
    return -(Math.tan(Math.acos(rb / r)) - Math.acos(rb / r) - involute_theta0);
  };

  // インボルート曲線
  const c_involute = [];
  const involute_n = 30; // 何分割するか
  let involute_rs = rk;               // 書き始めの r
  let involute_re = Math.max(rb, rf); // 書き終わりの r
  // 圧力角が大きいときは rk まで書くと書きすぎになる
  if (involute_r2theta(involute_rs) < 0) {
    for (
      ;
      involute_r2theta(involute_rs) < 0;
      involute_rs -= (involute_rs - involute_re) / 100
    ) {
      // do nothing
    }
    // involute_r2theta(r) = 0 となる点を求める
    involute_rs = bisect_root(
      involute_rs,
      involute_rs + (involute_rs - involute_re) / 100,
      involute_r2theta
    );
    c_involute.push([involute_rs, 0]); // 最初の点
  }
  // 残りの点を求める
  for (let i = c_involute.length; i <= involute_n; i++) {
    let r = involute_rs + ((involute_re - involute_rs) / involute_n) * i;
    c_involute.push([r, involute_r2theta(r)]);
  }
LANG: js
// 二分法を使い a と b の間で f(x) = 0 の点を探す
// epsilon は x 方向の精度
function bisect_root(a, b, f, epsilon = 1e-6) {
  const fa = f(a),
  fb = f(b);
  if (fa * fb > 0) return Math.NaN;

  const flag = fa < 0;
  for (; Math.abs(a - b) > epsilon; ) {
    let m = (a + b) / 2;
    if (flag ? f(m) < 0 : f(m) > 0) {
      a = m;
    } else {
      b = m;
    }
  }
  return a;
}
gear3.png

基準円の内側(歯元)の歯形

特にこだわらないのであれば基礎円より下はまっすぐ中心へ下ろせばよいのであるが、より丈夫にする為に歯の根元にフィレットを付けることも多い。

円形のフィレットをできるだけ大きく取りたければ、相手の歯先が当たらないよう $d_p$ から相手の波の長さ $d_k-d_p$ を引いた円と歯形との交点から引き始めれば良いのだが、その点で接する円が歯底に触れなくなってしまうなら調整が必要で、ちゃんとやろうとするとかなり面倒に感じられる?

歯を切る側のラックに付けるフィレット半径の最大値を与える条件式がこちらに書かれていた。

https://involutegearsoft.hatenablog.com/entry/2023/08/20/131301

歯底の間隙 $c=-(d_k+d_f)/2$、圧力角 $\alpha$ として、

● 開始点が本来の歯先面より下になる条件

$$ r\le\frac {c}{1-\sin\alpha} $$

● 歯底から浮いてしまわない条件

$$ r\le\frac {\pi/4-(1+c)\tan\alpha}{\tan((\pi/2-\alpha)/2)} $$

ちゃんとこれで切られた波形を描けばきれいに書けるはずだけれど、今はフィレットなしで計算した歯形に後からフィレットを付けているのでその半径をどう決めるべきなのか・・・相手と干渉しない限りそこまで重要な部分ではないので上手に手を抜いても良さそうだけれど???

歯数の少ない歯車では

歯数が少ない歯車、具体的には歯数が 18 未満になると、相手の歯数が大きいときに基準円あたりから下で相手の歯先と干渉してしまう。

相手の歯数が大きいほど干渉は大きくなるため、相手の歯数が無限大でも大丈夫な形で「逃げ」を作ろう。歯数無限大の歯車はラックであるから、以下ではラック&ピニオンでの状況を考える。ラックの歯の角度は圧力角 $\alpha$ そのもので、$r_p$ に相当する高さではやはり歯の幅はピッチの半分になる。高さは基準円から上が $m$、下が $1.25m$ である。

gear4.png

ラックの歯先が通る直線はピニオンの中心から $r_p-m$ の距離にあり、ラックの移動速度はピニオンの半径 $r_p$ の点の円周の速さと同じである。

下図の時点で頂点の $y$ 座標は $\pi m/4-m \tan \alpha$ である。このあとピニオンが $\omega t$ 回転したとき $y$ 座標は $\pi m/4-m \tan \alpha-r_p\omega t$ で $x$ 座標は $x=r_p-m$ で変わらないから、この点をピニオンの中心から見た極座標で表した曲座標 $\theta$ と動径 $r$ は

$$ r=\sqrt{(r_p-m)^2+(\pi m/4-m \tan \alpha-r_p\omega t)^2} $$ $$ \theta=\text{atan}\,\frac{\pi m/4-m \tan \alpha-r_p\omega t}{r_p-m} $$ このときピニオン自体 $\omega t$ 回転しているのでピニオンと共に回転する座標では $$ \theta'=\theta-\omega t $$ となる。この曲線はトロコイド曲線と呼ばれるそうだ。

歯先の中心を基準とした極座標に直すには、 $$ \theta''=-(\theta'-2\pi/z/4)+2\pi/z/4 $$ とする。

これがインボルート曲線より内側に入る部分を取り除いて、適当なフィレットを付けて求めたのが冒頭の歯車の形状である。

本当は、歯頭を間隙分だけ伸ばし、さらにフィレットを付けたラックとの干渉を考えて歯形を決めればフィレットも含めて完全な歯の形が書けるはずなのだけれど、、、そのためには歯頭を間隙分だけ伸ばし、フィレット半径だけ内側に引き戻した点でトロコイドを計算して、そこからフィレット半径だけオフセットさせた曲線を作ればいい? 曲線をオフセットするには各点の接線に垂直に距離を取ればいいとして・・・うまくできるかな???(未実装)

LANG: js
  // トロコイド曲線
  // t = 0 から増やすと歯元から歯先へ描けるが
  // t が負の領域が必要になることもある
  const trochoid = (t) => {
    let y = (Math.PI * m) / 4 - m * Math.tan(a) + rp * t;
    let x = rp - m;
    let r = Math.sqrt(x * x + y * y);
    let theta = Math.atan2(y, x) - t;
    return [r, -(theta - Math.PI / z / 2) + Math.PI / z / 2];
  };

バックラッシュ

歯を円周方向に薄くすることで無理なくバックラッシュを実装できる。

上では円周方向のバックラッシュをピッチに対するパーセンテージで指定できるようにした。

1% とすると歯面を円周方向にピッチの 0.5% だけ下げることで、2つの歯車をかみ合わせたときにピッチの 1% の遊びが生まれるようにしてある。

転位

通常より工具を離した状態で切削すると歯数は同じで一回り大きな歯車ができる。

これを使って歯車間の距離を調節したり、歯先の干渉を減らしたりできるらしい。

歯形の計算方法など学びたい。

内歯車

基本的には通常の歯車と同じ形状になるけれど、歯元と歯先が入れ替わるので長さを調整する。

内歯車は歯先に直線領域が残ると確実に相手の歯形と干渉するので、「接続点」を表示して直線領域が生成されないよう調整する。

また相手歯車から見ると、内歯車の歯先はラックの場合に比べても内側に入ってくるため、ラック形状でアンダーカットされた小歯数の外歯車では内歯車が相手の場合にインボルート干渉が生じてしまう。

小さな形の歯車を使いたいときには圧力角を大きくするか、転位を行うと良いとのこと。

内歯車の歯先にフィレットを付ければ歯車が回らないという問題は避けられそうに思えるけれど、かみ合い率が低下してしまうので良くないのだとか。こちらの説明が参考になった。

https://sites.google.com/view/involutegearsoft/%E3%81%8B%E3%81%BF%E5%90%88%E3%81%84%E7%8E%87%E8%A8%88%E7%AE%97?authuser=0

はすば歯車

hasuba.png

はすば歯車は歯が傾いた歯車。

歯がと歯が斜めに当たるため、軸方向に力を生じてしまうけれど、強度が高くなったり静かになったりと、いろいろ性能がいいそうだ。

はすばの傾き角 $\beta$ は基準半径の仮想的な円筒を切り開いた展開図上で歯と軸方向とがなす角のこと。

軸に垂直な断面上で見える歯の厚さに比べて、歯に垂直な方向に計った歯の厚さは傾いた分だけ薄くなり、$\cos \beta$ 倍になるから、歯に直角にモジュール $m_n$ を持つはすば歯車の、軸に垂直な断面には現れる軸直角モジュール $m_t$ は $$m_n=m_t\cos\beta$$ の関係にある。

ということで、歯に直角にモジュール $m_n$ を持つはすば歯車を作るには軸に直角な断面上にモジュール $m_t=m_n/\cos\beta$ の歯車形状を作り、それを中心軸に対してねじれ角 $\beta$ の分だけひねりながら押し出せばよい。

ただし、本当にそれをそのままやってしまうと歯の高さも $1/\cos\beta$ 倍になってしまうため、歯の高さを $m_n$ に合わせるため高さ方向は $\cos\beta$ 倍してモジュール $m_n$ の高さに戻してやる必要がある。

斜めにひねりながら押し出す際、押出ステップが大きいと精度が低下するから、たとえば1回の押出当たりの回転角を $\delta_\text{max}=3\ \text{deg}$ に制限したい、というようなことになる。これを元にステップ数を決めて全体として高さ $h$ だけ押し出すことを考えよう。

一回の押出のひねり角の最大値 $\delta_\text{max}$ と、そのときの押出量 $h_\text{max}$ は円周方向の回転量が $m_t z \delta_\text{max} / 2$ となることから、 $$ \tan\beta=\frac{m_t z \delta_\text{max} / 2}{h_\text{max}} $$ という関係がある。

そこで押し出し回数 $n$ を、 $$ n=\text{ceil}\frac{h}{h_\text{max}} $$ で決めて、1回あたりのひねり角 $\Delta\delta$ と押出量 $\Delta h$ を $$\Delta h=h/n$$

$$\Delta\delta= \frac{\tan\beta\Delta h}{m_t z /2}$$

とすればよい。

コメント・質問





Counter: 155 (from 2010/06/03), today: 1, yesterday: 13