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

更新


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

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

かさ歯車の歯形について

かさ歯車の歯形は球面上に描くことになるため、平面上での歯形計算とはずいぶん異なる計算方法が必要になることが分かりました。

描けるようになりました

画面のドラッグでカメラ位置を回転できます。 マウスホイールでズームできます。

LANG: p5js_live
(() => {
  // src/css-text.ts
  function generateRandomId(length) {
    let result = "";
    const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    const charactersLength = characters.length;
    let counter = 0;
    while (counter < length) {
      result += characters.charAt(Math.floor(Math.random() * charactersLength));
      counter += 1;
    }
    return result;
  }
  var cssText = (id) => `
  @media screen and (min-width: 640px) {
    #${id} {
      position: absolute;
      left: 0;
      top: 0;
      opacity: 0.2;
    }
    #${id}:hover {
      opacity: 0.7;
    }
    #${id} .control-slider {
      padding-left: 10px;
    }
  }
  @media screen and (max-width: 639px) {
    #${id} .check-hide-tools {
      display: none !important;
    }
    #${id} {
      height: 30vh;
      overflow-y: scroll;
      line-height: 2;
    }
    #${id} > :last-child {
      padding-bottom: 10vh;
    }
    #${id} .control-checkbox {
      width:85px !important;
    }
    #${id} .control-slider input {
      width: 200px !important;
    }
  }
  #${id} {
    display: block;
    max-width: 500px
  }
  #${id} .control-slider {
    display: block
  }
  #${id} .control-slider :first-child {
    display: inline-block;
    width: 80px
  }
  #${id} .control-slider :nth-child(2) {
    display: inline-block;
    width: 50px
  }
  #${id} .control-slider input {
    display: inline-block;
    width: 350px;
  }
  #${id} .control-checkbox {
    display:inline-block;
    width:110px;
    overflow:hidden
  }
`;

  // src/initializeControls.ts
  function initializeControls(parent, inputChanged = () => {
  }) {
    const id = generateRandomId(8);
    const wrapper = elem("div", "", [elem("style", cssText(id))], { id });
    parent.appendChild(wrapper);
    const wrapControls = elem("div", "", [], {});
    const checkHide = createCheckbox(wrapper, "ツールを非表示", () => {
      wrapControls.style.display = checkHide.checked ? "none" : "block";
    });
    checkHide.parentElement.classList.add("check-hide-tools");
    wrapper.appendChild(wrapControls);
    const controls = {
      alpha: createSlider(
        wrapControls,
        "圧力角",
        { min: 10, max: 32, value: 20, step: 1 },
        inputChanged
      ),
      z1: createSlider(wrapControls, "歯数1", { min: 4, max: 200, value: 12, step: 1 }, inputChanged),
      z2: createSlider(wrapControls, "歯数2", { min: 4, max: 200, value: 30, step: 1 }, inputChanged),
      axesAngle: createSlider(
        wrapControls,
        "軸角度",
        { min: 20, max: 160, value: 90, step: 5 },
        inputChanged
      ),
      backlash: createSlider(
        wrapControls,
        "バックラッシュ",
        { min: -1, max: 1, value: 0, step: 0.02 },
        inputChanged
      ),
      theta: createSlider(
        wrapControls,
        "θ",
        { min: -5, max: 5, value: 0, step: 5e-3 },
        inputChanged
      ),
      stop: false
    };
    controls.theta.addEventListener("mousedown", () => {
      controls.stop = true;
    });
    controls.theta.addEventListener("mouseup", () => {
      controls.stop = false;
    });
    return {
      get z1() {
        return Number(controls.z1.value);
      },
      get z2() {
        return Number(controls.z2.value);
      },
      get alpha() {
        return Number(controls.alpha.value) / 180 * Math.PI;
      },
      get fillet() {
        return 0.4;
      },
      get mk() {
        return 1;
      },
      get mf() {
        return 1.25;
      },
      get axesAngle() {
        return Number(controls.axesAngle.value);
      },
      get theta() {
        return Number(controls.theta.value);
      },
      get backlash() {
        return Number(controls.backlash.value);
      },
      set theta(value) {
        controls.theta.value = value.toPrecision(4);
        controls.theta.doInput();
      },
      incrementTheta() {
        const delta = 0.01;
        if (controls.stop) return;
        if (this.theta + delta >= Number(controls.theta.max)) {
          this.theta = this.theta + delta - Number(controls.theta.max) + Number(controls.theta.min);
        } else {
          this.theta += delta;
        }
      }
    };
  }
  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).sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0).forEach(([k, v]) => {
      if (k == "class") {
        result.className = String(v);
      } else if (typeof v == "object") {
        Object.entries(v).forEach(([k2, v2]) => result[k][k2] = v2);
      } else {
        result[k] = v;
      }
    });
    const notIsArray = (maybeArray) => {
      return !Array.isArray(maybeArray);
    };
    Object.entries(events).forEach(([k, v]) => {
      if (notIsArray(v)) v = [v];
      v.forEach((f) => result.addEventListener(k, f));
    });
    return result;
  }
  function createSlider(parent, label, option, oninput) {
    const value = elem("span", String(option.value));
    const slider = elem(
      "input",
      "",
      [],
      { type: "range", ...option },
      { input: () => slider.doInput() }
    );
    slider.doInput = () => {
      value.innerHTML = slider.value;
      oninput();
    };
    const wrapper = elem("div", "", [elem("span", label), value, slider], {
      class: "control-slider"
    });
    parent.appendChild(wrapper);
    return slider;
  }
  function createCheckbox(parent, label, oninput, checked = false) {
    const check = elem(
      "input",
      "",
      [],
      { type: "checkbox", checked },
      { input: (e) => check.doInput(e) }
    );
    check.doInput = oninput;
    const wrapper = elem("label", "", [check, elem("span", label)], {
      class: "control-checkbox"
    });
    parent.appendChild(wrapper);
    return check;
  }

  // src/function.ts
  function minimize(l, r, f, epsilon = 1e-6) {
    const phi = (1 + Math.sqrt(5)) / 2;
    let m1 = r - (r - l) / phi;
    let m2 = l + (r - l) / phi;
    let f1 = f(m1);
    let f2 = f(m2);
    while (Math.abs(l - r) > epsilon) {
      if (f1 < f2) {
        r = m2;
        m2 = m1;
        f2 = f1;
        m1 = r - (r - l) / phi;
        f1 = f(m1);
      } else {
        l = m1;
        m1 = m2;
        f1 = f2;
        m2 = l + (r - l) / phi;
        f2 = f(m2);
      }
    }
    return (l + r) / 2;
  }

  // src/vector3d.ts
  var Vector3D = class _Vector3D {
    x;
    y;
    z;
    constructor(x, y, z) {
      this.x = x;
      this.y = y;
      this.z = z;
    }
    static polar(r, theta, phi) {
      return new _Vector3D(
        r * Math.sin(phi) * Math.cos(theta),
        r * Math.sin(phi) * Math.sin(theta),
        r * Math.cos(phi)
      );
    }
    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 && Math.abs(this.z - v.z) < epsilon;
    }
    add(v) {
      return new _Vector3D(this.x + v.x, this.y + v.y, this.z + v.z);
    }
    sub(v) {
      return new _Vector3D(this.x - v.x, this.y - v.y, this.z - v.z);
    }
    mul(scalar) {
      return new _Vector3D(this.x * scalar, this.y * scalar, this.z * scalar);
    }
    rotate(axis, angle) {
      axis = axis.normalize();
      const cos2 = Math.cos(angle);
      const sin2 = Math.sin(angle);
      const { x, y, z } = this;
      const { x: ux, y: uy, z: uz } = axis;
      const dot = this.inner(axis);
      const cross = this.outer(axis);
      return new _Vector3D(
        x * cos2 + cross.x * sin2 + ux * dot * (1 - cos2),
        y * cos2 + cross.y * sin2 + uy * dot * (1 - cos2),
        z * cos2 + cross.z * sin2 + uz * dot * (1 - cos2)
      );
    }
    flipY() {
      return new _Vector3D(this.x, -this.y, this.z);
    }
    flipX() {
      return new _Vector3D(-this.x, this.y, this.z);
    }
    flipZ() {
      return new _Vector3D(this.x, this.y, -this.z);
    }
    norm() {
      return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
    }
    normalize(length = 1) {
      const len = this.norm() / length;
      return new _Vector3D(this.x / len, this.y / len, this.z / len);
    }
    polar() {
      return [this.norm(), this.angle()];
    }
    inner(v) {
      return this.x * v.x + this.y * v.y + this.z * v.z;
    }
    outer(v) {
      return new _Vector3D(
        this.y * v.z - this.z * v.y,
        this.z * v.x - this.x * v.z,
        this.x * v.y - this.y * v.x
      );
    }
    cross(v) {
      return this.outer(v);
    }
    distance(v) {
      return v.sub(this).norm();
    }
    normalVectors() {
      const x = this.x;
      const y = this.y;
      const z = this.z;
      if (Math.abs(x) > Math.abs(y)) {
        return [new _Vector3D(-z, 0, x).normalize(), new _Vector3D(0, -z, y).normalize()];
      } else {
        return [new _Vector3D(0, -z, y).normalize(), new _Vector3D(-z, 0, x).normalize()];
      }
    }
  };

  // src/main.ts
  var { abs, sqrt, max, sin, cos, tan, asin, acos, atan, atan2, PI } = Math;
  var cameraP = new Vector3D(3, 0, 0);
  var cameraU = new Vector3D(0, -1, 0);
  var cameraV = new Vector3D(0, 0, 1);
  var cameraW = PI / 8;
  var sketch = (p2) => {
    let params;
    const canvas = {
      width: 800,
      height: 800,
      ox: 0,
      oy: 0
    };
    p2.mouseDragged = (e) => {
      if (e.target.tagName !== "CANVAS") return;
      cameraP = cameraP.sub(cameraU.mul(2 * (p2.mouseX - p2.pmouseX) * (cameraP.norm() - 1) / canvas.width)).normalize(cameraP.norm());
      cameraU = cameraP.outer(cameraV).normalize();
      cameraP = cameraP.sub(cameraV.mul(2 * (p2.mouseY - p2.pmouseY) * (cameraP.norm() - 1) / canvas.width)).normalize(cameraP.norm());
      cameraV = cameraP.outer(cameraU).normalize(-1);
      inputChanged();
      return true;
    };
    p2.mouseWheel = (event) => {
      if (event.target.tagName !== "CANVAS") return;
      const delta = event.deltaY / 200;
      let n = cameraP.norm() * (1 + delta);
      n = Math.max(1.2, n);
      cameraP = cameraP.normalize(n);
      inputChanged();
      event.preventDefault();
      return true;
    };
    p2.setup = () => {
      const { width, height } = canvas;
      canvas.ox = width / 2;
      canvas.oy = height * 2 / 3;
      const c = p2.createCanvas(width, height);
      c.style("max-width", "100%");
      c.style("height", "auto");
      const wrapper = c.elt.parentElement;
      params = initializeControls(wrapper, inputChanged);
      inputChanged();
      setInterval(() => {
        params.incrementTheta();
      }, 100);
    };
    const inputChanged = () => {
      const { width, height } = canvas;
      const sigma = params.axesAngle * PI / 180;
      const gammaP1 = atan2(sin(sigma), params.z2 / params.z1 + cos(sigma));
      const gammaP2 = sigma - gammaP1;
      const r = params.z1 / (2 * sin(gammaP1));
      const rm = 1 / r;
      const axis1 = new Vector3D(cos(gammaP1), 0, sin(gammaP1));
      const axis2 = new Vector3D(cos(gammaP2), 0, -sin(gammaP2));
      const eps = 1e-5;
      function proj(lambdaOrVec, phi = 0) {
        if (typeof lambdaOrVec === "number") {
          lambdaOrVec = new Vector3D(
            cos(phi) * cos(lambdaOrVec),
            sin(phi) * cos(lambdaOrVec),
            sin(lambdaOrVec)
          );
        } else {
          lambdaOrVec = lambdaOrVec.normalize();
        }
        const p3 = lambdaOrVec.sub(cameraP);
        if (p3.inner(lambdaOrVec) > 0) return [NaN, NaN];
        const u = p3.inner(cameraU);
        const v = p3.inner(cameraV);
        const d = -p3.inner(cameraP.normalize());
        return [(1 + atan2(u, d) / cameraW) * (width / 2), (1 + atan2(v, d) / cameraW) * (width / 2)];
      }
      function isArrayOf(obj, predicate) {
        return Array.isArray(obj) && obj.every(predicate);
      }
      function drawCurve(points) {
        if (points.length === 0) return;
        if (isArrayOf(points, (item) => item instanceof Vector3D)) {
          points = points.map((p3) => proj(p3));
        }
        let last = [NaN, NaN];
        points.forEach((point) => {
          if (isNaN(point[0])) {
            if (!isNaN(last[0])) {
              p2.curveVertex(...last);
              p2.endShape();
            }
            last = point;
            return;
          }
          if (isNaN(last[0])) {
            p2.beginShape();
            p2.curveVertex(...point);
          }
          p2.curveVertex(...point);
          last = point;
        });
        if (!isNaN(last[0])) {
          p2.curveVertex(...last);
          p2.endShape();
        }
      }
      const initializeCanvas = () => {
        p2.fill(220);
        p2.rect(0, 0, width, height);
        p2.stroke(100);
        for (let j = 0; j <= 18; j++) {
          const phi = j * PI / 18 - PI / 2;
          const points = [];
          for (let lambda = -PI; lambda <= PI; lambda += PI / 60 - eps) {
            points.push(proj(phi, lambda));
          }
          if (j % 9 === 0) {
            p2.stroke(160);
          } else {
            p2.stroke(200);
          }
          p2.noFill();
          drawCurve(points);
        }
        for (let i = 0; i <= 36; i++) {
          const lambda = i * PI / 18;
          const points = [];
          for (let phi = -PI / 2; phi <= PI / 2; phi += PI / 30 - eps) {
            const pp = proj(phi, lambda);
            points.push(pp);
          }
          if (i % 9 === 0) {
            p2.stroke(160);
          } else {
            p2.stroke(200);
          }
          p2.noFill();
          drawCurve(points);
        }
        (() => {
          p2.drawingContext.setLineDash([10, 10]);
          const points = [];
          const a = new Vector3D(0, cos(params.alpha), -sin(params.alpha));
          const v = new Vector3D(1, 0, 0);
          for (let i = 0; i <= 20; i++) {
            points.push(proj(v.rotate(a, (-0.1 + 0.2 * i / 20) * PI)));
          }
          drawCurve(points);
          p2.drawingContext.setLineDash([]);
        })();
        (() => {
          p2.drawingContext.setLineDash([10, 10]);
          const points = [];
          const a = new Vector3D(0, sin(params.alpha), cos(params.alpha));
          const v = new Vector3D(1, 0, 0);
          for (let i = 0; i <= 20; i++) {
            points.push(proj(v.rotate(a, (-0.1 + 0.2 * i / 20) * PI)));
          }
          drawCurve(points);
          p2.drawingContext.setLineDash([]);
        })();
      };
      initializeCanvas();
      function drawCircle(axis, delta) {
        const [axis1u, axis1v] = axis.normalVectors();
        const points = [];
        const n = 36;
        for (let i = 0; i <= 2 * n; i++) {
          const t = i * PI / n;
          const p1 = axis.add(axis1u.mul(tan(delta) * cos(t))).add(axis1v.mul(tan(delta) * sin(t)));
          points.push(proj(p1));
        }
        drawCurve(points);
      }
      p2.stroke(0);
      p2.strokeWeight(2);
      p2.circle(...proj(axis1), 2);
      p2.circle(...proj(axis2), 2);
      const gammaF1 = gammaP1 - params.mf * atan(rm);
      const gammaK1 = gammaP1 + params.mk * atan(rm);
      const gammaT1 = gammaP1 + params.mf * atan(rm);
      const gammaF2 = gammaP2 - params.mf * atan(rm);
      const gammaK2 = gammaP2 + params.mk * atan(rm);
      const gammaT2 = gammaP2 + params.mf * atan(rm);
      p2.stroke(160);
      p2.strokeWeight(1);
      p2.noFill();
      p2.drawingContext.setLineDash([10, 10]);
      drawCircle(axis1, gammaP1);
      drawCircle(axis1, gammaF1);
      drawCircle(axis1, gammaK1);
      drawCircle(axis1, gammaT1);
      drawCircle(axis2, gammaP2);
      drawCircle(axis2, gammaF2);
      drawCircle(axis2, gammaK2);
      drawCircle(axis2, gammaT2);
      p2.drawingContext.setLineDash([]);
      const gammaB1 = asin(cos(params.alpha) * sin(gammaP1));
      const gammaB2 = asin(cos(params.alpha) * sin(gammaP2));
      drawCircle(axis1, gammaB1);
      drawCircle(axis2, gammaB2);
      function trans(epsilon, v, axis, gammaB) {
        axis = axis.normalize(cos(gammaB));
        v = v.normalize();
        const oq = v.sub(axis);
        const om = oq.rotate(axis, epsilon);
        const oom = axis.add(om);
        const axis22 = oom.sub(axis.normalize(1 / cos(gammaB)));
        const op = oom.rotate(axis22, epsilon * sin(gammaB));
        return op;
      }
      function sphericalInvolute(axis, v, gammaB, gammaF, gammaK) {
        const points = [];
        for (let i = 0; i <= 20; i++) {
          const t = gamma2epsilon(gammaF, gammaB) + (gamma2epsilon(gammaK, gammaB) - gamma2epsilon(gammaF, gammaB)) * i / 20;
          points.push(trans(t, v, axis, gammaB));
        }
        return points;
      }
      function gamma2theta(gamma, gammaB) {
        const varphi = acos(cos(gamma) / cos(gammaB));
        return varphi / sin(gammaB) - atan2(tan(varphi), sin(gammaB));
      }
      function gamma2epsilon(gamma, gammaB) {
        const varphi = acos(cos(gamma) / cos(gammaB));
        return varphi / sin(gammaB);
      }
      function drawArc(axis, p1, p2Angle, options = {}) {
        const { greatCircle = false, n = 6 } = options;
        const c = [];
        if (greatCircle) {
          const v1 = p1;
          const v2 = typeof p2Angle === "number" ? v1.rotate(axis, p2Angle) : p2Angle;
          const t = acos(v1.inner(v2) / (v1.norm() * v2.norm()));
          for (let j = 0; j <= n; j++) {
            const t22 = t * j / n;
            c.push(v1.rotate(axis, t22));
          }
        } else {
          const v1 = p1.sub(axis);
          const v2 = typeof p2Angle === "number" ? v1.rotate(axis, p2Angle) : p2Angle.sub(axis);
          const t = acos(v1.inner(v2) / (v1.norm() * v2.norm()));
          for (let j = 0; j <= n; j++) {
            const t22 = t * j / n;
            c.push(axis.add(v1.rotate(axis, t22)));
          }
        }
        drawCurve(c);
      }
      function rotateAround(v, axis, angle) {
        axis = axis.normalize(axis.normalize().inner(v));
        return axis.add(v.sub(axis).rotate(axis, angle));
      }
      p2.stroke(0);
      const q1 = new Vector3D(cos(gammaP1 - gammaB1), 0, sin(gammaP1 - gammaB1)).rotate(
        axis1,
        -gamma2theta(gammaP1, gammaB1)
      );
      const tooth1 = sphericalInvolute(axis1, q1, gammaB1, max(gammaB1, gammaF1), gammaK1);
      const t1 = trans(gamma2epsilon(gammaT1, gammaB1), q1, axis1, gammaB1);
      const q2 = new Vector3D(cos(gammaP2 - gammaB2), 0, -sin(gammaP2 - gammaB2)).rotate(
        axis2,
        -gamma2theta(gammaP2, gammaB2)
      );
      const tooth2 = sphericalInvolute(axis2, q2, gammaB2, max(gammaB2, gammaF2), gammaK2);
      const t2 = trans(gamma2epsilon(gammaT2, gammaB2), q2, axis2, gammaB2);
      function trochoid(t12, q22, axis12, axis22, z1, z2, tooth22, gammaB22, gammaF22) {
        const trochoid22 = (t) => rotateAround(rotateAround(t12, axis12, -t), axis22, -t / z2 * z1);
        const c1 = [];
        let t1a, t1b, t1c;
        for (let i = 0; ; i++) {
          const t = 0.02 * (i * PI) / sqrt(params.z1);
          c1.push(trochoid22(t));
          if (c1.at(-1).sub(axis22).norm() > tooth22.at(-1).sub(axis22).norm()) {
            t1a = minimize(0, t, (t3) => trochoid22(t3).sub(axis22).norm());
            t1b = t;
            break;
          }
        }
        if (gammaB22 > gammaF22) {
          t1c = minimize(t1a, t1b, (t) => abs(trochoid22(t).sub(axis22).norm() - q22.sub(axis22).norm()));
        } else {
          t1c = t1a;
        }
        const t1d = minimize(t1c, t1b, (t) => {
          const v = trochoid22(t);
          const gamma3 = acos(v.inner(axis22));
          const u = trans(gamma2epsilon(gamma3, gammaB22), q22, axis22, gammaB22);
          return u.sub(v).norm();
        });
        c1.splice(0);
        for (let i = 0; i <= 10; i++) {
          const t = t1a + (t1d - t1a) * i / 10;
          c1.push(trochoid22(t));
        }
        const gamma = acos(c1.at(-1).inner(axis22));
        return [c1, gamma];
      }
      const [trochoid1, gamma1] = trochoid(
        t2,
        q1,
        axis2,
        axis1,
        params.z2,
        params.z1,
        tooth1,
        gammaB1,
        gammaF1
      );
      const involute1 = sphericalInvolute(axis1, q1, gammaB1, gamma1, gammaK1);
      drawGear(axis1, involute1, trochoid1, params.z1, params.theta);
      const [trochoid2, gamma2] = trochoid(
        t1,
        q2,
        axis1,
        axis2,
        params.z1,
        params.z2,
        tooth2,
        gammaB2,
        gammaF2
      );
      const involute2 = sphericalInvolute(axis2, q2, gammaB2, gamma2, gammaK2);
      drawGear(axis2, involute2, trochoid2, params.z2, -params.theta);
      return;
      function drawGear(axis, involute, trochoid3, z, theta) {
        function angle(p3) {
          return acos(
            p3.sub(axis.normalize(p3.inner(axis))).normalize().inner(new Vector3D(1, 0, 0).sub(axis.normalize(new Vector3D(1, 0, 0).inner(axis))).normalize())
          );
        }
        function backlash(curve) {
          curve = curve.map((p3) => p3.rotate(axis, params.backlash * PI / z / 10));
          if (angle(curve[0]) > PI / z / 2) {
            while (angle(curve[0]) > PI / z / 2)
              curve.shift();
          }
          if (angle(curve.at(-1)) > PI / z / 2) {
            while (angle(curve.at(-1)) > PI / z / 2)
              curve.pop();
          }
          return curve;
        }
        involute = backlash(involute);
        trochoid3 = backlash(trochoid3);
        for (let i = 0; i < z; i++) {
          const c1 = involute.map((p3) => p3.rotate(axis, i * 2 * PI / z)).map((p3) => p3.rotate(axis, 2 * PI / z * theta));
          const c2 = involute.map((p3) => p3.flipY().rotate(axis, (i + 1 / 2) * 2 * PI / z)).map((p3) => p3.rotate(axis, 2 * PI / z * theta));
          drawCurve(c1);
          drawCurve(c2);
          const c3 = trochoid3.map((p3) => p3.rotate(axis, i * 2 * PI / z)).map((p3) => p3.rotate(axis, 2 * PI / z * theta));
          const c4 = trochoid3.map((p3) => p3.flipY().rotate(axis, (i + 1 / 2) * 2 * PI / z)).map((p3) => p3.rotate(axis, 2 * PI / z * theta));
          drawCurve(c3);
          drawCurve(c4);
          drawArc(axis.normalize(axis.inner(c2.at(-1))), c1.at(-1), c2.at(-1));
          drawArc(
            axis.normalize(axis.inner(c3.at(0))),
            c4.at(0),
            c3.at(0).rotate(axis, 2 * PI / z)
          );
        }
      }
    };
  };
  if (true) {
    sketch(p);
  } else {
    new p5(sketch, window["p5wrapper"]);
  }
})();

