milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 1 | import abc |
| 2 | import numpy as np |
| 3 | import sys |
| 4 | import traceback |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 5 | |
| 6 | # joint_center in x-y space. |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 7 | IN_TO_M = 0.0254 |
| 8 | joint_center = (-0.203, 0.787) |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 9 | |
| 10 | # Joint distances (l1 = "proximal", l2 = "distal") |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 11 | l1 = 20.0 * IN_TO_M |
milind-u | 68842e1 | 2023-02-26 12:45:40 -0800 | [diff] [blame] | 12 | l2 = 38.0 * IN_TO_M |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 13 | |
| 14 | max_dist = 0.01 |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 15 | max_dist_theta = np.pi / 64 |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 16 | xy_end_circle_size = 0.01 |
| 17 | theta_end_circle_size = 0.07 |
| 18 | |
| 19 | |
milind-u | 060e4cf | 2023-02-22 00:08:52 -0800 | [diff] [blame] | 20 | # Shift the angle between the convention used for input/output and the convention we use for some computations here |
| 21 | def shift_angle(theta): |
| 22 | return np.pi / 2 - theta |
| 23 | |
| 24 | |
| 25 | def shift_angles(thetas): |
| 26 | return [shift_angle(theta) for theta in thetas] |
| 27 | |
| 28 | |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 29 | # Convert from x-y coordinates to theta coordinates. |
| 30 | # orientation is a bool. This orientation is circular_index mod 2. |
| 31 | # where circular_index is the circular index, or the position in the |
| 32 | # "hyperextension" zones. "cross_point" allows shifting the place where |
| 33 | # it rounds the result so that it draws nicer (no other functional differences). |
milind-u | 600738b | 2023-02-22 14:42:19 -0800 | [diff] [blame] | 34 | def to_theta(pt, circular_index, cross_point=-np.pi, die=True): |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 35 | orient = (circular_index % 2) == 0 |
| 36 | x = pt[0] |
| 37 | y = pt[1] |
milind-u | 68842e1 | 2023-02-26 12:45:40 -0800 | [diff] [blame] | 38 | x -= joint_center[0] - 1e-9 |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 39 | y -= joint_center[1] |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 40 | l3 = np.hypot(x, y) |
| 41 | t3 = np.arctan2(y, x) |
| 42 | theta1 = np.arccos((l1**2 + l3**2 - l2**2) / (2 * l1 * l3)) |
| 43 | if np.isnan(theta1): |
milind-u | 600738b | 2023-02-22 14:42:19 -0800 | [diff] [blame] | 44 | print(("Couldn't fit triangle to %f, %f, %f" % (l1, l2, l3))) |
| 45 | if die: |
| 46 | traceback.print_stack() |
| 47 | sys.exit(1) |
| 48 | return None |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 49 | |
| 50 | if orient: |
| 51 | theta1 = -theta1 |
| 52 | theta1 += t3 |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 53 | theta1 = (theta1 - cross_point) % (2 * np.pi) + cross_point |
| 54 | theta2 = np.arctan2(y - l1 * np.sin(theta1), x - l1 * np.cos(theta1)) |
| 55 | return np.array((theta1, theta2)) |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 56 | |
| 57 | |
| 58 | # Simple trig to go back from theta1, theta2 to x-y |
| 59 | def to_xy(theta1, theta2): |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 60 | x = np.cos(theta1) * l1 + np.cos(theta2) * l2 + joint_center[0] |
| 61 | y = np.sin(theta1) * l1 + np.sin(theta2) * l2 + joint_center[1] |
| 62 | orient = ((theta2 - theta1) % (2.0 * np.pi)) < np.pi |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 63 | return (x, y, orient) |
| 64 | |
| 65 | |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 66 | END_EFFECTOR_X_LEN = (-1.0 * IN_TO_M, 10.425 * IN_TO_M) |
milind-u | 2a28c59 | 2023-02-24 23:13:04 -0800 | [diff] [blame] | 67 | END_EFFECTOR_Y_LEN = (-3.5 * IN_TO_M, 7.325 * IN_TO_M) |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 68 | END_EFFECTOR_Z_LEN = (-11.0 * IN_TO_M, 11.0 * IN_TO_M) |
| 69 | |
| 70 | |
| 71 | def abs_sum(l): |
| 72 | result = 0 |
| 73 | for e in l: |
| 74 | result += abs(e) |
| 75 | return result |
| 76 | |
| 77 | |
| 78 | def affine_3d(R, T): |
| 79 | H = np.eye(4) |
| 80 | H[:3, 3] = T |
| 81 | H[:3, :3] = R |
| 82 | return H |
| 83 | |
| 84 | |
| 85 | # Simple trig to go back from theta1, theta2, and theta3 to |
| 86 | # the 8 corners on the roll joint x-y-z |
| 87 | def to_end_effector_points(theta1, theta2, theta3): |
| 88 | x, y, _ = to_xy(theta1, theta2) |
| 89 | # Homogeneous end effector points relative to the end_effector |
| 90 | # ee = end effector |
| 91 | endpoints_ee = [] |
| 92 | for i in range(2): |
| 93 | for j in range(2): |
| 94 | for k in range(2): |
| 95 | endpoints_ee.append( |
| 96 | np.array((END_EFFECTOR_X_LEN[i], END_EFFECTOR_Y_LEN[j], |
| 97 | END_EFFECTOR_Z_LEN[k], 1.0))) |
| 98 | |
| 99 | # Only roll. |
| 100 | # rj = roll joint |
| 101 | roll = theta3 |
| 102 | T_rj_ee = np.zeros(3) |
| 103 | R_rj_ee = np.array([[1.0, 0.0, 0.0], [0.0, |
| 104 | np.cos(roll), -np.sin(roll)], |
| 105 | [0.0, np.sin(roll), np.cos(roll)]]) |
| 106 | H_rj_ee = affine_3d(R_rj_ee, T_rj_ee) |
| 107 | |
| 108 | # Roll joint pose relative to the origin |
| 109 | # o = origin |
| 110 | T_o_rj = np.array((x, y, 0)) |
| 111 | # Only yaw |
| 112 | yaw = theta1 + theta2 |
| 113 | R_o_rj = [[np.cos(yaw), -np.sin(yaw), 0.0], |
| 114 | [np.sin(yaw), np.cos(yaw), 0.0], [0.0, 0.0, 1.0]] |
| 115 | H_o_rj = affine_3d(R_o_rj, T_o_rj) |
| 116 | |
| 117 | # Now compute the pose of the end effector relative to the origin |
| 118 | H_o_ee = H_o_rj @ H_rj_ee |
| 119 | |
| 120 | # Get the translation from these transforms |
| 121 | endpoints_o = [(H_o_ee @ endpoint_ee)[:3] for endpoint_ee in endpoints_ee] |
| 122 | |
| 123 | diagonal_distance = np.linalg.norm( |
| 124 | np.array(endpoints_o[0]) - np.array(endpoints_o[-1])) |
| 125 | actual_diagonal_distance = np.linalg.norm( |
| 126 | np.array((abs_sum(END_EFFECTOR_X_LEN), abs_sum(END_EFFECTOR_Y_LEN), |
| 127 | abs_sum(END_EFFECTOR_Z_LEN)))) |
| 128 | assert abs(diagonal_distance - actual_diagonal_distance) < 1e-5 |
| 129 | |
| 130 | return np.array(endpoints_o) |
| 131 | |
| 132 | |
| 133 | # Returns all permutations of rectangle points given two opposite corners. |
| 134 | # x is the two x values, y is the two y values, z is the two z values |
| 135 | def rect_points(x, y, z): |
| 136 | points = [] |
| 137 | for i in range(2): |
| 138 | for j in range(2): |
| 139 | for k in range(2): |
| 140 | points.append((x[i], y[j], z[k])) |
| 141 | return np.array(points) |
| 142 | |
| 143 | |
| 144 | DRIVER_CAM_Z_OFFSET = 3.225 * IN_TO_M |
| 145 | DRIVER_CAM_POINTS = rect_points( |
milind-u | d11146f | 2023-03-25 18:36:47 -0700 | [diff] [blame^] | 146 | (-0.252, -0.252 + 0.098), |
| 147 | (-3.0 * IN_TO_M + joint_center[1], -8.0 * IN_TO_M + joint_center[1]), |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 148 | (-8.475 * IN_TO_M - DRIVER_CAM_Z_OFFSET, |
| 149 | -4.350 * IN_TO_M - DRIVER_CAM_Z_OFFSET)) |
| 150 | |
| 151 | |
milind-u | 2a28c59 | 2023-02-24 23:13:04 -0800 | [diff] [blame] | 152 | def rect_collision_1d(min_1, max_1, min_2, max_2): |
| 153 | return (min_1 <= min_2 <= max_1) or (min_1 <= max_2 <= max_1) or ( |
| 154 | min_2 < min_1 and max_2 > max_1) |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 155 | |
| 156 | |
| 157 | def roll_joint_collision(theta1, theta2, theta3): |
milind-u | 060e4cf | 2023-02-22 00:08:52 -0800 | [diff] [blame] | 158 | theta1 = shift_angle(theta1) |
| 159 | theta2 = shift_angle(theta2) |
| 160 | theta3 = shift_angle(theta3) |
| 161 | |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 162 | end_effector_points = to_end_effector_points(theta1, theta2, theta3) |
| 163 | |
| 164 | assert len(end_effector_points) == 8 and len(end_effector_points[0]) == 3 |
| 165 | assert len(DRIVER_CAM_POINTS) == 8 and len(DRIVER_CAM_POINTS[0]) == 3 |
milind-u | 2a28c59 | 2023-02-24 23:13:04 -0800 | [diff] [blame] | 166 | collided = True |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 167 | |
milind-u | 2a28c59 | 2023-02-24 23:13:04 -0800 | [diff] [blame] | 168 | for i in range(len(end_effector_points[0])): |
| 169 | min_ee = min(end_effector_points[:, i]) |
| 170 | max_ee = max(end_effector_points[:, i]) |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 171 | |
milind-u | 2a28c59 | 2023-02-24 23:13:04 -0800 | [diff] [blame] | 172 | min_dc = min(DRIVER_CAM_POINTS[:, i]) |
| 173 | max_dc = max(DRIVER_CAM_POINTS[:, i]) |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 174 | |
milind-u | 2a28c59 | 2023-02-24 23:13:04 -0800 | [diff] [blame] | 175 | collided &= rect_collision_1d(min_ee, max_ee, min_dc, max_dc) |
| 176 | return collided |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 177 | |
| 178 | |
milind-u | eeb08c5 | 2023-02-21 22:30:16 -0800 | [diff] [blame] | 179 | # Delta limit means theta2 - theta1. |
| 180 | # The limit for the proximal and distal is relative, |
| 181 | # so define constraints for this delta. |
| 182 | UPPER_DELTA_LIMIT = 0.0 |
Austin Schuh | 9b3e41c | 2023-02-26 22:29:53 -0800 | [diff] [blame] | 183 | LOWER_DELTA_LIMIT = -1.98 * np.pi |
milind-u | eeb08c5 | 2023-02-21 22:30:16 -0800 | [diff] [blame] | 184 | |
| 185 | # TODO(milind): put actual proximal limits |
Austin Schuh | 99dda68 | 2023-03-11 00:18:37 -0800 | [diff] [blame] | 186 | UPPER_PROXIMAL_LIMIT = np.pi * 3.0 |
Austin Schuh | 9b3e41c | 2023-02-26 22:29:53 -0800 | [diff] [blame] | 187 | LOWER_PROXIMAL_LIMIT = -np.pi * 2.0 |
milind-u | eeb08c5 | 2023-02-21 22:30:16 -0800 | [diff] [blame] | 188 | |
Austin Schuh | 8edaf3e | 2023-02-22 21:20:52 -0800 | [diff] [blame] | 189 | UPPER_DISTAL_LIMIT = 0.75 * np.pi |
| 190 | LOWER_DISTAL_LIMIT = -0.75 * np.pi |
| 191 | |
milind-u | eeb08c5 | 2023-02-21 22:30:16 -0800 | [diff] [blame] | 192 | UPPER_ROLL_JOINT_LIMIT = 0.75 * np.pi |
| 193 | LOWER_ROLL_JOINT_LIMIT = -0.75 * np.pi |
| 194 | |
| 195 | |
Austin Schuh | 99dda68 | 2023-03-11 00:18:37 -0800 | [diff] [blame] | 196 | def arm_past_limit(theta1, theta2, theta3, verbose=True): |
milind-u | eeb08c5 | 2023-02-21 22:30:16 -0800 | [diff] [blame] | 197 | delta = theta2 - theta1 |
Austin Schuh | 99dda68 | 2023-03-11 00:18:37 -0800 | [diff] [blame] | 198 | if delta > UPPER_DELTA_LIMIT or delta < LOWER_DELTA_LIMIT: |
| 199 | if verbose: |
| 200 | print( |
| 201 | f'Delta {delta} outside {LOWER_DELTA_LIMIT}, {UPPER_DELTA_LIMIT}' |
| 202 | ) |
| 203 | return True |
| 204 | if theta1 > UPPER_PROXIMAL_LIMIT or theta1 < LOWER_PROXIMAL_LIMIT: |
| 205 | if verbose: |
| 206 | print( |
| 207 | f'Proximal {theta1} outside {LOWER_PROXIMAL_LIMIT}, {UPPER_PROXIMAL_LIMIT}' |
| 208 | ) |
| 209 | return True |
| 210 | |
| 211 | if theta2 > UPPER_DISTAL_LIMIT or theta2 < LOWER_DISTAL_LIMIT: |
| 212 | if verbose: |
| 213 | print( |
| 214 | f'Proximal {theta2} outside {LOWER_DISTAL_LIMIT}, {UPPER_DISTAL_LIMIT}' |
| 215 | ) |
| 216 | return True |
| 217 | |
| 218 | if theta3 > UPPER_ROLL_JOINT_LIMIT or theta3 < LOWER_ROLL_JOINT_LIMIT: |
| 219 | if verbose: |
| 220 | print( |
| 221 | f'Proximal {theta3} outside {LOWER_ROLL_JOINT_LIMIT}, {UPPER_ROLL_JOINT_LIMIT}' |
| 222 | ) |
| 223 | return True |
| 224 | |
| 225 | return False |
milind-u | eeb08c5 | 2023-02-21 22:30:16 -0800 | [diff] [blame] | 226 | |
| 227 | |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 228 | def get_circular_index(theta): |
Austin Schuh | 9a11ebd | 2023-02-26 14:16:31 -0800 | [diff] [blame] | 229 | return int( |
| 230 | np.floor((shift_angle(theta[1]) - shift_angle(theta[0])) / np.pi)) |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 231 | |
| 232 | |
| 233 | def get_xy(theta): |
milind-u | 060e4cf | 2023-02-22 00:08:52 -0800 | [diff] [blame] | 234 | theta1 = shift_angle(theta[0]) |
| 235 | theta2 = shift_angle(theta[1]) |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 236 | x = np.cos(theta1) * l1 + np.cos(theta2) * l2 + joint_center[0] |
| 237 | y = np.sin(theta1) * l1 + np.sin(theta2) * l2 + joint_center[1] |
| 238 | return np.array((x, y)) |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 239 | |
| 240 | |
| 241 | # Subdivide in theta space. |
| 242 | def subdivide_theta(lines): |
| 243 | out = [] |
| 244 | last_pt = lines[0] |
| 245 | out.append(last_pt) |
| 246 | for n_pt in lines[1:]: |
| 247 | for pt in subdivide(last_pt, n_pt, max_dist_theta): |
| 248 | out.append(pt) |
| 249 | last_pt = n_pt |
| 250 | |
| 251 | return out |
| 252 | |
| 253 | |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 254 | def to_theta_with_ci(pt, circular_index): |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 255 | return (to_theta_with_circular_index(pt[0], pt[1], circular_index)) |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 256 | |
| 257 | |
| 258 | # to_theta, but distinguishes between |
| 259 | def to_theta_with_circular_index(x, y, circular_index): |
| 260 | theta1, theta2 = to_theta((x, y), circular_index) |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 261 | n_circular_index = int(np.floor((theta2 - theta1) / np.pi)) |
| 262 | theta2 = theta2 + ((circular_index - n_circular_index)) * np.pi |
milind-u | 060e4cf | 2023-02-22 00:08:52 -0800 | [diff] [blame] | 263 | return np.array((shift_angle(theta1), shift_angle(theta2))) |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 264 | |
| 265 | |
| 266 | # to_theta, but distinguishes between |
| 267 | def to_theta_with_circular_index_and_roll(x, y, roll, circular_index): |
| 268 | theta12 = to_theta_with_circular_index(x, y, circular_index) |
| 269 | theta3 = roll |
| 270 | return np.array((theta12[0], theta12[1], theta3)) |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 271 | |
| 272 | |
| 273 | # alpha is in [0, 1] and is the weight to merge a and b. |
| 274 | def alpha_blend(a, b, alpha): |
| 275 | """Blends a and b. |
| 276 | |
| 277 | Args: |
| 278 | alpha: double, Ratio. Needs to be in [0, 1] and is the weight to blend a |
| 279 | and b. |
| 280 | """ |
| 281 | return b * alpha + (1.0 - alpha) * a |
| 282 | |
| 283 | |
| 284 | def normalize(v): |
| 285 | """Normalize a vector while handling 0 length vectors.""" |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 286 | norm = np.linalg.norm(v) |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 287 | if norm == 0: |
| 288 | return v |
| 289 | return v / norm |
| 290 | |
| 291 | |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 292 | # Generic subdivision algorithm. |
| 293 | def subdivide(p1, p2, max_dist): |
| 294 | dx = p2[0] - p1[0] |
| 295 | dy = p2[1] - p1[1] |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 296 | dist = np.sqrt(dx**2 + dy**2) |
| 297 | n = int(np.ceil(dist / max_dist)) |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 298 | return [(alpha_blend(p1[0], p2[0], |
| 299 | float(i) / n), alpha_blend(p1[1], p2[1], |
| 300 | float(i) / n)) |
| 301 | for i in range(1, n + 1)] |
| 302 | |
| 303 | |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 304 | def spline_eval(start, control1, control2, end, alpha): |
| 305 | a = alpha_blend(start, control1, alpha) |
| 306 | b = alpha_blend(control1, control2, alpha) |
| 307 | c = alpha_blend(control2, end, alpha) |
| 308 | return alpha_blend(alpha_blend(a, b, alpha), alpha_blend(b, c, alpha), |
| 309 | alpha) |
| 310 | |
| 311 | |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 312 | SPLINE_SUBDIVISIONS = 100 |
| 313 | |
| 314 | |
| 315 | def subdivide_multistep(): |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 316 | # TODO: pick N based on spline parameters? or otherwise change it to be more evenly spaced? |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 317 | for i in range(0, SPLINE_SUBDIVISIONS + 1): |
| 318 | yield i / float(SPLINE_SUBDIVISIONS) |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 319 | |
| 320 | |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 321 | def get_proximal_distal_derivs(t_prev, t, t_next): |
| 322 | d_prev = normalize(t - t_prev) |
| 323 | d_next = normalize(t_next - t) |
| 324 | accel = (d_next - d_prev) / np.linalg.norm(t - t_next) |
| 325 | return (ThetaPoint(t[0], d_next[0], |
| 326 | accel[0]), ThetaPoint(t[1], d_next[1], accel[1])) |
| 327 | |
| 328 | |
| 329 | def get_roll_joint_theta(theta_i, theta_f, t): |
| 330 | # Fit a theta(t) = (1 - cos(pi*t)) / 2, |
| 331 | # so that theta(0) = theta_i, and theta(1) = theta_f |
| 332 | offset = theta_i |
| 333 | scalar = (theta_f - theta_i) / 2.0 |
| 334 | freq = np.pi |
| 335 | theta_curve = lambda t: scalar * (1 - np.cos(freq * t)) + offset |
| 336 | |
| 337 | return theta_curve(t) |
| 338 | |
| 339 | |
| 340 | def get_roll_joint_theta_multistep(alpha_rolls, alpha): |
| 341 | # Figure out which segment in the motion we're in |
| 342 | theta_i = None |
| 343 | theta_f = None |
| 344 | t = None |
| 345 | |
| 346 | for i in range(len(alpha_rolls) - 1): |
| 347 | # Find the alpha segment we're in |
| 348 | if alpha_rolls[i][0] <= alpha <= alpha_rolls[i + 1][0]: |
| 349 | theta_i = alpha_rolls[i][1] |
| 350 | theta_f = alpha_rolls[i + 1][1] |
| 351 | |
| 352 | total_dalpha = alpha_rolls[-1][0] - alpha_rolls[0][0] |
| 353 | assert total_dalpha == 1.0 |
| 354 | dalpha = alpha_rolls[i + 1][0] - alpha_rolls[i][0] |
| 355 | t = (alpha - alpha_rolls[i][0]) * (total_dalpha / dalpha) |
| 356 | break |
| 357 | assert theta_i is not None |
| 358 | assert theta_f is not None |
| 359 | assert t is not None |
| 360 | |
| 361 | return get_roll_joint_theta(theta_i, theta_f, t) |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 362 | |
| 363 | |
Maxwell Henderson | 83cf6d6 | 2023-02-10 20:29:26 -0800 | [diff] [blame] | 364 | # Draw a list of lines to a cairo context. |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 365 | def draw_lines(cr, lines): |
| 366 | cr.move_to(lines[0][0], lines[0][1]) |
| 367 | for pt in lines[1:]: |
| 368 | cr.line_to(pt[0], pt[1]) |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 369 | |
| 370 | |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 371 | class Path(abc.ABC): |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 372 | |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 373 | def __init__(self, name): |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 374 | self.name = name |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 375 | |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 376 | @abc.abstractmethod |
| 377 | def DoToThetaPoints(self): |
| 378 | pass |
| 379 | |
| 380 | @abc.abstractmethod |
| 381 | def DoDrawTo(self): |
| 382 | pass |
| 383 | |
| 384 | @abc.abstractmethod |
milind-u | add8fa3 | 2023-02-24 23:37:36 -0800 | [diff] [blame] | 385 | def joint_thetas(self): |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 386 | pass |
| 387 | |
| 388 | @abc.abstractmethod |
| 389 | def intersection(self, event): |
| 390 | pass |
| 391 | |
| 392 | def roll_joint_collision(self, points, verbose=False): |
| 393 | for point in points: |
| 394 | if roll_joint_collision(*point): |
| 395 | if verbose: |
| 396 | print("Roll joint collision for path %s in point %s" % |
| 397 | (self.name, point)) |
| 398 | return True |
| 399 | return False |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 400 | |
milind-u | eeb08c5 | 2023-02-21 22:30:16 -0800 | [diff] [blame] | 401 | def arm_past_limit(self, points, verbose=True): |
| 402 | for point in points: |
Austin Schuh | 99dda68 | 2023-03-11 00:18:37 -0800 | [diff] [blame] | 403 | if arm_past_limit(*point, verbose=verbose): |
milind-u | eeb08c5 | 2023-02-21 22:30:16 -0800 | [diff] [blame] | 404 | if verbose: |
| 405 | print("Arm past limit for path %s in point %s" % |
| 406 | (self.name, point)) |
| 407 | return True |
| 408 | return False |
| 409 | |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 410 | def DrawTo(self, cr, theta_version): |
milind-u | eeb08c5 | 2023-02-21 22:30:16 -0800 | [diff] [blame] | 411 | points = self.DoToThetaPoints() |
| 412 | if self.roll_joint_collision(points): |
| 413 | # Draw the spline red |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 414 | cr.set_source_rgb(1.0, 0.0, 0.0) |
milind-u | eeb08c5 | 2023-02-21 22:30:16 -0800 | [diff] [blame] | 415 | elif self.arm_past_limit(points): |
| 416 | # Draw the spline orange |
| 417 | cr.set_source_rgb(1.0, 0.5, 0.0) |
| 418 | |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 419 | self.DoDrawTo(cr, theta_version) |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 420 | |
milind-u | 4037bc7 | 2023-02-22 21:39:40 -0800 | [diff] [blame] | 421 | def VerifyPoints(self): |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 422 | points = self.DoToThetaPoints() |
milind-u | eeb08c5 | 2023-02-21 22:30:16 -0800 | [diff] [blame] | 423 | if self.roll_joint_collision(points, verbose=True) or \ |
| 424 | self.arm_past_limit(points, verbose=True): |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 425 | sys.exit(1) |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 426 | |
| 427 | |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 428 | class SplineSegmentBase(Path): |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 429 | |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 430 | def __init__(self, name): |
| 431 | super().__init__(name) |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 432 | |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 433 | @abc.abstractmethod |
| 434 | # Returns (start, control1, control2, end), each in the form |
| 435 | # (theta1, theta2, theta3) |
| 436 | def get_controls_theta(self): |
| 437 | pass |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 438 | |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 439 | def intersection(self, event): |
| 440 | start, control1, control2, end = self.get_controls_theta() |
| 441 | for alpha in subdivide_multistep(): |
| 442 | x, y = get_xy(spline_eval(start, control1, control2, end, alpha)) |
| 443 | spline_point = np.array([x, y]) |
| 444 | hovered_point = np.array([event.x, event.y]) |
| 445 | if np.linalg.norm(hovered_point - spline_point) < 0.03: |
| 446 | return alpha |
| 447 | return None |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 448 | |
| 449 | |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 450 | class ThetaSplineSegment(SplineSegmentBase): |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 451 | |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 452 | # start and end are [theta1, theta2, theta3]. |
| 453 | # controls are just [theta1, theta2]. |
| 454 | # control_alpha_rolls are a list of [alpha, roll] |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 455 | def __init__(self, |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 456 | name, |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 457 | start, |
| 458 | control1, |
| 459 | control2, |
| 460 | end, |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 461 | control_alpha_rolls=[], |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 462 | alpha_unitizer=None, |
| 463 | vmax=None): |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 464 | super().__init__(name) |
| 465 | self.start = start[:2] |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 466 | self.control1 = control1 |
| 467 | self.control2 = control2 |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 468 | self.end = end[:2] |
| 469 | # There will always be roll at alpha = 0 and 1 |
| 470 | self.alpha_rolls = [[0.0, start[2]] |
| 471 | ] + control_alpha_rolls + [[1.0, end[2]]] |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 472 | self.alpha_unitizer = alpha_unitizer |
| 473 | self.vmax = vmax |
| 474 | |
Austin Schuh | 9a11ebd | 2023-02-26 14:16:31 -0800 | [diff] [blame] | 475 | def Print(self, points): |
| 476 | # Find the name of the start end end points. |
| 477 | start_name = None |
| 478 | end_name = None |
| 479 | for name in points: |
| 480 | point = points[name] |
| 481 | if (self.start == point[:2]).all(): |
| 482 | start_name = name |
| 483 | elif (self.end == point[:2]).all(): |
| 484 | end_name = name |
| 485 | |
| 486 | alpha_points = '[' + ', '.join([ |
| 487 | f"({alpha}, np.pi * {theta / np.pi})" |
| 488 | for alpha, theta in self.alpha_rolls[1:-1] |
| 489 | ]) + ']' |
| 490 | |
| 491 | def FormatToTheta(point): |
| 492 | x, y = get_xy(point) |
| 493 | circular_index = get_circular_index(point) |
| 494 | return "to_theta_with_circular_index(%.3f, %.3f, circular_index=%d)" % ( |
| 495 | x, y, circular_index) |
| 496 | |
| 497 | def FormatToThetaRoll(point, roll): |
| 498 | x, y = get_xy(point) |
| 499 | circular_index = get_circular_index(point) |
| 500 | return "to_theta_with_circular_index_and_roll(%.3f, %.3f, np.pi * %.2f, circular_index=%d)" % ( |
| 501 | x, y, roll / np.pi, circular_index) |
| 502 | |
| 503 | print('named_segments.append(') |
| 504 | print(' ThetaSplineSegment(') |
| 505 | print(f' name="{self.name}",') |
| 506 | print( |
| 507 | f' start=points["{start_name}"], # {FormatToThetaRoll(self.start, self.alpha_rolls[0][1])}' |
| 508 | ) |
| 509 | print( |
| 510 | f' control1=np.array([{self.control1[0]}, {self.control1[1]}]), # {FormatToTheta(self.control1)}' |
| 511 | ) |
| 512 | print( |
| 513 | f' control2=np.array([{self.control2[0]}, {self.control2[1]}]), # {FormatToTheta(self.control2)}' |
| 514 | ) |
| 515 | print( |
| 516 | f' end=points["{end_name}"], # {FormatToThetaRoll(self.end, self.alpha_rolls[-1][1])}' |
| 517 | ) |
| 518 | print(f' control_alpha_rolls={alpha_points},') |
| 519 | print(f'))') |
| 520 | |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 521 | def __repr__(self): |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 522 | return "ThetaSplineSegment(%s, %s, %s, %s)" % (repr( |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 523 | self.start), repr(self.control1), repr( |
| 524 | self.control2), repr(self.end)) |
| 525 | |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 526 | def DoDrawTo(self, cr, theta_version): |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 527 | if (theta_version): |
| 528 | draw_lines(cr, [ |
milind-u | 060e4cf | 2023-02-22 00:08:52 -0800 | [diff] [blame] | 529 | shift_angles( |
| 530 | spline_eval(self.start, self.control1, self.control2, |
| 531 | self.end, alpha)) |
| 532 | for alpha in subdivide_multistep() |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 533 | ]) |
| 534 | else: |
| 535 | start = get_xy(self.start) |
| 536 | end = get_xy(self.end) |
| 537 | |
| 538 | draw_lines(cr, [ |
| 539 | get_xy( |
| 540 | spline_eval(self.start, self.control1, self.control2, |
| 541 | self.end, alpha)) |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 542 | for alpha in subdivide_multistep() |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 543 | ]) |
| 544 | |
| 545 | cr.move_to(start[0] + xy_end_circle_size, start[1]) |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 546 | cr.arc(start[0], start[1], xy_end_circle_size, 0, 2.0 * np.pi) |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 547 | cr.move_to(end[0] + xy_end_circle_size, end[1]) |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 548 | cr.arc(end[0], end[1], xy_end_circle_size, 0, 2.0 * np.pi) |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 549 | |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 550 | def DoToThetaPoints(self): |
| 551 | points = [] |
| 552 | for alpha in subdivide_multistep(): |
| 553 | proximal, distal = spline_eval(self.start, self.control1, |
| 554 | self.control2, self.end, alpha) |
| 555 | roll_joint = get_roll_joint_theta_multistep( |
| 556 | self.alpha_rolls, alpha) |
| 557 | points.append((proximal, distal, roll_joint)) |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 558 | |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 559 | return points |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 560 | |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 561 | def get_controls_theta(self): |
| 562 | return (self.start, self.control1, self.control2, self.end) |
Maxwell Henderson | f5123fe | 2023-02-04 13:44:41 -0800 | [diff] [blame] | 563 | |
milind-u | add8fa3 | 2023-02-24 23:37:36 -0800 | [diff] [blame] | 564 | def joint_thetas(self): |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 565 | ts = [] |
milind-u | add8fa3 | 2023-02-24 23:37:36 -0800 | [diff] [blame] | 566 | thetas = [[], [], []] |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 567 | for alpha in subdivide_multistep(): |
milind-u | add8fa3 | 2023-02-24 23:37:36 -0800 | [diff] [blame] | 568 | proximal, distal = spline_eval(self.start, self.control1, |
| 569 | self.control2, self.end, alpha) |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 570 | roll_joint = get_roll_joint_theta_multistep( |
| 571 | self.alpha_rolls, alpha) |
milind-u | add8fa3 | 2023-02-24 23:37:36 -0800 | [diff] [blame] | 572 | thetas[0].append(proximal) |
| 573 | thetas[1].append(distal) |
| 574 | thetas[2].append(roll_joint) |
milind-u | 18a901d | 2023-02-17 21:51:55 -0800 | [diff] [blame] | 575 | ts.append(alpha) |
| 576 | return ts, thetas |