# Animation of crank-slider mechanism using constraint-based solution
import warnings
warnings.filterwarnings('ignore', message='The iteration is not making good progress')
from matplotlib.animation import FuncAnimation
from IPython.display import HTML
import matplotlib
matplotlib.rcParams['animation.embed_limit'] = 60
r = 50.0 # Crank length
L = 150.0 # Connecting rod length
def constraint_equations(vars, theta):
"""Constraint equations for crank-slider mechanism."""
phi, x_A = vars
rr_OB = r * np.array([-np.cos(theta), np.sin(theta)])
rr_BA = L * np.array([np.cos(phi), np.sin(phi)])
rr_OA_computed = rr_OB + rr_BA
rr_OA_constraint = np.array([x_A, 0])
return rr_OA_computed - rr_OA_constraint
# Prepare animation data
theta_range = np.linspace(0, 2*np.pi, 60)
solutions = []
initial_guess = [np.deg2rad(180), -150]
for theta in theta_range:
sol = scipy.optimize.fsolve(constraint_equations, initial_guess, args=(theta,))
solutions.append((theta, sol[0], sol[1])) # (theta, phi, x_A)
initial_guess = sol
fig, ax = plt.subplots(figsize=(12, 6))
def plot_mechanism(theta, phi, x_A):
"""Plot the crank-slider mechanism for given configuration."""
ax.clear()
# Calculate positions
O = np.array([0, 0])
B = r * np.array([-np.cos(theta), np.sin(theta)])
A = np.array([x_A, 0])
# Plot ground
ground_x = np.linspace(-250, 50, 100)
ax.plot(ground_x, np.zeros_like(ground_x), 'k-', linewidth=3, alpha=0.3)
# Plot slider rail
ax.plot([-250, 50], [0, 0], 'k-', linewidth=5)
# Plot crank OB
ax.plot([O[0], B[0]], [O[1], B[1]], 'b-o', linewidth=3, markersize=8, label='Crank')
# Plot connecting rod BA
ax.plot([B[0], A[0]], [B[1], A[1]], 'r-o', linewidth=3, markersize=8, label='Connecting rod')
# Plot slider
slider_width = 20
slider_height = 15
slider_rect = plt.Rectangle((x_A - slider_width/2, -slider_height/2),
slider_width, slider_height,
facecolor='green', edgecolor='black', linewidth=2)
ax.add_patch(slider_rect)
# Plot origin
ax.plot(O[0], O[1], 'ko', markersize=10, label='O (origin)')
# Labels
ax.text(B[0] + 5, B[1] + 5, 'B', fontsize=12, fontweight='bold')
ax.text(A[0], A[1] + 10, 'A', fontsize=12, fontweight='bold')
ax.text(O[0] - 10, O[1] + 10, 'O', fontsize=12, fontweight='bold')
# Settings
ax.set_xlim(-220, 60)
ax.set_ylim(-80, 80)
ax.set_aspect('equal', adjustable='box')
ax.grid(True, alpha=0.3)
ax.set_xlabel('x [mm]')
ax.set_ylabel('y [mm]')
ax.set_title(f'Crank-Slider Mechanism (θ = {np.rad2deg(theta):.1f}°, φ = {np.rad2deg(phi):.1f}°)')
ax.legend(loc='upper right')
def animate(frame_idx):
"""Animation function."""
theta, phi, x_A = solutions[frame_idx]
plot_mechanism(theta, phi, x_A)
# Create animation
plt.close(fig)
anim = FuncAnimation(fig, animate, frames=len(solutions),
interval=50, repeat=True)
# Convert to HTML5 video
video_html = anim.to_html5_video()
video_html = video_html.replace('controls', 'controls autoplay loop muted')
video_html = video_html.replace('<video ', '<video style="width: 100%; height: auto;" ')
HTML(video_html)