球面インボリュート曲線

通常の歯車の歯形にインボリュート曲線が用いられるのに対して、 かさ歯車の歯形には球面インボリュート曲線を用いると良いのだそうです。

参照: https://thermalprocessing.com/computerized-design-of-straight-bevel-gears-with-optimized-profiles-for-forging-molding-or-3d-printing/

bevel3.png

球面インボリュート曲線は、図において基礎円の円弧 $\overset{\frown}{MQ}$ と大円の円弧 $\overset{\frown}{MP}$ との長さが等しくなるように取った曲線 $PQ$ のこと。

図において、球の半径を $r_0$ とすると、球の中心 $O_s$ から基礎円の半径 $r_b$ を見込む角度 $\gamma_b$ は $OsO$ と $OM$ とが直交するため $r_b=r_0\sin\gamma_b$ を満たす。

球の中心から $MP$ および $OP$ を見込む角度をそれぞれ $\varphi,\gamma$ とする。

このとき、 $$ \overset{\frown}{MQ}=\overset{\frown}{MP} $$ としたいので、大円の半径を $r_0$ とすれば、 $$ r_0\varepsilon\sin\gamma_b=r_0\varphi $$ が成り立つ必要があり、ここから、 $$ \varepsilon\sin\gamma_b=\varphi\tag{4} $$ を得る。

