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

更新


工作/歯車について勉強する

歯車について勉強するシリーズ

目次

歯車設計に必要なさまざまな計算を行います。

歯車を描く

LANG: p5js_live
   // src/initializeControls.ts
   var elements = [];
   function initializeControls(p2) {
     const checkHide = myCreateCheckbox(p2, "ツールを非表示", -1.5, -0.2);
     const controls = {
       m: myCreateSlider(p2, "モジュール", 0, {
         min: 10,
         max: 200,
         value: 80,
         step: 1
       }),
       alpha: myCreateSlider(p2, "圧力角", 1, {
         min: 10,
         max: 32,
         value: 20,
         step: 1
       }),
       z: myCreateSlider(p2, "歯数A", 2, { min: 4, max: 200, value: 19, step: 1 }),
       shift: myCreateSlider(p2, "転位A", 3, {
         min: -0.5,
         max: 2,
         value: 0,
         step: 0.05
       }),
       z2: myCreateSlider(p2, "歯数B", 4, { min: 4, max: 200, value: 6, step: 1 }),
       shift2: myCreateSlider(p2, "転位B", 5, {
         min: -0.5,
         max: 2,
         value: 0,
         step: 0.05
       }),
       fillet: myCreateSlider(p2, "フィレット", 6, {
         min: 0,
         max: 0.4,
         value: 0.4,
         step: 0.01
       }),
       backlash: myCreateSlider(p2, "バックラッシュ", 7, {
         min: -5,
         max: 5,
         value: 0,
         step: 0.02
       }),
       velocity: myCreateSlider(p2, "速度", 8, {
         min: -1,
         max: 10,
         value: 1,
         step: 0.1
       }),
       theta: myCreateSlider(p2, "回転角", 9, {
         min: -180,
         max: 180,
         value: 0,
         step: 0.05
       }),
       df: myCreateCheckbox(p2, "歯底円", 10.5, 0, false),
       db: myCreateCheckbox(p2, "基礎円", 10.5, 1, false),
       dp: myCreateCheckbox(p2, "基準円", 10.5, 2, true),
       dk: myCreateCheckbox(p2, "歯先円", 10.5, 3, false),
       play: myCreateCheckbox(p2, "自動更新", 11.5, 0, true),
       connection: myCreateCheckbox(p2, "接続点", 11.5, 1, false),
       inner: myCreateCheckbox(p2, "内歯車", 11.5, 2, false),
       stop: false
     };
     elements.forEach((tool, i) => {
       tool.style("opacity", "0.2");
       tool.mouseOver(() => elements.forEach((t) => t.style("opacity", "1")));
       tool.mouseOut(() => elements.forEach((t) => t.style("opacity", "0.2")));
     });
     controls.theta.mousePressed(() => {
       controls.stop = true;
     });
     controls.theta.mouseReleased(() => {
       controls.stop = false;
     });
     checkHide.elt.onchange = () => {
       elements.forEach((tool, i) => {
         if (i < 2) return;
         if (checkHide.checked()) {
           tool.hide();
         } else {
           tool.show();
         }
       });
     };
     return {
       get m() {
         return Number(controls.m.value());
       },
       get alpha() {
         return Number(controls.alpha.value()) / 180 * Math.PI;
       },
       get z() {
         return Number(controls.z.value());
       },
       get shift() {
         return Number(controls.shift.value()) * this.m;
       },
       get z2() {
         return Number(controls.z2.value());
       },
       get shift2() {
         return Number(controls.shift2.value()) * this.m;
       },
       get theta() {
         return -(Number(controls.theta.value()) / 180) * Math.PI;
       },
       set theta(value) {
         controls.theta.value(-value * 180 / Math.PI);
         controls.theta.elt.oninput();
       },
       get fillet() {
         return Number(controls.fillet.value()) * this.m;
       },
       get mk() {
         return 1 * this.m;
       },
       get mf() {
         return 1.25 * this.m;
       },
       get backlash() {
         return Number(controls.backlash.value()) * Math.PI * this.m / 100;
       },
       get play() {
         return controls.play.checked() && !controls.stop;
       },
       get velocity() {
         return Number(controls.velocity.value()) / 180 * Math.PI;
       },
       get df() {
         return controls.df.checked();
       },
       get db() {
         return controls.db.checked();
       },
       get dp() {
         return controls.dp.checked();
       },
       get dk() {
         return controls.dk.checked();
       },
       get connection() {
         return controls.connection.checked();
       },
       get inner() {
         return controls.inner.checked();
       },
       incrementTheta() {
         if (this.theta - this.velocity < -Math.PI) {
           this.theta += -this.velocity + 2 * Math.PI;
         } else {
           this.theta += -this.velocity;
         }
       }
     };
   }
   function myCreateSlider(p2, label, i, option) {
     let x = 20, y = i * 20 + 30;
     const span = p2.createSpan(label).position(x, y);
     const value = p2.createSpan(String(option.value));
     value.position(x + 80, y);
     const slider = p2.createSlider(
       option.min,
       option.max,
       option.value,
       option.step
     );
     slider.position(x + 130, y);
     slider.size(300);
     slider.elt.oninput = () => value.html(String(slider.value()));
     elements.push(span, value, slider);
     return slider;
   }
   function myCreateCheckbox(p2, label, i, j = 0, checked = false) {
     let x = j * 100 + 20, y = i * 20 + 30;
     const checkbox = p2.createCheckbox();
     checkbox.position(x, y);
     checkbox.checked(checked);
     const span = p2.createSpan(label).position(x + 20, y);
     span.mouseClicked(() => {
       checkbox.checked(!checkbox.checked());
       if (checkbox.elt.onchange) {
         checkbox.elt.onchange();
       }
     });
     span.style("cursor", "pointer ");
     elements.push(span, checkbox);
     return checkbox;
   }
 
   // src/vector.ts
   var Vector = class _Vector {
     x;
     y;
     constructor(x, y) {
       this.x = x;
       this.y = y;
     }
     static polar(r, theta) {
       return new _Vector(r * Math.cos(theta), r * Math.sin(theta));
     }
     angle() {
       return Math.atan2(this.y, this.x);
     }
     equal(v) {
       const epsilon = 1e-6;
       return Math.abs(this.x - v.x) < epsilon && Math.abs(this.y - v.y) < epsilon;
     }
     add(v) {
       return new _Vector(this.x + v.x, this.y + v.y);
     }
     sub(v) {
       return new _Vector(this.x - v.x, this.y - v.y);
     }
     mul(scalar) {
       return new _Vector(this.x * scalar, this.y * scalar);
     }
     rotate(angle) {
       const cos = Math.cos(angle);
       const sin = Math.sin(angle);
       return new _Vector(this.x * cos - this.y * sin, this.x * sin + this.y * cos);
     }
     addRotated(angle, v) {
       return this.add(v.rotate(angle));
     }
     flipY() {
       return new _Vector(this.x, -this.y);
     }
     flipX() {
       return new _Vector(-this.x, this.y);
     }
     norm() {
       return Math.sqrt(this.x * this.x + this.y * this.y);
     }
     normalize(length = 1) {
       const len = this.norm() / length;
       return new _Vector(this.x / len, this.y / len);
     }
     polar() {
       return [this.norm(), this.angle()];
     }
     inner(v) {
       return this.x * v.x + this.y * v.y;
     }
     outer(v) {
       return this.x * v.y - this.y * v.x;
     }
   };
   function vec(x, y) {
     return new Vector(x, y);
   }
 
   // src/curve.ts
   var Curve = class _Curve extends Array {
     constructor(point_or_option, ...points) {
       if (point_or_option instanceof Vector) {
         super(point_or_option, ...points);
         this.option = {};
       } else {
         super(...points);
         this.option = point_or_option ?? {};
       }
     }
     // 均等に nPoints 個の点を取り出して新しいカーブとして返す
     static extractPoints(c, nPoints) {
       if (c.length <= nPoints) return c;
       const total = c.totalLength();
       const c2 = new _Curve(c.option);
       let next = 0, len = 0;
       for (let i = 0; i < c.length - 1; i++) {
         if (len >= next) {
           c2.push(c[i]);
           next = total * c2.length / nPoints;
         }
         len += c[i].sub(c[i + 1]).norm();
       }
       c2.push(c[c.length - 1]);
       return c2;
     }
     static circle(r, origin = vec(0, 0), option = {}, n = 100) {
       const c = new _Curve(option);
       for (let i = 0; i <= n; i++) {
         const angle = 2 * Math.PI * i / n;
         c.push(vec(r * Math.cos(angle), r * Math.sin(angle)).add(origin));
       }
       return c;
     }
     option;
     totalLength() {
       let sum = 0;
       for (let i = 0; i < this.length - 1; i++) {
         sum += this[i].sub(this[i + 1]).norm();
       }
       return sum;
     }
     draw(p2, mark = false) {
       if (typeof this.option?.stroke == "undefined") {
         p2.stroke("black");
       } else {
         p2.stroke(this.option.stroke);
       }
       if (typeof this.option?.weight == "undefined") {
         p2.strokeWeight(1);
       } else {
         p2.strokeWeight(this.option.weight);
       }
       if (typeof this.option?.dash == "undefined") {
         p2.drawingContext.setLineDash([]);
       } else {
         p2.drawingContext.setLineDash(this.option.dash);
       }
       if (typeof this.option?.fill == "undefined") {
         p2.noFill();
         p2.beginShape();
         for (let i = 0; i < this.length; i++) {
           p2.vertex(this[i].x, this[i].y);
         }
         p2.endShape();
       } else {
         p2.fill(this.option.fill);
         p2.beginShape();
         for (let i = 0; i < this.length; i++) {
           p2.vertex(this[i].x, this[i].y);
         }
         p2.endShape(p2.CLOSE);
       }
       if (mark) {
         this.forEach((v) => p2.circle(v.x, v.y, 1.5));
       }
     }
     apply(f, option = this.option) {
       return new _Curve(option, ...this.map(f));
     }
     // c1, c2 は2点のみを含む線分
     crossPoint(c2) {
       return crossPoint(this[0], this[1], c2[0], c2[1]);
     }
     direction(i = 0) {
       return this[i].sub(this[i + 1]).normalize();
     }
   };
   function crossPoint(p1, p2, q1, q2) {
     const a = p2.sub(p1);
     const b = q2.sub(q1);
     const c = q1.sub(p1);
     const d = a.outer(b);
     if (d == 0) return;
     const s = c.outer(a) / d;
     const t = c.outer(b) / d;
     if (s < 0 || s > 1 || t < 0 || t > 1) return;
     return p1.add(a.mul(t));
   }
   function crossPointXDesc(c1, c2) {
     let i = 0, j = 0;
     while (i < c1.length - 1 && j < c2.length - 1) {
       if (c1[i + 1].x > c2[j].x) {
         i++;
         continue;
       }
       if (c1[i].x < c2[j + 1].x) {
         j++;
         continue;
       }
       const p2 = crossPoint(c1[i], c1[i + 1], c2[j], c2[j + 1]);
       if (p2) return [p2, i, j];
       if (c1[i + 1].x > c2[j + 1].x) {
         i++;
       } else {
         j++;
       }
     }
     return [];
   }
   function distanceFromLine(p2, q1, q2) {
     const a = q2.sub(q1);
     const b = p2.sub(q1);
     return Math.abs(a.outer(b) / a.norm());
   }
   function closestPointXDesc(c1, c2) {
     let i = 0, j = 0, minimum = [Infinity];
     while (i < c1.length - 1 && j < c2.length - 1) {
       if (c1[i + 1].x > c2[j].x) {
         i++;
         continue;
       }
       if (c1[i].x < c2[j + 1].x) {
         j++;
         continue;
       }
       if (c1[i].x < c2[j].x) {
         const d = distanceFromLine(c1[i], c2[j], c2[j + 1]);
         if (d < minimum[0]) minimum = [d, i, j, 0];
       } else {
         const d = distanceFromLine(c2[j], c1[i], c1[i + 1]);
         if (d < minimum[0]) minimum = [d, i, j, 1];
       }
       if (c1[i + 1].x > c2[j + 1].x) {
         i++;
       } else {
         j++;
       }
     }
     if (minimum.length == 1) {
       if (j < c2.length - 1) {
         const d = distanceFromLine(c1[i], c2[j], c2[j + 1]);
         return [d, i, j, 0];
       } else {
         const d = distanceFromLine(c2[j], c1[i], c1[i + 1]);
         return [d, i, j, 1];
       }
     }
     return minimum;
   }
 
   // src/rack.ts
   function rackCurves(params) {
     let { m, z, alpha, shift, fillet, mf, mk, backlash } = params;
     const rp = z * m / 2;
     const { sin, cos, tan, min, max, PI } = Math;
     const rc = 0.25 * m;
     let r1 = vec(rp + shift + mk, mk * tan(alpha) + backlash / 2);
     let r2 = vec(rp + shift - (mf - rc), -(mf - rc) * tan(alpha) + backlash / 2);
     let r3 = vec(rp + shift - mf, -mf * tan(alpha) + backlash / 2);
     const fillet1 = rc / (1 - sin(alpha));
     fillet = min(fillet, fillet1);
     let c = r3.add(vec(fillet, -fillet / tan((PI / 2 + alpha) / 2)));
     if (c.y < -PI * m / 4) {
       c = c.add(r3.sub(c).mul((-PI * m / 4 - c.y) / (r3.y - c.y)));
       fillet = c.x - r3.x;
     }
     return {
       r1,
       r2,
       filletCenter: c,
       filletRadius: fillet
     };
   }
 
   // src/function.ts
   function minimize(l, r, f, epsilon = 1e-6) {
     while (l + epsilon < r) {
       const m1 = l + (r - l) / 3;
       const m2 = r - (r - l) / 3;
       const f1 = f(m1);
       const f2 = f(m2);
       if (f1 < f2) {
         r = m2;
       } else {
         l = m1;
       }
     }
     return (l + r) / 2;
   }
   function root(l, r, f, epsilon = 1e-6) {
     let fl = f(l);
     let fr = f(r);
     if (fl * fr > 0) {
       return Math.abs(fl) < Math.abs(fr) ? l : r;
     }
     if (fl > fr) [l, r] = [r, l];
     while (Math.abs(l - r) > epsilon) {
       const m = (l + r) / 2;
       if (f(m) < 0) {
         l = m;
       } else {
         r = m;
       }
     }
     return (l + r) / 2;
   }
 
   // src/gear.ts
   function gearCurve(params) {
     params = { ...params };
     let { m, z } = params;
     if (params.inner) {
       [params.mk, params.mf] = [params.mf, params.mk];
       params.fillet = 0;
     }
     let rp = m * z / 2;
     let { r1, r2, filletCenter, filletRadius } = rackCurves(params);
     const shiftRotate = (t, p2) => p2.add(vec(0, -rp * t)).rotate(t);
     const dShiftRotate = (t, p2) => {
       const { sin, cos, PI } = Math;
       return p2.rotate(t + PI / 2).add(
         vec(
           rp * (sin(t) + t * cos(t)),
           //
           rp * (-cos(t) + t * sin(t))
         )
       );
     };
     const rackTrace = (t) => {
       const p1 = shiftRotate(t, r1);
       const Dp = shiftRotate(t, r2).sub(p1);
       const dp1 = dShiftRotate(t, r1);
       const dDp = dShiftRotate(t, r2).sub(dp1);
       const s = dp1.outer(Dp) / dDp.outer(Dp);
       return [p1.sub(Dp.mul(s)), s];
     };
     const filletTrace = (t) => {
       const p0 = shiftRotate(t, filletCenter);
       const v1 = dShiftRotate(t, filletCenter).normalize(filletRadius);
       const p2 = p0.addRotated(-Math.PI / 2, v1);
       const p3 = p0.addRotated(Math.PI / 2, v1);
       return p2.x - p2.y < p3.x - p3.y ? p2 : p3;
     };
     let { cInvolute, involuteT } = calculateInvoluteCurve(params, rackTrace);
     let cFillet = new Curve();
     let filletT = [];
     if (!params.inner) {
       [cFillet, filletT] = calculateFilletCurve(
         params,
         shiftRotate,
         filletTrace,
         filletCenter,
         filletRadius,
         r2
       );
       ({ cInvolute, involuteT, cFillet, filletT } = combineCurvesAtIntersection(
         cInvolute,
         involuteT,
         rackTrace,
         cFillet,
         filletT,
         filletTrace
       ));
     }
     return assembleGearCurves(params, cInvolute, cFillet);
   }
   function calculateInvoluteCurve(params, rackTrace) {
     const { alpha, mk, mf, shift, z, m, backlash } = params;
     const rp = m * z / 2;
     const { abs, PI } = Math;
     let involuteS, involuteE, involuteT = [];
     const involuteN = 300;
     involuteS = minimize(
       0,
       90 - alpha,
       (t) => abs(rackTrace(t)[0].norm() - rp - mk - shift)
     );
     if (rackTrace(involuteS)[0].angle() > PI / z / 2) {
       involuteS = minimize(
         0,
         involuteS,
         (t) => abs(rackTrace(t)[0].angle() - PI / z / 2)
       );
     }
     involuteE = minimize(0, -2 * alpha, (t) => rackTrace(t)[0].norm());
     if (rackTrace(involuteE)[0].norm()) {
       involuteE = root(0, involuteE, (t) => rackTrace(t)[0].norm() - (rp - mf + shift));
     }
     let cInvolute = new Curve();
     for (let i = 0; i < involuteN; i++) {
       const t = involuteS + (involuteE - involuteS) * i / involuteN;
       cInvolute.push(rackTrace(t)[0]);
       involuteT.push(t);
     }
     return { cInvolute, involuteT };
   }
   function calculateFilletCurve(params, shiftRotate, filletTrace, center, radius, r2) {
     const { abs, PI, ceil } = Math;
     const { shift, alpha, m, z, mk, backlash } = params;
     const rp = m * z / 2;
     const rk = rp + mk;
     let filletS, filletE, filletD = 0.05;
     for (filletS = 0; ; filletS -= filletD) {
       if (shiftRotate(filletS, center).norm() > rk + shift) break;
     }
     for (filletE = filletD; ; filletE += filletD) {
       if (shiftRotate(filletE, center).norm() > rk + shift) break;
     }
     const filletM = minimize(filletS, filletE, (t) => filletTrace(t).norm());
     if (shiftRotate(filletM, center).norm() > rp) {
       [filletS, filletE] = [filletE, filletS];
     }
     filletE = filletM;
     let filletF = false;
     let last = null;
     let di = 1;
     let filletS2 = filletS;
     let filletN = 60, filletT = [];
     let cFillet = new Curve();
     for (let i = 0; i <= filletN; i += di) {
       const t = filletS + (filletE - filletS) * i / filletN;
       const p2 = filletTrace(t);
       if (!filletF && !(p2.norm() > r2.norm() && p2.sub(r2).angle() < alpha)) {
         filletF = true;
         filletS2 = t;
         if (last) {
           cFillet.push(last);
           filletT.push(t - (filletE - filletS) * di / filletN);
         }
       }
       if (filletF) {
         cFillet.push(p2);
         filletT.push(t);
       }
       const c = shiftRotate(t, center);
       if (i > 0 && c.sub(shiftRotate(filletM, center)).norm() < m / 1e3) {
         const ts = abs(p2.sub(shiftRotate(filletM, center)).angle());
         const te = PI - PI / z / 2;
         const tn = ceil(abs(te - ts) / PI * 180);
         for (let j = 1; j <= tn; j++) {
           const tt = ts + (te - ts) * j / tn;
           cFillet.push(
             shiftRotate(filletM, center).add(Vector.polar(radius, tt))
           );
         }
         break;
       }
       if (i > 0 && p2.sub(last).norm() > m / 20) di /= 2;
       last = p2;
     }
     filletS = filletS2;
     return [cFillet, filletT];
   }
   function combineCurvesAtIntersection(cInvolute, involuteT, rackTrace, cFillet, filletT, filletTrace) {
     const [p2, i, j] = crossPointXDesc(cInvolute, cFillet);
     if (p2) {
       cInvolute.splice(i + 1, Infinity, p2);
       involuteT.splice(
         i + 1,
         Infinity,
         minimize(
           involuteT[i],
           involuteT[i + 1],
           (t) => rackTrace(t)[0].sub(p2).norm()
         )
       );
       cFillet.splice(0, j + 1, p2);
       filletT.splice(
         0,
         j + 1,
         minimize(filletT[j], filletT[j + 1], (t) => filletTrace(t).sub(p2).norm())
       );
     } else {
       const [p3, i2, j2, f] = closestPointXDesc(cInvolute, cFillet);
       if (f) {
         cInvolute.splice(i2 + 1, Infinity, cFillet[j2]);
         cFillet.splice(0, j2);
         involuteT.splice(
           i2 + 1,
           Infinity,
           minimize(
             involuteT[i2],
             involuteT[i2 + 1],
             (t) => rackTrace(t)[0].sub(cFillet[j2]).norm()
           )
         );
         filletT.splice(0, j2);
       } else {
         cInvolute.splice(i2 + 1, Infinity);
         cFillet.splice(0, j2 + 1, cInvolute[i2]);
         involuteT.splice(i2 + 1, Infinity);
         filletT.splice(
           0,
           j2 + 1,
           minimize(
             filletT[j2],
             filletT[j2 + 1],
             (t) => filletTrace(t).sub(cInvolute[i2]).norm()
           )
         );
       }
     }
     return { cInvolute, involuteT, cFillet, filletT };
   }
   function assembleGearCurves(params, cInvolute, cFillet) {
     const { m, mk, shift, z, backlash, mf } = params;
     const rk = m * z / 2 + mk;
     const rf = m * z / 2 - mf;
     const { PI } = Math;
     let gear = [];
     const cTop = arcCurve(
       cInvolute[0].norm(),
       PI / z / 2,
       cInvolute[0].angle()
     );
     gear.push(cTop);
     const cInvolute2 = Curve.extractPoints(cInvolute, 30).filter(
       (_, i, array) => i < 2 || i > array.length - 3 || i % 2 == 1
     );
     gear.push(new Curve(cInvolute.option, ...cInvolute2));
     const cFillet2 = Curve.extractPoints(cFillet, 30).filter(
       (_, i, array) => i < 2 || i > array.length - 3 || i % 2 == 1
     );
     gear.push(new Curve(cFillet.option, ...cFillet2));
     if (params.inner) {
       const cBottom = arcCurve(
         cInvolute.at(-1).norm(),
         cInvolute.at(-1).angle(),
         -PI / z / 2
       );
       gear.push(cBottom);
     } else if (cFillet.length > 0) {
       const cBottom = arcCurve(
         rf + shift,
         cFillet[cFillet.length - 1].angle(),
         -PI / z / 2
       );
       gear.push(cBottom);
     }
     gear = gear.map((c) => c.apply((v) => v.rotate(-PI / z / 2)));
     let connections = [];
     connections.push(cInvolute[0].rotate(-PI / z / 2).polar());
     connections.push(
       cInvolute.at(-1).rotate(-PI / z / 2).polar()
     );
     if (cFillet.length > 0) {
       connections.push(
         cFillet.at(-1).rotate(-PI / z / 2).polar()
       );
     }
     let tooth = gear.reduce(
       (a, c) => a.slice(0, -1).concat(c.map((v) => v.polar())),
       []
     );
     tooth = tooth.map((v) => [v[0], -v[1]]).reverse().concat(tooth);
     connections = connections.map((v) => [v[0], -v[1]]).reverse().concat(connections);
     let result = { curve: [...tooth], connections: [...connections] };
     for (let i = 1; i < z; i++) {
       result.curve = result.curve.concat(
         tooth.map((v) => [v[0], v[1] - i * 2 * PI / z])
       );
       result.connections = result.connections.concat(
         connections.map(
           (v) => [v[0], v[1] - i * 2 * PI / z]
         )
       );
     }
     return result;
   }
   function arcCurve(r, st, et, c = vec(0, 0), dt = 0.02, epsilon = 1e-4) {
     const { abs, ceil, max } = Math;
     const result = new Curve();
     if (r * abs(et - st) > epsilon) {
       const nt = max(2, ceil(abs(et - st) / dt));
       for (let i = 0; i <= nt; i++) {
         const t = st + (et - st) * i / nt;
         result.push(c.add(Vector.polar(r, t)));
       }
     }
     return result;
   }
 
   // src/gears_cache.ts
   var prevParams = null;
   var gear1 = null;
   var gear2 = null;
   function cachedGears(params) {
     let changedKeys = prevParams !== null ? getChangedKeys(params, prevParams) : [];
     if (!gear1 || changedKeys.filter((k) => !["m2", "shift2"].includes(k)).length > 0) {
       gear1 = gearCurve({ ...params, backlash: params.inner ? -params.backlash : params.backlash });
     }
     if (!gear2 || changedKeys.filter((k) => !["m", "shift", "inner"].includes(k)).length > 0) {
       gear2 = gearCurve({ ...params, z: params.z2, shift: params.shift2, inner: false });
     }
     prevParams = pickParams(params);
     return { gear1, gear2 };
   }
   function objectPick(obj, keys) {
     return keys.reduce((acc, key) => {
       acc[key] = obj[key];
       return acc;
     }, {});
   }
   function objectKeys(obj) {
     return Object.keys(obj);
   }
   function getChangedKeys(current, prev) {
     return objectKeys(prev).filter((key) => current[key] !== prev[key]);
   }
   function pickParams(params) {
     return objectPick(params, [
       "m",
       "z",
       "alpha",
       "shift",
       "z2",
       "shift2",
       "fillet",
       "backlash",
       "inner"
     ]);
   }
 
   // src/main.ts
   var sketch = (p2) => {
     let params;
     const canvas = {
       width: 1200,
       height: 1200,
       ox: 0,
       oy: 0
     };
     p2.setup = () => {
       const { width, height } = canvas;
       canvas.ox = width / 2;
       canvas.oy = height / 2;
       const c = p2.createCanvas(width, height);
       c.style("position", "relative");
       c.style("max-width", "100%");
       c.style("height", "auto");
       params = initializeControls(p2);
     };
     p2.draw = () => {
       const { ox, oy, width, height } = canvas;
       p2.fill(220);
       p2.rect(0, 0, width, height);
       const { gear1: gear12, gear2: gear22 } = cachedGears(params);
       if (params.play) params.incrementTheta();
       const { m, z, z2, shift, shift2, inner, theta, connection, alpha } = params;
       const [rp, rp2] = [m * z / 2, m * z2 / 2];
       const { PI, cos, tan, abs } = Math;
       const inv = (a) => tan(a) - a;
       const inverse_inv = (inva2) => {
         if (inva2 == 0) return 0;
         if (inva2 < 0) return -inverse_inv(-inva2);
         let a = inva2 > 2.4 ? Math.atan(inva2) : 1.441 * inva2 ** (1 / 3) - 0.374 * inva2;
         let a_prev = 2;
         for (; ; ) {
           let tana = tan(a);
           a += (inva2 - tana + a) / tana ** 2;
           if (abs(a_prev - a) < 1e-15) return a;
           a_prev = a;
         }
       };
       const s = inner ? -1 : 1;
       const a0 = m * (z + s * z2) / 2;
       const inva = tan(alpha) * (shift + s * shift2) / a0 + inv(alpha);
       const aw = inverse_inv(inva);
       let da = a0 * (cos(alpha) / cos(aw) - 1);
       if (inner) da = abs(da);
       if (!inner) {
         drawGear(p2, gear12, ox - rp - da / 2, oy, -theta / z, connection, 0);
         drawCircles(p2, ox - rp - da / 2, oy, params);
       } else {
         const innerR = rp + m * 5 / 2;
         const thetaI = (theta + PI) / z + PI;
         drawGear(p2, gear12, ox + rp + da / 2, oy, thetaI, connection, innerR);
         drawCircles(p2, ox + rp + da / 2, oy, params);
       }
       const theta2 = (theta + PI) / z2 + PI;
       drawGear(p2, gear22, ox + rp2 + s * da / 2, oy, theta2, connection);
       const params2 = { ...params, z: z2, shift: shift2 };
       drawCircles(p2, ox + rp2 + s * da / 2, oy, params2);
     };
     const drawGear = (p3, gear, ox, oy, dt = 0, connection = false, innerR = 0) => {
       p3.strokeWeight(1);
       p3.stroke("black");
       p3.fill("rgba(255, 255, 255, 0.5)");
       if (innerR) {
         p3.circle(ox, oy, innerR * 2);
         p3.fill(220);
       }
       p3.beginShape();
       gear.curve.forEach((v) => {
         p3.vertex(
           ox + v[0] * Math.cos(v[1] + dt),
           oy + v[0] * Math.sin(v[1] + dt)
         );
       });
       p3.endShape("close");
       if (connection) {
         gear.connections.forEach((v) => {
           p3.circle(
             ox + v[0] * Math.cos(v[1] + dt),
             oy + v[0] * Math.sin(v[1] + dt),
             2
           );
         });
       }
     };
     const drawCircles = (p3, ox, oy, params2) => {
       const { df, db, dp, dk, m, z, mk, mf, alpha, shift } = params2;
       p3.strokeWeight(1);
       p3.stroke("Blue");
       p3.noFill();
       p3.drawingContext.setLineDash([2, 6]);
       if (df) p3.circle(ox, oy, (m * z / 2 - mf + shift) * 2);
       if (db) p3.circle(ox, oy, m * z * Math.cos(alpha));
       if (dp) p3.circle(ox, oy, m * z);
       if (dp && shift) p3.circle(ox, oy, m * z + shift * 2);
       if (dk) p3.circle(ox, oy, (m * z / 2 + mk + shift) * 2);
       p3.drawingContext.setLineDash([]);
     };
   };
 
   sketch(p);

