05.2 - Project Prototype
Movement Mechanism
Inspiration
Concept & Mobility Calculations
Position & Velocity Calculations
The location of Point D was then calculated by translating the location of Point C by length l in the x-direction. Vx and Vy for Point C were calculated by taking the difference of the past x-position/y-position from the current x-position/y-position.
No acceleration analysis was conducted as the input speed would be constant and the acceleration of Points C and D would not affect the motion of the mechanism.
No force analysis calculations were conducted as this mechanism would not exert any movement related force on the rest of the system.
Plots
Animation
Prototype
Punching Mechanism
Inspiration
Calculations
Plots
Animation
Prototype
Bill Of Materials
Item | Description | Qty | Estimated Cost ($) |
3D printer filament | Fighting Robots/Complex Geometry | As needed | 0.00 |
¼” TIW plywood - Large | FIghting Ring Base & Walls | ~5 | 20.00 |
¼” TIW Acrylic Clear - Large | Fighting Ring Cover | ~1 | 5.00 |
DC motors | Actuate Punching Mechanism | 4 | 9.68 |
NEMA 17 Stepper motor | Actuate Reuleaux Movement | 1 | 15.98 |
L298 Motor Controller | Connect Arduino to Drive Motors | 2 | 25.80 |
Arduino Uno | Microcontroller for mechanism control | 1 | 27.60 |
Power Supply | Power Actuators | 1 | 0.00 |
Jumper Wire Cables | Wire Components | 50 | 6.98 |
Metric Nut/Bolt/Screw Set | Fasten Components | 1 | 25.99 |
Joystick Controller | Player Input for Mechanisms | 2 | 11.90 |
References
[1] https://en.wikipedia.org/wiki/Reuleaux_triangle
Movement Mechanism Code
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
class Visualize():
# Constructor
def __init__(self):
self.l = 2.5
self.lk = 2
self.ls = 2
self.x1 = -1.25
self.x2 = 1.25
self.y1 = -1.25
self.y2 = -1.25
self.ylim = 6
self.xlim = 6
ds = list(range(361))
self.xAs = []
self.xBs = []
self.ys = []
for d in ds:
if d < 90:
xA = 0
xB = self.l
y = (self.l/90)*d
elif d < 180:
xA = (-self.l/90)*(d-90)
xB = (-self.l/90)*(d-90)+self.l
y = self.l
elif d < 270:
xA = -self.l
xB = 0
y = ((-self.l/90)*(d-180))+self.l
else:
xA = ((self.l/90)*(d-270))-self.l
xB = ((self.l/90)*(d-270))
y = 0
self.xAs.append(xA)
self.xBs.append(xB)
self.ys.append(y)
self.yCs = []
self.xCs = []
for d in ds:
xA = self.xAs[d]
yA = self.ys[d]
lT = np.sqrt(((xA-self.x1)**2)+((yA-self.y1)**2))
C = np.arccos(((lT**2)+(self.ls**2)-(self.lk**2))/(2*lT*self.ls))
base = abs(xA-self.x1)
if base == 0:
al = np.deg2rad(90)
else:
al = np.arctan(abs(yA-self.y1)/base)
if xA < self.x1:
angle = np.deg2rad(180) - al - C
else:
angle = al - C
yC = self.y1 + self.ls*np.sin(angle)
xC = self.x1 + self.ls*np.cos(angle)
tx = np.sqrt(((yC-yA)**2)+((xC-xA)**2))
self.xCs.append(xC)
self.yCs.append(yC)
self.vxAs = []
self.vyAs = []
self.vxCs = []
self.vyCs = []
for d in ds:
vxA = self.xAs[d]-self.xAs[d-1]
self.vxAs.append(vxA)
vyA = self.ys[d]-self.ys[d-1]
self.vyAs.append(vyA)
vxC = self.xCs[d]-self.xCs[d-1]
self.vxCs.append(vxC)
vyC = self.yCs[d]-self.yCs[d-1]
self.vyCs.append(vyC)
plt.figure()
plt.grid()
plt.plot(ds, self.xAs)
plt.plot(ds, self.xBs)
plt.plot(ds, self.xCs)
plt.plot(ds, [x + self.l for x in self.xCs])
plt.title('Input Degree Versus X-Position')
plt.xlabel('Input Degree (°)')
plt.ylabel('X-Position (in)')
plt.legend(['Point A', 'Point B', 'Point C', 'Point D'])
plt.savefig('X-Postions Plot.png')
plt.figure()
plt.grid()
plt.plot(ds, self.ys)
plt.plot(ds, self.yCs)
plt.title('Input Degree Versus Y-Position')
plt.xlabel('Input Degree (°)')
plt.ylabel('Y-Position (in)')
plt.legend(['Points A & B', 'Points C & D'])
plt.savefig('Y-Postions Plot.png')
plt.figure()
plt.grid()
plt.plot(ds, self.vxAs)
plt.plot(ds, self.vxCs)
plt.title('Input Degree Versus X-Velocity')
plt.xlabel('Input Degree (°)')
plt.ylabel('X-Velocity (in/°)')
plt.legend(['Points A & B', 'Points C & D'])
plt.savefig('X-Velocities Plot.png')
plt.figure()
plt.grid()
plt.plot(ds, self.vyAs)
plt.plot(ds, self.vyCs)
plt.title('Input Degree Versus Y-Velocity')
plt.xlabel('Input Degree (°)')
plt.ylabel('Y-Velocity (in/°)')
plt.legend(['Points A & B', 'Points C & D'])
plt.savefig('Y-Velocities Plot.png')
self.fig, self.ax = plt.subplots(1,1,figsize=(6,6)) # New figure
self.ax.set_aspect('equal')
self.fig.set_tight_layout(True)
self.ax.set_xlim([-self.xlim,self.xlim])
self.ax.set_ylim([-self.ylim,self.ylim])
# Centers the axes
self.ax.spines['left'].set_position('center')
self.ax.spines['bottom'].set_position('center')
self.ax.spines['right'].set_color('none')
self.ax.spines['top'].set_color('none')
def plot(self,d):
d = int(d)
self.ax.clear()
self.ax.set_aspect('equal')
self.fig.set_tight_layout(True)
self.ax.set_xlim([-self.xlim,self.xlim])
self.ax.set_ylim([-self.ylim,self.ylim])
# Centers the axes
self.ax.spines['left'].set_position('center')
self.ax.spines['bottom'].set_position('center')
self.ax.spines['right'].set_color('none')
self.ax.spines['top'].set_color('none')
self.ax.plot([self.xAs[d],self.xBs[d]],[self.ys[d],self.ys[d]],color='purple', linestyle='solid')
self.ax.plot([self.x1,self.x2],[self.y1,self.y2],color='black', linestyle='solid')
self.ax.plot([self.xCs[d],self.xCs[d]+self.l],[self.yCs[d],self.yCs[d]],color='magenta', linestyle='solid')
self.ax.plot([self.xAs[d],self.xCs[d]],[self.ys[d],self.yCs[d]],color='pink', linestyle='solid')
self.ax.plot([self.xBs[d],self.xCs[d]+self.l],[self.ys[d],self.yCs[d]],color='blue', linestyle='solid')
self.ax.plot([self.xCs[d],self.x1],[self.yCs[d],self.y1],color='orange', linestyle='solid')
self.ax.plot([self.xCs[d]+self.l,self.x2],[self.yCs[d],self.y2],color='red', linestyle='solid')
self.ax.plot([self.xAs[d],self.xAs[d]],[self.ys[d],self.ys[d]+self.l],color='green', linestyle='solid')
self.ax.plot([self.xBs[d],self.xBs[d]],[self.ys[d],self.ys[d]+self.l],color='green', linestyle='solid')
self.ax.plot([self.xAs[d],self.xBs[d]],[self.ys[d]+self.l,self.ys[d]+self.l],color='green', linestyle='solid')
self.ax.arrow(self.xAs[d],self.ys[d],50*self.vxAs[d],50*self.vyAs[d],head_width=0.1,width=0.001,length_includes_head=True,linestyle='dashed',color='brown',overhang=1.0)
self.ax.arrow(self.xBs[d],self.ys[d],50*self.vxAs[d],50*self.vyAs[d],head_width=0.1,width=0.001,length_includes_head=True,linestyle='dashed',color='brown',overhang=1.0)
self.ax.arrow(self.xCs[d],self.yCs[d],50*self.vxCs[d],50*self.vyCs[d],head_width=0.1,width=0.001,length_includes_head=True,linestyle='dashed',color='cyan',overhang=1.0)
self.ax.arrow(self.xCs[d]+self.l,self.yCs[d],50*self.vxCs[d],50*self.vyCs[d],head_width=0.1,width=0.001,length_includes_head=True,linestyle='dashed',color='cyan',overhang=1.0)
self.ax.arrow(self.xAs[d],self.ys[d]+self.l,50*self.vxAs[d],50*self.vyAs[d],head_width=0.1,width=0.001,length_includes_head=True,linestyle='dashed',color='olive',overhang=1.0)
self.ax.arrow(self.xBs[d],self.ys[d]+self.l,50*self.vxAs[d],50*self.vyAs[d],head_width=0.1,width=0.001,length_includes_head=True,linestyle='dashed',color='olive',overhang=1.0)
self.ax.scatter([self.xAs[d]],[self.ys[d]], s=200, marker='.', color='black')
self.ax.scatter([self.xBs[d]],[self.ys[d]], s=200, marker='.', color='black')
self.ax.scatter([self.xCs[d]],[self.yCs[d]], s=200, marker='.', color='black')
self.ax.scatter([self.xCs[d]+self.l],[self.yCs[d]], s=200, marker='.', color='black')
self.ax.scatter([self.x1],[self.y1], s=200, marker='.', color='black')
self.ax.scatter([self.x2],[self.y2], s=200, marker='.', color='black')
self.ax.scatter([self.xAs[d]],[self.ys[d]+self.l], s=200, marker='.', color='black')
self.ax.scatter([self.xBs[d]],[self.ys[d]+self.l], s=200, marker='.', color='black')
self.ax.set_aspect('equal')
self.fig.set_tight_layout(True)
self.ax.set_xlim([-self.xlim,self.xlim])
self.ax.set_ylim([-self.ylim,self.ylim])
# Centers the axes
self.ax.spines['left'].set_position('center')
self.ax.spines['bottom'].set_position('center')
self.ax.spines['right'].set_color('none')
self.ax.spines['top'].set_color('none')
# Function to animate the crank
def animate(self):
# Runs on the first animation frame
def init():
self.ax.clear()
self.ax.set_aspect('equal')
self.fig.set_tight_layout(True)
self.ax.set_xlim([-self.xlim,self.xlim])
self.ax.set_ylim([-self.ylim,self.ylim])
# Centers the axes
self.ax.spines['left'].set_position('center')
self.ax.spines['bottom'].set_position('center')
self.ax.spines['right'].set_color('none')
self.ax.spines['top'].set_color('none')
# Adds the four bar plotting function to the animation
def getFrames(frames):
self.plot(frames)
# Animation function
delay_interval = 0.25
theta_frames = np.linspace(0,360,361)
anim = FuncAnimation(self.fig, getFrames, init_func=init, frames=theta_frames[::1], interval=delay_interval,repeat_delay=0.25)
# Save as GIF
anim.save('Triangle.gif', dpi=200, writer='pillow')
if __name__ == '__main__':
mechanism = Visualize()
mechanism.animate()
Punching Mechanism Analysis Code
"""
Team 5
Six-Bar Position & Velocity Analysis
(Four-Bar + Slider-Crank with offset)
"""
import numpy as np
import matplotlib.pyplot as plt
# offset of 0.5
# First all the helper functions
def fourbarpos(l, th_1, th_2, delta):
th_1_rad = th_1 * np.pi / 180
th_2_rad = th_2 * np.pi / 180
# Find my K constants for link lengths
K1 = l[0] / l[1]
K2 = l[0] / l[3]
K3 = (l[1] ** 2 - l[2] ** 2 + l[3] ** 2 + l[0] ** 2) / (2 * l[1] * l[3])
K4 = l[0] / l[2]
K5 = (l[3] ** 2 - l[0] ** 2 - l[1] ** 2 - l[2] ** 2) / (2 * l[1] * l[2])
# Quadratic constants with input angle th_2
A = -K1 - K2 * np.cos(th_2_rad) + K3 + np.cos(th_2_rad)
B = -2 * np.sin(th_2_rad)
C = K1 - K2 * np.cos(th_2_rad) + K3 - np.cos(th_2_rad)
D = np.cos(th_2_rad) - K1 + K4 * (np.cos(th_2_rad)) + K5
E = -2 * np.sin(th_2_rad)
F = K1 + (K4 - 1) * np.cos(th_2_rad) + K5
# Check for imaginary roots. We won't handle those here. -- should be eliminated with grashof condition but this can help check
disc_4 = B**2 - 4 * A * C
disc_3 = E**2 - 4 * D * F
if disc_4 < 0 or disc_3 < 0:
print("This function does not handle imaginary roots")
return
th_4_rad = 2 * np.arctan2((-B + delta * np.sqrt(disc_4)), (2 * A))
th_3_rad = 2 * np.arctan2((-E + delta * np.sqrt(disc_3)), (2 * D))
th_vec_rad = [th_1_rad, th_2_rad, th_3_rad, th_4_rad]
th_vec = [(x * 180 / np.pi) % 360 for x in th_vec_rad]
return th_vec
def fourbarvel(l, th_vec, w_2):
th_vec = [x * np.pi / 180 for x in th_vec]
w_3 = (
w_2
* l[1]
/ l[2]
* (np.sin(th_vec[3] - th_vec[1]))
/ (np.sin(th_vec[2] - th_vec[3]))
)
w_4 = (
w_2
* l[1]
/ l[3]
* (np.sin(th_vec[1] - th_vec[2]))
/ (np.sin(th_vec[3] - th_vec[2]))
)
VA = np.multiply([-np.sin(th_vec[1]), np.cos(th_vec[1])], (l[1] * w_2))
VBA = np.multiply([-np.sin(th_vec[2]), np.cos(th_vec[2])], (l[2] * w_3))
VB = np.multiply([-np.sin(th_vec[3]), np.cos(th_vec[3])], (l[3] * w_4))
w_vec_out = [0, w_2, w_3, w_4]
return w_vec_out, VA, VBA, VB
def slidercrankpos(l, th_1, th_2, th_4, delta):
th_1 = th_1 * np.pi / 180
th_2 = th_2 * np.pi / 180
th_4 = th_4 * np.pi / 180
if delta == -1:
# Obtain theta3 for open chain
th_3 = np.arcsin(-(l[0] * np.sin(th_2) - l[2]) / l[1]) + np.pi
else:
# Obtain theta3 for cross chain
th_3 = np.arcsin((l[0] * np.sin(th_2) - l[2]) / l[1])
# Compute l1
l1 = l[0] * np.cos(th_2) - l[1] * np.cos(th_3)
th_vec = [th_1, th_2, th_3, th_4]
return l1, [x * 180 / np.pi for x in th_vec]
def slidercrankvel(l_vec, th_vec, w_2):
th_vec = [x * np.pi / 180 for x in th_vec]
# w_3 for either configuration
w_3 = (l_vec[1]) / l_vec[2] * (np.cos(th_vec[1]) / np.cos(th_vec[2])) * w_2
# Compute l1_dot
l1_dot = -l_vec[1] * np.sin(th_vec[1]) * w_2 + l_vec[2] * w_3 * np.sin(th_vec[2])
return l1_dot, w_3
# %% Problem Code starts here
# Link lengths and input angle
l1 = 1.000 # [inches]
l2 = 2.000 # [inches]
l3 = 2.000 # [inches]
l4 = 2.300 # [inches]
l5 = 4 # [inches]
l6 = 0.5 # [inches] no offset
alpha_global = 102 # deg
w_2 = -1 # rad/sec
# Just some set up of variables
theta_2_vec = np.linspace(0, 360, 1000)
theta_2_vec_local = (theta_2_vec + 102) % 360
l_vec = [l1, l2, l3, l4]
delta1 = -1 # open circuit
delta2 = -1 # open circuit
# Setting up arrays
l1_slide = np.zeros([1, len(theta_2_vec)])
l4_arr = np.full([1, len(theta_2_vec)], l4)
l_vec_slide = np.concatenate(
(
l1_slide,
np.full([1, len(theta_2_vec)], l4),
np.full([1, len(theta_2_vec)], l5),
np.full([1, len(theta_2_vec)], l6),
),
axis=0,
)
th_vec_out = np.zeros([4, len(theta_2_vec)])
th_vec_slide = np.zeros([4, len(theta_2_vec)])
w_vec_out = np.zeros([4, len(theta_2_vec)])
VA = np.zeros([2, len(theta_2_vec)])
VBA = np.zeros([2, len(theta_2_vec)])
VB = np.zeros([2, len(theta_2_vec)])
l_1_dot_b = np.zeros(len(theta_2_vec))
w_5 = np.zeros(len(theta_2_vec))
VC_out = np.zeros(len(theta_2_vec))
# %% Calculations start here
for thIdx in range(0, len(theta_2_vec)):
# Original fourbar position analysis
th_vec_out[:, thIdx] = fourbarpos(l_vec, 0, theta_2_vec_local[thIdx], delta1)
# Put the important outputs into global terms
th_vec_out_global = [
0,
(th_vec_out[3, thIdx] - alpha_global) % 360,
0,
90,
] # th_5 is dummy
# Slider block analysis
l_vec_slide[0, thIdx], th_vec_slide[:, thIdx] = slidercrankpos(
l_vec_slide[1:, thIdx],
th_vec_out_global[0],
th_vec_out_global[1],
th_vec_out_global[3],
delta2,
)
# Fourbar velocity analysis
w_vec_out[:, thIdx], VA[:, thIdx], VBA[:, thIdx], VB[:, thIdx] = fourbarvel(
l_vec, th_vec_out[:, thIdx], w_2
)
# Slider block velocity analysis
l_1_dot_b[thIdx], w_5[thIdx] = slidercrankvel(
l_vec_slide[:, thIdx], th_vec_slide[:, thIdx], w_vec_out[3, thIdx]
)
VC_out[thIdx] = l_1_dot_b[thIdx]
# %% Plotting
fig, ax = plt.subplots()
plt.subplots_adjust(hspace=0.6)
ax.plot(theta_2_vec, (th_vec_out[2, :] - alpha_global) % 360)
ax.set_title(r"$\theta_3$ vs input angle")
ax.set_xlabel(r"$\theta_2$ (deg)")
ax.set_ylabel(r"$\theta_3$ (deg)")
ax.grid()
fig, ax = plt.subplots()
plt.subplots_adjust(hspace=0.6)
ax.plot(theta_2_vec, (th_vec_out[3, :] - alpha_global) % 360)
ax.set_title(r"$\theta_4$ vs input angle")
ax.set_xlabel(r"$\theta_2$ (deg)")
ax.set_ylabel(r"$\theta_4$ (deg)")
ax.grid()
fig, ax = plt.subplots()
plt.subplots_adjust(hspace=0.6)
ax.plot(theta_2_vec - alpha_global, (th_vec_out[3, :]) % 360)
ax.set_title(r"$\theta_4$ vs input angle (local)")
ax.set_xlabel(r"$\theta_2$ (deg)")
ax.set_ylabel(r"$\theta_4$ (deg)")
ax.grid()
fig, ax = plt.subplots()
plt.subplots_adjust(hspace=0.6)
ax.plot(theta_2_vec, (th_vec_slide[2, :]) % 360)
ax.set_title(r"$\theta_5$ vs input angle (global)")
ax.set_xlabel(r"$\theta_2$ (deg)")
ax.set_ylabel(r"$\theta_5$ (deg)")
ax.grid()
fig, ax = plt.subplots()
plt.subplots_adjust(hspace=0.6)
ax.plot(theta_2_vec, l_vec_slide[0, :])
ax.set_title(r"$l_{O_{4}O_{6}}$ vs input angle (global)")
ax.set_xlabel(r"$\theta_2$ (deg)")
ax.set_ylabel(r"$l_{O_{4}O_{6}}$")
ax.grid()
fig, ax = plt.subplots()
plt.subplots_adjust(hspace=0.6)
ax.plot(theta_2_vec, w_vec_out[2, :])
ax.set_title(r"$\omega_3$ vs input angle")
ax.set_xlabel(r"$\theta_2$ (deg)")
ax.set_ylabel(r"$\omega_3$ (rad/sec)")
ax.grid()
fig, ax = plt.subplots()
plt.subplots_adjust(hspace=0.6)
ax.plot(theta_2_vec, w_vec_out[3, :])
ax.set_title(r"$\omega_4$ vs input angle ($\theta_{2_{global}}=0$))")
ax.set_xlabel(r"$\theta_2$ (deg)")
ax.set_ylabel(r"$\omega_4$ (rad/sec)")
ax.grid()
th2idx = np.where(abs(theta_2_vec - 258) < 0.05)[0][0]
w_vec_plot = np.concatenate([w_vec_out[3, th2idx:], w_vec_out[3, 0:th2idx]])
fig, ax = plt.subplots()
plt.subplots_adjust(hspace=0.6)
ax.plot(theta_2_vec, w_vec_plot)
ax.set_title(r"$\omega_4$ vs input angle ($\theta_{2_{local}}=0$))")
ax.set_xlabel(r"$\theta_2$ (deg)")
ax.set_ylabel(r"$\omega_4$ (rad/sec)")
ax.grid()
fig, ax = plt.subplots()
plt.subplots_adjust(hspace=0.6)
ax.plot(theta_2_vec, VC_out)
ax.set_title(r"$V_C$ vs input angle ($\theta_{2_{global}}=0$))")
ax.set_xlabel(r"$\theta_2$ (deg)")
ax.set_ylabel(r"$V_C$")
ax.grid()
VC_vec_plot = np.concatenate([VC_out[th2idx:], VC_out[0:th2idx]])
fig, ax = plt.subplots()
plt.subplots_adjust(hspace=0.6)
ax.plot(theta_2_vec, VC_vec_plot)
ax.set_title(r"$V_C$ vs input angle ($\theta_{2_{local}}=0$))")
ax.set_xlabel(r"$\theta_2$ (deg)")
ax.set_ylabel(r"$V_C$")
ax.grid()
plt.show()
Punching Mechanism Animation Code
"""
Team 5
Six-Bar Visualization (Four-Bar + Slider-Crank with offset)
Based on Meredith Symmank – FourBarVisualize.py
"""
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
class FourBarVisualize:
# Constructor
def __init__(self, length=2.0):
# --- Mechanism parameters ---
self.l_vec = [1.000, 2, 2, 2.30] # l1–l4 (four-bar)
self.l5 = 4.0 # connecting-rod length
self.l6 = 0.5 # slider-offset distance
self.omega_2 = -1.0 # rad/s (CW)
self.offset = -102 # deg phase offset
# --- Figure setup ---
self.fig, self.ax = plt.subplots(figsize=(6, 6))
self.ax.set_aspect("equal")
self.fig.set_tight_layout(True)
lim = 2 * max(self.l_vec + [self.l5])
self.ax.set_xlim([-lim, lim])
self.ax.set_ylim([-lim, lim])
# Center the axes visually
self.ax.spines["left"].set_position("center")
self.ax.spines["bottom"].set_position("center")
self.ax.spines["right"].set_color("none")
self.ax.spines["top"].set_color("none")
# ------------------------------------------------------------------
# Geometry solvers
# ------------------------------------------------------------------
def fourbarpos(self, th_1, th_2, delta):
K1 = self.l_vec[0] / self.l_vec[1]
K2 = self.l_vec[0] / self.l_vec[3]
K3 = (
self.l_vec[1] ** 2
- self.l_vec[2] ** 2
+ self.l_vec[3] ** 2
+ self.l_vec[0] ** 2
) / (2 * self.l_vec[1] * self.l_vec[3])
K4 = self.l_vec[0] / self.l_vec[2]
K5 = (
self.l_vec[3] ** 2
- self.l_vec[0] ** 2
- self.l_vec[1] ** 2
- self.l_vec[2] ** 2
) / (2 * self.l_vec[1] * self.l_vec[2])
th_1 = np.deg2rad(th_1)
th_2 = np.deg2rad(th_2)
A = -K1 - K2 * np.cos(th_2) + K3 + np.cos(th_2)
B = -2 * np.sin(th_2)
C = K1 - K2 * np.cos(th_2) + K3 - np.cos(th_2)
D = np.cos(th_2) - K1 + K4 * np.cos(th_2) + K5
E = -2 * np.sin(th_2)
F = K1 + (K4 - 1) * np.cos(th_2) + K5
disc_4 = B**2 - 4 * A * C
disc_3 = E**2 - 4 * D * F
if disc_4 < 0 or disc_3 < 0:
return False
th_4 = 2 * np.arctan2((-B + delta * np.sqrt(disc_4)), (2 * A))
th_3 = 2 * np.arctan2((-E + delta * np.sqrt(disc_3)), (2 * D))
return [np.rad2deg(x) for x in [th_1, th_2, th_3, th_4]]
def fourbarvel(self, th_vec, omega_2):
th_vec = [np.deg2rad(x) for x in th_vec]
omega_3 = (
omega_2
* self.l_vec[1]
/ self.l_vec[2]
* (np.sin(th_vec[3] - th_vec[1]))
/ (np.sin(th_vec[2] - th_vec[3]))
)
omega_4 = (
omega_2
* self.l_vec[1]
/ self.l_vec[3]
* (np.sin(th_vec[1] - th_vec[2]))
/ (np.sin(th_vec[3] - th_vec[2]))
)
VA = [
self.l_vec[1] * omega_2 * x for x in [-np.sin(th_vec[1]), np.cos(th_vec[1])]
]
VBA = [
self.l_vec[2] * omega_3 * x for x in [-np.sin(th_vec[2]), np.cos(th_vec[2])]
]
VB = [
self.l_vec[3] * omega_4 * x for x in [-np.sin(th_vec[3]), np.cos(th_vec[3])]
]
return [0, omega_2, omega_3, omega_4], VA, VBA, VB
def slidercrankpos(self, l_vec, th_4_deg, delta=-1):
"""Return slider position and connecting-rod angle."""
l4, l5, l6 = l_vec
th4 = np.deg2rad(th_4_deg)
if delta == -1:
th5 = np.arcsin(-(l4 * np.sin(th4) - l6) / l5) + np.pi
else:
th5 = np.arcsin((l4 * np.sin(th4) - l6) / l5)
x_slider = l4 * np.cos(th4) - l5 * np.cos(th5)
return x_slider, np.rad2deg(th5)
def slider_points(self, O4, l_vec, th_4_deg, delta=-1):
"""Compute coordinates of crank pin, rod end, and slider center."""
l4, l5, l6 = l_vec
th4 = np.deg2rad(th_4_deg)
A = (O4[0] + l4 * np.cos(th4), O4[1] + l4 * np.sin(th4))
x_slider, th5_deg = self.slidercrankpos(l_vec, th_4_deg, delta)
B = (O4[0] + x_slider, O4[1] + l6)
return A, B, th5_deg
def getBoundedAngle(self, angle):
if angle < 0:
return 360 + angle
elif angle > 360:
return angle - 360
else:
return angle
# ------------------------------------------------------------------
# Plot single frame
# ------------------------------------------------------------------
def fourBarPlot(self, th_2_in, showVelArrows=True):
self.ax.clear()
self.ax.set_aspect("equal")
lim = 2 * max(self.l_vec + [self.l5])
self.ax.set_xlim([-lim, lim])
self.ax.set_ylim([-lim, lim])
for spine in ["left", "bottom"]:
self.ax.spines[spine].set_position("center")
for spine in ["right", "top"]:
self.ax.spines[spine].set_color("none")
th_pos_soln = self.fourbarpos(0, th_2_in, -1)
if not th_pos_soln:
return
th_pos_soln = [self.getBoundedAngle(x + self.offset) for x in th_pos_soln]
omega_vec_out, VA, VBA, VB = self.fourbarvel(th_pos_soln, self.omega_2)
th_pos_soln = [np.deg2rad(x) for x in th_pos_soln]
R_AO2X = self.l_vec[1] * np.cos(th_pos_soln[1])
R_AO2Y = self.l_vec[1] * np.sin(th_pos_soln[1])
R_BX = R_AO2X + self.l_vec[2] * np.cos(th_pos_soln[2])
R_BY = R_AO2Y + self.l_vec[2] * np.sin(th_pos_soln[2])
R_O4X = self.l_vec[0] * np.cos(th_pos_soln[0])
R_O4Y = self.l_vec[0] * np.sin(th_pos_soln[0])
# --- Draw four-bar ---
self.ax.plot([0, R_AO2X], [0, R_AO2Y], "r-", lw=2)
self.ax.plot([R_AO2X, R_BX], [R_AO2Y, R_BY], "b-", lw=2)
self.ax.plot([R_O4X, R_BX], [R_O4Y, R_BY], "g-", lw=2)
self.ax.plot([0, R_O4X], [0, R_O4Y], "k-", lw=2)
# --- Add slider-crank extension ---
A_slider, B_slider, th5 = self.slider_points(
(R_O4X, R_O4Y),
(self.l_vec[3], self.l5, self.l6),
np.rad2deg(th_pos_soln[3]),
)
self.ax.plot(
[R_O4X, A_slider[0]], [R_O4Y, A_slider[1]], color="purple", lw=2
) # crank
self.ax.plot(
[A_slider[0], B_slider[0]], [A_slider[1], B_slider[1]], color="orange", lw=2
) # connecting rod
slider_size = 0.25
self.ax.add_patch(
plt.Rectangle(
(B_slider[0] - slider_size / 2, B_slider[1] - slider_size / 2),
slider_size,
slider_size,
color="gray",
)
)
if showVelArrows:
self.ax.arrow(
R_AO2X,
R_AO2Y,
VA[0],
VA[1],
head_width=0.1,
color="red",
linestyle="dashed",
)
self.ax.arrow(
R_BX,
R_BY,
VBA[0],
VBA[1],
head_width=0.1,
color="blue",
linestyle="dashed",
)
self.ax.arrow(
R_BX,
R_BY,
VB[0],
VB[1],
head_width=0.1,
color="green",
linestyle="dashed",
)
self.ax.set_title("Six-Bar Mechanism (Four-Bar + Slider-Crank Offset l6)")
# ------------------------------------------------------------------
# Animation loop
# ------------------------------------------------------------------
def animate(self):
def init():
self.ax.clear()
def getFrames(frame):
self.fourBarPlot(frame - self.offset)
delay_interval = round(1 / abs(self.omega_2 * 180 / np.pi) * 2 * 1000)
theta_frames = np.linspace(0, 360, 361)
anim = FuncAnimation(
self.fig,
getFrames,
init_func=init,
frames=theta_frames[::-1],
interval=delay_interval,
repeat_delay=100,
)
# plt.show()
anim.save(
r"C:\Users\kevin\Desktop\RMD\punching_mech.gif", dpi=200, writer="pillow"
)
# ----------------------------------------------------------------------
# Run the animation
# ----------------------------------------------------------------------
if __name__ == "__main__":
mechanism = FourBarVisualize()
mechanism.animate()