blob: 31d7a37ea794bc0249eb5c8eccdb3d6c10782616 [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
Andrew Runke6842bf92019-01-26 15:38:25 -08005import copy
Tabitha Jarvis1007a132018-12-12 21:47:54 -08006from color import Color, palette
7import random
8import gi
9import numpy as np
10import scipy.spatial.distance
11gi.require_version('Gtk', '3.0')
John Park91e69732019-03-03 13:12:43 -080012gi.require_version('Gdk', '3.0')
Andrew Runke6842bf92019-01-26 15:38:25 -080013from gi.repository import Gdk, Gtk, GLib
Tabitha Jarvis1007a132018-12-12 21:47:54 -080014import cairo
John Park91e69732019-03-03 13:12:43 -080015from libspline import Spline, DistanceSpline, Trajectory
Tabitha Jarvis1007a132018-12-12 21:47:54 -080016import enum
John Park91e69732019-03-03 13:12:43 -080017import json
18from basic_window import *
19from constants import *
20from drawing_constants import *
21from points import Points
22from graph import Graph
Andrew Runke6842bf92019-01-26 15:38:25 -080023
Tabitha Jarvis1007a132018-12-12 21:47:54 -080024
25class Mode(enum.Enum):
26 kViewing = 0
27 kPlacing = 1
28 kEditing = 2
29 kExporting = 3
30 kImporting = 4
Andrew Runke6842bf92019-01-26 15:38:25 -080031
32
John Park91e69732019-03-03 13:12:43 -080033class GTK_Widget(BaseWindow):
Andrew Runke6842bf92019-01-26 15:38:25 -080034 """Create a GTK+ widget on which we will draw using Cairo"""
Tabitha Jarvis1007a132018-12-12 21:47:54 -080035 def __init__(self):
36 super(GTK_Widget, self).__init__()
37
John Park91e69732019-03-03 13:12:43 -080038 self.points = Points()
39
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
44 self.x = 0
45 self.y = 0
Andrew Runke0f945fd2019-01-27 21:10:37 -080046 module_path = os.path.dirname(os.path.realpath(sys.argv[0]))
47 self.path_to_export = os.path.join(module_path,
John Park91e69732019-03-03 13:12:43 -080048 'points_for_pathedit.json')
49
Tabitha Jarvis1007a132018-12-12 21:47:54 -080050 # update list of control points
51 self.point_selected = False
52 # self.adding_spline = False
53 self.index_of_selected = -1
54 self.new_point = []
55
56 # For the editing mode
Andrew Runke6842bf92019-01-26 15:38:25 -080057 self.index_of_edit = -1 # Can't be zero beause array starts at 0
Tabitha Jarvis1007a132018-12-12 21:47:54 -080058 self.held_x = 0
Andrew Runke6842bf92019-01-26 15:38:25 -080059 self.spline_edit = -1
Tabitha Jarvis1007a132018-12-12 21:47:54 -080060
Andrew Runke6842bf92019-01-26 15:38:25 -080061 self.curves = []
62
63 self.colors = []
64
65 for c in palette:
66 self.colors.append(palette[c])
67
Tabitha Jarvis1007a132018-12-12 21:47:54 -080068 self.reinit_extents()
69
Andrew Runke6842bf92019-01-26 15:38:25 -080070 self.inStart = None
71 self.inEnd = None
Andrew Runke6842bf92019-01-26 15:38:25 -080072 self.inValue = None
73 self.startSet = False
74
John Park909c0392020-03-05 23:56:30 -080075 self.module_path = os.path.dirname(os.path.realpath(sys.argv[0]))
76
Andrew Runke6842bf92019-01-26 15:38:25 -080077 """set extents on images"""
Tabitha Jarvis1007a132018-12-12 21:47:54 -080078 def reinit_extents(self):
John Park91e69732019-03-03 13:12:43 -080079 self.extents_x_min = -1.0 * SCREEN_SIZE
80 self.extents_x_max = SCREEN_SIZE
81 self.extents_y_min = -1.0 * SCREEN_SIZE
82 self.extents_y_max = SCREEN_SIZE
Tabitha Jarvis1007a132018-12-12 21:47:54 -080083
84 # this needs to be rewritten with numpy, i dont think this ought to have
85 # SciPy as a dependecy
86 def get_index_of_nearest_point(self):
87 cur_p = [[self.x, self.y]]
88 distances = scipy.spatial.distance.cdist(cur_p, self.all_controls)
89
90 return np.argmin(distances)
91
92 # return the closest point to the loc of the click event
93 def get_nearest_point(self):
94 return self.all_controls[self.get_index_of_nearest_point()]
95
John Parka30a7782019-02-01 18:47:26 -080096 def draw_field_elements(self, cr):
John Parkcf545162020-02-23 20:07:25 -080097 if FIELD == 2019:
98 draw_HAB(cr)
99 draw_rockets(cr)
100 draw_cargo_ship(cr)
101 elif FIELD == 2020:
102 set_color(cr, palette["BLACK"])
103 markers(cr)
104 draw_shield_generator(cr)
105 draw_trench_run(cr)
106 draw_init_lines(cr)
107 draw_control_panel(cr)
John Parka30a7782019-02-01 18:47:26 -0800108
John Park91e69732019-03-03 13:12:43 -0800109 def draw_robot_at_point(self, cr, i, p, spline):
110 p1 = [mToPx(spline.Point(i)[0]), mToPx(spline.Point(i)[1])]
111 p2 = [mToPx(spline.Point(i + p)[0]), mToPx(spline.Point(i + p)[1])]
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800112
John Park91e69732019-03-03 13:12:43 -0800113 #Calculate Robot
114 distance = np.sqrt((p2[1] - p1[1])**2 + (p2[0] - p1[0])**2)
115 x_difference_o = p2[0] - p1[0]
116 y_difference_o = p2[1] - p1[1]
117 x_difference = x_difference_o * mToPx(LENGTH_OF_ROBOT / 2) / distance
118 y_difference = y_difference_o * mToPx(LENGTH_OF_ROBOT / 2) / distance
119
120 front_middle = []
121 front_middle.append(p1[0] + x_difference)
122 front_middle.append(p1[1] + y_difference)
123
124 back_middle = []
125 back_middle.append(p1[0] - x_difference)
126 back_middle.append(p1[1] - y_difference)
127
128 slope = [-(1 / x_difference_o) / (1 / y_difference_o)]
129 angle = np.arctan(slope)
130
131 x_difference = np.sin(angle[0]) * mToPx(WIDTH_OF_ROBOT / 2)
132 y_difference = np.cos(angle[0]) * mToPx(WIDTH_OF_ROBOT / 2)
133
134 front_1 = []
135 front_1.append(front_middle[0] - x_difference)
136 front_1.append(front_middle[1] - y_difference)
137
138 front_2 = []
139 front_2.append(front_middle[0] + x_difference)
140 front_2.append(front_middle[1] + y_difference)
141
142 back_1 = []
143 back_1.append(back_middle[0] - x_difference)
144 back_1.append(back_middle[1] - y_difference)
145
146 back_2 = []
147 back_2.append(back_middle[0] + x_difference)
148 back_2.append(back_middle[1] + y_difference)
149
150 x_difference = x_difference_o * mToPx(
151 LENGTH_OF_ROBOT / 2 + ROBOT_SIDE_TO_BALL_CENTER) / distance
152 y_difference = y_difference_o * mToPx(
153 LENGTH_OF_ROBOT / 2 + ROBOT_SIDE_TO_BALL_CENTER) / distance
154
155 #Calculate Ball
156 ball_center = []
157 ball_center.append(p1[0] + x_difference)
158 ball_center.append(p1[1] + y_difference)
159
160 x_difference = x_difference_o * mToPx(
161 LENGTH_OF_ROBOT / 2 + ROBOT_SIDE_TO_HATCH_PANEL) / distance
162 y_difference = y_difference_o * mToPx(
163 LENGTH_OF_ROBOT / 2 + ROBOT_SIDE_TO_HATCH_PANEL) / distance
164
165 #Calculate Panel
166 panel_center = []
167 panel_center.append(p1[0] + x_difference)
168 panel_center.append(p1[1] + y_difference)
169
170 x_difference = np.sin(angle[0]) * mToPx(HATCH_PANEL_WIDTH / 2)
171 y_difference = np.cos(angle[0]) * mToPx(HATCH_PANEL_WIDTH / 2)
172
173 panel_1 = []
174 panel_1.append(panel_center[0] + x_difference)
175 panel_1.append(panel_center[1] + y_difference)
176
177 panel_2 = []
178 panel_2.append(panel_center[0] - x_difference)
179 panel_2.append(panel_center[1] - y_difference)
180
181 #Draw Robot
182 cr.move_to(front_1[0], front_1[1])
183 cr.line_to(back_1[0], back_1[1])
184 cr.line_to(back_2[0], back_2[1])
185 cr.line_to(front_2[0], front_2[1])
186 cr.line_to(front_1[0], front_1[1])
187
188 cr.stroke()
189
190 #Draw Ball
191 set_color(cr, palette["ORANGE"], 0.5)
192 cr.move_to(back_middle[0], back_middle[1])
193 cr.line_to(ball_center[0], ball_center[1])
194 cr.arc(ball_center[0], ball_center[1], mToPx(BALL_RADIUS), 0,
195 2 * np.pi)
196 cr.stroke()
197
198 #Draw Panel
199 set_color(cr, palette["YELLOW"], 0.5)
200 cr.move_to(panel_1[0], panel_1[1])
201 cr.line_to(panel_2[0], panel_2[1])
202
203 cr.stroke()
204 cr.set_source_rgba(0, 0, 0, 1)
205
206 def handle_draw(self, cr): # main
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800207 # Fill the background color of the window with grey
John Park91e69732019-03-03 13:12:43 -0800208 set_color(cr, palette["WHITE"])
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800209 cr.paint()
210
211 # Draw a extents rectangle
212 set_color(cr, palette["WHITE"])
213 cr.rectangle(self.extents_x_min, self.extents_y_min,
214 (self.extents_x_max - self.extents_x_min),
215 self.extents_y_max - self.extents_y_min)
216 cr.fill()
217
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800218 cr.move_to(0, 50)
219 cr.show_text('Press "e" to export')
220 cr.show_text('Press "i" to import')
221
James Kuszmaul1c933e02020-03-07 16:17:51 -0800222 cr.save()
223 cr.translate(mToPx(WIDTH_OF_FIELD_IN_METERS) / 2.0, 0.0)
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800224 set_color(cr, palette["BLACK"])
John Parkcf545162020-02-23 20:07:25 -0800225 if FIELD == 2020:
James Kuszmaul1c933e02020-03-07 16:17:51 -0800226 cr.rectangle(-mToPx(WIDTH_OF_FIELD_IN_METERS) / 2.0,
227 -mToPx(LENGTH_OF_FIELD_IN_METERS) / 2.0,
228 mToPx(WIDTH_OF_FIELD_IN_METERS),
229 mToPx(LENGTH_OF_FIELD_IN_METERS))
John Parkcf545162020-02-23 20:07:25 -0800230 else:
James Kuszmaul1c933e02020-03-07 16:17:51 -0800231 cr.rectangle(0, -SCREEN_SIZE / 2, SCREEN_SIZE, SCREEN_SIZE)
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800232 cr.set_line_join(cairo.LINE_JOIN_ROUND)
233 cr.stroke()
John Parka30a7782019-02-01 18:47:26 -0800234 self.draw_field_elements(cr)
Andrew Runke6842bf92019-01-26 15:38:25 -0800235 y = 0
Andrew Runke6842bf92019-01-26 15:38:25 -0800236
John Park91e69732019-03-03 13:12:43 -0800237 # update everything
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800238
John Park91e69732019-03-03 13:12:43 -0800239 if self.mode == Mode.kPlacing or self.mode == Mode.kViewing:
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800240 set_color(cr, palette["BLACK"])
John Park91e69732019-03-03 13:12:43 -0800241 cr.move_to(-SCREEN_SIZE, 170)
242 plotPoints = self.points.getPoints()
243 if plotPoints:
244 for i, point in enumerate(plotPoints):
John Park13d3e282019-01-26 20:16:48 -0800245 draw_px_x(cr, mToPx(point[0]), mToPx(point[1]), 10)
246 cr.move_to(mToPx(point[0]), mToPx(point[1]) - 15)
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800247 display_text(cr, str(i), 0.5, 0.5, 2, 2)
John Park91e69732019-03-03 13:12:43 -0800248 set_color(cr, palette["WHITE"])
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800249
250 elif self.mode == Mode.kEditing:
251 set_color(cr, palette["BLACK"])
John Park91e69732019-03-03 13:12:43 -0800252 cr.move_to(-SCREEN_SIZE, 170)
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800253 display_text(cr, "EDITING", 1, 1, 1, 1)
John Park91e69732019-03-03 13:12:43 -0800254 if self.points.getSplines():
255 self.draw_splines(cr)
256 for i, points in enumerate(self.points.getSplines()):
Andrew Runke6842bf92019-01-26 15:38:25 -0800257
John Park91e69732019-03-03 13:12:43 -0800258 p0 = np.array([mToPx(points[0][0]), mToPx(points[0][1])])
259 p1 = np.array([mToPx(points[1][0]), mToPx(points[1][1])])
260 p2 = np.array([mToPx(points[2][0]), mToPx(points[2][1])])
261 p3 = np.array([mToPx(points[3][0]), mToPx(points[3][1])])
262 p4 = np.array([mToPx(points[4][0]), mToPx(points[4][1])])
263 p5 = np.array([mToPx(points[5][0]), mToPx(points[5][1])])
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800264
John Park91e69732019-03-03 13:12:43 -0800265 draw_control_points(cr, [p0, p1, p2, p3, p4, p5])
266 first_tangent = p0 + 2.0 * (p1 - p0)
267 second_tangent = p5 + 2.0 * (p4 - p5)
268 cr.set_source_rgb(0, 0.5, 0)
269 cr.move_to(p0[0], p0[1])
270 cr.set_line_width(1.0)
271 cr.line_to(first_tangent[0], first_tangent[1])
272 cr.move_to(first_tangent[0], first_tangent[1])
273 cr.line_to(p2[0], p2[1])
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800274
John Park91e69732019-03-03 13:12:43 -0800275 cr.move_to(p5[0], p5[1])
276 cr.line_to(second_tangent[0], second_tangent[1])
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800277
John Park91e69732019-03-03 13:12:43 -0800278 cr.move_to(second_tangent[0], second_tangent[1])
279 cr.line_to(p3[0], p3[1])
280
281 cr.stroke()
282 cr.set_line_width(2.0)
283 self.points.update_lib_spline()
284 set_color(cr, palette["WHITE"])
285
286 cr.paint_with_alpha(0.2)
287
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800288 draw_px_cross(cr, self.x, self.y, 10)
James Kuszmaul1c933e02020-03-07 16:17:51 -0800289 cr.restore()
290 mygraph = Graph(cr, self.points)
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800291
John Park91e69732019-03-03 13:12:43 -0800292 def draw_splines(self, cr):
293 holder_spline = []
294 for i, points in enumerate(self.points.getSplines()):
295 array = np.zeros(shape=(6, 2), dtype=float)
296 for j, point in enumerate(points):
297 array[j, 0] = point[0]
298 array[j, 1] = point[1]
299 spline = Spline(np.ascontiguousarray(np.transpose(array)))
300 for k in np.linspace(0.01, 1, 100):
301 cr.move_to(mToPx(spline.Point(k - 0.01)[0]),
302 mToPx(spline.Point(k - 0.01)[1]))
303 cr.line_to(mToPx(spline.Point(k)[0]),
304 mToPx(spline.Point(k)[1]))
305 cr.stroke()
306 holding = [
307 spline.Point(k - 0.01)[0],
308 spline.Point(k - 0.01)[1]
309 ]
310 holder_spline.append(holding)
311 if i == 0:
312 self.draw_robot_at_point(cr, 0.00, 0.01, spline)
313 self.draw_robot_at_point(cr, 1, 0.01, spline)
314 self.curves.append(holder_spline)
315
316 def mouse_move(self, event):
317 old_x = self.x
318 old_y = self.y
James Kuszmaul1c933e02020-03-07 16:17:51 -0800319 self.x = event.x - mToPx(WIDTH_OF_FIELD_IN_METERS / 2.0)
John Park91e69732019-03-03 13:12:43 -0800320 self.y = event.y
James Kuszmaul1c933e02020-03-07 16:17:51 -0800321 dif_x = self.x - old_x
322 dif_y = self.y - old_y
John Park91e69732019-03-03 13:12:43 -0800323 difs = np.array([pxToM(dif_x), pxToM(dif_y)])
324
325 if self.mode == Mode.kEditing:
326 self.spline_edit = self.points.updates_for_mouse_move(
327 self.index_of_edit, self.spline_edit, self.x, self.y, difs)
328
John Park909c0392020-03-05 23:56:30 -0800329 def export_json(self, file_name):
330 self.path_to_export = os.path.join(self.module_path,
331 "spline_jsons/" + file_name)
332 if file_name[-5:] != ".json":
333 print("Error: Filename doesn't end in .json")
334 else:
335 # Will export to json file
336 self.mode = Mode.kEditing
337 exportList = [l.tolist() for l in self.points.getSplines()]
338 with open(self.path_to_export, mode='w') as points_file:
339 json.dump(exportList, points_file)
340
341 def import_json(self, file_name):
342 self.path_to_export = os.path.join(self.module_path,
343 "spline_jsons/" + file_name)
344 if file_name[-5:] != ".json":
345 print("Error: Filename doesn't end in .json")
346 else:
347 # import from json file
348 self.mode = Mode.kEditing
349 self.points.resetPoints()
350 self.points.resetSplines()
James Kuszmaul1c933e02020-03-07 16:17:51 -0800351 print("LOADING LOAD FROM " + file_name) # Load takes a few seconds
John Park909c0392020-03-05 23:56:30 -0800352 with open(self.path_to_export) as points_file:
353 self.points.setUpSplines(json.load(points_file))
354
355 self.points.update_lib_spline()
356 print("SPLINES LOADED")
357
John Park91e69732019-03-03 13:12:43 -0800358 def do_key_press(self, event, file_name):
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800359 keyval = Gdk.keyval_to_lower(event.keyval)
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800360 if keyval == Gdk.KEY_q:
361 print("Found q key and exiting.")
362 quit_main_loop()
John Park909c0392020-03-05 23:56:30 -0800363 if keyval == Gdk.KEY_e:
364 export_json(file_name)
John Park91e69732019-03-03 13:12:43 -0800365
John Park909c0392020-03-05 23:56:30 -0800366 if keyval == Gdk.KEY_i:
367 import_json(file_name)
John Park91e69732019-03-03 13:12:43 -0800368
Andrew Runke6842bf92019-01-26 15:38:25 -0800369 if keyval == Gdk.KEY_p:
370 self.mode = Mode.kPlacing
371 # F0 = A1
372 # B1 = 2F0 - E0
373 # C1= d0 + 4F0 - 4E0
John Park91e69732019-03-03 13:12:43 -0800374 spline_index = len(self.points.getSplines()) - 1
375 self.points.resetPoints()
376 self.points.extrapolate(
377 self.points.getSplines()[len(self.points.getSplines()) - 1][5],
378 self.points.getSplines()[len(self.points.getSplines()) - 1][4],
379 self.points.getSplines()[len(self.points.getSplines()) - 1][3])
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800380
381 def button_press_action(self):
Andrew Runke6842bf92019-01-26 15:38:25 -0800382 if self.mode == Mode.kPlacing:
John Park91e69732019-03-03 13:12:43 -0800383 if self.points.add_point(self.x, self.y):
384 self.mode = Mode.kEditing
Andrew Runke6842bf92019-01-26 15:38:25 -0800385 elif self.mode == Mode.kEditing:
386 # Now after index_of_edit is not -1, the point is selected, so
387 # user can click for new point
388 if self.index_of_edit > -1 and self.held_x != self.x:
John Park91e69732019-03-03 13:12:43 -0800389 self.points.setSplines(self.spline_edit, self.index_of_edit,
390 pxToM(self.x), pxToM(self.y))
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800391
John Park91e69732019-03-03 13:12:43 -0800392 self.spline_edit = self.points.splineExtrapolate(
393 self.spline_edit)
Andrew Runke6842bf92019-01-26 15:38:25 -0800394
Andrew Runke6842bf92019-01-26 15:38:25 -0800395 self.index_of_edit = -1
396 self.spline_edit = -1
397 else:
Andrew Runke6842bf92019-01-26 15:38:25 -0800398 # Get clicked point
399 # Find nearest
400 # Move nearest to clicked
John Park13d3e282019-01-26 20:16:48 -0800401 cur_p = [pxToM(self.x), pxToM(self.y)]
Andrew Runke6842bf92019-01-26 15:38:25 -0800402 # Get the distance between each for x and y
403 # Save the index of the point closest
John Park13d3e282019-01-26 20:16:48 -0800404 nearest = 1 # Max distance away a the selected point can be in meters
Andrew Runke6842bf92019-01-26 15:38:25 -0800405 index_of_closest = 0
James Kuszmaul1c933e02020-03-07 16:17:51 -0800406 for index_splines, points in enumerate(
407 self.points.getSplines()):
Andrew Runke6842bf92019-01-26 15:38:25 -0800408 for index_points, val in enumerate(points):
Andrew Runke6842bf92019-01-26 15:38:25 -0800409 distance = np.sqrt((cur_p[0] - val[0])**2 +
410 (cur_p[1] - val[1])**2)
411 if distance < nearest:
412 nearest = distance
413 index_of_closest = index_points
414 print("Nearest: " + str(nearest))
415 print("Index: " + str(index_of_closest))
416 self.index_of_edit = index_of_closest
417 self.spline_edit = index_splines
418 self.held_x = self.x
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800419
420 def do_button_press(self, event):
John Park13d3e282019-01-26 20:16:48 -0800421 # Be consistent with the scaling in the drawing_area
James Kuszmaul1c933e02020-03-07 16:17:51 -0800422 self.x = event.x * 2 - mToPx(WIDTH_OF_FIELD_IN_METERS / 2.0)
Andrew Runkea9c8de52019-01-26 19:54:29 -0800423 self.y = event.y * 2
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800424 self.button_press_action()