内歯車について:

  • インボリュート曲線部分のみで歯形としているため歯数の少ない場合に歯先円まで届かない(実質的な歯先円が大きくなる)
  • フィレットは付けていない
  • 転位の値によって表示されない

はすば歯車計算(転位係数から中心間距離)

はすば歯車に転位係数を入力して中心間距離を求められます。

3Dプリンタを使う場合は目的の中心間距離に合わせて変則的なモジュールを選ぶことが可能なので、 「目標中心間距離」を入れて「調整後モジュール」を計算する機能も付けました。

基本的には KHK 小原歯車工業さんの 歯車技術資料 にある、
https://www.khkgears.co.jp/gear_technology/pdf/gijutu.pdf#page=23 の計算を行っています。

LANG:p5js_live
const { tan, cos, sin, PI, atan, abs } = Math

// ====================================================== ここからが設定

const titles = ["項目名", "記号", "計算式", "歯車1", "歯車2"]

const definitions = [
  // 入力
  ["mn", "歯垂直モジュール", "m_n", "", [3], 1],
  ["alphan", "歯直角圧力角", "\\alpha_n", "", [20], PI / 180],
  ["beta", "基準円筒ねじれ角", "\\beta", "", [30], PI / 180],
  ["z", "歯数", "z", "", [12, 60], 1],
  ["xn", "歯直角転位係数", "x_n", "", [0.09809, 0], 1], // 出力
  [
    "alphat",
    "正面圧力角",
    "\\alpha_t",
    "\\tan^{-1}\\Big(\\frac{\\tan\\alpha_n}{\\cos\\beta}\\Big)",
    ["?"],
    PI / 180,
  ],
  [
    "invawt",
    "インボリュートαwt",
    "\\mathrm{inv}\\,\\alpha_{wt}",
    "2\\tan\\alpha_n\\big(\\frac{x_{n1}+x_{n2}}{z_1+z_2}\\big)+\\mathrm{inv}\\,\\alpha_t",
    ["?"],
    1,
  ],
  [
    "alphawt",
    "正面噛み合い圧力角",
    "\\alpha_{wt}",
    "\\mathrm{inv}^{-1}\\,(\\mathrm{inv}\\,\\alpha_{wt})",
    ["?"],
    PI / 180,
  ],
  [
    "y",
    "中心距離修正係数",
    "y",
    "\\frac{z_1+z_2}{2\\cos\\beta}\\Big(\\frac{\\cos\\alpha_t}{\\cos\\alpha_{wt}}-1\\Big)",
    ["?"],
    1,
  ],
  [
    "a",
    "中心距離",
    "a",
    "\\Big(\\frac{z_1+z_2}{2\\cos\\beta}+y\\Big)m_n",
    ["?"],
    1,
  ],
  ["d", "基準円直径", "d", "\\frac{zm_n}{\\cos\\beta}", ["?", "?"], 1],
  ["db", "基礎円直径", "d_b", "d\\cos\\alpha_t", ["?", "?"], 1],
  [
    "dw",
    "噛合ピッチ円直径",
    "d_w",
    "\\frac{d_b}{\\cos\\alpha_{wt}}",
    ["?", "?"],
    1,
  ],
  [
    "ha",
    "歯末の丈",
    "h_a",
    "\\begin{aligned}h_{a1}=(1+y-x_{n2})m_n\\\\h_{a2}=(1+y-x_{n1})m_n\\end{aligned}",
    ["?", "?"],
    1,
  ],
  ["h", "歯丈", "h", "\\{2.25+y-(x_{n1}+x_{n2})\\}m_n", ["?"], 1],
  ["da", "歯先円直径", "d_a", "d+2h_a", ["?", "?"], 1],
  ["df", "歯底円直径", "d_f", "d_a-2h", ["?", "?"], 1],
  ["ad", "目標中心距離", "a_d", "", [130], 1],
  ["mm", "調整後モジュール", "m_m", "m_n a_d / a", ["?"], 1],
]

