歯車について勉強する3 の履歴(No.23)
更新- 履歴一覧
- 差分 を表示
- 現在との差分 を表示
- ソース を表示
- 工作/歯車について勉強する3 へ行く。
歯車について勉強するシリーズ†
- 工作/歯車について勉強する 歯車の形状についての基礎
- 工作/歯車について勉強する2 仮想的なラックを使って実用的な歯車形状を切り出す話
- 工作/歯車について勉強する3 歯車に関するいろいろな計算機
- 工作/歯車について勉強する4 転位歯車の中心間距離と逆インボリュート関数
- 工作/歯車について勉強する5 ウォームホイールの歯形計算について
- 工作/歯車について勉強する/ゼネバ歯車 間欠歯車の一種です
- 工作/Fusion360歯車スクリプト 勉強した結果を使って作ってみました
- 工作/Fusion360歯車切削スクリプト Fusion 360 内で歯車の歯切りを行うスクリプトです
- 工作/Fusion360曲面生成スクリプト 歯切り結果の表面をきれいにするスクリプトです
- 工作/Fusion360ジョイント駆動スクリプト ジョイントを動かしてアニメーションさせるスクリプトです
目次†
歯車設計に必要なさまざまな計算を行います。
歯車を描く†
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) {
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 inputChanged = () => {
};
const controls = {
m: createSlider(
wrapControls,
"モジュール",
{ min: 10, max: 200, value: 80, step: 1 },
inputChanged
),
alpha: createSlider(
wrapControls,
"圧力角",
{ min: 10, max: 32, value: 20, step: 1 },
inputChanged
),
z: createSlider(wrapControls, "歯数A", { min: 4, max: 60, value: 19, step: 1 }, inputChanged),
shift: createSlider(
wrapControls,
"転位A",
{ min: -0.5, max: 2, value: 0, step: 0.05 },
inputChanged
),
z2: createSlider(wrapControls, "歯数B", { min: 4, max: 60, value: 6, step: 1 }, inputChanged),
shift2: createSlider(
wrapControls,
"転位B",
{ min: -0.5, max: 2, value: 0, step: 0.05 },
inputChanged
),
fillet: createSlider(
wrapControls,
"フィレット",
{ min: 0, max: 0.4, value: 0.4, step: 0.01 },
inputChanged
),
backlash: createSlider(
wrapControls,
"バックラッシュ",
{ min: -5, max: 5, value: 0, step: 0.02 },
inputChanged
),
velocity: createSlider(
wrapControls,
"速度",
{ min: -1, max: 10, value: 1, step: 0.1 },
inputChanged
),
theta: createSlider(
wrapControls,
"回転角",
{ min: -360, max: 360, value: 0, step: 0.1 },
inputChanged
),
df: createCheckbox(wrapControls, "歯底円", inputChanged, false),
db: createCheckbox(wrapControls, "基礎円", inputChanged, false),
dp: createCheckbox(wrapControls, "基準円", inputChanged, true),
dk: createCheckbox(wrapControls, "歯先円", inputChanged, false),
play: createCheckbox(wrapControls, "自動更新", inputChanged, true),
connection: createCheckbox(wrapControls, "接続点", inputChanged, false),
inner: createCheckbox(wrapControls, "内歯車", inputChanged, false),
stop: false
};
controls.theta.addEventListener("mousedown", () => {
controls.stop = true;
});
controls.theta.addEventListener("mouseup", () => {
controls.stop = false;
});
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 = String(-value * 180 / Math.PI);
controls.theta.doInput();
},
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 < -2 * Math.PI) {
this.theta += -this.velocity + 4 * Math.PI;
} else {
this.theta += -this.velocity;
}
}
};
}
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/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) {
const { m, z, alpha, shift, fillet, mf, mk, backlash } = params;
const rp = z * m / 2;
const { sin, tan, min, PI } = Math;
const rc = 0.25 * m;
const r1 = vec(rp + shift + mk, mk * tan(alpha) + backlash / 2);
const r2 = vec(rp + shift - (mf - rc), -(mf - rc) * tan(alpha) + backlash / 2);
const r3 = vec(rp + shift - mf, -mf * tan(alpha) + backlash / 2);
const fillet1 = rc / (1 - sin(alpha));
let fr = min(fillet, fillet1);
let c = r3.add(vec(fr, -fr / 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)));
fr = c.x - r3.x;
}
return {
r1,
r2,
filletCenter: c,
filletRadius: fr
};
}
// 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) {
const fl = f(l);
const 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 };
const { m, z } = params;
if (params.inner) {
[params.mk, params.mf] = [params.mf, params.mk];
params.fillet = 0;
}
const rp = m * z / 2;
const { 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 } = params;
const rp = m * z / 2;
const { abs, PI } = Math;
let involuteS, involuteE;
const 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));
}
const 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 } = params;
const rp = m * z / 2;
const rk = rp + mk;
let filletS, filletE;
const 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;
const filletN = 60, filletT = [];
const 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 [, 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, shift, z, mf } = params;
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);
const 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) {
const 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("max-width", "100%");
c.style("height", "auto");
const wrapper = c.elt.parentElement;
params = initializeControls(wrapper);
};
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 i = 0; i < 20; i++) {
const tana = tan(a);
a += (inva2 - tana + a) / tana ** 2;
if (abs(a_prev - a) < 1e-15) return a;
a_prev = a;
}
return 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);
const da = a0 * (cos(alpha) / cos(aw) - 1);
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 - s * da / 2, oy, thetaI, connection, innerR);
drawCircles(p2, ox + rp - s * da / 2, oy, params);
}
const theta2 = (theta + PI) / z2 + PI;
const params2 = { ...params, z: z2, shift: shift2 };
drawGear(p2, gear22, ox + rp2 + s * da / 2, oy, theta2, connection);
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
// src/main.ts
var sketch = (p2) => {
const { tan, cos, 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) return -inverse_inv(-inva);
let a = inva > 2.4 ? Math.atan(inva) : 1.441 * inva ** (1 / 3) - 0.374 * inva;
let a_prev = 2;
for (let i = 0; i < 20; i++) {
const tana = tan(a);
a += (inva - tana + a) / tana ** 2;
if (abs(a_prev - a) < 1e-15) return a;
a_prev = a;
}
return a;
}
const controls = {};
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: (v2) => e.innerHTML = (v2 / 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: (v2) => e.value = (v2 / 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() {
const table = elem(
"TABLE",
"",
[
elem("TBODY", "", [
elem(
"TR",
"",
titles.map((s) => elem("TH", s))
),
...definitions.map(definition2html)
])
],
{ border: "1", cellSpacing: "0", cellPadding: "2" }
);
p2.createCanvas(1, 1);
p2.createDiv().elt.appendChild(table);
recalc();
}
p2.setup = setup;
};
sketch(p);
はすば歯車設計(中心間距離から転位量)†
モジュール、歯数、ねじれ角と中心間距離を入れると、2つの歯車を合わせてどれだけ転位すればよいかを算出できる。
求まった「歯直角転位係数和」を2つに分けてそれぞれの歯車に振り分けることで目的の中心間距離で噛み合う2つの歯車を設計できる。
具体的には KHK 小原歯車工業さんの 歯車技術資料 にある、
https://www.khkgears.co.jp/gear_technology/pdf/gijutu.pdf#page=24 の計算を行っているだけです。
LANG: p5js_live
// src/main.ts
var sketch = (p2) => {
const { tan, cos, acos, PI, atan } = 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;
}
const controls = {};
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: (v2) => e.innerHTML = scale == 0 ? v2 : (v2 / 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: (v2) => e.value = scale == 0 ? v2 : (v2 / 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() {
const table = elem(
"TABLE",
"",
[
elem("TBODY", "", [
elem(
"TR",
"",
titles.map((s) => elem("TH", s))
),
...definitions.map(definition2html)
])
],
{ border: "1", cellSpacing: "0", cellPadding: "2" }
);
p2.createCanvas(1, 1);
p2.createDiv().elt.appendChild(table);
recalc();
}
p2.setup = setup;
};
sketch(p);
はすば内歯車計算(転位係数から中心間距離)†
はすばの場合に対応するため、
https://www.khkgears.co.jp/gear_technology/pdf/gijutu.pdf#page=19
にあった内容にねじれ角の項目を追加たのが以下のものです。
エラーチェックは、
- 内歯車の基礎円と歯先円の大小関係
- インボリュート干渉 = 内歯車の歯先が平歯車の歯元と干渉
- トロコイド干渉 = 内歯車の歯先が平歯車の歯先と干渉
を見ているつもりです。
通常の歯先の丈を指定して内歯車の基礎円が歯先円よりも大きくなった場合、 歯先の丈を短くして歯先円を大きく定義しなおせばかみ合い長が短くはなりますが使えないことはないので、 歯先の丈の調整を行ったうえでの計算結果を表示するようにしています。
LANG: p5js_live
// src/main.ts
var sketch = (p2) => {
const { tan, 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],
// 出力
["cam", "歯先の丈(切詰後)", "c_a'", "\\scriptsize d_b\\ge d\\ \\text{まで切詰}", [1, 1], 0],
[
"cond_inv",
"インボリュート限界",
"\\scriptsize \\frac{z_1}{z_2}\\ge",
"\\scriptsize1-\\frac{\\tan(\\alpha_{a2})}{\\tan\\alpha_{wt}}, ({\\tiny\\alpha_{a2}=\\cos^{-1}\\frac{d_{b2}}{d_{a2}}})",
["?"],
0
],
[
"cond_trochoid",
"トロコイド限界",
"\\scriptsize \\frac{z_1}{z_2}\\ge",
"\\scriptsize \\frac{\\cos^{-1}\\frac{r_{a2}^2-r_{a1}^2+a^2}{2ar_{a2}}+\\text{inv}\\,\\alpha_{a2}-\\text{inv}\\,\\alpha_{wt}}{\\cos^{-1}\\frac{r_{a2}^2-r_{a1}^2-a^2}{2ar_{a1}}+\\text{inv}\\,\\alpha_{a1}-\\text{inv}\\,\\alpha_{wt}}",
["?"],
0
],
["error", "エラーチェック", "-", "-", ["?"], 0],
[
"alphat",
"正面圧力角",
"\\alpha_t",
"\\scriptsize\\tan^{-1}\\big(\\frac{\\tan\\alpha_n}{\\cos\\beta}\\big)",
["?"],
PI / 180
],
[
"invawt",
"インボリュートαwt",
"\\mathrm{inv}\\,\\alpha_{wt}",
"\\scriptsize2\\tan\\alpha_n\\big(\\frac{x_{n2}-x_{n1}}{z_2-z_1}\\big)+\\mathrm{inv}\\,\\alpha_t",
["?"],
1
],
[
"alphawt",
"正面噛み合い圧力角",
"\\alpha_{wt}",
"\\scriptsize\\mathrm{inv}^{-1}\\,(\\mathrm{inv}\\,\\alpha_{wt})",
["?"],
PI / 180
],
[
"y",
"中心距離修正係数",
"y",
"\\scriptsize\\frac{z_2-z_1}{2\\cos\\beta}\\big(\\frac{\\cos\\alpha_t}{\\cos\\alpha_{wt}}-1\\big)",
["?"],
1
],
["a", "中心距離", "a", "\\scriptsize\\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", "\\scriptsize 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", "\\scriptsize(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", "\\scriptsize m_n a_d / a", ["?"], 1]
];
function recalc() {
const msg = (s, c2) => `<font style="color: ${c2}; font-weight: bold">${s}</font><br>`;
let error = "";
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.cam1 = String(c.ca1);
let ca2 = c.ca2;
if ((c.d2 - c.db2) / 2 / c.mn + c.xn2 < ca2) {
error += msg("歯先を切詰", "red");
ca2 = (c.d2 - c.db2 - 1e-6) / 2 / c.mn - c.xn2;
c.cam2 = msg(ca2.toPrecision(6), "red");
} else {
c.cam2 = String(ca2);
}
c.dw1 = c.db1 / cos(c.alphawt);
c.dw2 = c.db2 / cos(c.alphawt);
c.ha1 = (c.ca1 + c.xn1) * c.mn;
c.ha2 = (ca2 - c.xn2) * c.mn;
c.h1 = (c.ca1 + c.cf1) * c.mn;
c.h2 = (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;
const cond_inv = 1 - tan(acos(c.db2 / c.da2)) / tan(c.alphawt);
const z_ratio = c.z1 / c.z2;
if (z_ratio < cond_inv) {
error += msg("インボリュート干渉", "red");
c.cond_inv = msg(`${z_ratio.toPrecision(3)} < ${cond_inv.toPrecision(3)}`, "red");
} else {
c.cond_inv = msg(`${z_ratio.toPrecision(3)} > ${cond_inv.toPrecision(3)}`, "green");
}
const theta1 = acos((c.da2 ** 2 / 4 - c.da1 ** 2 / 4 - c.a ** 2) / (c.a * c.da1)) + (inv(acos(c.db1 / c.da1)) - c.invawt);
const theta2 = acos((c.da2 ** 2 / 4 - c.da1 ** 2 / 4 + c.a ** 2) / (c.a * c.da2)) + (inv(acos(c.db2 / c.da2)) - c.invawt);
const cond_trochoid = theta2 / theta1;
if (z_ratio < cond_trochoid) {
error += msg("トロコイド干渉", "red");
c.cond_trochoid = msg(`${z_ratio.toPrecision(3)} < ${cond_trochoid.toPrecision(3)}`, "red");
} else {
c.cond_trochoid = msg(`${z_ratio.toPrecision(3)} > ${cond_trochoid.toPrecision(3)}`, "green");
}
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) return -inverse_inv(-inva);
let a = inva > 2.4 ? Math.atan(inva) : 1.441 * inva ** (1 / 3) - 0.374 * inva;
let a_prev = 2;
for (let i = 0; i < 20; i++) {
const tana = tan(a);
a += (inva - tana + a) / tana ** 2;
if (abs(a_prev - a) < 1e-15) return a;
a_prev = a;
}
return a;
}
const controls = {};
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: (v2) => {
if (scale != 0) e.innerHTML = (v2 / scale).toPrecision(6);
else if (v2 instanceof HTMLElement) {
e.innerHTML = "";
e.appendChild(v2);
} else {
e.innerHTML = v2;
}
}
});
} 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: (v2) => e.value = scale == 0 ? v2 : (v2 / 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() {
const table = elem(
"TABLE",
"",
[
elem("TBODY", "", [
elem(
"TR",
"",
titles.map((s) => elem("TH", s))
),
...definitions.map(definition2html)
])
],
{ border: "1", cellSpacing: "0", cellPadding: "2" }
);
p2.createCanvas(1, 1);
p2.createDiv().elt.appendChild(table);
recalc();
}
p2.setup = setup;
};
sketch(p);
インボリュート干渉、トロコイド干渉について†
下図は https://www.khkgears.co.jp/gear_technology/pdf/gijutu.pdf#page=19 より引用
上の PDF に与えられた条件式は基礎円が歯先円を上回ってしまうと使えない。 しかしこの場合には歯先の丈を短くして歯先円を基礎円まで大きくしてやれば 噛み合い長が短くなるほかは実用的とみなせる歯車を作成可能である。
そこで標準の歯先の丈を取った時に基礎円が歯先円を上回る場合には 歯先の丈を切り詰め、歯先円を基礎円と合わせた上で計算を行うようにした。
PDF に与えられた式は恐らく下記の文献を参考にしたものです?
インボリュート内歯歯車の干渉 (荻野 修作)
日本機械学會論文集 21 巻 (1955) 110 号 p. 749-755
https://doi.org/10.1299/kikai1938.21.749
ただ上記の判別式は相手の小径歯車に切り下げがある場合を考慮していないのかもしれなくて(?)、
切り下げの行われた歯車との組み合わせであれば干渉しないのにも関わらず干渉が生じると表示される場合があるようです(?)
切り下げについて参考: https://www.khkgears.co.jp/gear_technology/basic_guide/KHK356_1.html
例えば標準的な歯数12の内歯車と歯数6の外歯車の組み合わせをバックラッシュ 0 で作成して組み合わせてみた下図からは、この組み合わせでインボリュート干渉が起きないことを確認できます。これは歯数6の歯車に切り下げが発生している(歯元のトロコイド曲線が基礎円より上まで来ておりインボリュート領域が本来の長さまで到達していない)ためであり、切り下げを行わなければ小歯車のインボリュート曲線と内歯車の歯先が干渉します。判別式は小歯車の切り下げを考慮していないためにこの「仮想的な干渉」を検出してしまうのだと思います。
インボリュート干渉の警告が出ても、小歯車側の基礎円が切り下げ開始点よりも小さい場合には、 「本当にダメなのか」を確認する余地はあると思った方が良さそうです???
はすば内歯車計算 (中心距離から転位量)†
https://www.khkgears.co.jp/gear_technology/pdf/gijutu.pdf#page=24 の計算をします。
はすばでも使えるよう少し変更しています。
LANG:p5js_live
// src/main.ts
var sketch = (p2) => {
const { tan, cos, acos, PI, atan } = 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;
}
const controls = {};
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: (v2) => e.innerHTML = scale == 0 ? v2 : (v2 / 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: (v2) => e.value = scale == 0 ? v2 : (v2 / 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() {
const table = elem(
"TABLE",
"",
[
elem("TBODY", "", [
elem(
"TR",
"",
titles.map((s) => elem("TH", s))
),
...definitions.map(definition2html)
])
],
{ border: "1", cellSpacing: "0", cellPadding: "2" }
);
p2.createCanvas(1, 1);
p2.createDiv().elt.appendChild(table);
recalc();
}
p2.setup = setup;
};
sketch(p);
標準かさ歯車設計†
https://www.khkgears.co.jp/gear_technology/pdf/gijutu.pdf#page=31 の計算をします。
はすばへの対応が中途半端なままです。
LANG:p5js_live
// src/main.ts
var sketch = (p2) => {
const { tan, sin, cos, PI, atan } = 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 * 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);
}
const controls = {};
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: (v2) => e.innerHTML = scale == 0 ? v2 : (v2 / 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: (v2) => e.value = scale == 0 ? v2 : (v2 / 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() {
const table = elem(
"TABLE",
"",
[
elem("TBODY", "", [
elem(
"TR",
"",
titles.map((s) => elem("TH", s))
),
...definitions.map(definition2html)
])
],
{ border: "1", cellSpacing: "0", cellPadding: "2" }
);
p2.createCanvas(1, 1);
p2.createDiv().elt.appendChild(table);
recalc();
}
p2.setup = setup;
};
sketch(p);
ウォームギア設計†
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 += ` ${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);
}

