codex/screw-thread-pitch-and-lead.py

screw-thread-pitch-and-lead.py
"""Illustrative unwrapped thread geometry; save PNG in the current directory.

Tested with Python 3.14.4, NumPy 2.3.5 and Matplotlib 3.10.7.
Curves are ideal reference helices, not manufactured thread profiles.
"""

from pathlib import Path

import matplotlib

matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np


turn = np.linspace(0, 1, 301)
pitch = 2.0  # millimetres, same in both examples
colors = ["#1766a0", "#ba5518", "#237b43"]
plt.rcParams.update({"font.size": 10, "figure.facecolor": "white"})
fig, axes = plt.subplots(1, 2, figsize=(8.6, 4.8), dpi=100, sharey=True)
fig.subplots_adjust(left=0.08, right=0.985, top=0.79, bottom=0.20, wspace=0.20)
fig.suptitle("Same pitch, different advance per turn", fontsize=15, y=0.96)
for ax, starts in zip(axes, [1, 3], strict=True):
    lead = starts * pitch
    for offset in range(-4, 6):
        ax.plot(turn, lead * turn + offset * pitch, color="#d4d4d4", lw=1.1)
    for start in range(starts):
        curve = lead * turn + start * pitch
        assert np.isclose(curve[-1] - curve[0], lead)
        ax.plot(turn, curve, color=colors[start], lw=2.3)
        ax.scatter([0, 1], [curve[0], curve[-1]], color=colors[start], s=20, zorder=4)
    ax.annotate("", xy=(-0.045, 2), xytext=(-0.045, 0),
                arrowprops={"arrowstyle": "<->", "color": "#333333"})
    ax.text(0.025, 1, "p = 2 mm", fontsize=9, va="center",
            bbox={"facecolor": "white", "edgecolor": "none", "alpha": 0.9})
    ax.annotate("", xy=(1.065, lead), xytext=(1.065, 0),
                arrowprops={"arrowstyle": "<->", "color": colors[0]})
    ax.text(1.095, lead / 2, f"L = {lead:.0f} mm", rotation=90, va="center", fontsize=9,
            color=colors[0])
    ax.set(xlim=(-0.09, 1.22), ylim=(-0.5, 10.7), xlabel="Angular position / one turn")
    ax.set_title("One start" if starts == 1 else "Three starts", fontsize=12)
    ax.set_xticks([0, 0.5, 1])
    ax.set_yticks([0, 2, 4, 6, 8, 10])
    ax.grid(alpha=0.15)
    ax.spines[["top", "right"]].set_visible(False)
axes[0].set_ylabel("Axial position (mm)")
fig.text(0.5, 0.085, "Unwrapped cylindrical surfaces: colored lines track one turn of each start.",
         ha="center", fontsize=10)
fig.text(0.5, 0.04, "Gray continuations repeat; ridge shapes and thread depth are not shown.",
         ha="center", fontsize=10)
fig.savefig(Path("screw-thread-pitch-and-lead.png"), facecolor="white")
plt.close(fig)