function recalc() {
  const c = controls
  c.alphat = atan(tan(c.alphan) / cos(c.beta))
  c.invawt =
    (2 * tan(c.alphan) * (c.xn1 + c.xn2)) / (c.z1 + c.z2) + inv(c.alphat)
  c.alphawt = inverse_inv(c.invawt)
  c.y =
    ((c.z1 + c.z2) / (2 * cos(c.beta))) * (cos(c.alphat) / cos(c.alphawt) - 1)
  c.a = ((c.z1 + c.z2) / (2 * cos(c.beta)) + c.y) * c.mn
  c.d1 = (c.z1 * c.mn) / cos(c.beta)
  c.d2 = (c.z2 * c.mn) / cos(c.beta)
  c.db1 = c.d1 * cos(c.alphat)
  c.db2 = c.d2 * cos(c.alphat)
  c.dw1 = c.db1 / cos(c.alphawt)
  c.dw2 = c.db2 / cos(c.alphawt)
  c.ha1 = (1 + c.y - c.xn2) * c.mn
  c.ha2 = (1 + c.y - c.xn1) * c.mn
  c.h = (2.25 + c.y - (c.xn1 + c.xn2)) * c.mn
  c.da1 = c.d1 + 2 * c.ha1
  c.da2 = c.d2 + 2 * c.ha2
  c.df1 = c.da1 - 2 * c.h
  c.df2 = c.da2 - 2 * c.h
  c.mm = (c.mn * c.ad) / c.a
}

