blob: 0cd41cb59073baec1d85dacf1ffc68a164ea2ba3 [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
milind-u0f9c2112023-03-11 20:36:19 -080020from drawing_constants import set_color, draw_px_cross, draw_px_x, display_text, draw_control_points_cross
Ravago Jonesfa8da562022-07-02 18:10:22 -070021from multispline import Multispline, ControlPointIndex
Ravago Jones6d460fe2021-07-03 16:59:55 -070022import time
milind-u0f9c2112023-03-11 20:36:19 -080023from pathlib import Path
Andrew Runke6842bf92019-01-26 15:38:25 -080024
Tabitha Jarvis1007a132018-12-12 21:47:54 -080025
26class Mode(enum.Enum):
27 kViewing = 0
28 kPlacing = 1
29 kEditing = 2
Andrew Runke6842bf92019-01-26 15:38:25 -080030
31
Ravago Jones6d460fe2021-07-03 16:59:55 -070032class FieldWidget(Gtk.DrawingArea):
Andrew Runke6842bf92019-01-26 15:38:25 -080033 """Create a GTK+ widget on which we will draw using Cairo"""
Ravago Jones5127ccc2022-07-31 16:32:45 -070034
Tabitha Jarvis1007a132018-12-12 21:47:54 -080035 def __init__(self):
Ravago Jones6d460fe2021-07-03 16:59:55 -070036 super(FieldWidget, self).__init__()
Ravago Jones086a8872021-08-07 15:49:40 -070037 self.set_field(FIELD)
Ravago Jones8da89c42022-07-17 19:34:06 -070038 self.set_size_request(self.mToPx(self.field.width),
39 self.mToPx(self.field.length))
Tabitha Jarvis1007a132018-12-12 21:47:54 -080040
Ravago Jonesfa8da562022-07-02 18:10:22 -070041 self.multisplines = []
Ravago Jones6d460fe2021-07-03 16:59:55 -070042 self.graph = Graph()
Ravago Jones0a1d4092022-06-03 12:47:32 -070043 self.graph.cursor_watcher = self
Ravago Jones6d460fe2021-07-03 16:59:55 -070044 self.set_vexpand(True)
45 self.set_hexpand(True)
Ravago Jonesfa8da562022-07-02 18:10:22 -070046 self.undo_history = []
Tabitha Jarvis1007a132018-12-12 21:47:54 -080047 # init field drawing
48 # add default spline for testing purposes
49 # init editing / viewing modes and pointer location
50 self.mode = Mode.kPlacing
Jason Anderson-Youngb3378152022-10-26 20:07:37 -070051 self.previous_mode = Mode.kPlacing
Ravago Jones6d460fe2021-07-03 16:59:55 -070052 self.mousex = 0
Jason Anderson-Young589e8fe2022-09-28 20:38:49 -070053 self.lastx = 0
Ravago Jones6d460fe2021-07-03 16:59:55 -070054 self.mousey = 0
Jason Anderson-Young589e8fe2022-09-28 20:38:49 -070055 self.lasty = 0
56 self.drag_start = None
Ravago Jones6d460fe2021-07-03 16:59:55 -070057 self.module_path = os.path.dirname(os.path.realpath(sys.argv[0]))
milind-u0f9c2112023-03-11 20:36:19 -080058 self.repository_root = Path(self.module_path, "../../..").resolve()
Ravago Jones6d460fe2021-07-03 16:59:55 -070059 self.path_to_export = os.path.join(self.module_path,
John Park91e69732019-03-03 13:12:43 -080060 'points_for_pathedit.json')
61
Tabitha Jarvis1007a132018-12-12 21:47:54 -080062 # For the editing mode
Ravago Jonesfa8da562022-07-02 18:10:22 -070063 self.control_point_index = None
Ravago Jones359bbd22022-07-07 01:07:02 -070064 self.active_multispline_index = 0
Tabitha Jarvis1007a132018-12-12 21:47:54 -080065
Ravago Jones54dafeb2022-03-02 20:41:47 -080066 self.zoom_transform = cairo.Matrix()
Ravago Jones797c49c2021-07-31 14:51:59 -070067
Ravago Jones76ecec82021-08-07 14:37:08 -070068 self.set_events(Gdk.EventMask.BUTTON_PRESS_MASK
69 | Gdk.EventMask.BUTTON_PRESS_MASK
70 | Gdk.EventMask.BUTTON_RELEASE_MASK
71 | Gdk.EventMask.POINTER_MOTION_MASK
72 | Gdk.EventMask.SCROLL_MASK)
73
Ravago Jonesfa8da562022-07-02 18:10:22 -070074 @property
75 def active_multispline(self):
76 """Get the current active multispline or create a new one"""
77 if not self.multisplines:
78 self.multisplines.append(Multispline())
Ravago Jones359bbd22022-07-07 01:07:02 -070079 self.active_multispline_index = len(self.multisplines) - 1
Ravago Jonesfa8da562022-07-02 18:10:22 -070080
81 return self.multisplines[self.active_multispline_index]
82
Ravago Jones086a8872021-08-07 15:49:40 -070083 def set_field(self, field):
84 self.field = field
Ravago Jonesc26b9162021-06-30 20:12:48 -070085 try:
Maxwell Henderson4a18b192023-03-10 15:04:04 -080086 if self.field.field_id.startswith('//'):
87 self.field_png = cairo.ImageSurface.create_from_png(
88 self.field.field_id[2:])
89 else:
90 self.field_png = cairo.ImageSurface.create_from_png(
91 "frc971/control_loops/python/field_images/" +
92 self.field.field_id + ".png")
Ravago Jonesc26b9162021-06-30 20:12:48 -070093 except cairo.Error:
94 self.field_png = None
Ravago Jones54dafeb2022-03-02 20:41:47 -080095
Ravago Jones086a8872021-08-07 15:49:40 -070096 self.queue_draw()
Ravago Jonesc26b9162021-06-30 20:12:48 -070097
Ravago Jones54dafeb2022-03-02 20:41:47 -080098 def invert(self, transform):
99 xx, yx, xy, yy, x0, y0 = transform
100 matrix = cairo.Matrix(xx, yx, xy, yy, x0, y0)
101 matrix.invert()
102 return matrix
103
Ravago Jones797c49c2021-07-31 14:51:59 -0700104 # returns the transform from widget space to field space
105 @property
106 def input_transform(self):
Ravago Jones797c49c2021-07-31 14:51:59 -0700107 # the transform for input needs to be the opposite of the transform for drawing
Ravago Jones54dafeb2022-03-02 20:41:47 -0800108 return self.invert(self.field_transform.multiply(self.zoom_transform))
109
110 @property
111 def field_transform(self):
112 field_transform = cairo.Matrix()
Ravago Jones8da89c42022-07-17 19:34:06 -0700113 field_transform.scale(1, -1) # flipped y-axis
Ravago Jones54dafeb2022-03-02 20:41:47 -0800114 field_transform.scale(1 / self.pxToM_scale(), 1 / self.pxToM_scale())
Ravago Jones8da89c42022-07-17 19:34:06 -0700115 field_transform.translate(self.field.width / 2,
116 -1 * self.field.length / 2)
Ravago Jones54dafeb2022-03-02 20:41:47 -0800117 return field_transform
Ravago Jones797c49c2021-07-31 14:51:59 -0700118
119 # returns the scale from pixels in field space to meters in field space
120 def pxToM_scale(self):
121 available_space = self.get_allocation()
Ravago Jones086a8872021-08-07 15:49:40 -0700122 return np.maximum(self.field.width / available_space.width,
123 self.field.length / available_space.height)
Ravago Jones797c49c2021-07-31 14:51:59 -0700124
125 def pxToM(self, p):
126 return p * self.pxToM_scale()
127
128 def mToPx(self, m):
129 return m / self.pxToM_scale()
130
Ravago Jonesb170ed32022-06-01 21:16:15 -0700131 def draw_robot_at_point(self, cr, spline, t):
132 """Draws the robot at a point along a Spline or DistanceSpline"""
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800133
Ravago Jonesb170ed32022-06-01 21:16:15 -0700134 # we accept both Spline and DistanceSpline
135 if type(spline) is Spline:
136 point = spline.Point(t)
137 theta = spline.Theta(t)
138 elif type(spline) is DistanceSpline:
139 point = spline.XY(t)
140 theta = spline.Theta(t)
141 else:
142 raise TypeError(
143 f"expected Spline or DistanceSpline (got {type(spline)})")
John Park91e69732019-03-03 13:12:43 -0800144
Ravago Jonesb170ed32022-06-01 21:16:15 -0700145 # Transform so that +y is forward along the spline
146 transform = cairo.Matrix()
147 transform.translate(*point)
148 transform.rotate(theta - np.pi / 2)
John Park91e69732019-03-03 13:12:43 -0800149
Ravago Jonesb170ed32022-06-01 21:16:15 -0700150 cr.save()
151 cr.set_matrix(transform.multiply(cr.get_matrix()))
John Park91e69732019-03-03 13:12:43 -0800152
Ravago Jonesb170ed32022-06-01 21:16:15 -0700153 # Draw Robot
154 set_color(cr, palette["BLACK"])
155 cr.rectangle(-self.field.robot.width / 2, -self.field.robot.length / 2,
156 self.field.robot.width, self.field.robot.length)
John Park91e69732019-03-03 13:12:43 -0800157 cr.stroke()
158
159 #Draw Ball
160 set_color(cr, palette["ORANGE"], 0.5)
Ravago Jonesb170ed32022-06-01 21:16:15 -0700161 cr.arc(0, self.field.robot.length / 2 + BALL_RADIUS, BALL_RADIUS, 0,
162 2 * np.pi)
John Park91e69732019-03-03 13:12:43 -0800163 cr.stroke()
164
Ravago Jonesb170ed32022-06-01 21:16:15 -0700165 # undo the transform
166 cr.restore()
John Park91e69732019-03-03 13:12:43 -0800167
Ravago Jones6d460fe2021-07-03 16:59:55 -0700168 def do_draw(self, cr): # main
Ravago Jones8da89c42022-07-17 19:34:06 -0700169 cr.set_matrix(
170 self.field_transform.multiply(self.zoom_transform).multiply(
171 cr.get_matrix()))
Ravago Jones797c49c2021-07-31 14:51:59 -0700172
James Kuszmaul1c933e02020-03-07 16:17:51 -0800173 cr.save()
Ravago Jones797c49c2021-07-31 14:51:59 -0700174
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800175 set_color(cr, palette["BLACK"])
Ravago Jones5f787df2021-01-23 16:26:27 -0800176
Henry Speiser51be5c62022-03-13 23:14:36 -0700177 cr.set_line_width(self.pxToM(1))
Ravago Jones8da89c42022-07-17 19:34:06 -0700178 cr.rectangle(-0.5 * self.field.width, -0.5 * self.field.length,
179 self.field.width, self.field.length)
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800180 cr.set_line_join(cairo.LINE_JOIN_ROUND)
181 cr.stroke()
Ravago Jones5f787df2021-01-23 16:26:27 -0800182
Ravago Jonesc26b9162021-06-30 20:12:48 -0700183 if self.field_png:
184 cr.save()
Ravago Jones54dafeb2022-03-02 20:41:47 -0800185 cr.translate(-0.5 * self.field.width, 0.5 * self.field.length)
Ravago Jonesc26b9162021-06-30 20:12:48 -0700186 cr.scale(
Ravago Jones54dafeb2022-03-02 20:41:47 -0800187 self.field.width / self.field_png.get_width(),
188 -self.field.length / self.field_png.get_height(),
Ravago Jones6d460fe2021-07-03 16:59:55 -0700189 )
Ravago Jonesc26b9162021-06-30 20:12:48 -0700190 cr.set_source_surface(self.field_png)
191 cr.paint()
192 cr.restore()
Andrew Runke6842bf92019-01-26 15:38:25 -0800193
John Park91e69732019-03-03 13:12:43 -0800194 # update everything
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800195
Henry Speiser51be5c62022-03-13 23:14:36 -0700196 cr.set_line_width(self.pxToM(1))
John Park91e69732019-03-03 13:12:43 -0800197 if self.mode == Mode.kPlacing or self.mode == Mode.kViewing:
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800198 set_color(cr, palette["BLACK"])
Ravago Jonesfa8da562022-07-02 18:10:22 -0700199 for multispline in self.multisplines:
200 for i, point in enumerate(multispline.staged_points):
201 draw_px_x(cr, point[0], point[1], self.pxToM(2))
Jason Anderson-Youngb3378152022-10-26 20:07:37 -0700202 if len(self.multisplines) != 0 and self.multisplines[0].getSplines(
203 ): #still in testing
milind-u0f9c2112023-03-11 20:36:19 -0800204 self.draw_cursor(cr)
Jason Anderson-Youngb3378152022-10-26 20:07:37 -0700205 self.draw_splines(cr)
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800206 elif self.mode == Mode.kEditing:
Ravago Jonesfa8da562022-07-02 18:10:22 -0700207 if len(self.multisplines) != 0 and self.multisplines[0].getSplines(
208 ):
milind-u0f9c2112023-03-11 20:36:19 -0800209 self.draw_cursor(cr)
John Park91e69732019-03-03 13:12:43 -0800210 self.draw_splines(cr)
milind-u0f9c2112023-03-11 20:36:19 -0800211 self.draw_control_points(cr)
John Park91e69732019-03-03 13:12:43 -0800212
Ravago Jones7db1c502022-07-09 02:13:08 -0700213 set_color(cr, palette["WHITE"])
John Park91e69732019-03-03 13:12:43 -0800214 cr.paint_with_alpha(0.2)
215
Henry Speiser51be5c62022-03-13 23:14:36 -0700216 draw_px_cross(cr, self.mousex, self.mousey, self.pxToM(2))
James Kuszmaul1c933e02020-03-07 16:17:51 -0800217 cr.restore()
Ravago Jones6d460fe2021-07-03 16:59:55 -0700218
milind-u0f9c2112023-03-11 20:36:19 -0800219 def draw_control_points(self, cr):
220 for i, points in enumerate(self.active_multispline.getSplines()):
221 points = [np.array([x, y]) for (x, y) in points]
222 draw_control_points_cross(cr,
223 points,
224 width=self.pxToM(5),
225 radius=self.pxToM(2))
226
227 p0, p1, p2, p3, p4, p5 = points
228 first_tangent = p0 + 2.0 * (p1 - p0)
229 second_tangent = p5 + 2.0 * (p4 - p5)
230 cr.set_source_rgb(0, 0.5, 0)
231 cr.move_to(*p0)
232 cr.set_line_width(self.pxToM(1.0))
233 cr.line_to(*first_tangent)
234 cr.move_to(*first_tangent)
235 cr.line_to(*p2)
236
237 cr.move_to(*p5)
238 cr.line_to(*second_tangent)
239
240 cr.move_to(*second_tangent)
241 cr.line_to(*p3)
242
243 cr.stroke()
244 cr.set_line_width(self.pxToM(2))
245
246 def draw_cursor(self, cr):
Ravago Jonesb170ed32022-06-01 21:16:15 -0700247 mouse = np.array((self.mousex, self.mousey))
248
Ravago Jonesfa8da562022-07-02 18:10:22 -0700249 multispline, result = Multispline.nearest_distance(
250 self.multisplines, mouse)
Ravago Jonesb170ed32022-06-01 21:16:15 -0700251
Ryan Yinc11e1132022-08-24 21:02:10 -0700252 if self.graph.cursor is not None:
253 cursor = self.graph.find_cursor()
254 if cursor is None:
255 return
256 multispline_index, x = cursor
Ravago Jonesfa8da562022-07-02 18:10:22 -0700257 distance_spline = DistanceSpline(
Ravago Jones291f5592022-07-07 20:40:37 -0700258 self.multisplines[multispline_index].getLibsplines())
Ravago Jonesfa8da562022-07-02 18:10:22 -0700259
260 self.draw_robot_at_point(cr, distance_spline, x)
261
262 # clear the cursor each draw so it doesn't persist
Ravago Jones0a1d4092022-06-03 12:47:32 -0700263 # after you move off the spline
264 self.graph.cursor = None
Ravago Jonesac952dd2022-07-29 21:44:12 -0700265 elif result is not None and result.fun < 2:
266 distance_spline = DistanceSpline(multispline.getLibsplines())
267 x = result.x[0]
268
269 # draw the robot to show its width
270 self.draw_robot_at_point(cr, distance_spline, x)
271
272 multispline_index = self.multisplines.index(multispline)
273 self.graph.place_cursor(multispline_index, distance=result.x[0])
John Park91e69732019-03-03 13:12:43 -0800274
milind-u0f9c2112023-03-11 20:36:19 -0800275 def draw_splines(self, cr):
Ravago Jones7db1c502022-07-09 02:13:08 -0700276 for multispline in self.multisplines:
277 for i, spline in enumerate(multispline.getLibsplines()):
278 alpha = 1 if multispline == self.active_multispline else 0.2
279 set_color(cr, palette["BLACK"], alpha)
280
281 # draw lots of really small line segments to
282 # approximate the shape of the spline
283 for k in np.linspace(0.005, 1, 200):
284 cr.move_to(*spline.Point(k - 0.008))
285 cr.line_to(*spline.Point(k))
286 cr.stroke()
287
288 if i == 0:
289 self.draw_robot_at_point(cr, spline, 0)
Ravago Jones30019ca2023-04-09 19:11:35 -0700290
291 is_last_spline = spline is multispline.getLibsplines()[-1]
292
293 if multispline == self.active_multispline or is_last_spline:
294 self.draw_robot_at_point(cr, spline, 1)
Ravago Jones7db1c502022-07-09 02:13:08 -0700295
John Park909c0392020-03-05 23:56:30 -0800296 def export_json(self, file_name):
milind-u0f9c2112023-03-11 20:36:19 -0800297 export_folder = Path(
298 self.repository_root,
Ravago Jones086a8872021-08-07 15:49:40 -0700299 get_json_folder(self.field), # path from the root
Ravago Jones09f59722021-03-03 21:11:41 -0800300 )
Ravago Jones3b92afa2021-02-05 14:27:32 -0800301
milind-u0f9c2112023-03-11 20:36:19 -0800302 filename = Path(export_folder, file_name)
303
304 # strip suffix
305 filename = filename.with_suffix("")
306 print(file_name, filename)
307
308 print(f"Exporting {len(self.multisplines)} splines")
309 # Export each multispline to its own json file
310 for index, multispline in enumerate(self.multisplines):
311 file = filename.with_suffix(f".{index}.json")
312 print(f" {file.relative_to(export_folder)}")
313 with open(file, mode='w') as points_file:
James Kuszmauld7938a62024-03-15 21:21:23 -0700314 # Indent to make the file actually readable
315 json.dump(multispline.toJsonObject(), points_file, indent=4)
John Park909c0392020-03-05 23:56:30 -0800316
317 def import_json(self, file_name):
milind-u0f9c2112023-03-11 20:36:19 -0800318 # Abort place mode
319 if self.mode is Mode.kPlacing and len(self.multisplines) > 0 and len(
320 self.multisplines[-1].getSplines()) == 0:
321 self.multisplines.pop()
322 self.mode = Mode.kEditing
323 self.queue_draw()
324
325 import_folder = Path(
326 self.repository_root,
Ravago Jones086a8872021-08-07 15:49:40 -0700327 get_json_folder(self.field), # path from the root
Ravago Jones09f59722021-03-03 21:11:41 -0800328 )
329
milind-u0f9c2112023-03-11 20:36:19 -0800330 file_candidates = []
331
332 # try exact match first
333 filename = Path(import_folder, file_name)
334 if filename.exists():
335 file_candidates.append(filename)
336 else:
337 # look for other files with the same stem but different numbers
338 stripped_stem = Path(file_name).with_suffix('').stem
339 file_candidates = list(
340 import_folder.glob(f"{stripped_stem}.*.json"))
341 print([file.stem for file in file_candidates])
342 file_candidates.sort()
343
344 print(f"Found {len(file_candidates)} files")
345 for file in file_candidates:
346 print(f" {file.relative_to(import_folder)}")
347
348 with open(file) as points_file:
349 self.multisplines.append(
350 Multispline.fromJsonObject(json.load(points_file)))
Ravago Jonesfa8da562022-07-02 18:10:22 -0700351
352 self.attempt_append_multisplines()
353
Ravago Jones6d460fe2021-07-03 16:59:55 -0700354 print("SPLINES LOADED")
355 self.mode = Mode.kEditing
Ravago Jones76ecec82021-08-07 14:37:08 -0700356 self.queue_draw()
Ravago Jonesfa8da562022-07-02 18:10:22 -0700357 self.graph.schedule_recalculate(self.multisplines)
John Park909c0392020-03-05 23:56:30 -0800358
Ravago Jonesfa8da562022-07-02 18:10:22 -0700359 def attempt_append_multisplines(self):
360 if len(self.undo_history
361 ) == 0 or self.multisplines != self.undo_history[-1]:
362 self.undo_history.append(copy.deepcopy(self.multisplines))
Ryan Yind8be3882021-10-13 20:59:41 -0700363
Ravago Jonesfa8da562022-07-02 18:10:22 -0700364 def clear(self, should_attempt_append=True):
Ryan Yind8be3882021-10-13 20:59:41 -0700365 if should_attempt_append:
Ravago Jonesfa8da562022-07-02 18:10:22 -0700366 self.attempt_append_multisplines()
367 self.multisplines = []
Ravago Jones7db1c502022-07-09 02:13:08 -0700368 self.active_multispline_index = 0
369 self.control_point_index = None
Ryan Yin85f861f2021-09-16 17:55:11 -0700370 #recalulate graph using new points
371 self.graph.axis.clear()
milind-u0f9c2112023-03-11 20:36:19 -0800372 self.graph.canvas.draw_idle()
373 #go back into viewing mode
374 self.mode = Mode.kViewing
Ryan Yin85f861f2021-09-16 17:55:11 -0700375 #redraw entire graph
376 self.queue_draw()
Ryan Yind8be3882021-10-13 20:59:41 -0700377
Ryan Yind8be3882021-10-13 20:59:41 -0700378 def undo(self):
379 try:
Ravago Jonesfa8da562022-07-02 18:10:22 -0700380 self.undo_history.pop()
Ryan Yind8be3882021-10-13 20:59:41 -0700381 except IndexError:
382 return
Ravago Jonesfa8da562022-07-02 18:10:22 -0700383 if len(self.undo_history) == 0:
384 self.clear(should_attempt_append=False) #clear, don't do anything
Ryan Yind8be3882021-10-13 20:59:41 -0700385 return
Ravago Jonesfa8da562022-07-02 18:10:22 -0700386 if len(self.multisplines) > 0 and not any(
387 multispline.staged_points
388 for multispline in self.multisplines):
Ravago Jones8da89c42022-07-17 19:34:06 -0700389 self.mode = Mode.kEditing
Ryan Yind8be3882021-10-13 20:59:41 -0700390 else:
391 self.mode = Mode.kPlacing
Ravago Jonesfa8da562022-07-02 18:10:22 -0700392 self.clear(should_attempt_append=False)
393 self.multisplines = copy.deepcopy(self.undo_history[-1])
Ryan Yind8be3882021-10-13 20:59:41 -0700394 self.queue_draw()
395
Ravago Jones76ecec82021-08-07 14:37:08 -0700396 def do_key_press_event(self, event):
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800397 keyval = Gdk.keyval_to_lower(event.keyval)
Ryan Yind8be3882021-10-13 20:59:41 -0700398 if keyval == Gdk.KEY_z and event.state & Gdk.ModifierType.CONTROL_MASK:
399 self.undo()
Nathan Leong295f7302022-07-23 16:09:31 -0700400
Andrew Runke6842bf92019-01-26 15:38:25 -0800401 if keyval == Gdk.KEY_p:
Nathan Leong295f7302022-07-23 16:09:31 -0700402 self.new_spline()
Ravago Jonesfa8da562022-07-02 18:10:22 -0700403 elif keyval == Gdk.KEY_m:
Nathan Leong295f7302022-07-23 16:09:31 -0700404 self.new_multispline()
405
406 def new_spline(self):
407 self.mode = Mode.kPlacing
408 # F0 = A1
409 # B1 = 2F0 - E0
410 # C1= d0 + 4F0 - 4E0
411 multispline = self.active_multispline
412 if len(multispline.getSplines()) != 0:
413 multispline.extrapolate(multispline.getSplines()[-1])
414 self.queue_draw()
415
416 def new_multispline(self):
417 if len(self.active_multispline.getSplines()) != 0:
Ravago Jonesfa8da562022-07-02 18:10:22 -0700418 self.mode = Mode.kPlacing
Ravago Jones359bbd22022-07-07 01:07:02 -0700419 self.active_multispline_index += 1
420 self.multisplines.insert(self.active_multispline_index,
421 Multispline())
Ravago Jonesfa8da562022-07-02 18:10:22 -0700422
Ravago Jones359bbd22022-07-07 01:07:02 -0700423 prev_multispline = self.multisplines[self.active_multispline_index
424 - 1]
Nathan Leong295f7302022-07-23 16:09:31 -0700425 if len(prev_multispline.getSplines()) != 0:
Ravago Jones359bbd22022-07-07 01:07:02 -0700426 self.active_multispline.extrapolate(
427 prev_multispline.getSplines()[-1])
Ravago Jones128fb992021-07-31 13:56:58 -0700428 self.queue_draw()
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800429
Ravago Jones318621a2023-04-09 18:23:12 -0700430 def on_graph_clicked(self):
431 if self.graph.cursor is not None:
432 cursor = self.graph.find_cursor()
433 if cursor is None:
434 return
435 multispline_index, x = cursor
436
437 self.active_multispline_index = multispline_index
438
Ravago Jones76ecec82021-08-07 14:37:08 -0700439 def do_button_release_event(self, event):
Jason Anderson-Young589e8fe2022-09-28 20:38:49 -0700440 self.drag_start = None
441
Ravago Jonesfa8da562022-07-02 18:10:22 -0700442 self.attempt_append_multisplines()
Ravago Jones76ecec82021-08-07 14:37:08 -0700443 self.mousex, self.mousey = self.input_transform.transform_point(
444 event.x, event.y)
445 if self.mode == Mode.kEditing:
Ravago Jonesfa8da562022-07-02 18:10:22 -0700446 if self.control_point_index != None:
447 multispline = self.multisplines[
448 self.control_point_index.multispline_index]
Ravago Jones76ecec82021-08-07 14:37:08 -0700449
Ravago Jonesfa8da562022-07-02 18:10:22 -0700450 multispline.setControlPoint(self.control_point_index,
451 self.mousex, self.mousey)
Ravago Jones76ecec82021-08-07 14:37:08 -0700452
Ravago Jones359bbd22022-07-07 01:07:02 -0700453 Multispline.splineExtrapolate(self.multisplines,
454 self.control_point_index)
Ravago Jones76ecec82021-08-07 14:37:08 -0700455
Ravago Jonesfa8da562022-07-02 18:10:22 -0700456 multispline.update_lib_spline()
457 self.graph.schedule_recalculate(self.multisplines)
Ravago Jones359bbd22022-07-07 01:07:02 -0700458 self.queue_draw()
Ravago Jonesfa8da562022-07-02 18:10:22 -0700459
460 self.control_point_index = None
Ravago Jones76ecec82021-08-07 14:37:08 -0700461
462 def do_button_press_event(self, event):
Ravago Jones797c49c2021-07-31 14:51:59 -0700463 self.mousex, self.mousey = self.input_transform.transform_point(
464 event.x, event.y)
Ravago Jones6d460fe2021-07-03 16:59:55 -0700465
Jason Anderson-Young589e8fe2022-09-28 20:38:49 -0700466 self.lastx = event.x
467 self.lasty = event.y
468
Andrew Runke6842bf92019-01-26 15:38:25 -0800469 if self.mode == Mode.kPlacing:
Ravago Jonesfa8da562022-07-02 18:10:22 -0700470 if self.active_multispline.addPoint(self.mousex, self.mousey):
John Park91e69732019-03-03 13:12:43 -0800471 self.mode = Mode.kEditing
Ravago Jonesfa8da562022-07-02 18:10:22 -0700472 self.graph.schedule_recalculate(self.multisplines)
Andrew Runke6842bf92019-01-26 15:38:25 -0800473 elif self.mode == Mode.kEditing:
Ravago Jonesfa8da562022-07-02 18:10:22 -0700474 # Now after we have no control point index,
475 # the user can click for new point
476 if self.control_point_index == None:
Andrew Runke6842bf92019-01-26 15:38:25 -0800477 # Get clicked point
478 # Find nearest
479 # Move nearest to clicked
Ravago Jones54dafeb2022-03-02 20:41:47 -0800480 cur_p = [self.mousex, self.mousey]
Ravago Jones7db1c502022-07-09 02:13:08 -0700481
Andrew Runke6842bf92019-01-26 15:38:25 -0800482 # Get the distance between each for x and y
483 # Save the index of the point closest
Ravago Jones7db1c502022-07-09 02:13:08 -0700484 nearest = 0.4 # Max distance away a the selected point can be in meters
Andrew Runke6842bf92019-01-26 15:38:25 -0800485 index_of_closest = 0
Ravago Jones7db1c502022-07-09 02:13:08 -0700486 index_multisplines = self.active_multispline_index
487 multispline = self.active_multispline
488
489 for index_splines, points in enumerate(
490 multispline.getSplines()):
491 for index_points, val in enumerate(points):
492 distance = np.sqrt((cur_p[0] - val[0])**2 +
493 (cur_p[1] - val[1])**2)
494 if distance < nearest:
495 nearest = distance
496 index_of_closest = index_points
497 self.control_point_index = ControlPointIndex(
498 index_multisplines, index_splines,
499 index_points)
500
Jason Anderson-Young589e8fe2022-09-28 20:38:49 -0700501 if self.control_point_index == None:
502 self.drag_start = (event.x, event.y)
503
Ravago Jones5127ccc2022-07-31 16:32:45 -0700504 multispline, result = Multispline.nearest_distance(
505 self.multisplines, cur_p)
Ravago Jonesdf6fe2c2023-04-09 18:48:32 -0700506 if self.control_point_index == None and result and result.fun < 0.1:
Ravago Jones5127ccc2022-07-31 16:32:45 -0700507 self.active_multispline_index = self.multisplines.index(
508 multispline)
Ravago Jones7db1c502022-07-09 02:13:08 -0700509
Jason Anderson-Youngb3378152022-10-26 20:07:37 -0700510 elif self.mode == Mode.kViewing:
511
512 if self.control_point_index == None:
513 self.drag_start = (event.x, event.y)
514
Ravago Jones128fb992021-07-31 13:56:58 -0700515 self.queue_draw()
516
Ravago Jones76ecec82021-08-07 14:37:08 -0700517 def do_motion_notify_event(self, event):
Ravago Jones797c49c2021-07-31 14:51:59 -0700518 self.mousex, self.mousey = self.input_transform.transform_point(
519 event.x, event.y)
Ravago Jones359bbd22022-07-07 01:07:02 -0700520 mouse = np.array([self.mousex, self.mousey])
Ravago Jones128fb992021-07-31 13:56:58 -0700521
Ravago Jonesfa8da562022-07-02 18:10:22 -0700522 if self.mode == Mode.kEditing and self.control_point_index != None:
523 multispline = self.multisplines[
524 self.control_point_index.multispline_index]
Ravago Jones359bbd22022-07-07 01:07:02 -0700525
526 multispline.updates_for_mouse_move(self.multisplines,
Ravago Jones5127ccc2022-07-31 16:32:45 -0700527 self.control_point_index, mouse)
Ravago Jones128fb992021-07-31 13:56:58 -0700528
Ravago Jonesfa8da562022-07-02 18:10:22 -0700529 multispline.update_lib_spline()
530 self.graph.schedule_recalculate(self.multisplines)
Jason Anderson-Young589e8fe2022-09-28 20:38:49 -0700531
Jason Anderson-Youngb3378152022-10-26 20:07:37 -0700532 if self.drag_start != None and self.control_point_index == None:
533 if self.mode == Mode.kEditing or self.mode == Mode.kViewing:
Jason Anderson-Young589e8fe2022-09-28 20:38:49 -0700534
Jason Anderson-Youngb3378152022-10-26 20:07:37 -0700535 self.zoom_transform.translate(event.x - self.lastx,
536 event.y - self.lasty)
537 self.lastx = event.x
538 self.lasty = event.y
Jason Anderson-Young589e8fe2022-09-28 20:38:49 -0700539
Ravago Jones76ecec82021-08-07 14:37:08 -0700540 self.queue_draw()
Ravago Jones128fb992021-07-31 13:56:58 -0700541
Ravago Jones76ecec82021-08-07 14:37:08 -0700542 def do_scroll_event(self, event):
Ravago Jones54dafeb2022-03-02 20:41:47 -0800543
Ravago Jones01781202021-08-01 15:25:11 -0700544 self.mousex, self.mousey = self.input_transform.transform_point(
545 event.x, event.y)
546
Ravago Jones54dafeb2022-03-02 20:41:47 -0800547 step_size = self.pxToM(20) # px
Ravago Jones01781202021-08-01 15:25:11 -0700548
549 if event.direction == Gdk.ScrollDirection.UP:
550 # zoom out
551 scale_by = step_size
552 elif event.direction == Gdk.ScrollDirection.DOWN:
553 # zoom in
554 scale_by = -step_size
555 else:
556 return
557
Ravago Jones54dafeb2022-03-02 20:41:47 -0800558 scale = (self.field.width + scale_by) / self.field.width
Ravago Jones01781202021-08-01 15:25:11 -0700559
Ribhav Kaulfa68ba82021-09-11 16:26:57 -0700560 # This restricts the amount it can be scaled.
Jason Anderson-Young589e8fe2022-09-28 20:38:49 -0700561 if self.zoom_transform.xx <= 0.05:
Ribhav Kaulfa68ba82021-09-11 16:26:57 -0700562 scale = max(scale, 1)
Jason Anderson-Young589e8fe2022-09-28 20:38:49 -0700563 elif self.zoom_transform.xx >= 32:
Ribhav Kaulfa68ba82021-09-11 16:26:57 -0700564 scale = min(scale, 1)
565
Ravago Jonesde18dfe2022-03-16 20:57:19 -0700566 # undo the scaled translation that the old zoom transform did
Ravago Jones8da89c42022-07-17 19:34:06 -0700567 x, y = self.invert(self.zoom_transform).transform_point(
568 event.x, event.y)
Ravago Jonesde18dfe2022-03-16 20:57:19 -0700569
Ravago Jones01781202021-08-01 15:25:11 -0700570 # move the origin to point
Ravago Jonesde18dfe2022-03-16 20:57:19 -0700571 self.zoom_transform.translate(x, y)
Ravago Jones01781202021-08-01 15:25:11 -0700572
573 # scale from new origin
Ravago Jones54dafeb2022-03-02 20:41:47 -0800574 self.zoom_transform.scale(scale, scale)
Ravago Jones01781202021-08-01 15:25:11 -0700575
576 # move back
Ravago Jonesde18dfe2022-03-16 20:57:19 -0700577 self.zoom_transform.translate(-x, -y)
Ravago Jones01781202021-08-01 15:25:11 -0700578
579 self.queue_draw()