codex/gear-contact-ratio-overlap.py

gear-contact-ratio-overlap.py
"""Plot ideal spur-gear contact overlap; write PNG in the current directory.

Tested with Python 3.14.4; dependencies: NumPy and Matplotlib (root pyproject.toml).
"""
from pathlib import Path
import os
os.environ.setdefault("MPLCONFIGDIR", "/tmp/codex-wiki-laplace-mpl")
import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt

alpha = np.deg2rad(20.0)
r1, r2, module = 20.0, 40.0, 2.0
rb1, rb2 = r1 * np.cos(alpha), r2 * np.cos(alpha)
length = np.sqrt(22.0**2-rb1**2) + np.sqrt(42.0**2-rb2**2) - 60.0*np.sin(alpha)
base_pitch = np.pi * module * np.cos(alpha)
epsilon = length / base_pitch
fig, (ax, count_ax) = plt.subplots(2, 1, figsize=(7.6, 4.2), dpi=100, sharex=True,
    gridspec_kw={"height_ratios": [2, 1]}, constrained_layout=True)
fig.patch.set_facecolor("white")
colors = ["#6b7280", "#147d92", "#d17b16", "#477b39"]
for j, color in zip(range(-1,3), colors):
    ax.broken_barh([(j, epsilon)], (j-.25, .5), facecolors=color)
ax.set_yticks([-1,0,1,2], ["Pair -1", "Pair 0", "Pair 1", "Pair 2"])
ax.set_ylim(-1.6, 2.6)
ax.set_title(f"Ideal tooth-contact timing: contact ratio = {epsilon:.3f}", fontsize=12)
ax.set_ylabel("Successive tooth pairs")
x = np.linspace(0, 3, 3001)
count = np.zeros_like(x)
for j in range(-2,4):
    count += ((x >= j) & (x < j+epsilon)).astype(int)
count_ax.step(x, count, where="post", color="#173a56", linewidth=2)
count_ax.set_yticks([1,2])
count_ax.set_ylim(.7, 2.3)
count_ax.set_ylabel("Pairs in contact")
count_ax.set_xlabel("Time / tooth-passage period")
for axis in [ax,count_ax]:
    axis.set_facecolor("white")
    axis.grid(axis="x", color="#d6dce0", linewidth=.7)
    axis.spines[["right","top"]].set_visible(False)
count_ax.set_xlim(0, 3)
fig.savefig(Path.cwd()/"gear-contact-ratio-overlap.png", facecolor="white")
plt.close(fig)