// インボリュート関数
function inv(x) {
  return tan(x) - x
}

// インボリュート関数の逆関数
function inverse_inv(inva) {
  if (inva == 0) return 0
  if (inva < 0 || 20 < inva) return NaN
  let a = inva < 2.4 ? 1.441 * inva ** (1 / 3) - 0.374 * inva : 1.48
  let a_prev = 2
  for (;;) {
    const tana = tan(a)
    a += (inva - tana + a) / tana ** 2
    if (abs(a_prev - a) < 1e-15) return a
    a_prev = a
  }
}

// UI の値を読み書きするためのオブジェクト
let controls = {}

// HTML 要素を生成するための関数
function elem(tag, html = "", children = [], props = {}, events = {}) {
  const result = document.createElement(tag)
  if (html != "") result.innerHTML = html
  children.forEach((c) => result.appendChild(c))
  Object.entries(props).forEach(([k, v]) => {
    if (typeof v == "string") {
      result[k] = v
    } else {
      Object.entries(v).forEach(([k2, v2]) => (result[k][k2] = v2))
    }
  })
  Object.entries(events).forEach(([k, v]) => result.addEventListener(k, v))
  return result
}

function katex2html(s) {
  return katex.renderToString(s, { throwOnError: false })
}

function definition2html(def) {
  const [name, label, notation, expression, values, scale] = def
  const createControl = (n, v) => {
    let e
    if (expression != "") {
      e = elem("SPAN", v)
      Object.defineProperty(controls, name + n, {
        get: () => e.innerHTML * scale,
        set: (v) => (e.innerHTML = (v / scale).toPrecision(6)),
      })
    } else {
      e = elem(
        "INPUT",
        "",
        [],
        {
          value: String(v),
          style: {
            textAlign: "right",
            paddingRight: "0.2em",
            width: "4em",
          },
        },
        { change: recalc },
      )
      Object.defineProperty(controls, name + n, {
        get: () => e.value * scale,
        set: (v) => (e.value = (v / scale).toPrecision(6)),
      })
    }
    return e
  }
  const values2controls = () =>
    values.length == 1
      ? [
          elem("TD", "", [createControl("", values[0])], {
            colSpan: "2",
            style: { textAlign: "center" },
          }),
        ]
      : [
          elem("TD", "", [createControl("1", values[0])]),
          elem("TD", "", [createControl("2", values[1])]),
        ]
  const backgroundColor = expression != "" ? "#ffe" : "#eff"
  return elem(
    "TR",
    "",
    [
      elem("TD", label),
      elem("TD", katex2html(notation)),
      elem("TD", katex2html(expression)),
      ...values2controls(),
    ],
    { style: { backgroundColor } },
  )
}

