歯車について勉強する の履歴(No.7)
更新ちょっと思い立って歯車について勉強してみました†
成果物†
一般的な歯車の歯形を計算して描画できるようになりました。
スライダーを動かすこといろいろな歯車を描くことができます。
LANG: p5js_live const sk = sketch; 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 + 370, y); // スライダー const slider = sk.createSlider(...option); slider.position(x + 60, y); slider.size(300); // 値が変更されれば表示を更新 slider.input(() => value.html(slider.value())); // ツールリストに登録 tools.push(span, value, slider); // スライダーを返す return slider; } // チェックボックスを作成するユーティリティ // ラベルをクリックしてもチェックできる function myCreateCheckbox(label, i, j = 0) { let x = j * 100 + 20, y = i * 30 + 20; // チェックボックス const checkbox = sk.createCheckbox(); checkbox.position(x, y); // ラベル (クリックでチェックを変更) 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; // ウィンドウサイズ // 初期化処理 sk.setup = () => { sk.createCanvas(wsize, wsize); checkHide = myCreateCheckbox("ツールを非表示", 19.5, 0); sliderM = myCreateSlider("スケール", 0, [10, 200, 40, 1]); sliderZA = myCreateSlider("歯数 A", 1, [6, 100, 8, 1]); sliderZB = myCreateSlider("歯数 B", 2, [6, 100, 15, 1]); sliderA = myCreateSlider("圧力角", 3, [10, 30, 20, 1]); sliderS = myCreateSlider("回転速度", 4, [-10, 50, 20, 1]); checkDk = myCreateCheckbox("歯先円", 5, 0); checkDp = myCreateCheckbox("基準円", 5, 1); checkDb = myCreateCheckbox("基礎円", 5, 2); checkDf = myCreateCheckbox("歯底円", 5, 3); 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.draw = () => { sk.background(220); sk.stroke("#00c"); sk.strokeWeight(1); sk.noFill(); const m = sliderM.value(); const za = sliderZA.value(); const zb = sliderZB.value(); const alpha = (sliderA.value() / 360) * 2 * Math.PI; let gap = 0; // モジュールをほんの少し小さくすると空隙が生じるはず let dd = zb % 2 ? Math.PI / zb : 0; // 歯数の偶奇で位相を調整する draw_gear( wsize / 2 - (m * za) / 2, wsize / 2, d / za, m - gap / za, za, alpha ); draw_gear( wsize / 2 + (m * zb) / 2, wsize / 2, -d / zb + dd, m - gap / zb, zb, alpha ); d += 0.001 * sliderS.value(); }; // 歯車を描く(d は回転角度) function draw_gear(ox, oy, d, m, z, a = (20 / 360) * 2 * Math.PI) { // 4 つの直径 let rp = (m * z) / 2; let rk = rp + (2.0 * m) / 2; let rf = rp - (2.5 * m) / 2; let rb = rp * Math.cos(a); 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 を求める関数 const inva = (r, offset = 0) => { return Math.tan(Math.acos(rb / r)) - Math.acos(rb / r) - offset; }; const a0 = inva(rp); let p1 = []; let n = 30, r0 = Math.max(rb, rf); // インボリュート曲線 for (let j = 0; j <= n; j++) { let rj = r0 + ((rk - r0) * j) / n; p1.unshift([rj, inva(rj, a0)]); } // 垂直に下ろす if (rf < rb) { for (let j = 0; j <= n; j++) { let rj = r0 + ((rf - r0) * j) / n; p1.push([rj, inva(rb, a0)]); } } // 基準円が歯底円より大きいとき // 歯に逃げを追加するかどうか検討する let p = []; if (rb > rf) { // トロコイド曲線 const trochoid = (dp, offset) => { let y = (Math.PI * m) / 4 - m * Math.tan(a) + rp * dp; let x = rp - m; let r = Math.sqrt(x * x + y * y); let theta = Math.atan2(y, x) - dp; return [r, theta + offset]; }; for (let dp = 0; p.length == 0 || p[0][0] < rp; dp += 0.01) { p.unshift(trochoid(dp, -Math.PI / z / 2)); } // 合成する let j = 0; f = 0; for (let i = 0; i < p1.length - 1; i++) { if (p[j][0] < p1[i][0]) continue; while (j < p.length - 1 && p[j + 1][0] > p1[i][0]) j++; if (j >= p.length - 1) break; let pa = ((p[j][0] - p1[i][0]) * p[j + 1][1] + (p1[i][0] - p[j + 1][0]) * p[j][1]) / (p[j][0] - p[j + 1][0]); if (p1[i][1] < pa) f = 1; if (f && p1[i][1] >= pa) { p = p.slice(j - 1, -1); break; } p1[i][1] = Math.max(p1[i][1], pa); } } // 歯先の中心 p1.unshift([p1[0][0], (Math.PI / z) * (1 / 2)]); // 歯底のフィレット半径 if (true) { for (let i = 0; i < p1.length; i++) { const rr = m / 3; // フィレット半径 if (p1[i][0] < rf + rr) { p1[i][1] += -(rr / rf) * (1 - Math.sqrt(Math.max(0, 1 - ((rf + rr - p1[i][0]) / rr) ** 2))); } } } // 歯底の中心 p1.push([rf, ((-Math.PI / z) * 1) / 2]); // 表示する for (let j = 0; j < z; j++) { let dd = ((2 * Math.PI) / z) * j; let dd2 = ((2 * Math.PI) / z) * (1 / 2 + j); for (let i = 0; i < p1.length - 1; i++) { sk.line( ox + p1[i][0] * Math.cos(d + p1[i][1] + dd), oy + p1[i][0] * Math.sin(d + p1[i][1] + dd), ox + p1[i + 1][0] * Math.cos(d + p1[i + 1][1] + dd), oy + p1[i + 1][0] * Math.sin(d + p1[i + 1][1] + dd) ); sk.line( ox + p1[i][0] * Math.cos(d - p1[i][1] + dd2), oy + p1[i][0] * Math.sin(d - p1[i][1] + dd2), ox + p1[i + 1][0] * Math.cos(d - p1[i + 1][1] + dd2), oy + p1[i + 1][0] * Math.sin(d - p1[i + 1][1] + dd2) ); } } }
歯車の形状†
参考: https://qiita.com/chromia/items/629311346c80dfd0eac7
形状を指定するパラメータ†
歯車は歯のピッチと歯の枚数、そして、歯の肉厚によって形が決まる。
- モジュール (歯のピッチを $\pi$ で割った値) $m$
- 歯の数 $z$
- 圧力角(歯の肉厚を決める) $\alpha$
歯のピッチは円周の $1/z$ だから歯車の直径 $d$ として $\pi d/z$
ここから $mz=d$ の関係が出る
圧力角†
- 圧力角が小さいと歯と歯の滑りが小さくなり摩擦が小さくなるが、
歯車が小さいときに接触時間が短くなる。 - 圧力角が大きいと歯と歯の滑りが大きくなり摩擦が大きくなるが、
歯車が小さいときも接触時間は長く取れる。
圧力角は 20°とするのが一般的だそう。
基準円とその他の円†
上の $d$ は「基準円」の直径であり、他の直径と区別するため以下では $d_p$ と書く。
- かみ合う2つの歯車は基準円同士がちょうど接する
- 基準円の上で歯の厚さはピッチの半分になり、残りの半分が隙間になる
- 基準円の上で歯の外形線と半径とがなす角が圧力角 $\alpha$ である
その他に重要な直径として以下がある。
- 歯先円(歯の先端) $d_k=d_p+2m$
- 基礎円(歯の外形と半径とが接する) $d_b=d_p\cos \alpha$
- 歯底円(歯間の底) $d_f=d_p-2.5m$
基礎円の外側の歯形†
基礎円より外側の歯の形状はインボリュート曲線とすることが多いらしい。 この曲線は外形線が基礎円と交わる点を基準とした極座標 $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; }
基準円の内側の歯形†
特にこだわらないのであれば基礎円より下はまっすぐ中心へ下ろせばよいのであるが、より丈夫にする為に歯の根元にフィレットを付けることも多い。フィレットは直径 $d_f+m$ あたりから緩やかに歯底円へ落とす。
歯数の少ない歯車では†
歯数が少ない歯車、具体的には歯数が 18 未満になると、相手の歯数が大きいときに基準円より下で相手の歯先と干渉してしまう。
相手の歯数が大きいほど干渉は大きくなるため、相手の歯数が無限大でも大丈夫な形で「逃げ」を作ろう。歯数無限大の歯車はラックであるから、以下ではラック&ピニオンでの状況を考える。ラックの歯の角度は圧力角 $\alpha$ そのもので、$r_p$ に相当する高さではやはり歯の幅はピッチの半分になる。高さは基準円から上が $m$、下が $1.25m$ である。
ラックの歯先が通る直線はピニオンの中心から $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]; };
はすば歯車†
はすば歯車は歯が傾いた歯車。
歯がと歯が斜めに当たるため、軸方向に力を生じてしまうけれど、強度が高くなったり静かになったりと、いろいろ性能がいいそうだ。
はすばの傾き角 $\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_\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}$$
とすればよい。