gokul00060 commited on
Commit
f153e8e
·
0 Parent(s):

initial commit

Browse files
Files changed (3) hide show
  1. README.md +10 -0
  2. app.py +241 -0
  3. requirements.txt +3 -0
README.md ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Interactive Arm Simulator
3
+ emoji: 🤖
4
+ colorFrom: blue
5
+ colorTo: green
6
+ sdk: gradio
7
+ sdk_version: 3.45.0
8
+ app_file: app.py
9
+ pinned: false
10
+ ---
app.py ADDED
@@ -0,0 +1,241 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ # Original main code from: https://github.com/gokul6350/ARMv6/blob/main/main_src/inverse_k.py
3
+ # Adapted and built into an interactive Gradio App for educational purposes.
4
+ # --- MODIFIED TO INCLUDE A CODE-COPYING DROPDOWN ---
5
+
6
+ import gradio as gr
7
+ import matplotlib.pyplot as plt
8
+ import numpy as np
9
+ import math
10
+
11
+ # --- Core Inverse Kinematics Logic (Refactored and Improved) ---
12
+ def inverse_kinematics_2dof(x, y, l1, l2):
13
+ """
14
+ Calculates the joint angles (q1, q2) for a 2-DOF robotic arm.
15
+
16
+ Args:
17
+ x (float): The x-coordinate of the end effector.
18
+ y (float): The y-coordinate of the end effector.
19
+ l1 (float): The length of the first arm segment.
20
+ l2 (float): The length of the second arm segment.
21
+
22
+ Returns:
23
+ tuple: (success, q1, q2) where q1 and q2 are in radians.
24
+ 'success' is False if the point is unreachable.
25
+ """
26
+ # Check if the target is reachable
27
+ dist_sq = x**2 + y**2
28
+ if dist_sq > (l1 + l2)**2 or dist_sq < (l1 - l2)**2:
29
+ return (False, 0, 0)
30
+
31
+ # Calculate q2 using the Law of Cosines
32
+ # The value must be clamped to [-1.0, 1.0] to avoid math errors from float precision
33
+ cos_q2_arg = (dist_sq - l1**2 - l2**2) / (2 * l1 * l2)
34
+ cos_q2_arg = np.clip(cos_q2_arg, -1.0, 1.0)
35
+ q2 = math.acos(cos_q2_arg)
36
+
37
+ # Calculate q1 using atan2 for full quadrant support
38
+ angle_to_target = math.atan2(y, x)
39
+ angle_l1_to_ee = math.atan2(l2 * math.sin(q2), l1 + l2 * math.cos(q2))
40
+ q1 = angle_to_target - angle_l1_to_ee
41
+
42
+ return (True, q1, q2)
43
+
44
+ # --- Main Simulation and Plotting Function for Gradio ---
45
+ def run_simulation(x, y, l1, l2):
46
+ """
47
+ This function is called by Gradio. It runs the inverse kinematics,
48
+ generates a plot using Matplotlib, and returns the results.
49
+ """
50
+ success, q1_rad, q2_rad = inverse_kinematics_2dof(x, y, l1, l2)
51
+
52
+ status_message = "Success!"
53
+ shoulder_angle_text = ""
54
+ elbow_angle_text = ""
55
+
56
+ # Create the plot using Matplotlib. This is the standard way to create
57
+ # complex visualizations for Gradio's gr.Plot component.
58
+ fig, ax = plt.subplots(figsize=(6, 6))
59
+
60
+ if not success:
61
+ status_message = f"Target ({x:.1f}, {y:.1f}) is unreachable."
62
+ ax.plot(x, y, 'rx', markersize=10, label='Unreachable Target')
63
+ ax.legend()
64
+ else:
65
+ q1_deg = math.degrees(q1_rad)
66
+ q2_deg = math.degrees(q2_rad)
67
+
68
+ joint2_x = l1 * np.cos(q1_rad)
69
+ joint2_y = l1 * np.sin(q1_rad)
70
+ end_effector_x = joint2_x + l2 * np.cos(q1_rad + q2_rad)
71
+ end_effector_y = joint2_y + l2 * np.sin(q1_rad + q2_rad)
72
+
73
+ ax.plot([0, joint2_x], [0, joint2_y], 'r-', linewidth=4, label=f'Arm 1 (L1={l1})')
74
+ ax.plot([joint2_x, end_effector_x], [joint2_y, end_effector_y], 'b-', linewidth=4, label=f'Arm 2 (L2={l2})')
75
+ ax.plot(0, 0, 'ko', markersize=10, label='Joint 1 (Shoulder)')
76
+ ax.plot(joint2_x, joint2_y, 'go', markersize=10, label='Joint 2 (Elbow)')
77
+ ax.plot(end_effector_x, end_effector_y, 'mo', markersize=10, label='End Effector')
78
+ ax.legend()
79
+
80
+ shoulder_angle_text = f"{q1_deg:.2f}°"
81
+ elbow_angle_text = f"{180.0 - q2_deg:.2f}°" # Common servo angle representation
82
+
83
+ max_reach = l1 + l2 + 2
84
+ ax.set_xlim([-max_reach, max_reach])
85
+ ax.set_ylim([-max_reach, max_reach])
86
+ ax.set_aspect('equal', adjustable='box')
87
+ ax.set_xlabel('X-axis')
88
+ ax.set_ylabel('Y-axis')
89
+ ax.set_title('2-DOF Robotic Arm Simulation')
90
+ ax.grid(True)
91
+ plt.tight_layout() # Helps fit everything nicely
92
+
93
+ # Return the Matplotlib figure to be displayed by gr.Plot, along with other values
94
+ return fig, status_message, shoulder_angle_text, elbow_angle_text
95
+
96
+ # --- Python Code Snippet for the UI ---
97
+ # This string contains the self-contained Python function for easy copying.
98
+ # It's defined here for cleanliness and then used in the Gradio UI below.
99
+ CODE_SNIPPET = '''
100
+ import math
101
+ import numpy as np
102
+
103
+ def inver_k(l1, l2, x, y):
104
+ """
105
+ Calculates the joint angles (q1, q2) for a 2-DOF robotic arm.
106
+ This is the core inverse kinematics logic.
107
+
108
+ Args:
109
+ l1 (float): The length of the first arm segment.
110
+ l2 (float): The length of the second arm segment.
111
+ x (float): The x-coordinate of the end effector.
112
+ y (float): The y-coordinate of the end effector.
113
+
114
+ Returns:
115
+ tuple: (success, q1, q2) where q1 and q2 are in radians.
116
+ 'success' is False if the point is unreachable.
117
+ Returns (False, 0, 0) on failure.
118
+ """
119
+ # Check if the target is reachable.
120
+ # The distance from origin to target squared
121
+ dist_sq = x**2 + y**2
122
+
123
+ # Check if the target is outside the outer circle or inside the inner circle.
124
+ if dist_sq > (l1 + l2)**2 or dist_sq < (l1 - l2)**2:
125
+ return (False, 0, 0)
126
+
127
+ # Calculate q2 (elbow angle) using the Law of Cosines.
128
+ # The value must be clamped to [-1.0, 1.0] to avoid math domain errors
129
+ # from floating-point inaccuracies.
130
+ cos_q2_arg = (dist_sq - l1**2 - l2**2) / (2 * l1 * l2)
131
+ cos_q2_arg = np.clip(cos_q2_arg, -1.0, 1.0)
132
+ q2 = math.acos(cos_q2_arg)
133
+
134
+ # Calculate q1 (shoulder angle).
135
+ # q1 is the angle to the target minus the angle within the arm triangle.
136
+ angle_to_target = math.atan2(y, x)
137
+ angle_l1_to_ee = math.atan2(l2 * math.sin(q2), l1 + l2 * math.cos(q2))
138
+ q1 = angle_to_target - angle_l1_to_ee
139
+
140
+ return (True, q1, q2)
141
+
142
+ # --- Example Usage ---
143
+ # arm1_len = 12.5
144
+ # arm2_len = 12.5
145
+ # target_x = -10
146
+ # target_y = 15
147
+ #
148
+ # success, q1_rad, q2_rad = inver_k(arm1_len, arm2_len, target_x, target_y)
149
+ #
150
+ # if success:
151
+ # print(f"Success! Angles in Radians: q1={q1_rad:.4f}, q2={q2_rad:.4f}")
152
+ # print(f"Success! Angles in Degrees: q1={math.degrees(q1_rad):.2f}, q2={math.degrees(q2_rad):.2f}")
153
+ # else:
154
+ # print("The target point is unreachable.")
155
+ '''
156
+
157
+ # --- Build the Gradio Interface ---
158
+ with gr.Blocks(theme=gr.themes.Soft(), title="2-DOF Arm Simulator") as app:
159
+ gr.Markdown("# 2-DOF Robotic Arm: Inverse Kinematics Simulator")
160
+ gr.Markdown(
161
+ "This app provides an easy way to understand how a 2-DOF (two-degree-of-freedom) robotic arm works. "
162
+ "Use the sliders on the left to set the target coordinates (X, Y) and arm lengths (L1, L2). "
163
+ "The simulation on the right will update in real-time to show the required joint angles."
164
+ )
165
+ gr.Markdown("---")
166
+
167
+ with gr.Row():
168
+ with gr.Column(scale=1, min_width=300):
169
+ gr.Markdown("### Controls")
170
+ x_slider = gr.Slider(minimum=-25, maximum=0, value=-10, step=0.5, label="Target X Coordinate")
171
+ y_slider = gr.Slider(minimum=-25, maximum=25, value=15, step=0.5, label="Target Y Coordinate")
172
+ l1_slider = gr.Slider(minimum=1, maximum=15, value=12.5, step=0.5, label="Arm 1 Length (L1)")
173
+ l2_slider = gr.Slider(minimum=1, maximum=15, value=12.5, step=0.5, label="Arm 2 Length (L2)")
174
+
175
+ gr.Markdown("### Calculated Angles")
176
+ status_output = gr.Textbox(label="Status", interactive=False)
177
+ shoulder_output = gr.Textbox(label="Shoulder Angle (q1)", interactive=False)
178
+ elbow_output = gr.Textbox(label="Elbow Angle (servo-style)", interactive=False)
179
+
180
+ gr.Markdown("--- \n *Original code from [ARMv6 by gokul6350](https://github.com/gokul6350/ARMv6/blob/main/main_src/inverse_k.py)*")
181
+
182
+ with gr.Column(scale=2, min_width=500):
183
+ gr.Markdown("### Simulation")
184
+ # The gr.Plot component is a "picture frame" that displays plots
185
+ # generated by libraries like Matplotlib.
186
+ plot_output = gr.Plot()
187
+
188
+ with gr.Accordion("Understanding the Math: Inverse Kinematics Formulas", open=False):
189
+ # Using a raw string r"""...""" ensures LaTeX renders correctly without Python misinterpreting backslashes.
190
+ # Formatting is adjusted to match the source image.
191
+ gr.Markdown(
192
+ r"""
193
+ To find the joint angles ($$q_1, q_2$$) for a target position $$(x, y)$$, we use the laws of geometry.
194
+
195
+ **1. Calculate the Elbow Angle ($$q_2$$)**
196
+
197
+ We use the **Law of Cosines** on the triangle formed by the two arm links and the line from the origin to the end-effector. The distance-squared from the origin to the target is $$d^2 = x^2 + y^2$$.
198
+
199
+ $$ \cos(q_2) = \frac{x^2 + y^2 - L_1^2 - L_2^2}{2L_1L_2} $$
200
+
201
+ From this, we can find $$q_2$$:
202
+
203
+ $$ q_2 = \arccos\left(\frac{x^2 + y^2 - L_1^2 - L_2^2}{2L_1L_2}\right) $$
204
+
205
+ *Note: The angle displayed in the app for the elbow is $$180^\circ - q_2$$, which is common for servo control.*
206
+
207
+ **2. Calculate the Shoulder Angle ($$q_1$$)**
208
+
209
+ The shoulder angle $$q_1$$ is composed of two parts: the angle to the target point ($$\alpha$$) and the angle within the arm triangle ($$\beta$$).
210
+
211
+ $$ q_1 = \alpha - \beta $$
212
+
213
+ Where:
214
+ * $$\alpha$$ is the angle to the target point: `atan2(y, x)`
215
+ * $$\beta$$ is the angle between the first link and the line to the end-effector: `atan2(L2 * sin(q2), L1 + L2 * cos(q2))`
216
+
217
+ `atan2(y, x)` is used instead of `atan(y/x)` because it correctly computes the angle in all four quadrants.
218
+ """
219
+ )
220
+
221
+ # *** NEWLY ADDED DROPDOWN FOR COPYING PYTHON CODE ***
222
+ with gr.Accordion("Copy the Core Python Function", open=False):
223
+ gr.Code(
224
+ value=CODE_SNIPPET,
225
+ language="python",
226
+ label="Python Code for inver_k(l1, l2, x, y)"
227
+ )
228
+
229
+ inputs = [x_slider, y_slider, l1_slider, l2_slider]
230
+ outputs = [plot_output, status_output, shoulder_output, elbow_output]
231
+
232
+ # Use .change for live updates as sliders move
233
+ for component in inputs:
234
+ component.change(fn=run_simulation, inputs=inputs, outputs=outputs, api_name=False)
235
+
236
+ # Use .load to run the simulation once when the app starts
237
+ app.load(fn=run_simulation, inputs=inputs, outputs=outputs, api_name=False)
238
+
239
+
240
+ if __name__ == "__main__":
241
+ app.launch()
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ gradio
2
+ matplotlib
3
+ numpy