function setup() {
  let table = elem(
    "TABLE",
    "",
    [
      elem("TBODY", "", [
        elem(
          "TR",
          "",
          titles.map((s) => elem("TH", s)),
        ),
        ...definitions.map(definition2html),
      ]),
    ],
    { border: "1", cellSpacing: "0", cellPadding: "2" },
  )

  // キャンバスを最小化する
  p.createCanvas(1, 1)
  // DIV を作ってテーブルを追加する
  p.createDiv().elt.appendChild(table)
  // 値を更新する
  recalc()
}

p.setup = setup

はすば歯車設計(中心間距離から転位量)

モジュール、歯数、ねじれ角と中心間距離を入れると、2つの歯車を合わせてどれだけ転位すればよいかを算出できる。

求まった「歯直角転位係数和」を2つに分けてそれぞれの歯車に振り分けることで目的の中心間距離で噛み合う2つの歯車を設計できる。

具体的には KHK 小原歯車工業さんの 歯車技術資料 にある、
https://www.khkgears.co.jp/gear_technology/pdf/gijutu.pdf#page=24 の計算を行っているだけです。

LANG: p5js_live
const { tan, cos, acos, PI, atan, abs } = Math

// ====================================================== ここからが設定

const titles = ["項目名", "記号", "計算式", "歯車1", "歯車2"]

const definitions = [
  // 入力
  ["mn", "歯垂直モジュール", "m_n", "", [3], 1],
  ["alphan", "歯直角圧力角", "\\alpha_n", "", [20], PI / 180],
  ["beta", "基準円筒ねじれ角", "\\beta", "", [30], PI / 180],
  ["z", "歯数", "z", "", [12, 60], 1],
  ["a", "中心距離", "a", "", [125], 1], // 出力
  [
    "alphat",
    "正面圧力角",
    "\\alpha_t",
    "\\tan^{-1}\\Big(\\frac{\\tan\\alpha_n}{\\cos\\beta}\\Big)",
    ["?"],
    PI / 180,
  ],
  [
    "y",
    "中心距離修正係数",
    "y",
    "\\frac{a}{m_n}-\\frac{z_1+z_2}{2\\cos\\beta}",
    ["?"],
    1,
  ],
  [
    "alphawt",
    "正面噛み合い圧力角",
    "\\alpha_{wt}",
    "\\cos^{-1}\\Big(\\frac{\\cos\\alpha_t}{\\frac{2y\\cos\\beta}{z_1+z_2}+1}\\Big)",
    ["?"],
    PI / 180,
  ],
  [
    "sumxn",
    "歯直角転位係数和",
    "x_{n1}+x_{n2}",
    "\\frac{(z_1+z_2)(\\mathrm{inv}\\,\\alpha_{wt}-\\mathrm{inv}\\,\\alpha_t)}{2\\tan\\alpha_n}",
    ["?"],
    1,
  ],
  [
    "sumxnmm",
    "転位の和 (距離)",
    "(x_{n1}+x_{n2})m_n",
    "(x_{n1}+x_{n2})m_n",
    ["?"],
    1,
  ],
]

function recalc() {
  const c = controls
  c.alphat = atan(tan(c.alphan) / cos(c.beta))
  c.y = c.a / c.mn - (c.z1 + c.z2) / (2 * cos(c.beta))
  c.alphawt = acos(
    cos(c.alphat) / ((2 * c.y * cos(c.beta)) / (c.z1 + c.z2) + 1),
  )
  c.sumxn =
    ((c.z1 + c.z2) * (inv(c.alphawt) - inv(c.alphat))) / (2 * tan(c.alphan))
  c.sumxnmm = c.sumxn * c.mn
}

// インボリュート関数
function inv(x) {
  return tan(x) - x
}

// インボリュート関数の逆関数
function inverse_inv(inva) {
  if (inva == 0) return 0
  if (inva < 0 || 20 < inva) return NaN
  let a = inva < 2.4 ? 1.441 * inva ** (1 / 3) - 0.374 * inva : 1.48
  let a_prev = 2
  for (;;) {
    const tana = tan(a)
    a += (inva - tana + a) / tana ** 2
    if (abs(a_prev - a) < 1e-15) return a
    a_prev = a
  }
}

// UI の値を読み書きするためのオブジェクト
let controls = {}

// HTML 要素を生成するための関数
function elem(tag, html = "", children = [], props = {}, events = {}) {
  const result = document.createElement(tag)
  if (html != "") result.innerHTML = html
  children.forEach((c) => result.appendChild(c))
  Object.entries(props).forEach(([k, v]) => {
    if (typeof v == "string") {
      result[k] = v
    } else {
      Object.entries(v).forEach(([k2, v2]) => (result[k][k2] = v2))
    }
  })
  Object.entries(events).forEach(([k, v]) => result.addEventListener(k, v))
  return result
}

function katex2html(s) {
  return katex.renderToString(s, { throwOnError: false })
}

function definition2html(def) {
  const [name, label, notation, expression, values, scale] = def
  const createControl = (n, v) => {
    let e
    if (expression != "") {
      e = elem("SPAN", v)
      Object.defineProperty(controls, name + n, {
        get: () => e.innerHTML * scale,
        set: (v) => (e.innerHTML = (v / scale).toPrecision(6)),
      })
    } else {
      e = elem(
        "INPUT",
        "",
        [],
        {
          value: String(v),
          style: {
            textAlign: "right",
            paddingRight: "0.2em",
            width: "4em",
          },
        },
        { change: recalc },
      )
      Object.defineProperty(controls, name + n, {
        get: () => e.value * scale,
        set: (v) => (e.value = (v / scale).toPrecision(6)),
      })
    }
    return e
  }
  const values2controls = () =>
    values.length == 1
      ? [
          elem("TD", "", [createControl("", values[0])], {
            colSpan: "2",
            style: { textAlign: "center" },
          }),
        ]
      : [
          elem("TD", "", [createControl("1", values[0])]),
          elem("TD", "", [createControl("2", values[1])]),
        ]
  const backgroundColor = expression != "" ? "#ffe" : "#eff"
  return elem(
    "TR",
    "",
    [
      elem("TD", label),
      elem("TD", katex2html(notation)),
      elem("TD", katex2html(expression)),
      ...values2controls(),
    ],
    { style: { backgroundColor } },
  )
}

function setup() {
  let table = elem(
    "TABLE",
    "",
    [
      elem("TBODY", "", [
        elem(
          "TR",
          "",
          titles.map((s) => elem("TH", s)),
        ),
        ...definitions.map(definition2html),
      ]),
    ],
    { border: "1", cellSpacing: "0", cellPadding: "2" },
  )

  // キャンバスを最小化する
  p.createCanvas(1, 1)
  // DIV を作ってテーブルを追加する
  p.createDiv().elt.appendChild(table)
  // 値を更新する
  recalc()
}

p.setup = setup

はすば内歯車計算(転位係数から中心間距離)

はすばの場合に対応するため、
https://www.khkgears.co.jp/gear_technology/pdf/gijutu.pdf#page=19
にあった内容にねじれ角の項目を追加たのが以下のものです。

エラーチェックは、

  • 内歯車の基礎円と歯先円の大小関係
  • インボリュート干渉 = 内歯車の歯先が平歯車の歯元と干渉
  • トロコイド干渉 = 内歯車の歯先が平歯車の歯先と干渉

を見ているつもりです。

普通に設計して内歯車の基礎円が歯先円よりも大きくなった場合、 歯先の丈を短くして歯先円を大きく定義しなおせばかみ合い長が短くはなりますが使えないことはないので、 歯先と歯元の丈も指定できるようにしています。

LANG: p5js_live
const { tan, sin, cos, acos, PI, atan, abs } = Math;

const titles = ["項目名", "記号", "計算式", "外歯車", "内歯車"]

