blob: d402593fdd28739cdbeb7bd86198b1d92cbd2356 [file] [log] [blame]
Tabitha Jarvis1007a132018-12-12 21:47:54 -08001#!/usr/bin/python3
Tabitha Jarvis1007a132018-12-12 21:47:54 -08002from __future__ import print_function
3import os
Andrew Runke0f945fd2019-01-27 21:10:37 -08004import sys
Ravago Jones6d460fe2021-07-03 16:59:55 -07005from color import palette
6from graph import Graph
Tabitha Jarvis1007a132018-12-12 21:47:54 -08007import gi
8import numpy as np
Tabitha Jarvis1007a132018-12-12 21:47:54 -08009gi.require_version('Gtk', '3.0')
John Park91e69732019-03-03 13:12:43 -080010gi.require_version('Gdk', '3.0')
Andrew Runke6842bf92019-01-26 15:38:25 -080011from gi.repository import Gdk, Gtk, GLib
Tabitha Jarvis1007a132018-12-12 21:47:54 -080012import cairo
Ravago Jones6d460fe2021-07-03 16:59:55 -070013from libspline import Spline
Tabitha Jarvis1007a132018-12-12 21:47:54 -080014import enum
John Park91e69732019-03-03 13:12:43 -080015import json
John Park91e69732019-03-03 13:12:43 -080016from constants import *
Ravago Jones6d460fe2021-07-03 16:59:55 -070017from drawing_constants import set_color, draw_px_cross, draw_px_x, display_text, draw_control_points
John Park91e69732019-03-03 13:12:43 -080018from points import Points
Ravago Jones6d460fe2021-07-03 16:59:55 -070019import time
Andrew Runke6842bf92019-01-26 15:38:25 -080020
Tabitha Jarvis1007a132018-12-12 21:47:54 -080021
22class Mode(enum.Enum):
23 kViewing = 0
24 kPlacing = 1
25 kEditing = 2
Andrew Runke6842bf92019-01-26 15:38:25 -080026
27
Ravago Jones6d460fe2021-07-03 16:59:55 -070028class FieldWidget(Gtk.DrawingArea):
Andrew Runke6842bf92019-01-26 15:38:25 -080029 """Create a GTK+ widget on which we will draw using Cairo"""
Ravago Jones26f7ad02021-02-05 15:45:59 -080030
Tabitha Jarvis1007a132018-12-12 21:47:54 -080031 def __init__(self):
Ravago Jones6d460fe2021-07-03 16:59:55 -070032 super(FieldWidget, self).__init__()
33 self.set_size_request(mToPx(FIELD.width), mToPx(FIELD.length))
Tabitha Jarvis1007a132018-12-12 21:47:54 -080034
John Park91e69732019-03-03 13:12:43 -080035 self.points = Points()
Ravago Jones6d460fe2021-07-03 16:59:55 -070036 self.graph = Graph()
37 self.set_vexpand(True)
38 self.set_hexpand(True)
John Park91e69732019-03-03 13:12:43 -080039
Tabitha Jarvis1007a132018-12-12 21:47:54 -080040 # init field drawing
41 # add default spline for testing purposes
42 # init editing / viewing modes and pointer location
43 self.mode = Mode.kPlacing
Ravago Jones6d460fe2021-07-03 16:59:55 -070044 self.mousex = 0
45 self.mousey = 0
46 self.module_path = os.path.dirname(os.path.realpath(sys.argv[0]))
47 self.path_to_export = os.path.join(self.module_path,
John Park91e69732019-03-03 13:12:43 -080048 'points_for_pathedit.json')
49
Tabitha Jarvis1007a132018-12-12 21:47:54 -080050 # For the editing mode
Andrew Runke6842bf92019-01-26 15:38:25 -080051 self.index_of_edit = -1 # Can't be zero beause array starts at 0
Tabitha Jarvis1007a132018-12-12 21:47:54 -080052 self.held_x = 0
Andrew Runke6842bf92019-01-26 15:38:25 -080053 self.spline_edit = -1
Tabitha Jarvis1007a132018-12-12 21:47:54 -080054
Andrew Runke6842bf92019-01-26 15:38:25 -080055 self.curves = []
56
Ravago Jonesc26b9162021-06-30 20:12:48 -070057 try:
Ravago Jones6d460fe2021-07-03 16:59:55 -070058 self.field_png = cairo.ImageSurface.create_from_png(
59 "frc971/control_loops/python/field_images/" + FIELD.field_id +
60 ".png")
Ravago Jonesc26b9162021-06-30 20:12:48 -070061 except cairo.Error:
62 self.field_png = None
63
John Park91e69732019-03-03 13:12:43 -080064 def draw_robot_at_point(self, cr, i, p, spline):
65 p1 = [mToPx(spline.Point(i)[0]), mToPx(spline.Point(i)[1])]
66 p2 = [mToPx(spline.Point(i + p)[0]), mToPx(spline.Point(i + p)[1])]
Tabitha Jarvis1007a132018-12-12 21:47:54 -080067
John Park91e69732019-03-03 13:12:43 -080068 #Calculate Robot
69 distance = np.sqrt((p2[1] - p1[1])**2 + (p2[0] - p1[0])**2)
70 x_difference_o = p2[0] - p1[0]
71 y_difference_o = p2[1] - p1[1]
Ravago Jones6d460fe2021-07-03 16:59:55 -070072 x_difference = x_difference_o * mToPx(
73 FIELD.robot.length / 2) / distance
74 y_difference = y_difference_o * mToPx(
75 FIELD.robot.length / 2) / distance
John Park91e69732019-03-03 13:12:43 -080076
77 front_middle = []
78 front_middle.append(p1[0] + x_difference)
79 front_middle.append(p1[1] + y_difference)
80
81 back_middle = []
82 back_middle.append(p1[0] - x_difference)
83 back_middle.append(p1[1] - y_difference)
84
85 slope = [-(1 / x_difference_o) / (1 / y_difference_o)]
86 angle = np.arctan(slope)
87
Ravago Jones5e09c072021-03-27 13:21:03 -070088 x_difference = np.sin(angle[0]) * mToPx(FIELD.robot.width / 2)
89 y_difference = np.cos(angle[0]) * mToPx(FIELD.robot.width / 2)
John Park91e69732019-03-03 13:12:43 -080090
91 front_1 = []
92 front_1.append(front_middle[0] - x_difference)
93 front_1.append(front_middle[1] - y_difference)
94
95 front_2 = []
96 front_2.append(front_middle[0] + x_difference)
97 front_2.append(front_middle[1] + y_difference)
98
99 back_1 = []
100 back_1.append(back_middle[0] - x_difference)
101 back_1.append(back_middle[1] - y_difference)
102
103 back_2 = []
104 back_2.append(back_middle[0] + x_difference)
105 back_2.append(back_middle[1] + y_difference)
106
107 x_difference = x_difference_o * mToPx(
Ravago Jones5e09c072021-03-27 13:21:03 -0700108 FIELD.robot.length / 2 + ROBOT_SIDE_TO_BALL_CENTER) / distance
John Park91e69732019-03-03 13:12:43 -0800109 y_difference = y_difference_o * mToPx(
Ravago Jones5e09c072021-03-27 13:21:03 -0700110 FIELD.robot.length / 2 + ROBOT_SIDE_TO_BALL_CENTER) / distance
John Park91e69732019-03-03 13:12:43 -0800111
112 #Calculate Ball
113 ball_center = []
114 ball_center.append(p1[0] + x_difference)
115 ball_center.append(p1[1] + y_difference)
116
117 x_difference = x_difference_o * mToPx(
Ravago Jones5e09c072021-03-27 13:21:03 -0700118 FIELD.robot.length / 2 + ROBOT_SIDE_TO_HATCH_PANEL) / distance
John Park91e69732019-03-03 13:12:43 -0800119 y_difference = y_difference_o * mToPx(
Ravago Jones5e09c072021-03-27 13:21:03 -0700120 FIELD.robot.length / 2 + ROBOT_SIDE_TO_HATCH_PANEL) / distance
John Park91e69732019-03-03 13:12:43 -0800121
122 #Calculate Panel
123 panel_center = []
124 panel_center.append(p1[0] + x_difference)
125 panel_center.append(p1[1] + y_difference)
126
127 x_difference = np.sin(angle[0]) * mToPx(HATCH_PANEL_WIDTH / 2)
128 y_difference = np.cos(angle[0]) * mToPx(HATCH_PANEL_WIDTH / 2)
129
130 panel_1 = []
131 panel_1.append(panel_center[0] + x_difference)
132 panel_1.append(panel_center[1] + y_difference)
133
134 panel_2 = []
135 panel_2.append(panel_center[0] - x_difference)
136 panel_2.append(panel_center[1] - y_difference)
137
138 #Draw Robot
139 cr.move_to(front_1[0], front_1[1])
140 cr.line_to(back_1[0], back_1[1])
141 cr.line_to(back_2[0], back_2[1])
142 cr.line_to(front_2[0], front_2[1])
143 cr.line_to(front_1[0], front_1[1])
144
145 cr.stroke()
146
147 #Draw Ball
148 set_color(cr, palette["ORANGE"], 0.5)
149 cr.move_to(back_middle[0], back_middle[1])
150 cr.line_to(ball_center[0], ball_center[1])
151 cr.arc(ball_center[0], ball_center[1], mToPx(BALL_RADIUS), 0,
152 2 * np.pi)
153 cr.stroke()
154
155 #Draw Panel
156 set_color(cr, palette["YELLOW"], 0.5)
157 cr.move_to(panel_1[0], panel_1[1])
158 cr.line_to(panel_2[0], panel_2[1])
159
160 cr.stroke()
161 cr.set_source_rgba(0, 0, 0, 1)
162
Ravago Jones6d460fe2021-07-03 16:59:55 -0700163 def do_draw(self, cr): # main
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800164
Ravago Jones6d460fe2021-07-03 16:59:55 -0700165 start_time = time.perf_counter()
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800166
James Kuszmaul1c933e02020-03-07 16:17:51 -0800167 cr.save()
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800168 set_color(cr, palette["BLACK"])
Ravago Jones5f787df2021-01-23 16:26:27 -0800169
Ravago Jones6d460fe2021-07-03 16:59:55 -0700170 cr.rectangle(0, 0, mToPx(FIELD.width), mToPx(FIELD.length))
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800171 cr.set_line_join(cairo.LINE_JOIN_ROUND)
172 cr.stroke()
Ravago Jones5f787df2021-01-23 16:26:27 -0800173
Ravago Jonesc26b9162021-06-30 20:12:48 -0700174 if self.field_png:
175 cr.save()
Ravago Jonesc26b9162021-06-30 20:12:48 -0700176 cr.scale(
Ravago Jones6d460fe2021-07-03 16:59:55 -0700177 mToPx(FIELD.width) / self.field_png.get_width(),
178 mToPx(FIELD.length) / self.field_png.get_height(),
179 )
Ravago Jonesc26b9162021-06-30 20:12:48 -0700180 cr.set_source_surface(self.field_png)
181 cr.paint()
182 cr.restore()
Andrew Runke6842bf92019-01-26 15:38:25 -0800183
John Park91e69732019-03-03 13:12:43 -0800184 # update everything
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800185
John Park91e69732019-03-03 13:12:43 -0800186 if self.mode == Mode.kPlacing or self.mode == Mode.kViewing:
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800187 set_color(cr, palette["BLACK"])
John Park91e69732019-03-03 13:12:43 -0800188 plotPoints = self.points.getPoints()
189 if plotPoints:
190 for i, point in enumerate(plotPoints):
John Park13d3e282019-01-26 20:16:48 -0800191 draw_px_x(cr, mToPx(point[0]), mToPx(point[1]), 10)
192 cr.move_to(mToPx(point[0]), mToPx(point[1]) - 15)
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800193 display_text(cr, str(i), 0.5, 0.5, 2, 2)
John Park91e69732019-03-03 13:12:43 -0800194 set_color(cr, palette["WHITE"])
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800195
196 elif self.mode == Mode.kEditing:
197 set_color(cr, palette["BLACK"])
John Park91e69732019-03-03 13:12:43 -0800198 cr.move_to(-SCREEN_SIZE, 170)
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800199 display_text(cr, "EDITING", 1, 1, 1, 1)
John Park91e69732019-03-03 13:12:43 -0800200 if self.points.getSplines():
201 self.draw_splines(cr)
202 for i, points in enumerate(self.points.getSplines()):
Andrew Runke6842bf92019-01-26 15:38:25 -0800203
John Park91e69732019-03-03 13:12:43 -0800204 p0 = np.array([mToPx(points[0][0]), mToPx(points[0][1])])
205 p1 = np.array([mToPx(points[1][0]), mToPx(points[1][1])])
206 p2 = np.array([mToPx(points[2][0]), mToPx(points[2][1])])
207 p3 = np.array([mToPx(points[3][0]), mToPx(points[3][1])])
208 p4 = np.array([mToPx(points[4][0]), mToPx(points[4][1])])
209 p5 = np.array([mToPx(points[5][0]), mToPx(points[5][1])])
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800210
John Park91e69732019-03-03 13:12:43 -0800211 draw_control_points(cr, [p0, p1, p2, p3, p4, p5])
212 first_tangent = p0 + 2.0 * (p1 - p0)
213 second_tangent = p5 + 2.0 * (p4 - p5)
214 cr.set_source_rgb(0, 0.5, 0)
215 cr.move_to(p0[0], p0[1])
216 cr.set_line_width(1.0)
217 cr.line_to(first_tangent[0], first_tangent[1])
218 cr.move_to(first_tangent[0], first_tangent[1])
219 cr.line_to(p2[0], p2[1])
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800220
John Park91e69732019-03-03 13:12:43 -0800221 cr.move_to(p5[0], p5[1])
222 cr.line_to(second_tangent[0], second_tangent[1])
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800223
John Park91e69732019-03-03 13:12:43 -0800224 cr.move_to(second_tangent[0], second_tangent[1])
225 cr.line_to(p3[0], p3[1])
226
227 cr.stroke()
228 cr.set_line_width(2.0)
229 self.points.update_lib_spline()
230 set_color(cr, palette["WHITE"])
231
232 cr.paint_with_alpha(0.2)
233
Ravago Jones6d460fe2021-07-03 16:59:55 -0700234 draw_px_cross(cr, self.mousex, self.mousey, 10)
James Kuszmaul1c933e02020-03-07 16:17:51 -0800235 cr.restore()
Ravago Jones6d460fe2021-07-03 16:59:55 -0700236 if self.points.getLibsplines():
237 self.graph.recalculate_graph(self.points)
238
239 print("spent {:.2f} ms drawing the field widget".format(1000 * (time.perf_counter() - start_time)))
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800240
John Park91e69732019-03-03 13:12:43 -0800241 def draw_splines(self, cr):
242 holder_spline = []
243 for i, points in enumerate(self.points.getSplines()):
244 array = np.zeros(shape=(6, 2), dtype=float)
245 for j, point in enumerate(points):
246 array[j, 0] = point[0]
247 array[j, 1] = point[1]
248 spline = Spline(np.ascontiguousarray(np.transpose(array)))
249 for k in np.linspace(0.01, 1, 100):
Ravago Jones26f7ad02021-02-05 15:45:59 -0800250 cr.move_to(
251 mToPx(spline.Point(k - 0.01)[0]),
252 mToPx(spline.Point(k - 0.01)[1]))
253 cr.line_to(
254 mToPx(spline.Point(k)[0]), mToPx(spline.Point(k)[1]))
John Park91e69732019-03-03 13:12:43 -0800255 cr.stroke()
256 holding = [
257 spline.Point(k - 0.01)[0],
258 spline.Point(k - 0.01)[1]
259 ]
260 holder_spline.append(holding)
261 if i == 0:
262 self.draw_robot_at_point(cr, 0.00, 0.01, spline)
263 self.draw_robot_at_point(cr, 1, 0.01, spline)
264 self.curves.append(holder_spline)
265
266 def mouse_move(self, event):
Ravago Jones6d460fe2021-07-03 16:59:55 -0700267 old_x = self.mousex
268 old_y = self.mousey
269 self.mousex = event.x
270 self.mousey = event.y
271 dif_x = self.mousex - old_x
272 dif_y = self.mousey - old_y
John Park91e69732019-03-03 13:12:43 -0800273 difs = np.array([pxToM(dif_x), pxToM(dif_y)])
274
275 if self.mode == Mode.kEditing:
Ravago Jones6d460fe2021-07-03 16:59:55 -0700276 self.points.updates_for_mouse_move(self.index_of_edit,
277 self.spline_edit, self.mousex,
278 self.mousey, difs)
John Park91e69732019-03-03 13:12:43 -0800279
John Park909c0392020-03-05 23:56:30 -0800280 def export_json(self, file_name):
Ravago Jones09f59722021-03-03 21:11:41 -0800281 self.path_to_export = os.path.join(
282 self.module_path, # position of the python
283 "../../..", # root of the repository
284 get_json_folder(FIELD), # path from the root
285 file_name # selected file
286 )
Ravago Jones3b92afa2021-02-05 14:27:32 -0800287
Ravago Jones6d460fe2021-07-03 16:59:55 -0700288 # Will export to json file
289 multi_spline = self.points.toMultiSpline()
290 print(multi_spline)
291 with open(self.path_to_export, mode='w') as points_file:
292 json.dump(multi_spline, points_file)
John Park909c0392020-03-05 23:56:30 -0800293
294 def import_json(self, file_name):
Ravago Jones09f59722021-03-03 21:11:41 -0800295 self.path_to_export = os.path.join(
296 self.module_path, # position of the python
297 "../../..", # root of the repository
298 get_json_folder(FIELD), # path from the root
299 file_name # selected file
300 )
301
Ravago Jones6d460fe2021-07-03 16:59:55 -0700302 # import from json file
303 print("LOADING LOAD FROM " + file_name) # Load takes a few seconds
304 with open(self.path_to_export) as points_file:
305 multi_spline = json.load(points_file)
John Park909c0392020-03-05 23:56:30 -0800306
Ravago Jones6d460fe2021-07-03 16:59:55 -0700307 # if people messed with the spline json,
308 # it might not be the right length
309 # so give them a nice error message
310 try: # try to salvage as many segments of the spline as possible
311 self.points.fromMultiSpline(multi_spline)
312 except IndexError:
313 # check if they're both 6+5*(k-1) long
314 expected_length = 6 + 5 * (multi_spline["spline_count"] - 1)
315 x_len = len(multi_spline["spline_x"])
316 y_len = len(multi_spline["spline_x"])
317 if x_len is not expected_length:
318 print(
319 "Error: spline x values were not the expected length; expected {} got {}"
320 .format(expected_length, x_len))
321 elif y_len is not expected_length:
322 print(
323 "Error: spline y values were not the expected length; expected {} got {}"
324 .format(expected_length, y_len))
Ravago Jones3b92afa2021-02-05 14:27:32 -0800325
Ravago Jones6d460fe2021-07-03 16:59:55 -0700326 print("SPLINES LOADED")
327 self.mode = Mode.kEditing
John Park909c0392020-03-05 23:56:30 -0800328
Ravago Jones6d460fe2021-07-03 16:59:55 -0700329 def key_press(self, event, file_name):
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800330 keyval = Gdk.keyval_to_lower(event.keyval)
John Park91e69732019-03-03 13:12:43 -0800331
Ravago Jones6d460fe2021-07-03 16:59:55 -0700332 # TODO: This should be a button
Andrew Runke6842bf92019-01-26 15:38:25 -0800333 if keyval == Gdk.KEY_p:
334 self.mode = Mode.kPlacing
335 # F0 = A1
336 # B1 = 2F0 - E0
337 # C1= d0 + 4F0 - 4E0
John Park91e69732019-03-03 13:12:43 -0800338 spline_index = len(self.points.getSplines()) - 1
339 self.points.resetPoints()
340 self.points.extrapolate(
341 self.points.getSplines()[len(self.points.getSplines()) - 1][5],
342 self.points.getSplines()[len(self.points.getSplines()) - 1][4],
343 self.points.getSplines()[len(self.points.getSplines()) - 1][3])
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800344
Ravago Jones6d460fe2021-07-03 16:59:55 -0700345 def button_press(self, event):
346 self.mousex = event.x
347 self.mousey = event.y
348
Andrew Runke6842bf92019-01-26 15:38:25 -0800349 if self.mode == Mode.kPlacing:
Ravago Jones6d460fe2021-07-03 16:59:55 -0700350 if self.points.add_point(self.mousex, self.mousey):
John Park91e69732019-03-03 13:12:43 -0800351 self.mode = Mode.kEditing
Andrew Runke6842bf92019-01-26 15:38:25 -0800352 elif self.mode == Mode.kEditing:
353 # Now after index_of_edit is not -1, the point is selected, so
354 # user can click for new point
Ravago Jones6d460fe2021-07-03 16:59:55 -0700355 if self.index_of_edit > -1 and self.held_x != self.mousex:
John Park91e69732019-03-03 13:12:43 -0800356 self.points.setSplines(self.spline_edit, self.index_of_edit,
Ravago Jones6d460fe2021-07-03 16:59:55 -0700357 pxToM(self.mousex), pxToM(self.mousey))
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800358
James Kuszmaulb33b07c2021-02-25 22:39:24 -0800359 self.points.splineExtrapolate(self.spline_edit)
Andrew Runke6842bf92019-01-26 15:38:25 -0800360
Andrew Runke6842bf92019-01-26 15:38:25 -0800361 self.index_of_edit = -1
362 self.spline_edit = -1
363 else:
Andrew Runke6842bf92019-01-26 15:38:25 -0800364 # Get clicked point
365 # Find nearest
366 # Move nearest to clicked
Ravago Jones6d460fe2021-07-03 16:59:55 -0700367 cur_p = [pxToM(self.mousex), pxToM(self.mousey)]
Andrew Runke6842bf92019-01-26 15:38:25 -0800368 # Get the distance between each for x and y
369 # Save the index of the point closest
John Park13d3e282019-01-26 20:16:48 -0800370 nearest = 1 # Max distance away a the selected point can be in meters
Andrew Runke6842bf92019-01-26 15:38:25 -0800371 index_of_closest = 0
James Kuszmaul1c933e02020-03-07 16:17:51 -0800372 for index_splines, points in enumerate(
373 self.points.getSplines()):
Andrew Runke6842bf92019-01-26 15:38:25 -0800374 for index_points, val in enumerate(points):
Andrew Runke6842bf92019-01-26 15:38:25 -0800375 distance = np.sqrt((cur_p[0] - val[0])**2 +
376 (cur_p[1] - val[1])**2)
377 if distance < nearest:
378 nearest = distance
379 index_of_closest = index_points
380 print("Nearest: " + str(nearest))
381 print("Index: " + str(index_of_closest))
382 self.index_of_edit = index_of_closest
383 self.spline_edit = index_splines
Ravago Jones6d460fe2021-07-03 16:59:55 -0700384 self.held_x = self.mousex