$\varphi(\gamma)$ を求めるには、 $$ \cos\gamma=\cos\varphi\cos\gamma_b\tag{6} $$ より得られる $$ \cos\varphi=\frac{\cos\gamma}{\cos\gamma_b} $$ により $$ \phi(\gamma)=\cos^{-1}\frac{\cos\gamma}{\cos\gamma_b} $$ とすればよい。 (4) と $$ \tan\varphi=\sin\gamma_b\tan\phi\tag{11} $$ を用いると、 $$ \begin{aligned} \theta(\gamma) &=\varepsilon(\gamma)-\phi(\gamma)\\ &=\frac{\varphi(\gamma)}{\sin\gamma_b}-\tan^{-1}\frac{\tan\varphi(\gamma)}{\sin\gamma_b}\\ \end{aligned} $$ として $\gamma$ から $\theta$ を求められる。

これが歯形そのものを表す関係式なのだと思うのだけれど、なぜかリンク先ではこの形の式が紹介されていないようだ???

実は上のような3D的な画を描くには、リンク先のように三角関数をごにょごにょするだけで計算しようとするととてもややこしい。上の式によって必要なパラメータを得た後は、点 $Q$ の座標を歯車の軸に対して $\varepsilon$ 左に回転して $M$ を見つけ、その後、対円に沿って $\varphi=\varepsilon\sin(\gamma_b)$ だけ回転することで点 $P$ を求められるので、そのようにして曲線を描く方が分かりやすいようだった。