const definitions = [
  // 入力
  ["mn", "歯垂直モジュール", "m_n", "", [3], 1],
  ["alphan", "歯直角圧力角", "\\alpha_n", "", [20], PI / 180],
  ["beta", "基準円筒ねじれ角", "\\beta", "", [0], PI / 180],
  ["z", "歯数", "z", "", [16, 24], 1],
  ["xn", "歯直角転位係数", "x_n", "", [0, 0.516], 1],
  ["ca", "歯先の丈", "c_a", "", [1, 1], 1],
  ["cf", "歯元の丈", "c_f", "", [1.25, 1.25], 1], // 出力
  ["error", "エラーチェック", "-", "-", ["?"], 0],
  [
    "alphat",
    "正面圧力角",
    "\\alpha_t",
    "\\tan^{-1}\\big(\\frac{\\tan\\alpha_n}{\\cos\\beta}\\big)",
    ["?"],
    PI / 180,
  ],
  [
    "invawt",
    "インボリュートαwt",
    "\\mathrm{inv}\\,\\alpha_{wt}",
    "2\\tan\\alpha_n\\big(\\frac{x_{n2}-x_{n1}}{z_2-z_1}\\big)+\\mathrm{inv}\\,\\alpha_t",
    ["?"],
    1,
  ],
  [
    "alphawt",
    "正面噛み合い圧力角",
    "\\alpha_{wt}",
    "\\mathrm{inv}^{-1}\\,(\\mathrm{inv}\\,\\alpha_{wt})",
    ["?"],
    PI / 180,
  ],
  [
    "y",
    "中心距離修正係数",
    "y",
    "\\frac{z_2-z_1}{2\\cos\\beta}\\big(\\frac{\\cos\\alpha_t}{\\cos\\alpha_{wt}}-1\\big)",
    ["?"],
    1,
  ],
  [
    "a",
    "中心距離",
    "a",
    "\\big(\\frac{z_2-z_1}{2\\cos\\beta}+y\\big)m_n",
    ["?"],
    1,
  ],
  ["d", "基準円直径", "d", "\\frac{zm_n}{\\cos\\beta}", ["?", "?"], 1],
  ["db", "基礎円直径", "d_b", "d\\cos\\alpha_t", ["?", "?"], 1],
  [
    "dw",
    "噛合ピッチ円直径",
    "d_w",
    "\\frac{d_b}{\\cos\\alpha_{wt}}",
    ["?", "?"],
    1,
  ],
  [
    "ha",
    "歯末の丈",
    "h_a",
    "\\scriptsize\\begin{aligned}h_{a1}=(c_{a1}+x_{n1})m_n\\\\h_{a2}=(c_{a2}-x_{n2})m_n\\end{aligned}",
    ["?", "?"],
    1,
  ],
  ["h", "歯丈", "h", "(c_a+c_f)m_n", ["?", "?"], 1],
  [
    "da",
    "歯先円直径",
    "d_a",
    "\\scriptsize\\begin{aligned}d_{a1}: d_1+2h_{a1}\\\\d_{a2}: d_2-2h_{a2}\\end{aligned}",
    ["?", "?"],
    1,
  ],
  [
    "df",
    "歯底円直径",
    "d_f",
    "\\scriptsize\\begin{aligned}d_{f1}: d_{a1}-2h_1\\\\d_{f2}: d_{a2}+2h_2\\end{aligned}",
    ["?", "?"],
    1,
  ],
  ["ad", "目標中心距離", "a_d", "", [13], 1],
  ["mm", "調整後モジュール", "m_m", "m_n a_d / a", ["?"], 1],
]

function recalc() {
  const c = controls
  c.alphat = atan(tan(c.alphan) / cos(c.beta))
  c.invawt =
    (2 * tan(c.alphan) * (c.xn2 - c.xn1)) / (c.z2 - c.z1) + inv(c.alphat)
  c.alphawt = inverse_inv(c.invawt)
  c.y =
    ((c.z2 - c.z1) / (2 * cos(c.beta))) * (cos(c.alphat) / cos(c.alphawt) - 1)
  c.a = ((c.z2 - c.z1) / (2 * cos(c.beta)) + c.y) * c.mn
  c.d1 = (c.z1 * c.mn) / cos(c.beta)
  c.d2 = (c.z2 * c.mn) / cos(c.beta)
  c.db1 = c.d1 * cos(c.alphat)
  c.db2 = c.d2 * cos(c.alphat)
  c.dw1 = c.db1 / cos(c.alphawt)
  c.dw2 = c.db2 / cos(c.alphawt)
  c.ha1 = (c.ca1 + c.xn1) * c.mn
  c.ha2 = (c.ca2 - c.xn2) * c.mn
  c.h1 = (c.ca1 + c.cf1) * c.mn
  c.h2 = (c.ca2 + c.cf2) * c.mn
  c.da1 = c.d1 + 2 * c.ha1
  c.da2 = c.d2 - 2 * c.ha2
  c.df1 = c.da1 - 2 * c.h1
  c.df2 = c.da2 + 2 * c.h2
  c.mm = (c.mn * c.ad) / c.a

  // エラーチェック
  let error = ""
  const msg = (s, c) =>
    `<font style="color: ${c}; font-weight: bold">${s}</font><br>`
  if (c.db2 > c.da2) {
    error += msg("基礎円 &gt; 歯先円", "red")
  } else {
    ;() => {}
    if (
      c.z1 <= (2 * (c.ha2 / (c.mn / cos(c.beta)))) / sin(c.alphat) ** 2 ||
      c.z2 <
        (c.z1 ** 2 -
          (4 * (c.ha2 / (c.mn / cos(c.beta))) ** 2) / sin(c.alphat) ** 2) /
          (c.z1 - (2 * (c.ha2 / (c.mn / cos(c.beta)))) / sin(c.alphat) ** 2) /
          2 ||
      c.z1 >= c.z2
    ) {
      error += msg("インボリュート干渉", "red")
    }
    ;(() => {
      let alphaa1 = acos(c.db1 / c.da1)
      let alphaa2 = acos(c.db2 / c.da2)
      let theta1 =
        acos(
          (c.da2 ** 2 / 4 - c.da1 ** 2 / 4 - c.a ** 2) /
            ((2 * c.a * c.da1) / 2),
        ) +
        inv(alphaa1) -
        c.invawt
      let theta2 = acos(
        (c.a ** 2 + c.da2 ** 2 / 4 - c.da1 ** 2 / 4) / ((2 * c.a * c.da2) / 2),
      )
      if ((theta1 * c.z1) / c.z2 + c.invawt - inv(alphaa2) < theta2) {
        error += msg("トロコイド干渉", "red")
      }
    })()
  }
  if (error == "") {
    error = msg("問題なし", "green")
  }
  c.error = error
}

// インボリュート関数
function inv(x) {
  return tan(x) - x
}

// インボリュート関数の逆関数
function inverse_inv(inva) {
  if (inva == 0) return 0
  if (inva < 0 || 20 < inva) return NaN
  let a = inva < 2.4 ? 1.441 * inva ** (1 / 3) - 0.374 * inva : 1.48
  let a_prev = 2
  for (;;) {
    const tana = tan(a)
    a += (inva - tana + a) / tana ** 2
    if (abs(a_prev - a) < 1e-15) return a
    a_prev = a
  }
}

// UI の値を読み書きするためのオブジェクト
let controls = {}

// HTML 要素を生成するための関数
function elem(tag, html = "", children = [], props = {}, events = {}) {
  const result = document.createElement(tag)
  if (html != "") result.innerHTML = html
  children.forEach((c) => result.appendChild(c))
  Object.entries(props).forEach(([k, v]) => {
    if (typeof v == "string") {
      result[k] = v
    } else {
      Object.entries(v).forEach(([k2, v2]) => (result[k][k2] = v2))
    }
  })
  Object.entries(events).forEach(([k, v]) => result.addEventListener(k, v))
  return result
}

function katex2html(s) {
  return katex.renderToString(s, { throwOnError: false })
}

function definition2html(def) {
  const [name, label, notation, expression, values, scale] = def
  const createControl = (n, v) => {
    let e
    if (expression != "") {
      e = elem("SPAN", v)
      Object.defineProperty(controls, name + n, {
        get: () => (scale == 0 ? e.innerHTML : e.innerHTML * scale),
        set: (v) => (e.innerHTML = scale == 0 ? v : (v / scale).toPrecision(6)),
      })
    } else {
      e = elem(
        "INPUT",
        "",
        [],
        {
          value: String(v),
          style: {
            textAlign: "right",
            paddingRight: "0.2em",
            width: "4em",
          },
        },
        { change: recalc },
      )
      Object.defineProperty(controls, name + n, {
        get: () => (scale == 0 ? e.value : e.value * scale),
        set: (v) => (e.value = scale == 0 ? v : (v / scale).toPrecision(6)),
      })
    }
    return e
  }
  const values2controls = () =>
    values.length == 1
      ? [
          elem("TD", "", [createControl("", values[0])], {
            colSpan: "2",
            style: { textAlign: "center" },
          }),
        ]
      : [
          elem("TD", "", [createControl("1", values[0])]),
          elem("TD", "", [createControl("2", values[1])]),
        ]
  const backgroundColor = expression != "" ? "#ffe" : "#eff"
  return elem(
    "TR",
    "",
    [
      elem("TD", label),
      elem("TD", katex2html(notation)),
      elem("TD", katex2html(expression)),
      ...values2controls(),
    ],
    { style: { backgroundColor } },
  )
}

function setup() {
  let table = elem(
    "TABLE",
    "",
    [
      elem("TBODY", "", [
        elem(
          "TR",
          "",
          titles.map((s) => elem("TH", s)),
        ),
        ...definitions.map(definition2html),
      ]),
    ],
    { border: "1", cellSpacing: "0", cellPadding: "2" },
  )

  // キャンバスを最小化する
  p.createCanvas(1, 1)
  // DIV を作ってテーブルを追加する
  p.createDiv().elt.appendChild(table)
  // 値を更新する
  recalc()
}

p.setup = setup

インボリュート干渉、トロコイド干渉について

下図は https://www.khkgears.co.jp/gear_technology/pdf/gijutu.pdf#page=19 より引用

internal_interference.png

上記のスクリプトでは標準歯車で基準円が歯先円を上回ってしまうとエラーになる。 しかし、このエラーは歯先円の方を大きくしてやれば回避できる。

歯元や歯先の丈が標準でない場合にも干渉が生じるかどうかをチェックしたかったので その条件式について調べたところ次の文献を見つけました。

インボリュート内歯歯車の干渉 (荻野 修作)
日本機械学會論文集 21 巻 (1955) 110 号 p. 749-755
https://doi.org/10.1299/kikai1938.21.749

