blob: f3035f11f5f426582702cf2b947cf129e96492c6 [file] [log] [blame]
Maxwell Henderson7af00982023-02-04 12:42:07 -08001#!/usr/bin/python3
2
3from __future__ import print_function
Maxwell Henderson765567c2023-03-05 14:22:17 -08004# matplotlib overrides fontconfig locations, so it needs to be imported before gtk.
5import matplotlib.pyplot as plt
Maxwell Hendersonc7bdf9d2023-03-23 10:15:31 -07006from matplotlib.backends.backend_gtk3agg import (FigureCanvasGTK3Agg as
7 FigureCanvas)
Maxwell Henderson7af00982023-02-04 12:42:07 -08008import os
9from frc971.control_loops.python import basic_window
10from frc971.control_loops.python.color import Color, palette
11import random
12import gi
milind-u18a901d2023-02-17 21:51:55 -080013import numpy as np
Maxwell Henderson7af00982023-02-04 12:42:07 -080014
15gi.require_version('Gtk', '3.0')
16from gi.repository import Gdk, Gtk
17import cairo
Maxwell Henderson165b17e2023-03-09 17:14:04 -080018from y2023.control_loops.python.graph_tools import to_theta, to_xy, alpha_blend, shift_angles, get_xy
milind-u600738b2023-02-22 14:42:19 -080019from y2023.control_loops.python.graph_tools import l1, l2, joint_center
20from y2023.control_loops.python.graph_tools import DRIVER_CAM_POINTS
21from y2023.control_loops.python import graph_paths
Maxwell Henderson7af00982023-02-04 12:42:07 -080022
Maxwell Henderson83cf6d62023-02-10 20:29:26 -080023from frc971.control_loops.python.basic_window import quit_main_loop, set_color, OverrideMatrix, identity
Maxwell Henderson7af00982023-02-04 12:42:07 -080024
25import shapely
26from shapely.geometry import Polygon
27
Maxwell Hendersonbfaf5ef2023-03-22 13:31:32 -070028from frc971.control_loops.python.constants import *
29
Maxwell Henderson7af00982023-02-04 12:42:07 -080030
Maxwell Henderson83cf6d62023-02-10 20:29:26 -080031def px(cr):
32 return OverrideMatrix(cr, identity)
33
34
35# Draw lines to cr + stroke.
36def draw_lines(cr, lines):
37 cr.move_to(lines[0][0], lines[0][1])
38 for pt in lines[1:]:
39 cr.line_to(pt[0], pt[1])
40 with px(cr):
41 cr.stroke()
42
43
Maxwell Henderson7af00982023-02-04 12:42:07 -080044def draw_px_cross(cr, length_px):
45 """Draws a cross with fixed dimensions in pixel space."""
46 with px(cr):
47 x, y = cr.get_current_point()
48 cr.move_to(x, y - length_px)
49 cr.line_to(x, y + length_px)
50 cr.stroke()
51
52 cr.move_to(x - length_px, y)
53 cr.line_to(x + length_px, y)
54 cr.stroke()
55
56
57def angle_dist_sqr(a1, a2):
58 """Distance between two points in angle space."""
59 return (a1[0] - a2[0])**2 + (a1[1] - a2[1])**2
60
61
62# Find the highest y position that intersects the vertical line defined by x.
63def inter_y(x):
milind-u18a901d2023-02-17 21:51:55 -080064 return np.sqrt((l2 + l1)**2 - (x - joint_center[0])**2) + joint_center[1]
Maxwell Henderson7af00982023-02-04 12:42:07 -080065
66
67# Define min and max l1 angles based on vertical constraints.
68def get_angle(boundary):
milind-u18a901d2023-02-17 21:51:55 -080069 h = np.sqrt((l1)**2 - (boundary - joint_center[0])**2) + joint_center[1]
70 return np.arctan2(h, boundary - joint_center[0])
Maxwell Henderson7af00982023-02-04 12:42:07 -080071
72
Maxwell Henderson7af00982023-02-04 12:42:07 -080073# Rotate a rasterized loop such that it aligns to when the parameters loop
74def rotate_to_jump_point(points):
75 last_pt = points[0]
76 for pt_i in range(1, len(points)):
77 pt = points[pt_i]
78 delta = last_pt[1] - pt[1]
milind-u18a901d2023-02-17 21:51:55 -080079 if abs(delta) > np.pi:
Maxwell Henderson7af00982023-02-04 12:42:07 -080080 return points[pt_i:] + points[:pt_i]
81 last_pt = pt
82 return points
83
84
85# shift points vertically by dy.
86def y_shift(points, dy):
87 return [(x, y + dy) for x, y in points]
88
89
Maxwell Henderson7af00982023-02-04 12:42:07 -080090# Get the closest point to a line from a test pt.
91def get_closest(prev, cur, pt):
92 dx_ang = (cur[0] - prev[0])
93 dy_ang = (cur[1] - prev[1])
94
milind-u18a901d2023-02-17 21:51:55 -080095 d = np.sqrt(dx_ang**2 + dy_ang**2)
Maxwell Henderson7af00982023-02-04 12:42:07 -080096 if (d < 0.000001):
milind-u18a901d2023-02-17 21:51:55 -080097 return prev, np.sqrt((prev[0] - pt[0])**2 + (prev[1] - pt[1])**2)
Maxwell Henderson7af00982023-02-04 12:42:07 -080098
99 pdx = -dy_ang / d
100 pdy = dx_ang / d
101
102 dpx = pt[0] - prev[0]
103 dpy = pt[1] - prev[1]
104
105 alpha = (dx_ang * dpx + dy_ang * dpy) / d / d
106
107 if (alpha < 0):
milind-u18a901d2023-02-17 21:51:55 -0800108 return prev, np.sqrt((prev[0] - pt[0])**2 + (prev[1] - pt[1])**2)
Maxwell Henderson7af00982023-02-04 12:42:07 -0800109 elif (alpha > 1):
milind-u18a901d2023-02-17 21:51:55 -0800110 return cur, np.sqrt((cur[0] - pt[0])**2 + (cur[1] - pt[1])**2)
Maxwell Henderson7af00982023-02-04 12:42:07 -0800111 else:
112 return (alpha_blend(prev[0], cur[0], alpha), alpha_blend(prev[1], cur[1], alpha)), \
113 abs(dpx * pdx + dpy * pdy)
114
115
116def closest_segment(lines, pt):
117 c_pt, c_pt_dist = get_closest(lines[-1], lines[0], pt)
118 for i in range(1, len(lines)):
119 prev = lines[i - 1]
120 cur = lines[i]
121 c_pt_new, c_pt_new_dist = get_closest(prev, cur, pt)
122 if c_pt_new_dist < c_pt_dist:
123 c_pt = c_pt_new
124 c_pt_dist = c_pt_new_dist
125 return c_pt, c_pt_dist
126
127
Maxwell Henderson4b8d02a2023-02-24 15:53:26 -0800128# Defining outline of robot
129class RobotOutline():
130
131 def __init__(self, drivetrain_pos, drivetrain_width, joint_center_radius,
132 joint_tower_pos, joint_tower_width, joint_tower_height,
133 driver_cam_pos, driver_cam_width, driver_cam_height):
134 self.drivetrain_pos = drivetrain_pos
135 self.drivetrain_width = drivetrain_width
136
137 self.joint_center_radius = joint_center_radius
138 self.joint_tower_pos = joint_tower_pos
139 self.joint_tower_width = joint_tower_width
140 self.joint_tower_height = joint_tower_height
141
142 self.driver_cam_pos = driver_cam_pos
143 self.driver_cam_width = driver_cam_width
144 self.driver_cam_height = driver_cam_height
145
146 def draw(self, cr):
147 set_color(cr, palette["BLUE"])
148
149 cr.move_to(self.drivetrain_pos[0], self.drivetrain_pos[1])
150 cr.line_to(self.drivetrain_pos[0] + self.drivetrain_width,
151 self.drivetrain_pos[1])
152
153 with px(cr):
154 cr.stroke()
155
156 # Draw joint center
157 cr.arc(joint_center[0], joint_center[1], self.joint_center_radius, 0,
158 2.0 * np.pi)
159 with px(cr):
160 cr.stroke()
161
162 # Draw joint tower
163 cr.rectangle(self.joint_tower_pos[0], self.joint_tower_pos[1],
164 self.joint_tower_width, self.joint_tower_height)
165
166 with px(cr):
167 cr.stroke()
168
169 # Draw driver cam
170 cr.set_source_rgba(1, 0, 0, 0.5)
171 cr.rectangle(self.driver_cam_pos[0], self.driver_cam_pos[1],
172 self.driver_cam_width, self.driver_cam_height)
173
174 with px(cr):
175 cr.fill()
176
177 def draw_theta(self, cr):
178 # TOOD(Max): add theta mode drawing
179 pass
180
181
182DRIVETRAIN_X = -0.490
183DRIVETRAIN_Y = 0.184
184DRIVETRAIN_WIDTH = 0.980
185
186JOINT_CENTER_RADIUS = 0.173 / 2
187
188JOINT_TOWER_X = -0.252
189JOINT_TOWER_Y = DRIVETRAIN_Y
190JOINT_TOWER_WIDTH = 0.098
191JOINT_TOWER_HEIGHT = 0.864
192
193DRIVER_CAM_X = DRIVER_CAM_POINTS[0][0]
194DRIVER_CAM_Y = DRIVER_CAM_POINTS[0][1]
195DRIVER_CAM_WIDTH = DRIVER_CAM_POINTS[-1][0] - DRIVER_CAM_POINTS[0][0]
196DRIVER_CAM_HEIGHT = DRIVER_CAM_POINTS[-1][1] - DRIVER_CAM_POINTS[0][1]
197
198
Maxwell Henderson0b5f1d12023-03-08 20:01:35 -0800199class SegmentSelector(basic_window.BaseWindow):
200
201 def __init__(self, segments):
202 super(SegmentSelector, self).__init__()
203
204 self.window = Gtk.Window()
205 self.window.set_title("Segment Selector")
206
207 self.segments = segments
208
209 self.segment_store = Gtk.ListStore(int, str)
210
211 for i, segment in enumerate(segments):
212 self.segment_store.append([i, segment.name])
213
214 self.segment_box = Gtk.ComboBox.new_with_model_and_entry(
215 self.segment_store)
216 self.segment_box.connect("changed", self.on_combo_changed)
217 self.segment_box.set_entry_text_column(1)
218
219 self.current_path_index = None
220
221 self.window.add(self.segment_box)
222 self.window.show_all()
223
224 def on_combo_changed(self, combo):
225 iter = combo.get_active_iter()
226
227 if iter is not None:
228 model = combo.get_model()
229 id, name = model[iter][:2]
230 print("Selected: ID=%d, name=%s" % (id, name))
231 self.current_path_index = id
232
233
Maxwell Hendersonc7bdf9d2023-03-23 10:15:31 -0700234ARM_AREA_WIDTH = 2 * (SCREEN_SIZE - 200)
235ARM_AREA_HEIGHT = SCREEN_SIZE
236
237
Maxwell Henderson7af00982023-02-04 12:42:07 -0800238# Create a GTK+ widget on which we will draw using Cairo
Maxwell Hendersonbfaf5ef2023-03-22 13:31:32 -0700239class ArmUi(Gtk.DrawingArea):
Maxwell Henderson7af00982023-02-04 12:42:07 -0800240
Maxwell Henderson0b5f1d12023-03-08 20:01:35 -0800241 def __init__(self, segments):
milind-u18a901d2023-02-17 21:51:55 -0800242 super(ArmUi, self).__init__()
Maxwell Henderson7af00982023-02-04 12:42:07 -0800243
Maxwell Hendersonc7bdf9d2023-03-23 10:15:31 -0700244 self.set_size_request(ARM_AREA_WIDTH, ARM_AREA_HEIGHT)
Maxwell Hendersonbfaf5ef2023-03-22 13:31:32 -0700245 self.center = (0, 0)
Maxwell Hendersonc7bdf9d2023-03-23 10:15:31 -0700246 self.shape = (ARM_AREA_WIDTH, ARM_AREA_HEIGHT)
Maxwell Henderson7ba3a6a2023-03-25 19:51:41 -0700247 self.window_shape = (ARM_AREA_WIDTH, ARM_AREA_HEIGHT)
Maxwell Henderson7af00982023-02-04 12:42:07 -0800248 self.theta_version = False
Maxwell Hendersonbfaf5ef2023-03-22 13:31:32 -0700249
250 self.init_extents()
251
252 self.connect('draw', self.on_draw)
Maxwell Henderson7af00982023-02-04 12:42:07 -0800253
Austin Schuh9a11ebd2023-02-26 14:16:31 -0800254 self.last_pos = to_xy(*graph_paths.points['Neutral'][:2])
milind-u600738b2023-02-22 14:42:19 -0800255 self.circular_index_select = 1
Maxwell Henderson7af00982023-02-04 12:42:07 -0800256
257 # Extra stuff for drawing lines.
Maxwell Henderson0b5f1d12023-03-08 20:01:35 -0800258 self.segments = segments
Maxwell Henderson7af00982023-02-04 12:42:07 -0800259 self.now_segment_pt = None
260 self.spline_edit = 0
261 self.edit_control1 = True
262
milind-uadd8fa32023-02-24 23:37:36 -0800263 self.joint_thetas = None
264 self.joint_points = None
milind-u18a901d2023-02-17 21:51:55 -0800265 self.fig = plt.figure()
milind-uadd8fa32023-02-24 23:37:36 -0800266 self.axes = [
267 self.fig.add_subplot(3, 1, 1),
268 self.fig.add_subplot(3, 1, 2),
269 self.fig.add_subplot(3, 1, 3)
270 ]
271 self.fig.subplots_adjust(hspace=1.0)
milind-u18a901d2023-02-17 21:51:55 -0800272
Maxwell Hendersonf5c08a52023-02-23 17:28:35 -0800273 self.index = 0
274
Maxwell Henderson4b8d02a2023-02-24 15:53:26 -0800275 self.outline = RobotOutline([DRIVETRAIN_X, DRIVETRAIN_Y],
276 DRIVETRAIN_WIDTH, JOINT_CENTER_RADIUS,
277 [JOINT_TOWER_X, JOINT_TOWER_Y],
278 JOINT_TOWER_WIDTH, JOINT_TOWER_HEIGHT,
279 [DRIVER_CAM_X, DRIVER_CAM_Y],
280 DRIVER_CAM_WIDTH, DRIVER_CAM_HEIGHT)
281
Maxwell Hendersonc088b842023-03-11 07:22:02 -0800282 self.show_indicators = True
Maxwell Henderson4e57e0c2023-03-11 12:25:04 -0800283 # Lets you only view selected path
284 self.view_current = False
Maxwell Hendersonc088b842023-03-11 07:22:02 -0800285
Maxwell Hendersonbf209a22023-04-14 20:55:10 -0700286 self.previous_segment = None
287
Maxwell Henderson4890c202023-03-21 12:09:04 -0700288 self.editing = True
289
Maxwell Hendersonbfaf5ef2023-03-22 13:31:32 -0700290 self.x_offset = 0
291 self.y_offset = 0
292
Maxwell Henderson7af00982023-02-04 12:42:07 -0800293 def _do_button_press_internal(self, event):
294 o_x = event.x
295 o_y = event.y
Maxwell Hendersonbfaf5ef2023-03-22 13:31:32 -0700296 event.y -= self.y_offset
297 event.x -= self.x_offset
Maxwell Henderson7af00982023-02-04 12:42:07 -0800298 x = event.x - self.window_shape[0] / 2
299 y = self.window_shape[1] / 2 - event.y
300 scale = self.get_current_scale()
301 event.x = x / scale + self.center[0]
302 event.y = y / scale + self.center[1]
303 self.do_button_press(event)
304 event.x = o_x
305 event.y = o_y
306
Maxwell Hendersonbfaf5ef2023-03-22 13:31:32 -0700307 def init_extents(self):
Maxwell Henderson7af00982023-02-04 12:42:07 -0800308 if self.theta_version:
milind-u18a901d2023-02-17 21:51:55 -0800309 self.extents_x_min = -np.pi * 2
310 self.extents_x_max = np.pi * 2
311 self.extents_y_min = -np.pi * 2
312 self.extents_y_max = np.pi * 2
Maxwell Henderson7af00982023-02-04 12:42:07 -0800313 else:
314 self.extents_x_min = -40.0 * 0.0254
315 self.extents_x_max = 40.0 * 0.0254
316 self.extents_y_min = -4.0 * 0.0254
317 self.extents_y_max = 110.0 * 0.0254
318
Maxwell Hendersonbfaf5ef2023-03-22 13:31:32 -0700319 self.center = (0.5 * (self.extents_x_min + self.extents_x_max),
320 0.5 * (self.extents_y_max + self.extents_y_min))
321 self.shape = (1.0 * (self.extents_x_max - self.extents_x_min),
322 1.0 * (self.extents_y_max - self.extents_y_min))
323
324 def get_current_scale(self):
325 w_w, w_h = self.window_shape
326 w, h = self.shape
327 return min((w_w / w), (w_h / h))
328
329 def on_draw(self, widget, event):
330 cr = self.get_window().cairo_create()
331
Maxwell Hendersonbfaf5ef2023-03-22 13:31:32 -0700332 cr.save()
333 cr.set_font_size(20)
334 cr.translate(self.window_shape[0] / 2, self.window_shape[1] / 2)
335 scale = self.get_current_scale()
336 cr.scale(scale, -scale)
337 cr.translate(-self.center[0], -self.center[1])
338 cr.reset_clip()
339 self.handle_draw(cr)
340 cr.restore()
341
342 def method_connect(self, event, cb):
343
344 def handler(obj, *args):
345 cb(*args)
346
347 self.window.connect(event, handler)
Maxwell Henderson7af00982023-02-04 12:42:07 -0800348
349 # Handle the expose-event by drawing
350 def handle_draw(self, cr):
Maxwell Henderson7af00982023-02-04 12:42:07 -0800351 # Fill the background color of the window with grey
352 set_color(cr, palette["GREY"])
353 cr.paint()
354
355 # Draw a extents rectangle
356 set_color(cr, palette["WHITE"])
357 cr.rectangle(self.extents_x_min, self.extents_y_min,
358 (self.extents_x_max - self.extents_x_min),
359 self.extents_y_max - self.extents_y_min)
360 cr.fill()
361
Maxwell Henderson93380322023-02-04 16:31:54 -0800362 if self.theta_version:
363 # Draw a filled white rectangle.
364 set_color(cr, palette["WHITE"])
milind-u18a901d2023-02-17 21:51:55 -0800365 cr.rectangle(-np.pi, -np.pi, np.pi * 2.0, np.pi * 2.0)
Maxwell Henderson93380322023-02-04 16:31:54 -0800366 cr.fill()
367
368 set_color(cr, palette["BLUE"])
369 for i in range(-6, 6):
milind-u18a901d2023-02-17 21:51:55 -0800370 cr.move_to(-40, -40 + i * np.pi)
371 cr.line_to(40, 40 + i * np.pi)
Maxwell Henderson93380322023-02-04 16:31:54 -0800372 with px(cr):
373 cr.stroke()
374
Maxwell Henderson93380322023-02-04 16:31:54 -0800375 set_color(cr, Color(0.0, 1.0, 0.2))
376 cr.move_to(self.last_pos[0], self.last_pos[1])
377 draw_px_cross(cr, 5)
378
Maxwell Henderson93380322023-02-04 16:31:54 -0800379 else:
Maxwell Henderson7af00982023-02-04 12:42:07 -0800380 # Draw a filled white rectangle.
381 set_color(cr, palette["WHITE"])
382 cr.rectangle(-2.0, -2.0, 4.0, 4.0)
383 cr.fill()
384
Maxwell Henderson4b8d02a2023-02-24 15:53:26 -0800385 self.outline.draw(cr)
milind-u18a901d2023-02-17 21:51:55 -0800386
387 # Draw max radius
Maxwell Henderson7af00982023-02-04 12:42:07 -0800388 set_color(cr, palette["BLUE"])
milind-u18a901d2023-02-17 21:51:55 -0800389 cr.arc(joint_center[0], joint_center[1], l2 + l1, 0, 2.0 * np.pi)
390 with px(cr):
391 cr.stroke()
392 cr.arc(joint_center[0], joint_center[1], l1 - l2, 0, 2.0 * np.pi)
Maxwell Henderson7af00982023-02-04 12:42:07 -0800393 with px(cr):
394 cr.stroke()
395
milind-u18a901d2023-02-17 21:51:55 -0800396 set_color(cr, Color(0.5, 1.0, 1))
Maxwell Henderson7af00982023-02-04 12:42:07 -0800397
milind-u600738b2023-02-22 14:42:19 -0800398 if not self.theta_version:
399 theta1, theta2 = to_theta(self.last_pos,
400 self.circular_index_select)
401 x, y = joint_center[0], joint_center[1]
402 cr.move_to(x, y)
403
404 x += np.cos(theta1) * l1
405 y += np.sin(theta1) * l1
406 cr.line_to(x, y)
407 x += np.cos(theta2) * l2
408 y += np.sin(theta2) * l2
409 cr.line_to(x, y)
410 with px(cr):
411 cr.stroke()
412
413 cr.move_to(self.last_pos[0], self.last_pos[1])
414 set_color(cr, Color(0.0, 1.0, 0.2))
415 draw_px_cross(cr, 20)
416
417 if self.theta_version:
418 set_color(cr, Color(0.0, 1.0, 0.2))
419 cr.move_to(self.last_pos[0], self.last_pos[1])
420 draw_px_cross(cr, 5)
421
Maxwell Henderson4b8d02a2023-02-24 15:53:26 -0800422 self.outline.draw_theta(cr)
milind-u600738b2023-02-22 14:42:19 -0800423
Maxwell Henderson7af00982023-02-04 12:42:07 -0800424 set_color(cr, Color(0.0, 0.5, 1.0))
Maxwell Henderson4e57e0c2023-03-11 12:25:04 -0800425 if not self.view_current:
426 for i in range(len(self.segments)):
427 color = None
428 if i == self.index:
429 continue
430 color = [0, random.random(), 1]
431 random.shuffle(color)
432 set_color(cr, Color(color[0], color[1], color[2]))
433 self.segments[i].DrawTo(cr, self.theta_version)
Maxwell Henderson165b17e2023-03-09 17:14:04 -0800434
Maxwell Henderson4e57e0c2023-03-11 12:25:04 -0800435 with px(cr):
436 cr.stroke()
Maxwell Henderson7af00982023-02-04 12:42:07 -0800437
Austin Schuhdba32702023-03-04 22:51:05 -0800438 # Draw current spline in black
439 color = [0, 0, 0]
440 set_color(cr, Color(color[0], color[1], color[2]))
441 self.segments[self.index].DrawTo(cr, self.theta_version)
Maxwell Henderson165b17e2023-03-09 17:14:04 -0800442
Maxwell Hendersonc088b842023-03-11 07:22:02 -0800443 with px(cr):
444 cr.stroke()
Maxwell Henderson165b17e2023-03-09 17:14:04 -0800445 control1 = get_xy(self.segments[self.index].control1)
446 control2 = get_xy(self.segments[self.index].control2)
447
448 if self.theta_version:
449 control1 = shift_angles(self.segments[self.index].control1)
450 control2 = shift_angles(self.segments[self.index].control2)
451
Maxwell Hendersonc088b842023-03-11 07:22:02 -0800452 if self.show_indicators:
453 set_color(cr, Color(1.0, 0.0, 1.0))
454 cr.move_to(control1[0] + 0.02, control1[1])
455 cr.arc(control1[0], control1[1], 0.02, 0, 2.0 * np.pi)
456 with px(cr):
457 cr.stroke()
458 set_color(cr, Color(1.0, 0.7, 0.0))
459 cr.move_to(control2[0] + 0.02, control2[1])
460 cr.arc(control2[0], control2[1], 0.02, 0, 2.0 * np.pi)
Maxwell Henderson165b17e2023-03-09 17:14:04 -0800461
Austin Schuhdba32702023-03-04 22:51:05 -0800462 with px(cr):
463 cr.stroke()
464
Maxwell Henderson7af00982023-02-04 12:42:07 -0800465 set_color(cr, Color(0.0, 1.0, 0.5))
milind-u18a901d2023-02-17 21:51:55 -0800466
milind-uadd8fa32023-02-24 23:37:36 -0800467 # Create the plots
468 if self.joint_thetas:
469 if self.joint_points:
470 titles = ["Proximal", "Distal", "Roll joint"]
471 for i in range(len(self.joint_points)):
472 self.axes[i].clear()
473 self.axes[i].plot(self.joint_thetas[0],
474 self.joint_thetas[1][i])
475 self.axes[i].scatter([self.joint_points[i][0]],
476 [self.joint_points[i][1]],
477 s=10,
478 c="red")
479 self.axes[i].set_title(titles[i])
480 plt.title("Joint Angle")
milind-u18a901d2023-02-17 21:51:55 -0800481 plt.xlabel("t (0 to 1)")
482 plt.ylabel("theta (rad)")
483
484 self.fig.canvas.draw()
Maxwell Henderson7af00982023-02-04 12:42:07 -0800485
486 def cur_pt_in_theta(self):
milind-u18a901d2023-02-17 21:51:55 -0800487 if self.theta_version: return self.last_pos
milind-u600738b2023-02-22 14:42:19 -0800488 return to_theta(self.last_pos,
489 self.circular_index_select,
490 cross_point=-np.pi,
491 die=False)
Maxwell Henderson7af00982023-02-04 12:42:07 -0800492
milind-u18a901d2023-02-17 21:51:55 -0800493 def do_motion(self, event):
494 o_x = event.x
495 o_y = event.y
Maxwell Hendersonbfaf5ef2023-03-22 13:31:32 -0700496 event.x -= self.x_offset
497 event.y -= self.y_offset
milind-u18a901d2023-02-17 21:51:55 -0800498 x = event.x - self.window_shape[0] / 2
499 y = self.window_shape[1] / 2 - event.y
500 scale = self.get_current_scale()
501 event.x = x / scale + self.center[0]
502 event.y = y / scale + self.center[1]
503
milind-u52af04c2023-02-25 14:03:08 -0800504 segment = self.segments[self.index]
505 self.joint_thetas = segment.joint_thetas()
milind-u18a901d2023-02-17 21:51:55 -0800506
milind-u52af04c2023-02-25 14:03:08 -0800507 hovered_t = segment.intersection(event)
508 if hovered_t:
509 min_diff = np.inf
510 closest_t = None
511 closest_thetas = None
512 for i in range(len(self.joint_thetas[0])):
513 t = self.joint_thetas[0][i]
514 diff = abs(t - hovered_t)
515 if diff < min_diff:
516 min_diff = diff
517 closest_t = t
518 closest_thetas = [
519 self.joint_thetas[1][0][i], self.joint_thetas[1][1][i],
520 self.joint_thetas[1][2][i]
521 ]
522 self.joint_points = [(closest_t, closest_theta)
523 for closest_theta in closest_thetas]
milind-u18a901d2023-02-17 21:51:55 -0800524
525 event.x = o_x
526 event.y = o_y
527
Maxwell Hendersonbfaf5ef2023-03-22 13:31:32 -0700528 self.queue_draw()
Maxwell Henderson7af00982023-02-04 12:42:07 -0800529
Maxwell Henderson75c70122023-03-22 20:16:47 -0700530 def switch_theta(self):
531 # Toggle between theta and xy renderings
532 if self.theta_version:
533 theta1, theta2 = self.last_pos
534 data = to_xy(theta1, theta2)
535 self.circular_index_select = int(
536 np.floor((theta2 - theta1) / np.pi))
537 self.last_pos = (data[0], data[1])
538 else:
539 self.last_pos = self.cur_pt_in_theta()
540
541 self.theta_version = not self.theta_version
542 self.init_extents()
Maxwell Henderson7af00982023-02-04 12:42:07 -0800543
Maxwell Hendersonbf209a22023-04-14 20:55:10 -0700544 def undo(self):
545 if self.previous_segment is not None:
546 if self.edit_control1:
547 self.segments[self.index].control1 = self.previous_segment
548 else:
549 self.segments[self.index].control2 = self.previous_segment
550
Maxwell Henderson7af00982023-02-04 12:42:07 -0800551 def do_key_press(self, event):
552 keyval = Gdk.keyval_to_lower(event.keyval)
553 print("Gdk.KEY_" + Gdk.keyval_name(keyval))
554 if keyval == Gdk.KEY_q:
555 print("Found q key and exiting.")
556 quit_main_loop()
557 elif keyval == Gdk.KEY_c:
558 # Increment which arm solution we render
559 self.circular_index_select += 1
560 print(self.circular_index_select)
561 elif keyval == Gdk.KEY_v:
562 # Decrement which arm solution we render
563 self.circular_index_select -= 1
564 print(self.circular_index_select)
Maxwell Henderson7af00982023-02-04 12:42:07 -0800565
Maxwell Hendersonf5c08a52023-02-23 17:28:35 -0800566 elif keyval == Gdk.KEY_o:
567 # Only prints current segment
568 print(repr(self.segments[self.index]))
Maxwell Henderson7af00982023-02-04 12:42:07 -0800569 elif keyval == Gdk.KEY_g:
570 # Generate theta points.
571 if self.segments:
Maxwell Hendersonf5c08a52023-02-23 17:28:35 -0800572 print(repr(self.segments[self.index].ToThetaPoints()))
Maxwell Henderson7af00982023-02-04 12:42:07 -0800573
Maxwell Hendersonbf209a22023-04-14 20:55:10 -0700574 elif keyval == Gdk.KEY_u:
575 self.undo()
576
Austin Schuha09e1bb2023-02-25 22:34:41 -0800577 elif keyval == Gdk.KEY_p:
578 if self.index > 0:
579 self.index -= 1
580 else:
581 self.index = len(self.segments) - 1
582 print("Switched to segment:", self.segments[self.index].name)
Austin Schuh9a11ebd2023-02-26 14:16:31 -0800583 self.segments[self.index].Print(graph_paths.points)
Austin Schuha09e1bb2023-02-25 22:34:41 -0800584
Maxwell Hendersonc088b842023-03-11 07:22:02 -0800585 elif keyval == Gdk.KEY_i:
586 self.show_indicators = not self.show_indicators
587
Maxwell Hendersonda409642023-03-11 13:35:20 -0800588 elif keyval == Gdk.KEY_h:
589 print("q: Quit the program")
590 print("c: Incriment which arm solution we render")
591 print("v: Decrement which arm solution we render")
592 print("o: Print the current segment")
593 print("g: Generate theta points")
594 print("p: Move to the previous segment")
595 print("n: Move to the next segment")
596 print("i: Switch on or off the control point indicators")
597 print("l: Switch on or off viewing only the selected spline")
598 print("t: Toggle between xy or theta renderings")
599 print("z: Switch between editing control point 1 and 2")
600
Austin Schuha09e1bb2023-02-25 22:34:41 -0800601 elif keyval == Gdk.KEY_n:
Maxwell Hendersonf5c08a52023-02-23 17:28:35 -0800602 self.index += 1
603 self.index = self.index % len(self.segments)
milind-u2a28c592023-02-24 23:13:04 -0800604 print("Switched to segment:", self.segments[self.index].name)
Austin Schuh9a11ebd2023-02-26 14:16:31 -0800605 self.segments[self.index].Print(graph_paths.points)
Maxwell Hendersonf5c08a52023-02-23 17:28:35 -0800606
Maxwell Henderson4890c202023-03-21 12:09:04 -0700607 elif keyval == Gdk.KEY_d:
608 self.editing = not self.editing
609
Maxwell Henderson4e57e0c2023-03-11 12:25:04 -0800610 elif keyval == Gdk.KEY_l:
611 self.view_current = not self.view_current
612
Maxwell Henderson7af00982023-02-04 12:42:07 -0800613 elif keyval == Gdk.KEY_t:
Maxwell Henderson75c70122023-03-22 20:16:47 -0700614 self.switch_theta()
Maxwell Henderson7af00982023-02-04 12:42:07 -0800615
616 elif keyval == Gdk.KEY_z:
617 self.edit_control1 = not self.edit_control1
618 if self.edit_control1:
Maxwell Hendersonf5c08a52023-02-23 17:28:35 -0800619 self.now_segment_pt = self.segments[self.index].control1
Maxwell Henderson7af00982023-02-04 12:42:07 -0800620 else:
Maxwell Hendersonf5c08a52023-02-23 17:28:35 -0800621 self.now_segment_pt = self.segments[self.index].control2
Maxwell Henderson7af00982023-02-04 12:42:07 -0800622 if not self.theta_version:
623 data = to_xy(self.now_segment_pt[0], self.now_segment_pt[1])
624 self.last_pos = (data[0], data[1])
625 else:
626 self.last_pos = self.now_segment_pt
627
628 print("self.last_pos: ", self.last_pos, " ci: ",
629 self.circular_index_select)
630
Maxwell Hendersonbfaf5ef2023-03-22 13:31:32 -0700631 self.queue_draw()
Maxwell Henderson7af00982023-02-04 12:42:07 -0800632
633 def do_button_press(self, event):
Maxwell Henderson4890c202023-03-21 12:09:04 -0700634
milind-u600738b2023-02-22 14:42:19 -0800635 last_pos = self.last_pos
Maxwell Henderson7af00982023-02-04 12:42:07 -0800636 self.last_pos = (event.x, event.y)
milind-u600738b2023-02-22 14:42:19 -0800637 pt_theta = self.cur_pt_in_theta()
638 if pt_theta is None:
639 self.last_pos = last_pos
640 return
641
642 self.now_segment_pt = np.array(shift_angles(pt_theta))
Maxwell Henderson4890c202023-03-21 12:09:04 -0700643 if self.editing:
644 if self.edit_control1:
Maxwell Hendersonbf209a22023-04-14 20:55:10 -0700645 if (self.now_segment_pt !=
646 self.segments[self.index].control1).any():
647 self.previous_segment = self.segments[self.index].control1
Maxwell Henderson4890c202023-03-21 12:09:04 -0700648 self.segments[self.index].control1 = self.now_segment_pt
649 else:
Maxwell Hendersonbf209a22023-04-14 20:55:10 -0700650 if (self.now_segment_pt !=
651 self.segments[self.index].control2).any():
652 self.previous_segment = self.segments[self.index].control2
Maxwell Henderson4890c202023-03-21 12:09:04 -0700653 self.segments[self.index].control2 = self.now_segment_pt
Maxwell Henderson7af00982023-02-04 12:42:07 -0800654
Austin Schuh9a11ebd2023-02-26 14:16:31 -0800655 print('Clicked at theta: np.array([%s, %s])' %
656 (self.now_segment_pt[0], self.now_segment_pt[1]))
Maxwell Henderson7af00982023-02-04 12:42:07 -0800657 if not self.theta_version:
Austin Schuh9a11ebd2023-02-26 14:16:31 -0800658 print(
659 'Clicked at to_theta_with_circular_index(%.3f, %.3f, circular_index=%d)'
660 % (self.last_pos[0], self.last_pos[1],
Maxwell Henderson7af00982023-02-04 12:42:07 -0800661 self.circular_index_select))
662
Austin Schuh9a11ebd2023-02-26 14:16:31 -0800663 self.segments[self.index].Print(graph_paths.points)
Maxwell Henderson7af00982023-02-04 12:42:07 -0800664
Maxwell Hendersonbfaf5ef2023-03-22 13:31:32 -0700665 self.queue_draw()
Maxwell Henderson7af00982023-02-04 12:42:07 -0800666
667
Maxwell Hendersonbfaf5ef2023-03-22 13:31:32 -0700668class Window(Gtk.Window):
669
670 def __init__(self, segments):
671 super().__init__(title="Drawing Area")
672
673 self.segment_store = Gtk.ListStore(int, str)
674
675 for i, segment in enumerate(segments):
676 self.segment_store.append([i, segment.name])
677
678 self.segment_box = Gtk.ComboBox.new_with_model_and_entry(
679 self.segment_store)
680 self.segment_box.connect("changed", self.on_combo_changed)
681 self.segment_box.set_entry_text_column(1)
682
683 self.arm_draw = ArmUi(segments)
684
685 self.arm_draw.y_offset = self.segment_box.get_allocation().width
686
687 print('Starting with segment: ',
688 self.arm_draw.segments[self.arm_draw.index].name)
689 self.arm_draw.segments[self.arm_draw.index].Print(graph_paths.points)
690
691 self.set_events(Gdk.EventMask.BUTTON_PRESS_MASK
692 | Gdk.EventMask.BUTTON_RELEASE_MASK
693 | Gdk.EventMask.POINTER_MOTION_MASK
694 | Gdk.EventMask.SCROLL_MASK
695 | Gdk.EventMask.KEY_PRESS_MASK)
696 self.method_connect('map-event', self.do_map_event)
697 self.method_connect("key-press-event", self.arm_draw.do_key_press)
698 self.method_connect("motion-notify-event", self.arm_draw.do_motion)
699 self.method_connect("button-press-event",
700 self.arm_draw._do_button_press_internal)
Maxwell Hendersonbfaf5ef2023-03-22 13:31:32 -0700701
702 self.grid = Gtk.Grid()
703 self.add(self.grid)
704
705 self.grid.attach(self.arm_draw, 0, 1, 1, 1)
706
Maxwell Henderson75c70122023-03-22 20:16:47 -0700707 self.isolate_button = Gtk.Button(label="Toggle Path Isolation")
708 self.isolate_button.connect('clicked', self.on_button_click)
709
710 self.theta_button = Gtk.Button(label="Toggle Theta Mode")
711 self.theta_button.connect('clicked', self.on_button_click)
712
713 self.editing_button = Gtk.Button(label="Toggle Editing Mode")
714 self.editing_button.connect('clicked', self.on_button_click)
715
716 self.indicator_button = Gtk.Button(
717 label="Toggle Control Point Indicators")
718 self.indicator_button.connect('clicked', self.on_button_click)
719
Maxwell Hendersonbf209a22023-04-14 20:55:10 -0700720 self.undo_button = Gtk.Button(label="Undo")
721 self.undo_button.connect('clicked', self.on_button_click)
722
Maxwell Henderson75c70122023-03-22 20:16:47 -0700723 self.box = Gtk.Box(spacing=6)
724 self.grid.attach(self.box, 0, 0, 1, 1)
725
Maxwell Hendersonc7bdf9d2023-03-23 10:15:31 -0700726 self.figure_canvas = FigureCanvas(self.arm_draw.fig)
727 self.figure_canvas.set_size_request(500, 300)
728
729 self.grid.attach(self.figure_canvas, 1, 1, 1, 1)
730
Maxwell Henderson75c70122023-03-22 20:16:47 -0700731 self.box.pack_start(self.segment_box, False, False, 0)
732 self.box.pack_start(self.isolate_button, False, False, 0)
733 self.box.pack_start(self.theta_button, False, False, 0)
734 self.box.pack_start(self.editing_button, False, False, 0)
735 self.box.pack_start(self.indicator_button, False, False, 0)
Maxwell Hendersonbf209a22023-04-14 20:55:10 -0700736 self.box.pack_start(self.undo_button, False, False, 0)
Maxwell Hendersonbfaf5ef2023-03-22 13:31:32 -0700737
738 def on_combo_changed(self, combo):
739 iter = combo.get_active_iter()
740
741 if iter is not None:
742 model = combo.get_model()
743 id, name = model[iter][:2]
744 print("Selected: ID=%d, name=%s" % (id, name))
745 self.arm_draw.index = id
Maxwell Henderson75c70122023-03-22 20:16:47 -0700746 self.arm_draw.queue_draw()
Maxwell Hendersonbfaf5ef2023-03-22 13:31:32 -0700747
748 def method_connect(self, event, cb):
749
750 def handler(obj, *args):
751 cb(*args)
752
753 self.connect(event, handler)
754
755 def do_map_event(self, event):
Maxwell Henderson75c70122023-03-22 20:16:47 -0700756 self.arm_draw.y_offset = self.box.get_allocation().height
757
758 def on_button_click(self, button):
759 if self.isolate_button == button:
760 self.arm_draw.view_current = not self.arm_draw.view_current
761 elif self.theta_button == button:
762 self.arm_draw.switch_theta()
763 elif self.editing_button == button:
764 self.arm_draw.editing = not self.arm_draw.editing
765 elif self.indicator_button == button:
766 self.arm_draw.show_indicators = not self.arm_draw.show_indicators
Maxwell Hendersonbf209a22023-04-14 20:55:10 -0700767 elif self.undo_button == button:
768 self.arm_draw.undo()
Maxwell Henderson75c70122023-03-22 20:16:47 -0700769 self.arm_draw.queue_draw()
Maxwell Hendersonbfaf5ef2023-03-22 13:31:32 -0700770
771
772window = Window(graph_paths.segments)
773window.show_all()
Maxwell Henderson7af00982023-02-04 12:42:07 -0800774basic_window.RunApp()