blob: bbc7a1b2e28a916bf658e84badbef8d54e96705f [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 Henderson7af00982023-02-04 12:42:07 -0800247 self.theta_version = False
Maxwell Hendersonbfaf5ef2023-03-22 13:31:32 -0700248
249 self.init_extents()
250
251 self.connect('draw', self.on_draw)
Maxwell Henderson7af00982023-02-04 12:42:07 -0800252
Austin Schuh9a11ebd2023-02-26 14:16:31 -0800253 self.last_pos = to_xy(*graph_paths.points['Neutral'][:2])
milind-u600738b2023-02-22 14:42:19 -0800254 self.circular_index_select = 1
Maxwell Henderson7af00982023-02-04 12:42:07 -0800255
256 # Extra stuff for drawing lines.
Maxwell Henderson0b5f1d12023-03-08 20:01:35 -0800257 self.segments = segments
Maxwell Henderson7af00982023-02-04 12:42:07 -0800258 self.now_segment_pt = None
259 self.spline_edit = 0
260 self.edit_control1 = True
261
milind-uadd8fa32023-02-24 23:37:36 -0800262 self.joint_thetas = None
263 self.joint_points = None
milind-u18a901d2023-02-17 21:51:55 -0800264 self.fig = plt.figure()
milind-uadd8fa32023-02-24 23:37:36 -0800265 self.axes = [
266 self.fig.add_subplot(3, 1, 1),
267 self.fig.add_subplot(3, 1, 2),
268 self.fig.add_subplot(3, 1, 3)
269 ]
270 self.fig.subplots_adjust(hspace=1.0)
milind-u18a901d2023-02-17 21:51:55 -0800271
Maxwell Hendersonf5c08a52023-02-23 17:28:35 -0800272 self.index = 0
273
Maxwell Henderson4b8d02a2023-02-24 15:53:26 -0800274 self.outline = RobotOutline([DRIVETRAIN_X, DRIVETRAIN_Y],
275 DRIVETRAIN_WIDTH, JOINT_CENTER_RADIUS,
276 [JOINT_TOWER_X, JOINT_TOWER_Y],
277 JOINT_TOWER_WIDTH, JOINT_TOWER_HEIGHT,
278 [DRIVER_CAM_X, DRIVER_CAM_Y],
279 DRIVER_CAM_WIDTH, DRIVER_CAM_HEIGHT)
280
Maxwell Hendersonc088b842023-03-11 07:22:02 -0800281 self.show_indicators = True
Maxwell Henderson4e57e0c2023-03-11 12:25:04 -0800282 # Lets you only view selected path
283 self.view_current = False
Maxwell Hendersonc088b842023-03-11 07:22:02 -0800284
Maxwell Henderson4890c202023-03-21 12:09:04 -0700285 self.editing = True
286
Maxwell Hendersonbfaf5ef2023-03-22 13:31:32 -0700287 self.x_offset = 0
288 self.y_offset = 0
289
Maxwell Henderson7af00982023-02-04 12:42:07 -0800290 def _do_button_press_internal(self, event):
291 o_x = event.x
292 o_y = event.y
Maxwell Hendersonbfaf5ef2023-03-22 13:31:32 -0700293 event.y -= self.y_offset
294 event.x -= self.x_offset
Maxwell Henderson7af00982023-02-04 12:42:07 -0800295 x = event.x - self.window_shape[0] / 2
296 y = self.window_shape[1] / 2 - event.y
297 scale = self.get_current_scale()
298 event.x = x / scale + self.center[0]
299 event.y = y / scale + self.center[1]
300 self.do_button_press(event)
301 event.x = o_x
302 event.y = o_y
303
Maxwell Hendersonbfaf5ef2023-03-22 13:31:32 -0700304 def init_extents(self):
Maxwell Henderson7af00982023-02-04 12:42:07 -0800305 if self.theta_version:
milind-u18a901d2023-02-17 21:51:55 -0800306 self.extents_x_min = -np.pi * 2
307 self.extents_x_max = np.pi * 2
308 self.extents_y_min = -np.pi * 2
309 self.extents_y_max = np.pi * 2
Maxwell Henderson7af00982023-02-04 12:42:07 -0800310 else:
311 self.extents_x_min = -40.0 * 0.0254
312 self.extents_x_max = 40.0 * 0.0254
313 self.extents_y_min = -4.0 * 0.0254
314 self.extents_y_max = 110.0 * 0.0254
315
Maxwell Hendersonbfaf5ef2023-03-22 13:31:32 -0700316 self.center = (0.5 * (self.extents_x_min + self.extents_x_max),
317 0.5 * (self.extents_y_max + self.extents_y_min))
318 self.shape = (1.0 * (self.extents_x_max - self.extents_x_min),
319 1.0 * (self.extents_y_max - self.extents_y_min))
320
321 def get_current_scale(self):
322 w_w, w_h = self.window_shape
323 w, h = self.shape
324 return min((w_w / w), (w_h / h))
325
326 def on_draw(self, widget, event):
327 cr = self.get_window().cairo_create()
328
329 self.window_shape = (self.get_window().get_geometry().width,
330 self.get_window().get_geometry().height)
331
332 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
544 def do_key_press(self, event):
545 keyval = Gdk.keyval_to_lower(event.keyval)
546 print("Gdk.KEY_" + Gdk.keyval_name(keyval))
547 if keyval == Gdk.KEY_q:
548 print("Found q key and exiting.")
549 quit_main_loop()
550 elif keyval == Gdk.KEY_c:
551 # Increment which arm solution we render
552 self.circular_index_select += 1
553 print(self.circular_index_select)
554 elif keyval == Gdk.KEY_v:
555 # Decrement which arm solution we render
556 self.circular_index_select -= 1
557 print(self.circular_index_select)
Maxwell Henderson7af00982023-02-04 12:42:07 -0800558
Maxwell Hendersonf5c08a52023-02-23 17:28:35 -0800559 elif keyval == Gdk.KEY_o:
560 # Only prints current segment
561 print(repr(self.segments[self.index]))
Maxwell Henderson7af00982023-02-04 12:42:07 -0800562 elif keyval == Gdk.KEY_g:
563 # Generate theta points.
564 if self.segments:
Maxwell Hendersonf5c08a52023-02-23 17:28:35 -0800565 print(repr(self.segments[self.index].ToThetaPoints()))
Maxwell Henderson7af00982023-02-04 12:42:07 -0800566
Austin Schuha09e1bb2023-02-25 22:34:41 -0800567 elif keyval == Gdk.KEY_p:
568 if self.index > 0:
569 self.index -= 1
570 else:
571 self.index = len(self.segments) - 1
572 print("Switched to segment:", self.segments[self.index].name)
Austin Schuh9a11ebd2023-02-26 14:16:31 -0800573 self.segments[self.index].Print(graph_paths.points)
Austin Schuha09e1bb2023-02-25 22:34:41 -0800574
Maxwell Hendersonc088b842023-03-11 07:22:02 -0800575 elif keyval == Gdk.KEY_i:
576 self.show_indicators = not self.show_indicators
577
Maxwell Hendersonda409642023-03-11 13:35:20 -0800578 elif keyval == Gdk.KEY_h:
579 print("q: Quit the program")
580 print("c: Incriment which arm solution we render")
581 print("v: Decrement which arm solution we render")
582 print("o: Print the current segment")
583 print("g: Generate theta points")
584 print("p: Move to the previous segment")
585 print("n: Move to the next segment")
586 print("i: Switch on or off the control point indicators")
587 print("l: Switch on or off viewing only the selected spline")
588 print("t: Toggle between xy or theta renderings")
589 print("z: Switch between editing control point 1 and 2")
590
Austin Schuha09e1bb2023-02-25 22:34:41 -0800591 elif keyval == Gdk.KEY_n:
Maxwell Hendersonf5c08a52023-02-23 17:28:35 -0800592 self.index += 1
593 self.index = self.index % len(self.segments)
milind-u2a28c592023-02-24 23:13:04 -0800594 print("Switched to segment:", self.segments[self.index].name)
Austin Schuh9a11ebd2023-02-26 14:16:31 -0800595 self.segments[self.index].Print(graph_paths.points)
Maxwell Hendersonf5c08a52023-02-23 17:28:35 -0800596
Maxwell Henderson4890c202023-03-21 12:09:04 -0700597 elif keyval == Gdk.KEY_d:
598 self.editing = not self.editing
599
Maxwell Henderson4e57e0c2023-03-11 12:25:04 -0800600 elif keyval == Gdk.KEY_l:
601 self.view_current = not self.view_current
602
Maxwell Henderson7af00982023-02-04 12:42:07 -0800603 elif keyval == Gdk.KEY_t:
Maxwell Henderson75c70122023-03-22 20:16:47 -0700604 self.switch_theta()
Maxwell Henderson7af00982023-02-04 12:42:07 -0800605
606 elif keyval == Gdk.KEY_z:
607 self.edit_control1 = not self.edit_control1
608 if self.edit_control1:
Maxwell Hendersonf5c08a52023-02-23 17:28:35 -0800609 self.now_segment_pt = self.segments[self.index].control1
Maxwell Henderson7af00982023-02-04 12:42:07 -0800610 else:
Maxwell Hendersonf5c08a52023-02-23 17:28:35 -0800611 self.now_segment_pt = self.segments[self.index].control2
Maxwell Henderson7af00982023-02-04 12:42:07 -0800612 if not self.theta_version:
613 data = to_xy(self.now_segment_pt[0], self.now_segment_pt[1])
614 self.last_pos = (data[0], data[1])
615 else:
616 self.last_pos = self.now_segment_pt
617
618 print("self.last_pos: ", self.last_pos, " ci: ",
619 self.circular_index_select)
620
Maxwell Hendersonbfaf5ef2023-03-22 13:31:32 -0700621 self.queue_draw()
Maxwell Henderson7af00982023-02-04 12:42:07 -0800622
623 def do_button_press(self, event):
Maxwell Henderson4890c202023-03-21 12:09:04 -0700624
milind-u600738b2023-02-22 14:42:19 -0800625 last_pos = self.last_pos
Maxwell Henderson7af00982023-02-04 12:42:07 -0800626 self.last_pos = (event.x, event.y)
milind-u600738b2023-02-22 14:42:19 -0800627 pt_theta = self.cur_pt_in_theta()
628 if pt_theta is None:
629 self.last_pos = last_pos
630 return
631
632 self.now_segment_pt = np.array(shift_angles(pt_theta))
Maxwell Henderson7af00982023-02-04 12:42:07 -0800633
Maxwell Henderson4890c202023-03-21 12:09:04 -0700634 if self.editing:
635 if self.edit_control1:
636 self.segments[self.index].control1 = self.now_segment_pt
637 else:
638 self.segments[self.index].control2 = self.now_segment_pt
Maxwell Henderson7af00982023-02-04 12:42:07 -0800639
Austin Schuh9a11ebd2023-02-26 14:16:31 -0800640 print('Clicked at theta: np.array([%s, %s])' %
641 (self.now_segment_pt[0], self.now_segment_pt[1]))
Maxwell Henderson7af00982023-02-04 12:42:07 -0800642 if not self.theta_version:
Austin Schuh9a11ebd2023-02-26 14:16:31 -0800643 print(
644 'Clicked at to_theta_with_circular_index(%.3f, %.3f, circular_index=%d)'
645 % (self.last_pos[0], self.last_pos[1],
Maxwell Henderson7af00982023-02-04 12:42:07 -0800646 self.circular_index_select))
647
Austin Schuh9a11ebd2023-02-26 14:16:31 -0800648 self.segments[self.index].Print(graph_paths.points)
Maxwell Henderson7af00982023-02-04 12:42:07 -0800649
Maxwell Hendersonbfaf5ef2023-03-22 13:31:32 -0700650 self.queue_draw()
Maxwell Henderson7af00982023-02-04 12:42:07 -0800651
652
Maxwell Hendersonbfaf5ef2023-03-22 13:31:32 -0700653class Window(Gtk.Window):
654
655 def __init__(self, segments):
656 super().__init__(title="Drawing Area")
657
658 self.segment_store = Gtk.ListStore(int, str)
659
660 for i, segment in enumerate(segments):
661 self.segment_store.append([i, segment.name])
662
663 self.segment_box = Gtk.ComboBox.new_with_model_and_entry(
664 self.segment_store)
665 self.segment_box.connect("changed", self.on_combo_changed)
666 self.segment_box.set_entry_text_column(1)
667
668 self.arm_draw = ArmUi(segments)
669
670 self.arm_draw.y_offset = self.segment_box.get_allocation().width
671
672 print('Starting with segment: ',
673 self.arm_draw.segments[self.arm_draw.index].name)
674 self.arm_draw.segments[self.arm_draw.index].Print(graph_paths.points)
675
676 self.set_events(Gdk.EventMask.BUTTON_PRESS_MASK
677 | Gdk.EventMask.BUTTON_RELEASE_MASK
678 | Gdk.EventMask.POINTER_MOTION_MASK
679 | Gdk.EventMask.SCROLL_MASK
680 | Gdk.EventMask.KEY_PRESS_MASK)
681 self.method_connect('map-event', self.do_map_event)
682 self.method_connect("key-press-event", self.arm_draw.do_key_press)
683 self.method_connect("motion-notify-event", self.arm_draw.do_motion)
684 self.method_connect("button-press-event",
685 self.arm_draw._do_button_press_internal)
Maxwell Hendersonbfaf5ef2023-03-22 13:31:32 -0700686
687 self.grid = Gtk.Grid()
688 self.add(self.grid)
689
690 self.grid.attach(self.arm_draw, 0, 1, 1, 1)
691
Maxwell Henderson75c70122023-03-22 20:16:47 -0700692 self.isolate_button = Gtk.Button(label="Toggle Path Isolation")
693 self.isolate_button.connect('clicked', self.on_button_click)
694
695 self.theta_button = Gtk.Button(label="Toggle Theta Mode")
696 self.theta_button.connect('clicked', self.on_button_click)
697
698 self.editing_button = Gtk.Button(label="Toggle Editing Mode")
699 self.editing_button.connect('clicked', self.on_button_click)
700
701 self.indicator_button = Gtk.Button(
702 label="Toggle Control Point Indicators")
703 self.indicator_button.connect('clicked', self.on_button_click)
704
705 self.box = Gtk.Box(spacing=6)
706 self.grid.attach(self.box, 0, 0, 1, 1)
707
Maxwell Hendersonc7bdf9d2023-03-23 10:15:31 -0700708 self.figure_canvas = FigureCanvas(self.arm_draw.fig)
709 self.figure_canvas.set_size_request(500, 300)
710
711 self.grid.attach(self.figure_canvas, 1, 1, 1, 1)
712
Maxwell Henderson75c70122023-03-22 20:16:47 -0700713 self.box.pack_start(self.segment_box, False, False, 0)
714 self.box.pack_start(self.isolate_button, False, False, 0)
715 self.box.pack_start(self.theta_button, False, False, 0)
716 self.box.pack_start(self.editing_button, False, False, 0)
717 self.box.pack_start(self.indicator_button, False, False, 0)
Maxwell Hendersonbfaf5ef2023-03-22 13:31:32 -0700718
719 def on_combo_changed(self, combo):
720 iter = combo.get_active_iter()
721
722 if iter is not None:
723 model = combo.get_model()
724 id, name = model[iter][:2]
725 print("Selected: ID=%d, name=%s" % (id, name))
726 self.arm_draw.index = id
Maxwell Henderson75c70122023-03-22 20:16:47 -0700727 self.arm_draw.queue_draw()
Maxwell Hendersonbfaf5ef2023-03-22 13:31:32 -0700728
729 def method_connect(self, event, cb):
730
731 def handler(obj, *args):
732 cb(*args)
733
734 self.connect(event, handler)
735
736 def do_map_event(self, event):
Maxwell Henderson75c70122023-03-22 20:16:47 -0700737 self.arm_draw.y_offset = self.box.get_allocation().height
738
739 def on_button_click(self, button):
740 if self.isolate_button == button:
741 self.arm_draw.view_current = not self.arm_draw.view_current
742 elif self.theta_button == button:
743 self.arm_draw.switch_theta()
744 elif self.editing_button == button:
745 self.arm_draw.editing = not self.arm_draw.editing
746 elif self.indicator_button == button:
747 self.arm_draw.show_indicators = not self.arm_draw.show_indicators
748 self.arm_draw.queue_draw()
Maxwell Hendersonbfaf5ef2023-03-22 13:31:32 -0700749
750
751window = Window(graph_paths.segments)
752window.show_all()
Maxwell Henderson7af00982023-02-04 12:42:07 -0800753basic_window.RunApp()