blob: 86777e5100aa23b4c90d17451748f7d65d716b75 [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
Ravago Jones5127ccc2022-07-31 16:32:45 -07009
Tabitha Jarvis1007a132018-12-12 21:47:54 -080010gi.require_version('Gtk', '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 Jonesb170ed32022-06-01 21:16:15 -070013from libspline import Spline, DistanceSpline
Tabitha Jarvis1007a132018-12-12 21:47:54 -080014import enum
John Park91e69732019-03-03 13:12:43 -080015import json
Ravago Jonesfa8da562022-07-02 18:10:22 -070016import copy
Ravago Jones797c49c2021-07-31 14:51:59 -070017from constants import FIELD
18from constants import get_json_folder
19from constants import ROBOT_SIDE_TO_BALL_CENTER, ROBOT_SIDE_TO_HATCH_PANEL, HATCH_PANEL_WIDTH, BALL_RADIUS
Ravago Jones6d460fe2021-07-03 16:59:55 -070020from drawing_constants import set_color, draw_px_cross, draw_px_x, display_text, draw_control_points
Ravago Jonesfa8da562022-07-02 18:10:22 -070021from multispline import Multispline, ControlPointIndex
Ravago Jones6d460fe2021-07-03 16:59:55 -070022import time
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
Andrew Runke6842bf92019-01-26 15:38:25 -080029
30
Ravago Jones6d460fe2021-07-03 16:59:55 -070031class FieldWidget(Gtk.DrawingArea):
Andrew Runke6842bf92019-01-26 15:38:25 -080032 """Create a GTK+ widget on which we will draw using Cairo"""
Ravago Jones5127ccc2022-07-31 16:32:45 -070033
Tabitha Jarvis1007a132018-12-12 21:47:54 -080034 def __init__(self):
Ravago Jones6d460fe2021-07-03 16:59:55 -070035 super(FieldWidget, self).__init__()
Ravago Jones086a8872021-08-07 15:49:40 -070036 self.set_field(FIELD)
Ravago Jones8da89c42022-07-17 19:34:06 -070037 self.set_size_request(self.mToPx(self.field.width),
38 self.mToPx(self.field.length))
Tabitha Jarvis1007a132018-12-12 21:47:54 -080039
Ravago Jonesfa8da562022-07-02 18:10:22 -070040 self.multisplines = []
Ravago Jones6d460fe2021-07-03 16:59:55 -070041 self.graph = Graph()
Ravago Jones0a1d4092022-06-03 12:47:32 -070042 self.graph.cursor_watcher = self
Ravago Jones6d460fe2021-07-03 16:59:55 -070043 self.set_vexpand(True)
44 self.set_hexpand(True)
Ravago Jonesfa8da562022-07-02 18:10:22 -070045 self.undo_history = []
Tabitha Jarvis1007a132018-12-12 21:47:54 -080046 # init field drawing
47 # add default spline for testing purposes
48 # init editing / viewing modes and pointer location
49 self.mode = Mode.kPlacing
Jason Anderson-Youngb3378152022-10-26 20:07:37 -070050 self.previous_mode = Mode.kPlacing
Ravago Jones6d460fe2021-07-03 16:59:55 -070051 self.mousex = 0
Jason Anderson-Young589e8fe2022-09-28 20:38:49 -070052 self.lastx = 0
Ravago Jones6d460fe2021-07-03 16:59:55 -070053 self.mousey = 0
Jason Anderson-Young589e8fe2022-09-28 20:38:49 -070054 self.lasty = 0
55 self.drag_start = None
Ravago Jones6d460fe2021-07-03 16:59:55 -070056 self.module_path = os.path.dirname(os.path.realpath(sys.argv[0]))
57 self.path_to_export = os.path.join(self.module_path,
John Park91e69732019-03-03 13:12:43 -080058 'points_for_pathedit.json')
59
Tabitha Jarvis1007a132018-12-12 21:47:54 -080060 # For the editing mode
Ravago Jonesfa8da562022-07-02 18:10:22 -070061 self.control_point_index = None
Ravago Jones359bbd22022-07-07 01:07:02 -070062 self.active_multispline_index = 0
Tabitha Jarvis1007a132018-12-12 21:47:54 -080063
Ravago Jones54dafeb2022-03-02 20:41:47 -080064 self.zoom_transform = cairo.Matrix()
Ravago Jones797c49c2021-07-31 14:51:59 -070065
Ravago Jones76ecec82021-08-07 14:37:08 -070066 self.set_events(Gdk.EventMask.BUTTON_PRESS_MASK
67 | Gdk.EventMask.BUTTON_PRESS_MASK
68 | Gdk.EventMask.BUTTON_RELEASE_MASK
69 | Gdk.EventMask.POINTER_MOTION_MASK
70 | Gdk.EventMask.SCROLL_MASK)
71
Ravago Jonesfa8da562022-07-02 18:10:22 -070072 @property
73 def active_multispline(self):
74 """Get the current active multispline or create a new one"""
75 if not self.multisplines:
76 self.multisplines.append(Multispline())
Ravago Jones359bbd22022-07-07 01:07:02 -070077 self.active_multispline_index = len(self.multisplines) - 1
Ravago Jonesfa8da562022-07-02 18:10:22 -070078
79 return self.multisplines[self.active_multispline_index]
80
Ravago Jones086a8872021-08-07 15:49:40 -070081 def set_field(self, field):
82 self.field = field
Ravago Jonesc26b9162021-06-30 20:12:48 -070083 try:
Maxwell Henderson4a18b192023-03-10 15:04:04 -080084 if self.field.field_id.startswith('//'):
85 self.field_png = cairo.ImageSurface.create_from_png(
86 self.field.field_id[2:])
87 else:
88 self.field_png = cairo.ImageSurface.create_from_png(
89 "frc971/control_loops/python/field_images/" +
90 self.field.field_id + ".png")
Ravago Jonesc26b9162021-06-30 20:12:48 -070091 except cairo.Error:
92 self.field_png = None
Ravago Jones54dafeb2022-03-02 20:41:47 -080093
Ravago Jones086a8872021-08-07 15:49:40 -070094 self.queue_draw()
Ravago Jonesc26b9162021-06-30 20:12:48 -070095
Ravago Jones54dafeb2022-03-02 20:41:47 -080096 def invert(self, transform):
97 xx, yx, xy, yy, x0, y0 = transform
98 matrix = cairo.Matrix(xx, yx, xy, yy, x0, y0)
99 matrix.invert()
100 return matrix
101
Ravago Jones797c49c2021-07-31 14:51:59 -0700102 # returns the transform from widget space to field space
103 @property
104 def input_transform(self):
Ravago Jones797c49c2021-07-31 14:51:59 -0700105 # the transform for input needs to be the opposite of the transform for drawing
Ravago Jones54dafeb2022-03-02 20:41:47 -0800106 return self.invert(self.field_transform.multiply(self.zoom_transform))
107
108 @property
109 def field_transform(self):
110 field_transform = cairo.Matrix()
Ravago Jones8da89c42022-07-17 19:34:06 -0700111 field_transform.scale(1, -1) # flipped y-axis
Ravago Jones54dafeb2022-03-02 20:41:47 -0800112 field_transform.scale(1 / self.pxToM_scale(), 1 / self.pxToM_scale())
Ravago Jones8da89c42022-07-17 19:34:06 -0700113 field_transform.translate(self.field.width / 2,
114 -1 * self.field.length / 2)
Ravago Jones54dafeb2022-03-02 20:41:47 -0800115 return field_transform
Ravago Jones797c49c2021-07-31 14:51:59 -0700116
117 # returns the scale from pixels in field space to meters in field space
118 def pxToM_scale(self):
119 available_space = self.get_allocation()
Ravago Jones086a8872021-08-07 15:49:40 -0700120 return np.maximum(self.field.width / available_space.width,
121 self.field.length / available_space.height)
Ravago Jones797c49c2021-07-31 14:51:59 -0700122
123 def pxToM(self, p):
124 return p * self.pxToM_scale()
125
126 def mToPx(self, m):
127 return m / self.pxToM_scale()
128
Ravago Jonesb170ed32022-06-01 21:16:15 -0700129 def draw_robot_at_point(self, cr, spline, t):
130 """Draws the robot at a point along a Spline or DistanceSpline"""
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800131
Ravago Jonesb170ed32022-06-01 21:16:15 -0700132 # we accept both Spline and DistanceSpline
133 if type(spline) is Spline:
134 point = spline.Point(t)
135 theta = spline.Theta(t)
136 elif type(spline) is DistanceSpline:
137 point = spline.XY(t)
138 theta = spline.Theta(t)
139 else:
140 raise TypeError(
141 f"expected Spline or DistanceSpline (got {type(spline)})")
John Park91e69732019-03-03 13:12:43 -0800142
Ravago Jonesb170ed32022-06-01 21:16:15 -0700143 # Transform so that +y is forward along the spline
144 transform = cairo.Matrix()
145 transform.translate(*point)
146 transform.rotate(theta - np.pi / 2)
John Park91e69732019-03-03 13:12:43 -0800147
Ravago Jonesb170ed32022-06-01 21:16:15 -0700148 cr.save()
149 cr.set_matrix(transform.multiply(cr.get_matrix()))
John Park91e69732019-03-03 13:12:43 -0800150
Ravago Jonesb170ed32022-06-01 21:16:15 -0700151 # Draw Robot
152 set_color(cr, palette["BLACK"])
153 cr.rectangle(-self.field.robot.width / 2, -self.field.robot.length / 2,
154 self.field.robot.width, self.field.robot.length)
John Park91e69732019-03-03 13:12:43 -0800155 cr.stroke()
156
157 #Draw Ball
158 set_color(cr, palette["ORANGE"], 0.5)
Ravago Jonesb170ed32022-06-01 21:16:15 -0700159 cr.arc(0, self.field.robot.length / 2 + BALL_RADIUS, BALL_RADIUS, 0,
160 2 * np.pi)
John Park91e69732019-03-03 13:12:43 -0800161 cr.stroke()
162
Ravago Jonesb170ed32022-06-01 21:16:15 -0700163 # undo the transform
164 cr.restore()
John Park91e69732019-03-03 13:12:43 -0800165
Ravago Jones6d460fe2021-07-03 16:59:55 -0700166 def do_draw(self, cr): # main
Ravago Jones8da89c42022-07-17 19:34:06 -0700167 cr.set_matrix(
168 self.field_transform.multiply(self.zoom_transform).multiply(
169 cr.get_matrix()))
Ravago Jones797c49c2021-07-31 14:51:59 -0700170
James Kuszmaul1c933e02020-03-07 16:17:51 -0800171 cr.save()
Ravago Jones797c49c2021-07-31 14:51:59 -0700172
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800173 set_color(cr, palette["BLACK"])
Ravago Jones5f787df2021-01-23 16:26:27 -0800174
Henry Speiser51be5c62022-03-13 23:14:36 -0700175 cr.set_line_width(self.pxToM(1))
Ravago Jones8da89c42022-07-17 19:34:06 -0700176 cr.rectangle(-0.5 * self.field.width, -0.5 * self.field.length,
177 self.field.width, self.field.length)
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800178 cr.set_line_join(cairo.LINE_JOIN_ROUND)
179 cr.stroke()
Ravago Jones5f787df2021-01-23 16:26:27 -0800180
Ravago Jonesc26b9162021-06-30 20:12:48 -0700181 if self.field_png:
182 cr.save()
Ravago Jones54dafeb2022-03-02 20:41:47 -0800183 cr.translate(-0.5 * self.field.width, 0.5 * self.field.length)
Ravago Jonesc26b9162021-06-30 20:12:48 -0700184 cr.scale(
Ravago Jones54dafeb2022-03-02 20:41:47 -0800185 self.field.width / self.field_png.get_width(),
186 -self.field.length / self.field_png.get_height(),
Ravago Jones6d460fe2021-07-03 16:59:55 -0700187 )
Ravago Jonesc26b9162021-06-30 20:12:48 -0700188 cr.set_source_surface(self.field_png)
189 cr.paint()
190 cr.restore()
Andrew Runke6842bf92019-01-26 15:38:25 -0800191
John Park91e69732019-03-03 13:12:43 -0800192 # update everything
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800193
Henry Speiser51be5c62022-03-13 23:14:36 -0700194 cr.set_line_width(self.pxToM(1))
John Park91e69732019-03-03 13:12:43 -0800195 if self.mode == Mode.kPlacing or self.mode == Mode.kViewing:
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800196 set_color(cr, palette["BLACK"])
Ravago Jonesfa8da562022-07-02 18:10:22 -0700197 for multispline in self.multisplines:
198 for i, point in enumerate(multispline.staged_points):
199 draw_px_x(cr, point[0], point[1], self.pxToM(2))
Jason Anderson-Youngb3378152022-10-26 20:07:37 -0700200 if len(self.multisplines) != 0 and self.multisplines[0].getSplines(
201 ): #still in testing
202 self.draw_splines(cr)
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800203 elif self.mode == Mode.kEditing:
Ravago Jonesfa8da562022-07-02 18:10:22 -0700204 if len(self.multisplines) != 0 and self.multisplines[0].getSplines(
205 ):
John Park91e69732019-03-03 13:12:43 -0800206 self.draw_splines(cr)
Ravago Jonesfa8da562022-07-02 18:10:22 -0700207
Ravago Jones5127ccc2022-07-31 16:32:45 -0700208 for i, points in enumerate(
209 self.active_multispline.getSplines()):
Ravago Jones8da89c42022-07-17 19:34:06 -0700210 points = [np.array([x, y]) for (x, y) in points]
211 draw_control_points(cr,
212 points,
213 width=self.pxToM(5),
214 radius=self.pxToM(2))
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800215
Ravago Jones36c92f02021-07-24 16:35:33 -0700216 p0, p1, p2, p3, p4, p5 = points
John Park91e69732019-03-03 13:12:43 -0800217 first_tangent = p0 + 2.0 * (p1 - p0)
218 second_tangent = p5 + 2.0 * (p4 - p5)
219 cr.set_source_rgb(0, 0.5, 0)
Ravago Jonesb170ed32022-06-01 21:16:15 -0700220 cr.move_to(*p0)
Ravago Jones54dafeb2022-03-02 20:41:47 -0800221 cr.set_line_width(self.pxToM(1.0))
Ravago Jonesb170ed32022-06-01 21:16:15 -0700222 cr.line_to(*first_tangent)
223 cr.move_to(*first_tangent)
224 cr.line_to(*p2)
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800225
Ravago Jonesb170ed32022-06-01 21:16:15 -0700226 cr.move_to(*p5)
227 cr.line_to(*second_tangent)
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800228
Ravago Jonesb170ed32022-06-01 21:16:15 -0700229 cr.move_to(*second_tangent)
230 cr.line_to(*p3)
John Park91e69732019-03-03 13:12:43 -0800231
232 cr.stroke()
Ravago Jones54dafeb2022-03-02 20:41:47 -0800233 cr.set_line_width(self.pxToM(2))
John Park91e69732019-03-03 13:12:43 -0800234
Ravago Jones7db1c502022-07-09 02:13:08 -0700235 set_color(cr, palette["WHITE"])
John Park91e69732019-03-03 13:12:43 -0800236 cr.paint_with_alpha(0.2)
237
Henry Speiser51be5c62022-03-13 23:14:36 -0700238 draw_px_cross(cr, self.mousex, self.mousey, self.pxToM(2))
James Kuszmaul1c933e02020-03-07 16:17:51 -0800239 cr.restore()
Ravago Jones6d460fe2021-07-03 16:59:55 -0700240
John Park91e69732019-03-03 13:12:43 -0800241 def draw_splines(self, cr):
Ravago Jonesb170ed32022-06-01 21:16:15 -0700242 mouse = np.array((self.mousex, self.mousey))
243
Ravago Jonesfa8da562022-07-02 18:10:22 -0700244 multispline, result = Multispline.nearest_distance(
245 self.multisplines, mouse)
Ravago Jonesb170ed32022-06-01 21:16:15 -0700246
Ryan Yinc11e1132022-08-24 21:02:10 -0700247 if self.graph.cursor is not None:
248 cursor = self.graph.find_cursor()
249 if cursor is None:
250 return
251 multispline_index, x = cursor
Ravago Jonesfa8da562022-07-02 18:10:22 -0700252 distance_spline = DistanceSpline(
Ravago Jones291f5592022-07-07 20:40:37 -0700253 self.multisplines[multispline_index].getLibsplines())
Ravago Jonesfa8da562022-07-02 18:10:22 -0700254
255 self.draw_robot_at_point(cr, distance_spline, x)
256
257 # clear the cursor each draw so it doesn't persist
Ravago Jones0a1d4092022-06-03 12:47:32 -0700258 # after you move off the spline
259 self.graph.cursor = None
Ravago Jonesac952dd2022-07-29 21:44:12 -0700260 elif result is not None and result.fun < 2:
261 distance_spline = DistanceSpline(multispline.getLibsplines())
262 x = result.x[0]
263
264 # draw the robot to show its width
265 self.draw_robot_at_point(cr, distance_spline, x)
266
267 multispline_index = self.multisplines.index(multispline)
268 self.graph.place_cursor(multispline_index, distance=result.x[0])
John Park91e69732019-03-03 13:12:43 -0800269
Ravago Jones7db1c502022-07-09 02:13:08 -0700270 for multispline in self.multisplines:
271 for i, spline in enumerate(multispline.getLibsplines()):
272 alpha = 1 if multispline == self.active_multispline else 0.2
273 set_color(cr, palette["BLACK"], alpha)
274
275 # draw lots of really small line segments to
276 # approximate the shape of the spline
277 for k in np.linspace(0.005, 1, 200):
278 cr.move_to(*spline.Point(k - 0.008))
279 cr.line_to(*spline.Point(k))
280 cr.stroke()
281
282 if i == 0:
283 self.draw_robot_at_point(cr, spline, 0)
284 self.draw_robot_at_point(cr, spline, 1)
285
John Park909c0392020-03-05 23:56:30 -0800286 def export_json(self, file_name):
Ravago Jones09f59722021-03-03 21:11:41 -0800287 self.path_to_export = os.path.join(
288 self.module_path, # position of the python
289 "../../..", # root of the repository
Ravago Jones086a8872021-08-07 15:49:40 -0700290 get_json_folder(self.field), # path from the root
Ravago Jones09f59722021-03-03 21:11:41 -0800291 file_name # selected file
292 )
Ravago Jones3b92afa2021-02-05 14:27:32 -0800293
Ravago Jones6d460fe2021-07-03 16:59:55 -0700294 # Will export to json file
Ravago Jonesfa8da562022-07-02 18:10:22 -0700295 multisplines_object = [
296 multispline.toJsonObject() for multispline in self.multisplines
297 ]
298 print(multisplines_object)
Ravago Jones6d460fe2021-07-03 16:59:55 -0700299 with open(self.path_to_export, mode='w') as points_file:
Ravago Jonesfa8da562022-07-02 18:10:22 -0700300 json.dump(multisplines_object, points_file)
John Park909c0392020-03-05 23:56:30 -0800301
302 def import_json(self, file_name):
Ravago Jones09f59722021-03-03 21:11:41 -0800303 self.path_to_export = os.path.join(
304 self.module_path, # position of the python
305 "../../..", # root of the repository
Ravago Jones086a8872021-08-07 15:49:40 -0700306 get_json_folder(self.field), # path from the root
Ravago Jones09f59722021-03-03 21:11:41 -0800307 file_name # selected file
308 )
309
Ravago Jones6d460fe2021-07-03 16:59:55 -0700310 # import from json file
311 print("LOADING LOAD FROM " + file_name) # Load takes a few seconds
312 with open(self.path_to_export) as points_file:
Ravago Jonesfa8da562022-07-02 18:10:22 -0700313 multisplines_object = json.load(points_file)
314
315 self.attempt_append_multisplines()
316
317 # TODO: Export multisplines in different files
318 if type(multisplines_object) is dict:
319 multisplines_object = [multisplines_object]
320 else:
321 self.multisplines = []
John Park909c0392020-03-05 23:56:30 -0800322
Ravago Jones6d460fe2021-07-03 16:59:55 -0700323 # if people messed with the spline json,
324 # it might not be the right length
325 # so give them a nice error message
Ravago Jonesfa8da562022-07-02 18:10:22 -0700326 for multispline_object in multisplines_object:
327 print(multispline_object)
328 try: # try to salvage as many segments of the spline as possible
329 self.multisplines.append(
330 Multispline.fromJsonObject(multispline_object))
331 except IndexError:
332 # check if they're both 6+5*(k-1) long
333 expected_length = 6 + 5 * (multispline_object["spline_count"] -
334 1)
335 x_len = len(multispline_object["spline_x"])
336 y_len = len(multispline_object["spline_x"])
337 if x_len is not expected_length:
338 print(
339 "Error: spline x values were not the expected length; expected {} got {}"
340 .format(expected_length, x_len))
341 elif y_len is not expected_length:
342 print(
343 "Error: spline y values were not the expected length; expected {} got {}"
344 .format(expected_length, y_len))
Ravago Jones3b92afa2021-02-05 14:27:32 -0800345
Ravago Jones6d460fe2021-07-03 16:59:55 -0700346 print("SPLINES LOADED")
347 self.mode = Mode.kEditing
Ravago Jones76ecec82021-08-07 14:37:08 -0700348 self.queue_draw()
Ravago Jonesfa8da562022-07-02 18:10:22 -0700349 self.graph.schedule_recalculate(self.multisplines)
John Park909c0392020-03-05 23:56:30 -0800350
Ravago Jonesfa8da562022-07-02 18:10:22 -0700351 def attempt_append_multisplines(self):
352 if len(self.undo_history
353 ) == 0 or self.multisplines != self.undo_history[-1]:
354 self.undo_history.append(copy.deepcopy(self.multisplines))
Ryan Yind8be3882021-10-13 20:59:41 -0700355
Ravago Jonesfa8da562022-07-02 18:10:22 -0700356 def clear(self, should_attempt_append=True):
Ryan Yind8be3882021-10-13 20:59:41 -0700357 if should_attempt_append:
Ravago Jonesfa8da562022-07-02 18:10:22 -0700358 self.attempt_append_multisplines()
359 self.multisplines = []
Ravago Jones7db1c502022-07-09 02:13:08 -0700360 self.active_multispline_index = 0
361 self.control_point_index = None
Ryan Yin85f861f2021-09-16 17:55:11 -0700362 #recalulate graph using new points
363 self.graph.axis.clear()
364 self.graph.queue_draw()
365 #allow placing again
366 self.mode = Mode.kPlacing
367 #redraw entire graph
368 self.queue_draw()
Ryan Yind8be3882021-10-13 20:59:41 -0700369
Ryan Yind8be3882021-10-13 20:59:41 -0700370 def undo(self):
371 try:
Ravago Jonesfa8da562022-07-02 18:10:22 -0700372 self.undo_history.pop()
Ryan Yind8be3882021-10-13 20:59:41 -0700373 except IndexError:
374 return
Ravago Jonesfa8da562022-07-02 18:10:22 -0700375 if len(self.undo_history) == 0:
376 self.clear(should_attempt_append=False) #clear, don't do anything
Ryan Yind8be3882021-10-13 20:59:41 -0700377 return
Ravago Jonesfa8da562022-07-02 18:10:22 -0700378 if len(self.multisplines) > 0 and not any(
379 multispline.staged_points
380 for multispline in self.multisplines):
Ravago Jones8da89c42022-07-17 19:34:06 -0700381 self.mode = Mode.kEditing
Ryan Yind8be3882021-10-13 20:59:41 -0700382 else:
383 self.mode = Mode.kPlacing
Ravago Jonesfa8da562022-07-02 18:10:22 -0700384 self.clear(should_attempt_append=False)
385 self.multisplines = copy.deepcopy(self.undo_history[-1])
Ryan Yind8be3882021-10-13 20:59:41 -0700386 self.queue_draw()
387
Ravago Jones76ecec82021-08-07 14:37:08 -0700388 def do_key_press_event(self, event):
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800389 keyval = Gdk.keyval_to_lower(event.keyval)
Ryan Yind8be3882021-10-13 20:59:41 -0700390 if keyval == Gdk.KEY_z and event.state & Gdk.ModifierType.CONTROL_MASK:
391 self.undo()
Nathan Leong295f7302022-07-23 16:09:31 -0700392
Andrew Runke6842bf92019-01-26 15:38:25 -0800393 if keyval == Gdk.KEY_p:
Nathan Leong295f7302022-07-23 16:09:31 -0700394 self.new_spline()
Ravago Jonesfa8da562022-07-02 18:10:22 -0700395 elif keyval == Gdk.KEY_m:
Nathan Leong295f7302022-07-23 16:09:31 -0700396 self.new_multispline()
397
398 def new_spline(self):
399 self.mode = Mode.kPlacing
400 # F0 = A1
401 # B1 = 2F0 - E0
402 # C1= d0 + 4F0 - 4E0
403 multispline = self.active_multispline
404 if len(multispline.getSplines()) != 0:
405 multispline.extrapolate(multispline.getSplines()[-1])
406 self.queue_draw()
407
408 def new_multispline(self):
409 if len(self.active_multispline.getSplines()) != 0:
Ravago Jonesfa8da562022-07-02 18:10:22 -0700410 self.mode = Mode.kPlacing
Ravago Jones359bbd22022-07-07 01:07:02 -0700411 self.active_multispline_index += 1
412 self.multisplines.insert(self.active_multispline_index,
413 Multispline())
Ravago Jonesfa8da562022-07-02 18:10:22 -0700414
Ravago Jones359bbd22022-07-07 01:07:02 -0700415 prev_multispline = self.multisplines[self.active_multispline_index
416 - 1]
Nathan Leong295f7302022-07-23 16:09:31 -0700417 if len(prev_multispline.getSplines()) != 0:
Ravago Jones359bbd22022-07-07 01:07:02 -0700418 self.active_multispline.extrapolate(
419 prev_multispline.getSplines()[-1])
Ravago Jones128fb992021-07-31 13:56:58 -0700420 self.queue_draw()
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800421
Ravago Jones76ecec82021-08-07 14:37:08 -0700422 def do_button_release_event(self, event):
Jason Anderson-Young589e8fe2022-09-28 20:38:49 -0700423 self.drag_start = None
424
Ravago Jonesfa8da562022-07-02 18:10:22 -0700425 self.attempt_append_multisplines()
Ravago Jones76ecec82021-08-07 14:37:08 -0700426 self.mousex, self.mousey = self.input_transform.transform_point(
427 event.x, event.y)
428 if self.mode == Mode.kEditing:
Ravago Jonesfa8da562022-07-02 18:10:22 -0700429 if self.control_point_index != None:
430 multispline = self.multisplines[
431 self.control_point_index.multispline_index]
Ravago Jones76ecec82021-08-07 14:37:08 -0700432
Ravago Jonesfa8da562022-07-02 18:10:22 -0700433 multispline.setControlPoint(self.control_point_index,
434 self.mousex, self.mousey)
Ravago Jones76ecec82021-08-07 14:37:08 -0700435
Ravago Jones359bbd22022-07-07 01:07:02 -0700436 Multispline.splineExtrapolate(self.multisplines,
437 self.control_point_index)
Ravago Jones76ecec82021-08-07 14:37:08 -0700438
Ravago Jonesfa8da562022-07-02 18:10:22 -0700439 multispline.update_lib_spline()
440 self.graph.schedule_recalculate(self.multisplines)
Ravago Jones359bbd22022-07-07 01:07:02 -0700441 self.queue_draw()
Ravago Jonesfa8da562022-07-02 18:10:22 -0700442
443 self.control_point_index = None
Ravago Jones76ecec82021-08-07 14:37:08 -0700444
445 def do_button_press_event(self, event):
Ravago Jones797c49c2021-07-31 14:51:59 -0700446 self.mousex, self.mousey = self.input_transform.transform_point(
447 event.x, event.y)
Ravago Jones6d460fe2021-07-03 16:59:55 -0700448
Jason Anderson-Young589e8fe2022-09-28 20:38:49 -0700449 self.lastx = event.x
450 self.lasty = event.y
451
Andrew Runke6842bf92019-01-26 15:38:25 -0800452 if self.mode == Mode.kPlacing:
Ravago Jonesfa8da562022-07-02 18:10:22 -0700453 if self.active_multispline.addPoint(self.mousex, self.mousey):
John Park91e69732019-03-03 13:12:43 -0800454 self.mode = Mode.kEditing
Ravago Jonesfa8da562022-07-02 18:10:22 -0700455 self.graph.schedule_recalculate(self.multisplines)
Andrew Runke6842bf92019-01-26 15:38:25 -0800456 elif self.mode == Mode.kEditing:
Ravago Jonesfa8da562022-07-02 18:10:22 -0700457 # Now after we have no control point index,
458 # the user can click for new point
459 if self.control_point_index == None:
Andrew Runke6842bf92019-01-26 15:38:25 -0800460 # Get clicked point
461 # Find nearest
462 # Move nearest to clicked
Ravago Jones54dafeb2022-03-02 20:41:47 -0800463 cur_p = [self.mousex, self.mousey]
Ravago Jones7db1c502022-07-09 02:13:08 -0700464
Andrew Runke6842bf92019-01-26 15:38:25 -0800465 # Get the distance between each for x and y
466 # Save the index of the point closest
Ravago Jones7db1c502022-07-09 02:13:08 -0700467 nearest = 0.4 # Max distance away a the selected point can be in meters
Andrew Runke6842bf92019-01-26 15:38:25 -0800468 index_of_closest = 0
Ravago Jones7db1c502022-07-09 02:13:08 -0700469 index_multisplines = self.active_multispline_index
470 multispline = self.active_multispline
471
472 for index_splines, points in enumerate(
473 multispline.getSplines()):
474 for index_points, val in enumerate(points):
475 distance = np.sqrt((cur_p[0] - val[0])**2 +
476 (cur_p[1] - val[1])**2)
477 if distance < nearest:
478 nearest = distance
479 index_of_closest = index_points
480 self.control_point_index = ControlPointIndex(
481 index_multisplines, index_splines,
482 index_points)
483
Jason Anderson-Young589e8fe2022-09-28 20:38:49 -0700484 if self.control_point_index == None:
485 self.drag_start = (event.x, event.y)
486
Ravago Jones5127ccc2022-07-31 16:32:45 -0700487 multispline, result = Multispline.nearest_distance(
488 self.multisplines, cur_p)
Ravago Jones7db1c502022-07-09 02:13:08 -0700489 if result and result.fun < 0.1:
Ravago Jones5127ccc2022-07-31 16:32:45 -0700490 self.active_multispline_index = self.multisplines.index(
491 multispline)
Ravago Jones7db1c502022-07-09 02:13:08 -0700492
Jason Anderson-Youngb3378152022-10-26 20:07:37 -0700493 elif self.mode == Mode.kViewing:
494
495 if self.control_point_index == None:
496 self.drag_start = (event.x, event.y)
497
Ravago Jones128fb992021-07-31 13:56:58 -0700498 self.queue_draw()
499
Ravago Jones76ecec82021-08-07 14:37:08 -0700500 def do_motion_notify_event(self, event):
Ravago Jones797c49c2021-07-31 14:51:59 -0700501 self.mousex, self.mousey = self.input_transform.transform_point(
502 event.x, event.y)
Ravago Jones359bbd22022-07-07 01:07:02 -0700503 mouse = np.array([self.mousex, self.mousey])
Ravago Jones128fb992021-07-31 13:56:58 -0700504
Ravago Jonesfa8da562022-07-02 18:10:22 -0700505 if self.mode == Mode.kEditing and self.control_point_index != None:
506 multispline = self.multisplines[
507 self.control_point_index.multispline_index]
Ravago Jones359bbd22022-07-07 01:07:02 -0700508
509 multispline.updates_for_mouse_move(self.multisplines,
Ravago Jones5127ccc2022-07-31 16:32:45 -0700510 self.control_point_index, mouse)
Ravago Jones128fb992021-07-31 13:56:58 -0700511
Ravago Jonesfa8da562022-07-02 18:10:22 -0700512 multispline.update_lib_spline()
513 self.graph.schedule_recalculate(self.multisplines)
Jason Anderson-Young589e8fe2022-09-28 20:38:49 -0700514
Jason Anderson-Youngb3378152022-10-26 20:07:37 -0700515 if self.drag_start != None and self.control_point_index == None:
516 if self.mode == Mode.kEditing or self.mode == Mode.kViewing:
Jason Anderson-Young589e8fe2022-09-28 20:38:49 -0700517
Jason Anderson-Youngb3378152022-10-26 20:07:37 -0700518 self.zoom_transform.translate(event.x - self.lastx,
519 event.y - self.lasty)
520 self.lastx = event.x
521 self.lasty = event.y
Jason Anderson-Young589e8fe2022-09-28 20:38:49 -0700522
Ravago Jones76ecec82021-08-07 14:37:08 -0700523 self.queue_draw()
Ravago Jones128fb992021-07-31 13:56:58 -0700524
Ravago Jones76ecec82021-08-07 14:37:08 -0700525 def do_scroll_event(self, event):
Ravago Jones54dafeb2022-03-02 20:41:47 -0800526
Ravago Jones01781202021-08-01 15:25:11 -0700527 self.mousex, self.mousey = self.input_transform.transform_point(
528 event.x, event.y)
529
Ravago Jones54dafeb2022-03-02 20:41:47 -0800530 step_size = self.pxToM(20) # px
Ravago Jones01781202021-08-01 15:25:11 -0700531
532 if event.direction == Gdk.ScrollDirection.UP:
533 # zoom out
534 scale_by = step_size
535 elif event.direction == Gdk.ScrollDirection.DOWN:
536 # zoom in
537 scale_by = -step_size
538 else:
539 return
540
Ravago Jones54dafeb2022-03-02 20:41:47 -0800541 scale = (self.field.width + scale_by) / self.field.width
Ravago Jones01781202021-08-01 15:25:11 -0700542
Ribhav Kaulfa68ba82021-09-11 16:26:57 -0700543 # This restricts the amount it can be scaled.
Jason Anderson-Young589e8fe2022-09-28 20:38:49 -0700544 if self.zoom_transform.xx <= 0.05:
Ribhav Kaulfa68ba82021-09-11 16:26:57 -0700545 scale = max(scale, 1)
Jason Anderson-Young589e8fe2022-09-28 20:38:49 -0700546 elif self.zoom_transform.xx >= 32:
Ribhav Kaulfa68ba82021-09-11 16:26:57 -0700547 scale = min(scale, 1)
548
Ravago Jonesde18dfe2022-03-16 20:57:19 -0700549 # undo the scaled translation that the old zoom transform did
Ravago Jones8da89c42022-07-17 19:34:06 -0700550 x, y = self.invert(self.zoom_transform).transform_point(
551 event.x, event.y)
Ravago Jonesde18dfe2022-03-16 20:57:19 -0700552
Ravago Jones01781202021-08-01 15:25:11 -0700553 # move the origin to point
Ravago Jonesde18dfe2022-03-16 20:57:19 -0700554 self.zoom_transform.translate(x, y)
Ravago Jones01781202021-08-01 15:25:11 -0700555
556 # scale from new origin
Ravago Jones54dafeb2022-03-02 20:41:47 -0800557 self.zoom_transform.scale(scale, scale)
Ravago Jones01781202021-08-01 15:25:11 -0700558
559 # move back
Ravago Jonesde18dfe2022-03-16 20:57:19 -0700560 self.zoom_transform.translate(-x, -y)
Ravago Jones01781202021-08-01 15:25:11 -0700561
562 self.queue_draw()