05.2 - Project Prototype

05.2 - Project Prototype

Movement Mechanism

Inspiration

Reuleaux Triangle Animation [1]

 

Concept & Mobility Calculations

image_crop.JPG

 

Position & Velocity Calculations

ME 380R Project Prototype Calculations.jpg

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

X-Postions Plot.png
X-Velocities Plot.png

 

Y-Postions Plot.png
Y-Velocities Plot.png

 

Animation

Triangle.gif

Prototype

image.png
20251105_081725-rotate.JPG
20251105_081737.mp4
20251105_084648.jpg
20251104_201257.mp4

 

Punching Mechanism

Inspiration

Screenshot 2025-11-05 214501.png
Velocity Analysis Practice Problems - Problem 2

 

Calculations

Plots

Fig1_theta3.png
fig3_theta4local.jpeg
Fig5_l046.jpeg
Fig7_omega4.jpeg
Fig9_Vcglobal.jpeg
Fig2_theta4.jpeg
Fig4_theta5global.jpeg
Fig6_omega3.jpeg
Fig8_omega4local.jpeg
Fig10_Vclocal.jpeg

 

Animation

punching_mech.gif

 

Prototype

IMG_6832.jpg
IMG_6833.jpg

 

IMG_6834.jpg

 

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()