これによると、歯末のたけのモジュールに対する比を $\rho_1,\rho_2$ とした場合のインボルート干渉の起きない条件は、

$$ \begin{cases} \displaystyle z_1>2c_{a2}\cosec^2\alpha\\ \displaystyle z_2\ge \frac{z_1^2-4c_{a2}^2\cosec^2\alpha}{2(z_1-2c_{a2}\cosec^2\alpha)}\\ \displaystyle z_2>z_1 \end{cases} $$

で与えられるそうなのでこれを使いました。→ なんか正しく検出できていないかもしれません・・・

ただし、この式が与える条件は相手の小径歯車の切り下げを考慮していないのかもしれなくて(?)、 切り下げの行われた歯車との組み合わせであれば干渉しないのにも関わらず干渉が生じると表示される場合があるようです(?)
切り下げについて参考: https://www.khkgears.co.jp/gear_technology/basic_guide/KHK356_1.html

例えば歯数12の内歯車と歯数6の外歯車の組み合わせをバックラッシュ 0 で作成して組み合わせてみた下図からは、この組み合わせでインボリュート干渉が起きないことを確認できます。これは歯数6の歯車に切り下げが発生している(歯元のトロコイド曲線が基礎円より上まで来ておりインボリュート領域が本来の長さまで到達していない)ためであり、切り下げを行わなければ小歯車のインボリュート曲線と内歯車の歯先が干渉します。判別式は小歯車の切り下げを考慮していないためにこの「仮想的な干渉」を検出してしまうのだと思います。

internal8.png

インボリュート干渉のエラーが出ても、小歯車側の基礎円が切り下げ開始点よりも小さい場合には、 「本当にダメなのか」を確認する余地はあると思った方が良さそうです???

はすば内歯車計算 (中心距離から転位量)

https://www.khkgears.co.jp/gear_technology/pdf/gijutu.pdf#page=24 の計算をします。

はすばでも使えるよう少し変更しています。

LANG:p5js_live
const { tan, cos, acos, PI, atan, abs } = Math

// ====================================================== ここからが設定

const titles = ["項目名", "記号", "計算式", "歯車1", "歯車2"]

const definitions = [
  // 入力
  ["mn", "歯垂直モジュール", "m_n", "", [3], 1],
  ["alphan", "歯直角圧力角", "\\alpha_n", "", [20], PI / 180],
  ["beta", "基準円筒ねじれ角", "\\beta", "", [0], PI / 180],
  ["z", "歯数", "z", "", [16, 24], 1],
  ["a", "中心距離", "a", "", [13.1683], 1], // 出力
  [
    "alphat",
    "正面圧力角",
    "\\alpha_t",
    "\\tan^{-1}\\Big(\\frac{\\tan\\alpha_n}{\\cos\\beta}\\Big)",
    ["?"],
    PI / 180,
  ],
  [
    "y",
    "中心距離修正係数",
    "y",
    "\\frac{a}{m_n}-\\frac{z_2-z_1}{2\\cos\\beta}",
    ["?"],
    1,
  ],
  [
    "alphawt",
    "正面噛み合い圧力角",
    "\\alpha_{wt}",
    "\\cos^{-1}\\Big(\\frac{\\cos\\alpha_t}{\\frac{2y\\cos\\beta}{z_2-z_1}+1}\\Big)",
    ["?"],
    PI / 180,
  ],
  [
    "diffxn",
    "歯直角転位係数差",
    "x_{n2}-x_{n1}",
    "\\frac{(z_2-z_1)(\\mathrm{inv}\\,\\alpha_{wt}-\\mathrm{inv}\\,\\alpha_t)}{2\\tan\\alpha_n}",
    ["?"],
    1,
  ],
  [
    "diffxnmm",
    "転位の差 (距離)",
    "(x_{n1}+x_{n2})m_n",
    "(x_{n1}+x_{n2})m_n",
    ["?"],
    1,
  ],
]

function recalc() {
  const c = controls
  c.alphat = atan(tan(c.alphan) / cos(c.beta))
  c.y = c.a / c.mn - (c.z2 - c.z1) / (2 * cos(c.beta))
  c.alphawt = acos(
    cos(c.alphat) / ((2 * c.y * cos(c.beta)) / (c.z2 - c.z1) + 1),
  )
  c.diffxn =
    ((c.z2 - c.z1) * (inv(c.alphawt) - inv(c.alphat))) / (2 * tan(c.alphan))
  c.diffxnmm = c.diffxn * c.mn
}

// インボリュート関数
function inv(x) {
  return tan(x) - x
}

// インボリュート関数の逆関数
function inverse_inv(inva) {
  if (inva == 0) return 0
  if (inva < 0 || 20 < inva) return NaN
  let a = inva < 2.4 ? 1.441 * inva ** (1 / 3) - 0.374 * inva : 1.48
  let a_prev = 2
  for (;;) {
    const tana = tan(a)
    a += (inva - tana + a) / tana ** 2
    if (abs(a_prev - a) < 1e-15) return a
    a_prev = a
  }
}

// UI の値を読み書きするためのオブジェクト
let controls = {}

// HTML 要素を生成するための関数
function elem(tag, html = "", children = [], props = {}, events = {}) {
  const result = document.createElement(tag)
  if (html != "") result.innerHTML = html
  children.forEach((c) => result.appendChild(c))
  Object.entries(props).forEach(([k, v]) => {
    if (typeof v == "string") {
      result[k] = v
    } else {
      Object.entries(v).forEach(([k2, v2]) => (result[k][k2] = v2))
    }
  })
  Object.entries(events).forEach(([k, v]) => result.addEventListener(k, v))
  return result
}

function katex2html(s) {
  return katex.renderToString(s, { throwOnError: false })
}

function definition2html(def) {
  const [name, label, notation, expression, values, scale] = def
  const createControl = (n, v) => {
    let e
    if (expression != "") {
      e = elem("SPAN", v)
      Object.defineProperty(controls, name + n, {
        get: () => (scale == 0 ? e.innerHTML : e.innerHTML * scale),
        set: (v) => (e.innerHTML = scale == 0 ? v : (v / scale).toPrecision(6)),
      })
    } else {
      e = elem(
        "INPUT",
        "",
        [],
        {
          value: String(v),
          style: {
            textAlign: "right",
            paddingRight: "0.2em",
            width: "4em",
          },
        },
        { change: recalc },
      )
      Object.defineProperty(controls, name + n, {
        get: () => (scale == 0 ? e.value : e.value * scale),
        set: (v) => (e.value = scale == 0 ? v : (v / scale).toPrecision(6)),
      })
    }
    return e
  }
  const values2controls = () =>
    values.length == 1
      ? [
          elem("TD", "", [createControl("", values[0])], {
            colSpan: "2",
            style: { textAlign: "center" },
          }),
        ]
      : [
          elem("TD", "", [createControl("1", values[0])]),
          elem("TD", "", [createControl("2", values[1])]),
        ]
  const backgroundColor = expression != "" ? "#ffe" : "#eff"
  return elem(
    "TR",
    "",
    [
      elem("TD", label),
      elem("TD", katex2html(notation)),
      elem("TD", katex2html(expression)),
      ...values2controls(),
    ],
    { style: { backgroundColor } },
  )
}

function setup() {
  let table = elem(
    "TABLE",
    "",
    [
      elem("TBODY", "", [
        elem(
          "TR",
          "",
          titles.map((s) => elem("TH", s)),
        ),
        ...definitions.map(definition2html),
      ]),
    ],
    { border: "1", cellSpacing: "0", cellPadding: "2" },
  )

  // キャンバスを最小化する
  p.createCanvas(1, 1)
  // DIV を作ってテーブルを追加する
  p.createDiv().elt.appendChild(table)
  // 値を更新する
  recalc()
}

p.setup = setup

標準かさ歯車設計

https://www.khkgears.co.jp/gear_technology/pdf/gijutu.pdf#page=31 の計算をします。

はすばへの対応が中途半端なままです。

LANG:p5js_live
const { tan, sin, cos, PI, atan, abs } = Math

// ====================================================== ここからが設定

const titles = ["項目名", "記号", "計算式", "外歯車", "内歯車"]

