blob: c15b49de1ae3a8dfb951b7a29261689dead5fad1 [file] [log] [blame]
milind-u18a901d2023-02-17 21:51:55 -08001import abc
2import numpy as np
3import sys
4import traceback
Maxwell Hendersonf5123fe2023-02-04 13:44:41 -08005
6# joint_center in x-y space.
milind-u18a901d2023-02-17 21:51:55 -08007IN_TO_M = 0.0254
8joint_center = (-0.203, 0.787)
Maxwell Hendersonf5123fe2023-02-04 13:44:41 -08009
10# Joint distances (l1 = "proximal", l2 = "distal")
milind-u18a901d2023-02-17 21:51:55 -080011l1 = 20.0 * IN_TO_M
milind-u68842e12023-02-26 12:45:40 -080012l2 = 38.0 * IN_TO_M
Maxwell Hendersonf5123fe2023-02-04 13:44:41 -080013
14max_dist = 0.01
milind-u18a901d2023-02-17 21:51:55 -080015max_dist_theta = np.pi / 64
Maxwell Hendersonf5123fe2023-02-04 13:44:41 -080016xy_end_circle_size = 0.01
17theta_end_circle_size = 0.07
18
19
milind-u060e4cf2023-02-22 00:08:52 -080020# Shift the angle between the convention used for input/output and the convention we use for some computations here
21def shift_angle(theta):
22 return np.pi / 2 - theta
23
24
25def shift_angles(thetas):
26 return [shift_angle(theta) for theta in thetas]
27
28
Maxwell Hendersonf5123fe2023-02-04 13:44:41 -080029# 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-u600738b2023-02-22 14:42:19 -080034def to_theta(pt, circular_index, cross_point=-np.pi, die=True):
Maxwell Hendersonf5123fe2023-02-04 13:44:41 -080035 orient = (circular_index % 2) == 0
36 x = pt[0]
37 y = pt[1]
milind-u68842e12023-02-26 12:45:40 -080038 x -= joint_center[0] - 1e-9
Maxwell Hendersonf5123fe2023-02-04 13:44:41 -080039 y -= joint_center[1]
milind-u18a901d2023-02-17 21:51:55 -080040 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-u600738b2023-02-22 14:42:19 -080044 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 Hendersonf5123fe2023-02-04 13:44:41 -080049
50 if orient:
51 theta1 = -theta1
52 theta1 += t3
milind-u18a901d2023-02-17 21:51:55 -080053 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 Hendersonf5123fe2023-02-04 13:44:41 -080056
57
58# Simple trig to go back from theta1, theta2 to x-y
59def to_xy(theta1, theta2):
milind-u18a901d2023-02-17 21:51:55 -080060 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 Hendersonf5123fe2023-02-04 13:44:41 -080063 return (x, y, orient)
64
65
milind-u18a901d2023-02-17 21:51:55 -080066END_EFFECTOR_X_LEN = (-1.0 * IN_TO_M, 10.425 * IN_TO_M)
milind-u2a28c592023-02-24 23:13:04 -080067END_EFFECTOR_Y_LEN = (-3.5 * IN_TO_M, 7.325 * IN_TO_M)
milind-u18a901d2023-02-17 21:51:55 -080068END_EFFECTOR_Z_LEN = (-11.0 * IN_TO_M, 11.0 * IN_TO_M)
69
70
71def abs_sum(l):
72 result = 0
73 for e in l:
74 result += abs(e)
75 return result
76
77
78def 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
87def 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
135def 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
144DRIVER_CAM_Z_OFFSET = 3.225 * IN_TO_M
145DRIVER_CAM_POINTS = rect_points(
milind-ud11146f2023-03-25 18:36:47 -0700146 (-0.252, -0.252 + 0.098),
147 (-3.0 * IN_TO_M + joint_center[1], -8.0 * IN_TO_M + joint_center[1]),
milind-u18a901d2023-02-17 21:51:55 -0800148 (-8.475 * IN_TO_M - DRIVER_CAM_Z_OFFSET,
149 -4.350 * IN_TO_M - DRIVER_CAM_Z_OFFSET))
150
151
milind-u2a28c592023-02-24 23:13:04 -0800152def 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-u18a901d2023-02-17 21:51:55 -0800155
156
157def roll_joint_collision(theta1, theta2, theta3):
milind-u060e4cf2023-02-22 00:08:52 -0800158 theta1 = shift_angle(theta1)
159 theta2 = shift_angle(theta2)
160 theta3 = shift_angle(theta3)
161
milind-u18a901d2023-02-17 21:51:55 -0800162 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-u2a28c592023-02-24 23:13:04 -0800166 collided = True
milind-u18a901d2023-02-17 21:51:55 -0800167
milind-u2a28c592023-02-24 23:13:04 -0800168 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-u18a901d2023-02-17 21:51:55 -0800171
milind-u2a28c592023-02-24 23:13:04 -0800172 min_dc = min(DRIVER_CAM_POINTS[:, i])
173 max_dc = max(DRIVER_CAM_POINTS[:, i])
milind-u18a901d2023-02-17 21:51:55 -0800174
milind-u2a28c592023-02-24 23:13:04 -0800175 collided &= rect_collision_1d(min_ee, max_ee, min_dc, max_dc)
176 return collided
milind-u18a901d2023-02-17 21:51:55 -0800177
178
milind-ueeb08c52023-02-21 22:30:16 -0800179# Delta limit means theta2 - theta1.
180# The limit for the proximal and distal is relative,
181# so define constraints for this delta.
182UPPER_DELTA_LIMIT = 0.0
Austin Schuh9b3e41c2023-02-26 22:29:53 -0800183LOWER_DELTA_LIMIT = -1.98 * np.pi
milind-ueeb08c52023-02-21 22:30:16 -0800184
185# TODO(milind): put actual proximal limits
Austin Schuh99dda682023-03-11 00:18:37 -0800186UPPER_PROXIMAL_LIMIT = np.pi * 3.0
Austin Schuh9b3e41c2023-02-26 22:29:53 -0800187LOWER_PROXIMAL_LIMIT = -np.pi * 2.0
milind-ueeb08c52023-02-21 22:30:16 -0800188
Austin Schuh8edaf3e2023-02-22 21:20:52 -0800189UPPER_DISTAL_LIMIT = 0.75 * np.pi
190LOWER_DISTAL_LIMIT = -0.75 * np.pi
191
milind-ueeb08c52023-02-21 22:30:16 -0800192UPPER_ROLL_JOINT_LIMIT = 0.75 * np.pi
193LOWER_ROLL_JOINT_LIMIT = -0.75 * np.pi
194
195
Austin Schuh99dda682023-03-11 00:18:37 -0800196def arm_past_limit(theta1, theta2, theta3, verbose=True):
milind-ueeb08c52023-02-21 22:30:16 -0800197 delta = theta2 - theta1
Austin Schuh99dda682023-03-11 00:18:37 -0800198 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-ueeb08c52023-02-21 22:30:16 -0800226
227
Maxwell Hendersonf5123fe2023-02-04 13:44:41 -0800228def get_circular_index(theta):
Austin Schuh9a11ebd2023-02-26 14:16:31 -0800229 return int(
230 np.floor((shift_angle(theta[1]) - shift_angle(theta[0])) / np.pi))
Maxwell Hendersonf5123fe2023-02-04 13:44:41 -0800231
232
233def get_xy(theta):
milind-u060e4cf2023-02-22 00:08:52 -0800234 theta1 = shift_angle(theta[0])
235 theta2 = shift_angle(theta[1])
milind-u18a901d2023-02-17 21:51:55 -0800236 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 Hendersonf5123fe2023-02-04 13:44:41 -0800239
240
241# Subdivide in theta space.
242def 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 Hendersonf5123fe2023-02-04 13:44:41 -0800254def to_theta_with_ci(pt, circular_index):
milind-u18a901d2023-02-17 21:51:55 -0800255 return (to_theta_with_circular_index(pt[0], pt[1], circular_index))
Maxwell Hendersonf5123fe2023-02-04 13:44:41 -0800256
257
258# to_theta, but distinguishes between
259def to_theta_with_circular_index(x, y, circular_index):
260 theta1, theta2 = to_theta((x, y), circular_index)
milind-u18a901d2023-02-17 21:51:55 -0800261 n_circular_index = int(np.floor((theta2 - theta1) / np.pi))
262 theta2 = theta2 + ((circular_index - n_circular_index)) * np.pi
milind-u060e4cf2023-02-22 00:08:52 -0800263 return np.array((shift_angle(theta1), shift_angle(theta2)))
milind-u18a901d2023-02-17 21:51:55 -0800264
265
266# to_theta, but distinguishes between
267def 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 Hendersonf5123fe2023-02-04 13:44:41 -0800271
272
273# alpha is in [0, 1] and is the weight to merge a and b.
274def 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
284def normalize(v):
285 """Normalize a vector while handling 0 length vectors."""
milind-u18a901d2023-02-17 21:51:55 -0800286 norm = np.linalg.norm(v)
Maxwell Hendersonf5123fe2023-02-04 13:44:41 -0800287 if norm == 0:
288 return v
289 return v / norm
290
291
Maxwell Hendersonf5123fe2023-02-04 13:44:41 -0800292# Generic subdivision algorithm.
293def subdivide(p1, p2, max_dist):
294 dx = p2[0] - p1[0]
295 dy = p2[1] - p1[1]
milind-u18a901d2023-02-17 21:51:55 -0800296 dist = np.sqrt(dx**2 + dy**2)
297 n = int(np.ceil(dist / max_dist))
Maxwell Hendersonf5123fe2023-02-04 13:44:41 -0800298 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 Hendersonf5123fe2023-02-04 13:44:41 -0800304def 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-u18a901d2023-02-17 21:51:55 -0800312SPLINE_SUBDIVISIONS = 100
313
314
315def subdivide_multistep():
Maxwell Hendersonf5123fe2023-02-04 13:44:41 -0800316 # TODO: pick N based on spline parameters? or otherwise change it to be more evenly spaced?
milind-u18a901d2023-02-17 21:51:55 -0800317 for i in range(0, SPLINE_SUBDIVISIONS + 1):
318 yield i / float(SPLINE_SUBDIVISIONS)
Maxwell Hendersonf5123fe2023-02-04 13:44:41 -0800319
320
milind-u18a901d2023-02-17 21:51:55 -0800321def 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
329def 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
340def 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 Hendersonf5123fe2023-02-04 13:44:41 -0800362
363
Maxwell Henderson83cf6d62023-02-10 20:29:26 -0800364# Draw a list of lines to a cairo context.
Maxwell Hendersonf5123fe2023-02-04 13:44:41 -0800365def 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 Hendersonf5123fe2023-02-04 13:44:41 -0800369
370
milind-u18a901d2023-02-17 21:51:55 -0800371class Path(abc.ABC):
Maxwell Hendersonf5123fe2023-02-04 13:44:41 -0800372
milind-u18a901d2023-02-17 21:51:55 -0800373 def __init__(self, name):
Maxwell Hendersonf5123fe2023-02-04 13:44:41 -0800374 self.name = name
Maxwell Hendersonf5123fe2023-02-04 13:44:41 -0800375
milind-u18a901d2023-02-17 21:51:55 -0800376 @abc.abstractmethod
377 def DoToThetaPoints(self):
378 pass
379
380 @abc.abstractmethod
381 def DoDrawTo(self):
382 pass
383
384 @abc.abstractmethod
milind-uadd8fa32023-02-24 23:37:36 -0800385 def joint_thetas(self):
milind-u18a901d2023-02-17 21:51:55 -0800386 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 Hendersonf5123fe2023-02-04 13:44:41 -0800400
milind-ueeb08c52023-02-21 22:30:16 -0800401 def arm_past_limit(self, points, verbose=True):
402 for point in points:
Austin Schuh99dda682023-03-11 00:18:37 -0800403 if arm_past_limit(*point, verbose=verbose):
milind-ueeb08c52023-02-21 22:30:16 -0800404 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 Hendersonf5123fe2023-02-04 13:44:41 -0800410 def DrawTo(self, cr, theta_version):
milind-ueeb08c52023-02-21 22:30:16 -0800411 points = self.DoToThetaPoints()
412 if self.roll_joint_collision(points):
413 # Draw the spline red
milind-u18a901d2023-02-17 21:51:55 -0800414 cr.set_source_rgb(1.0, 0.0, 0.0)
milind-ueeb08c52023-02-21 22:30:16 -0800415 elif self.arm_past_limit(points):
416 # Draw the spline orange
417 cr.set_source_rgb(1.0, 0.5, 0.0)
418
milind-u18a901d2023-02-17 21:51:55 -0800419 self.DoDrawTo(cr, theta_version)
Maxwell Hendersonf5123fe2023-02-04 13:44:41 -0800420
milind-u4037bc72023-02-22 21:39:40 -0800421 def VerifyPoints(self):
milind-u18a901d2023-02-17 21:51:55 -0800422 points = self.DoToThetaPoints()
milind-ueeb08c52023-02-21 22:30:16 -0800423 if self.roll_joint_collision(points, verbose=True) or \
424 self.arm_past_limit(points, verbose=True):
milind-u18a901d2023-02-17 21:51:55 -0800425 sys.exit(1)
Maxwell Hendersonf5123fe2023-02-04 13:44:41 -0800426
427
milind-u18a901d2023-02-17 21:51:55 -0800428class SplineSegmentBase(Path):
Maxwell Hendersonf5123fe2023-02-04 13:44:41 -0800429
milind-u18a901d2023-02-17 21:51:55 -0800430 def __init__(self, name):
431 super().__init__(name)
Maxwell Hendersonf5123fe2023-02-04 13:44:41 -0800432
milind-u18a901d2023-02-17 21:51:55 -0800433 @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 Hendersonf5123fe2023-02-04 13:44:41 -0800438
milind-u18a901d2023-02-17 21:51:55 -0800439 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 Hendersonf5123fe2023-02-04 13:44:41 -0800448
449
milind-u18a901d2023-02-17 21:51:55 -0800450class ThetaSplineSegment(SplineSegmentBase):
Maxwell Hendersonf5123fe2023-02-04 13:44:41 -0800451
milind-u18a901d2023-02-17 21:51:55 -0800452 # start and end are [theta1, theta2, theta3].
453 # controls are just [theta1, theta2].
454 # control_alpha_rolls are a list of [alpha, roll]
Maxwell Hendersonf5123fe2023-02-04 13:44:41 -0800455 def __init__(self,
milind-u18a901d2023-02-17 21:51:55 -0800456 name,
Maxwell Hendersonf5123fe2023-02-04 13:44:41 -0800457 start,
458 control1,
459 control2,
460 end,
milind-u18a901d2023-02-17 21:51:55 -0800461 control_alpha_rolls=[],
Maxwell Hendersonf5123fe2023-02-04 13:44:41 -0800462 alpha_unitizer=None,
463 vmax=None):
milind-u18a901d2023-02-17 21:51:55 -0800464 super().__init__(name)
465 self.start = start[:2]
Maxwell Hendersonf5123fe2023-02-04 13:44:41 -0800466 self.control1 = control1
467 self.control2 = control2
milind-u18a901d2023-02-17 21:51:55 -0800468 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 Hendersonf5123fe2023-02-04 13:44:41 -0800472 self.alpha_unitizer = alpha_unitizer
473 self.vmax = vmax
474
Austin Schuh9a11ebd2023-02-26 14:16:31 -0800475 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 Hendersonf5123fe2023-02-04 13:44:41 -0800521 def __repr__(self):
milind-u18a901d2023-02-17 21:51:55 -0800522 return "ThetaSplineSegment(%s, %s, %s, %s)" % (repr(
Maxwell Hendersonf5123fe2023-02-04 13:44:41 -0800523 self.start), repr(self.control1), repr(
524 self.control2), repr(self.end))
525
milind-u18a901d2023-02-17 21:51:55 -0800526 def DoDrawTo(self, cr, theta_version):
Maxwell Hendersonf5123fe2023-02-04 13:44:41 -0800527 if (theta_version):
528 draw_lines(cr, [
milind-u060e4cf2023-02-22 00:08:52 -0800529 shift_angles(
530 spline_eval(self.start, self.control1, self.control2,
531 self.end, alpha))
532 for alpha in subdivide_multistep()
Maxwell Hendersonf5123fe2023-02-04 13:44:41 -0800533 ])
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-u18a901d2023-02-17 21:51:55 -0800542 for alpha in subdivide_multistep()
Maxwell Hendersonf5123fe2023-02-04 13:44:41 -0800543 ])
544
545 cr.move_to(start[0] + xy_end_circle_size, start[1])
milind-u18a901d2023-02-17 21:51:55 -0800546 cr.arc(start[0], start[1], xy_end_circle_size, 0, 2.0 * np.pi)
Maxwell Hendersonf5123fe2023-02-04 13:44:41 -0800547 cr.move_to(end[0] + xy_end_circle_size, end[1])
milind-u18a901d2023-02-17 21:51:55 -0800548 cr.arc(end[0], end[1], xy_end_circle_size, 0, 2.0 * np.pi)
Maxwell Hendersonf5123fe2023-02-04 13:44:41 -0800549
milind-u18a901d2023-02-17 21:51:55 -0800550 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 Hendersonf5123fe2023-02-04 13:44:41 -0800558
milind-u18a901d2023-02-17 21:51:55 -0800559 return points
Maxwell Hendersonf5123fe2023-02-04 13:44:41 -0800560
milind-u18a901d2023-02-17 21:51:55 -0800561 def get_controls_theta(self):
562 return (self.start, self.control1, self.control2, self.end)
Maxwell Hendersonf5123fe2023-02-04 13:44:41 -0800563
milind-uadd8fa32023-02-24 23:37:36 -0800564 def joint_thetas(self):
milind-u18a901d2023-02-17 21:51:55 -0800565 ts = []
milind-uadd8fa32023-02-24 23:37:36 -0800566 thetas = [[], [], []]
milind-u18a901d2023-02-17 21:51:55 -0800567 for alpha in subdivide_multistep():
milind-uadd8fa32023-02-24 23:37:36 -0800568 proximal, distal = spline_eval(self.start, self.control1,
569 self.control2, self.end, alpha)
milind-u18a901d2023-02-17 21:51:55 -0800570 roll_joint = get_roll_joint_theta_multistep(
571 self.alpha_rolls, alpha)
milind-uadd8fa32023-02-24 23:37:36 -0800572 thetas[0].append(proximal)
573 thetas[1].append(distal)
574 thetas[2].append(roll_joint)
milind-u18a901d2023-02-17 21:51:55 -0800575 ts.append(alpha)
576 return ts, thetas