歯元の形状

本来なら通常の歯車と同様に相手の歯形の先端をフィレット付きで延長して歯元に自然なフィレットを付けられるよう計算したいところなのだけれど、 球面上でいい感じにフィレットを付けたり、フィレットの描く包絡線を計算するのが大変そうだったため、フィレットを付けずにそのまま歯先を延長し、その頂点が描く球面トロコイド曲線で歯元の形状を決定することにした。

bevel1.png bevel2.png

下の歯形の左側のインボリュート曲線を頂隙分だけ延長した点を基準にして、 この点を軸1に対して $\theta/z_1$ だけ回して軸2に対して $\theta/z_2$ だけ戻す、 という操作により得られる点を $\theta$ を連続的に変化させながら結ぶことで球面トロコイド曲線が得られる。

図では上の歯の歯先円を超えるまでが、左図では基礎円が歯底円より小さい場合、右図では基礎円が歯底円より大きい場合、について描かれている。

この球面トロコイド曲線のうち、最も上の歯車の軸に近づくところから、基礎円より外で最もインボリュート曲線に近づくところまで、が歯形として利用される。

この計算では歯元が必要以上にえぐれてしまうため、相手歯先を正しく丸めた場合に比べると強度が低下してしまうものの、歯元形状をインボリュート曲線のままにしたり、基礎円から下を真下に下ろすのに比べれば多少なりともフィレットが付いている。また小径歯車に対して必要な切り下げが十分に行われるため、干渉によって回らないという事態は避けられている。

バックラッシュ

バックラッシュは計算済みの球面インボリュート曲線と球面トロコイド曲線を円周方向に移動することで実現した。

転位

未対応


Counter: 74 (from 2010/06/03), today: 5, yesterday: 8