const definitions = [
  // 入力
  ["sigma", "軸角", "\\Sigma", "", [90], PI / 180],
  ["m", "外端正面モジュール", "m", "", [3], 1],
  ["alphan", "歯垂直圧力角", "\\alpha_n", "", [20], PI / 180],
  ["z", "歯数", "z", "", [20, 40], 1],
  ["betam", "ねじれ角", "\\beta_m", "", [35], PI / 180],
  [
    "alphat",
    "正面圧力角",
    "\\alpha_t",
    "\\tan^{-1}\\big(\\frac{\\tan\\alpha_n}{\\cos\\beta_m}\\big)",
    ["?"],
    PI / 180,
  ],
  ["d", "基準円直径", "d", "zm", ["?", "?"], 1],
  [
    "delta",
    "基準円錐角",
    "\\begin{aligned}\\delta_1\\\\\\delta_2\\end{aligned}",
    "\\begin{aligned}&\\tan^{-1}\\big({\\scriptsize\\frac{\\sin\\Sigma}{z_2/z_1+\\cos\\Sigma}}\\big)\\\\&\\Sigma-\\delta_1\\end{aligned}",
    ["?", "?"],
    PI / 180,
  ],
  ["r", "円錐距離", "R", "d_2/2\\sin\\delta_2", ["?"], 1],
  ["b", "歯幅", "b", "", [22], 1],
  ["warning", "歯幅警告", "", "b \\le 0.3R\\ \\|\\ b \\le 10m", ["?"], 0],
  ["h", "歯丈", "h", "2.25m", ["?"], 1],
  ["ha", "歯末の丈", "h_a", "1.00m", ["?"], 1],
  ["hf", "歯元の丈", "h_f", "1.25m", ["?"], 1],
  ["thetaf", "歯元角", "\\theta_f", "\\tan^{-1}(h_f/R)", ["?"], PI / 180],
  ["thetaa", "歯末角", "\\theta_a", "\\tan^{^1}(h_a/R)", ["?"], PI / 180],
  [
    "deltaa",
    "歯先円錐角",
    "\\delta_a",
    "\\delta+\\theta_a",
    ["?", "?"],
    PI / 180,
  ],
  [
    "deltaf",
    "歯底円錐角",
    "\\delta_f",
    "\\delta-\\theta_f",
    ["?", "?"],
    PI / 180,
  ],
  ["da", "外端歯先円直径", "d_a", "d+2h_a\\cos\\delta", ["?", "?"], 1],
  [
    "x",
    "頂点から外端歯先",
    "X",
    "R\\cos\\delta-h_a\\sin\\delta",
    ["?", "?"],
    1,
  ],
  [
    "xb",
    "歯先間の軸方向距離",
    "X_b",
    "\\frac{b\\cos\\delta_a}{\\cos\\theta_a}",
    ["?", "?"],
    1,
  ],
  [
    "di",
    "内端歯先円直径",
    "d_i",
    "d_a-\\frac{2b\\sin\\delta_a}{\\cos\\theta_a}",
    ["?", "?"],
    1,
  ],
]

function recalc() {
  const c = controls
  c.alphat = atan(tan(c.alphan) / cos(c.betam))
  c.d1 = c.z1 * c.m
  c.d2 = c.z2 * c.m
  c.delta1 = atan(sin(c.sigma) / (c.z2 / c.z1 + cos(c.sigma)))
  c.delta2 = c.sigma - c.delta1
  c.r = c.d2 / (2 * sin(c.delta2))
  c.warning =
    c.b > 0.3 * c.r && c.b > 10 * c.m
      ? '<b style="color:red">歯幅過大</b>'
      : '<span style="color:green">なし</span>'
  c.h = 2.25 * c.m
  c.ha = 1.0 * c.m
  c.hf = c.h - c.ha
  c.thetaf = atan(c.hf / c.r)
  c.thetaf = atan(c.hf / c.r)
  c.thetaa = atan(c.ha / c.r)
  c.thetaa = atan(c.ha / c.r)
  c.deltaa1 = c.delta1 + c.thetaa
  c.deltaa2 = c.delta2 + c.thetaa
  c.deltaf1 = c.delta1 - c.thetaf
  c.deltaf2 = c.delta2 - c.thetaf
  c.da1 = c.d1 + 2 * c.ha * cos(c.delta1)
  c.da2 = c.d2 + 2 * c.ha * cos(c.delta2)
  c.x1 = c.r * cos(c.delta1) - c.ha * sin(c.delta1)
  c.x2 = c.r * cos(c.delta2) - c.ha * sin(c.delta2)
  c.xb1 = (c.b * cos(c.deltaa1)) / cos(c.thetaa)
  c.xb2 = (c.b * cos(c.deltaa2)) / cos(c.thetaa)
  c.di1 = c.da1 - (2 * c.b * sin(c.deltaa1)) / cos(c.thetaa)
  c.di2 = c.da2 - (2 * c.b * sin(c.deltaa2)) / cos(c.thetaa)
}

// インボリュート関数
function inv(x) {
  return tan(x) - x
}

// インボリュート関数の逆関数
function inverse_inv(inva) {
  if (inva == 0) return 0
  if (inva < 0 || 20 < inva) return NaN
  let a = inva < 2.4 ? 1.441 * inva ** (1 / 3) - 0.374 * inva : 1.48
  let a_prev = 2
  for (;;) {
    const tana = tan(a)
    a += (inva - tana + a) / tana ** 2
    if (abs(a_prev - a) < 1e-15) return a
    a_prev = a
  }
}

// UI の値を読み書きするためのオブジェクト
let controls = {}

// HTML 要素を生成するための関数
function elem(tag, html = "", children = [], props = {}, events = {}) {
  const result = document.createElement(tag)
  if (html != "") result.innerHTML = html
  children.forEach((c) => result.appendChild(c))
  Object.entries(props).forEach(([k, v]) => {
    if (typeof v == "string") {
      result[k] = v
    } else {
      Object.entries(v).forEach(([k2, v2]) => (result[k][k2] = v2))
    }
  })
  Object.entries(events).forEach(([k, v]) => result.addEventListener(k, v))
  return result
}

function katex2html(s) {
  return katex.renderToString(s, { throwOnError: false })
}

function definition2html(def) {
  const [name, label, notation, expression, values, scale] = def
  const createControl = (n, v) => {
    let e
    if (expression != "") {
      e = elem("SPAN", v)
      Object.defineProperty(controls, name + n, {
        get: () => (scale == 0 ? e.innerHTML : e.innerHTML * scale),
        set: (v) => (e.innerHTML = scale == 0 ? v : (v / scale).toPrecision(6)),
      })
    } else {
      e = elem(
        "INPUT",
        "",
        [],
        {
          value: String(v),
          style: {
            textAlign: "right",
            paddingRight: "0.2em",
            width: "4em",
          },
        },
        { change: recalc },
      )
      Object.defineProperty(controls, name + n, {
        get: () => (scale == 0 ? e.value : e.value * scale),
        set: (v) => (e.value = scale == 0 ? v : (v / scale).toPrecision(6)),
      })
    }
    return e
  }
  const values2controls = () =>
    values.length == 1
      ? [
          elem("TD", "", [createControl("", values[0])], {
            colSpan: "2",
            style: { textAlign: "center" },
          }),
        ]
      : [
          elem("TD", "", [createControl("1", values[0])]),
          elem("TD", "", [createControl("2", values[1])]),
        ]
  const backgroundColor = expression != "" ? "#ffe" : "#eff"
  return elem(
    "TR",
    "",
    [
      elem("TD", label),
      elem("TD", katex2html(notation)),
      elem("TD", katex2html(expression)),
      ...values2controls(),
    ],
    { style: { backgroundColor } },
  )
}

function setup() {
  let table = elem(
    "TABLE",
    "",
    [
      elem("TBODY", "", [
        elem(
          "TR",
          "",
          titles.map((s) => elem("TH", s)),
        ),
        ...definitions.map(definition2html),
      ]),
    ],
    { border: "1", cellSpacing: "0", cellPadding: "2" },
  )

  // キャンバスを最小化する
  p.createCanvas(1, 1)
  // DIV を作ってテーブルを追加する
  p.createDiv().elt.appendChild(table)
  // 値を更新する
  recalc()
}

p.setup = setup

ウォームギア設計

LANG:p5js_live
function input(i, label, value, func) {
  p.createSpan(label).position(0, 20 * i + 10);
  myInput = p.createInput(value).position(150, 20 * i + 10);
  if (func) {
    myInput.input(func);
  } else {
    myInput.elt.disabled = true;
  }
  return myInput;
}

const inputs = {};

function recalc() {
  inputs.beta.value(Math.asin(inputs.n.value() * inputs.m.value() / inputs.dp.value())/Math.PI*180)
  inputs.pitch.value(inputs.m.value() * Math.PI);
  inputs.mn.value(
    inputs.m.value() / Math.cos((inputs.beta.value() / 180) * Math.PI)
  );
  inputs.pitchn.value(
    Math.PI * inputs.mn.value()
  );
  inputs.vdp.value(
    inputs.dp.value()/Math.sin(inputs.beta.value()/180*Math.PI)
  )
}

p.setup = () => {
  p.createCanvas(300,240)
  inputs.m = input(1, "歯直角モジュール", 4, recalc);
  inputs.dp = input(2, "基準円直径", 30, recalc);
  inputs.n = input(3, "条数", 1, recalc);
  inputs.pitch = input(5, "歯直角ピッチ", 0);
  inputs.beta = input(6, "ねじれ角", 0);
  inputs.mn = input(7, "正面モジュール", 4);
  inputs.pitchn = input(8, "正面ピッチ", 0);
  inputs.vdp = input(9, "仮想基準円直径", 0)

  recalc();
}

逆インボリュート計算機

正でも負でも、大きな値でも、発散せず値を求められるはず。

LANG: p5js_live
p.setup = () => {
  p.createCanvas(1,1);
  p.createSpan('inv(a): ');
  const invAlpha = p.createInput();
  invAlpha.value(0.1);
  const answer = p.createDiv();
  const handler = () => {
    let inva = Number(invAlpha.value());
    const s = inva < 0 ? -1 : 1;
    inva = Math.abs(inva)
    let a = 1.441 * inva ** (1/3) - 0.374*inva;
    if (inva > 2) a = Math.atan(inva);
    let str = '';
    for (let i = 0; i < 10; i++) {
      str += `&nbsp; &nbsp; ${i} : ${a}<br/>`;
      if(a > 0) {
        const tana = Math.tan(a);
        a += (inva - tana + a) / tana ** 2;
      }
    }
    str += `<br>inv(${a}) = ${Math.tan(s*a) - s*a}`;
    answer.html(str);
  }
  handler();
  invAlpha.input(handler);
}

Counter: 260 (from 2010/06/03), today: 2, yesterday: 1