blob: 91f31d4a7ebb52be099e077ae6bed06b88115266 [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')
Andrew Runke6842bf92019-01-26 15:38:25 -080010from gi.repository import Gdk, Gtk, GLib
Tabitha Jarvis1007a132018-12-12 21:47:54 -080011import cairo
Ravago Jonesb170ed32022-06-01 21:16:15 -070012from libspline import Spline, DistanceSpline
Tabitha Jarvis1007a132018-12-12 21:47:54 -080013import enum
John Park91e69732019-03-03 13:12:43 -080014import json
Ravago Jonesfa8da562022-07-02 18:10:22 -070015import copy
Ravago Jones797c49c2021-07-31 14:51:59 -070016from constants import FIELD
17from constants import get_json_folder
18from constants import ROBOT_SIDE_TO_BALL_CENTER, ROBOT_SIDE_TO_HATCH_PANEL, HATCH_PANEL_WIDTH, BALL_RADIUS
Ravago Jones6d460fe2021-07-03 16:59:55 -070019from drawing_constants import set_color, draw_px_cross, draw_px_x, display_text, draw_control_points
Ravago Jonesfa8da562022-07-02 18:10:22 -070020from multispline import Multispline, ControlPointIndex
Ravago Jones6d460fe2021-07-03 16:59:55 -070021import time
Andrew Runke6842bf92019-01-26 15:38:25 -080022
Tabitha Jarvis1007a132018-12-12 21:47:54 -080023
24class Mode(enum.Enum):
25 kViewing = 0
26 kPlacing = 1
27 kEditing = 2
Andrew Runke6842bf92019-01-26 15:38:25 -080028
29
Ravago Jones6d460fe2021-07-03 16:59:55 -070030class FieldWidget(Gtk.DrawingArea):
Andrew Runke6842bf92019-01-26 15:38:25 -080031 """Create a GTK+ widget on which we will draw using Cairo"""
Tabitha Jarvis1007a132018-12-12 21:47:54 -080032 def __init__(self):
Ravago Jones6d460fe2021-07-03 16:59:55 -070033 super(FieldWidget, self).__init__()
Ravago Jones086a8872021-08-07 15:49:40 -070034 self.set_field(FIELD)
Ravago Jones8da89c42022-07-17 19:34:06 -070035 self.set_size_request(self.mToPx(self.field.width),
36 self.mToPx(self.field.length))
Tabitha Jarvis1007a132018-12-12 21:47:54 -080037
Ravago Jonesfa8da562022-07-02 18:10:22 -070038 self.multisplines = []
Ravago Jones6d460fe2021-07-03 16:59:55 -070039 self.graph = Graph()
Ravago Jones0a1d4092022-06-03 12:47:32 -070040 self.graph.cursor_watcher = self
Ravago Jones6d460fe2021-07-03 16:59:55 -070041 self.set_vexpand(True)
42 self.set_hexpand(True)
Ravago Jonesfa8da562022-07-02 18:10:22 -070043 self.undo_history = []
Tabitha Jarvis1007a132018-12-12 21:47:54 -080044 # init field drawing
45 # add default spline for testing purposes
46 # init editing / viewing modes and pointer location
47 self.mode = Mode.kPlacing
Ravago Jones6d460fe2021-07-03 16:59:55 -070048 self.mousex = 0
49 self.mousey = 0
50 self.module_path = os.path.dirname(os.path.realpath(sys.argv[0]))
51 self.path_to_export = os.path.join(self.module_path,
John Park91e69732019-03-03 13:12:43 -080052 'points_for_pathedit.json')
53
Tabitha Jarvis1007a132018-12-12 21:47:54 -080054 # For the editing mode
Ravago Jonesfa8da562022-07-02 18:10:22 -070055 self.control_point_index = None
56 self.active_multispline_index = -1
Tabitha Jarvis1007a132018-12-12 21:47:54 -080057
Ravago Jones54dafeb2022-03-02 20:41:47 -080058 self.zoom_transform = cairo.Matrix()
Ravago Jones797c49c2021-07-31 14:51:59 -070059
Ravago Jones76ecec82021-08-07 14:37:08 -070060 self.set_events(Gdk.EventMask.BUTTON_PRESS_MASK
61 | Gdk.EventMask.BUTTON_PRESS_MASK
62 | Gdk.EventMask.BUTTON_RELEASE_MASK
63 | Gdk.EventMask.POINTER_MOTION_MASK
64 | Gdk.EventMask.SCROLL_MASK)
65
Ravago Jonesfa8da562022-07-02 18:10:22 -070066 @property
67 def active_multispline(self):
68 """Get the current active multispline or create a new one"""
69 if not self.multisplines:
70 self.multisplines.append(Multispline())
71 self.active_multispline_index = -1
72
73 return self.multisplines[self.active_multispline_index]
74
Ravago Jones086a8872021-08-07 15:49:40 -070075 def set_field(self, field):
76 self.field = field
Ravago Jonesc26b9162021-06-30 20:12:48 -070077 try:
Ravago Jones6d460fe2021-07-03 16:59:55 -070078 self.field_png = cairo.ImageSurface.create_from_png(
Ravago Jones086a8872021-08-07 15:49:40 -070079 "frc971/control_loops/python/field_images/" +
80 self.field.field_id + ".png")
Ravago Jonesc26b9162021-06-30 20:12:48 -070081 except cairo.Error:
82 self.field_png = None
Ravago Jones54dafeb2022-03-02 20:41:47 -080083
Ravago Jones086a8872021-08-07 15:49:40 -070084 self.queue_draw()
Ravago Jonesc26b9162021-06-30 20:12:48 -070085
Ravago Jones54dafeb2022-03-02 20:41:47 -080086 def invert(self, transform):
87 xx, yx, xy, yy, x0, y0 = transform
88 matrix = cairo.Matrix(xx, yx, xy, yy, x0, y0)
89 matrix.invert()
90 return matrix
91
Ravago Jones797c49c2021-07-31 14:51:59 -070092 # returns the transform from widget space to field space
93 @property
94 def input_transform(self):
Ravago Jones797c49c2021-07-31 14:51:59 -070095 # the transform for input needs to be the opposite of the transform for drawing
Ravago Jones54dafeb2022-03-02 20:41:47 -080096 return self.invert(self.field_transform.multiply(self.zoom_transform))
97
98 @property
99 def field_transform(self):
100 field_transform = cairo.Matrix()
Ravago Jones8da89c42022-07-17 19:34:06 -0700101 field_transform.scale(1, -1) # flipped y-axis
Ravago Jones54dafeb2022-03-02 20:41:47 -0800102 field_transform.scale(1 / self.pxToM_scale(), 1 / self.pxToM_scale())
Ravago Jones8da89c42022-07-17 19:34:06 -0700103 field_transform.translate(self.field.width / 2,
104 -1 * self.field.length / 2)
Ravago Jones54dafeb2022-03-02 20:41:47 -0800105 return field_transform
Ravago Jones797c49c2021-07-31 14:51:59 -0700106
107 # returns the scale from pixels in field space to meters in field space
108 def pxToM_scale(self):
109 available_space = self.get_allocation()
Ravago Jones086a8872021-08-07 15:49:40 -0700110 return np.maximum(self.field.width / available_space.width,
111 self.field.length / available_space.height)
Ravago Jones797c49c2021-07-31 14:51:59 -0700112
113 def pxToM(self, p):
114 return p * self.pxToM_scale()
115
116 def mToPx(self, m):
117 return m / self.pxToM_scale()
118
Ravago Jonesb170ed32022-06-01 21:16:15 -0700119 def draw_robot_at_point(self, cr, spline, t):
120 """Draws the robot at a point along a Spline or DistanceSpline"""
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800121
Ravago Jonesb170ed32022-06-01 21:16:15 -0700122 # we accept both Spline and DistanceSpline
123 if type(spline) is Spline:
124 point = spline.Point(t)
125 theta = spline.Theta(t)
126 elif type(spline) is DistanceSpline:
127 point = spline.XY(t)
128 theta = spline.Theta(t)
129 else:
130 raise TypeError(
131 f"expected Spline or DistanceSpline (got {type(spline)})")
John Park91e69732019-03-03 13:12:43 -0800132
Ravago Jonesb170ed32022-06-01 21:16:15 -0700133 # Transform so that +y is forward along the spline
134 transform = cairo.Matrix()
135 transform.translate(*point)
136 transform.rotate(theta - np.pi / 2)
John Park91e69732019-03-03 13:12:43 -0800137
Ravago Jonesb170ed32022-06-01 21:16:15 -0700138 cr.save()
139 cr.set_matrix(transform.multiply(cr.get_matrix()))
John Park91e69732019-03-03 13:12:43 -0800140
Ravago Jonesb170ed32022-06-01 21:16:15 -0700141 # Draw Robot
142 set_color(cr, palette["BLACK"])
143 cr.rectangle(-self.field.robot.width / 2, -self.field.robot.length / 2,
144 self.field.robot.width, self.field.robot.length)
John Park91e69732019-03-03 13:12:43 -0800145 cr.stroke()
146
147 #Draw Ball
148 set_color(cr, palette["ORANGE"], 0.5)
Ravago Jonesb170ed32022-06-01 21:16:15 -0700149 cr.arc(0, self.field.robot.length / 2 + BALL_RADIUS, BALL_RADIUS, 0,
150 2 * np.pi)
John Park91e69732019-03-03 13:12:43 -0800151 cr.stroke()
152
Ravago Jonesb170ed32022-06-01 21:16:15 -0700153 # undo the transform
154 cr.restore()
John Park91e69732019-03-03 13:12:43 -0800155
Ravago Jones6d460fe2021-07-03 16:59:55 -0700156 def do_draw(self, cr): # main
Ravago Jones8da89c42022-07-17 19:34:06 -0700157 cr.set_matrix(
158 self.field_transform.multiply(self.zoom_transform).multiply(
159 cr.get_matrix()))
Ravago Jones797c49c2021-07-31 14:51:59 -0700160
James Kuszmaul1c933e02020-03-07 16:17:51 -0800161 cr.save()
Ravago Jones797c49c2021-07-31 14:51:59 -0700162
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800163 set_color(cr, palette["BLACK"])
Ravago Jones5f787df2021-01-23 16:26:27 -0800164
Henry Speiser51be5c62022-03-13 23:14:36 -0700165 cr.set_line_width(self.pxToM(1))
Ravago Jones8da89c42022-07-17 19:34:06 -0700166 cr.rectangle(-0.5 * self.field.width, -0.5 * self.field.length,
167 self.field.width, self.field.length)
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800168 cr.set_line_join(cairo.LINE_JOIN_ROUND)
169 cr.stroke()
Ravago Jones5f787df2021-01-23 16:26:27 -0800170
Ravago Jonesc26b9162021-06-30 20:12:48 -0700171 if self.field_png:
172 cr.save()
Ravago Jones54dafeb2022-03-02 20:41:47 -0800173 cr.translate(-0.5 * self.field.width, 0.5 * self.field.length)
Ravago Jonesc26b9162021-06-30 20:12:48 -0700174 cr.scale(
Ravago Jones54dafeb2022-03-02 20:41:47 -0800175 self.field.width / self.field_png.get_width(),
176 -self.field.length / self.field_png.get_height(),
Ravago Jones6d460fe2021-07-03 16:59:55 -0700177 )
Ravago Jonesc26b9162021-06-30 20:12:48 -0700178 cr.set_source_surface(self.field_png)
179 cr.paint()
180 cr.restore()
Andrew Runke6842bf92019-01-26 15:38:25 -0800181
John Park91e69732019-03-03 13:12:43 -0800182 # update everything
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800183
Henry Speiser51be5c62022-03-13 23:14:36 -0700184 cr.set_line_width(self.pxToM(1))
John Park91e69732019-03-03 13:12:43 -0800185 if self.mode == Mode.kPlacing or self.mode == Mode.kViewing:
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800186 set_color(cr, palette["BLACK"])
Ravago Jonesfa8da562022-07-02 18:10:22 -0700187 for multispline in self.multisplines:
188 for i, point in enumerate(multispline.staged_points):
189 draw_px_x(cr, point[0], point[1], self.pxToM(2))
John Park91e69732019-03-03 13:12:43 -0800190 set_color(cr, palette["WHITE"])
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800191 elif self.mode == Mode.kEditing:
192 set_color(cr, palette["BLACK"])
Ravago Jonesfa8da562022-07-02 18:10:22 -0700193 if len(self.multisplines) != 0 and self.multisplines[0].getSplines(
194 ):
John Park91e69732019-03-03 13:12:43 -0800195 self.draw_splines(cr)
Ravago Jonesfa8da562022-07-02 18:10:22 -0700196
197 for multispline in self.multisplines:
198 for i, points in enumerate(multispline.getSplines()):
Ravago Jones8da89c42022-07-17 19:34:06 -0700199 points = [np.array([x, y]) for (x, y) in points]
200 draw_control_points(cr,
201 points,
202 width=self.pxToM(5),
203 radius=self.pxToM(2))
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800204
Ravago Jones36c92f02021-07-24 16:35:33 -0700205 p0, p1, p2, p3, p4, p5 = points
John Park91e69732019-03-03 13:12:43 -0800206 first_tangent = p0 + 2.0 * (p1 - p0)
207 second_tangent = p5 + 2.0 * (p4 - p5)
208 cr.set_source_rgb(0, 0.5, 0)
Ravago Jonesb170ed32022-06-01 21:16:15 -0700209 cr.move_to(*p0)
Ravago Jones54dafeb2022-03-02 20:41:47 -0800210 cr.set_line_width(self.pxToM(1.0))
Ravago Jonesb170ed32022-06-01 21:16:15 -0700211 cr.line_to(*first_tangent)
212 cr.move_to(*first_tangent)
213 cr.line_to(*p2)
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800214
Ravago Jonesb170ed32022-06-01 21:16:15 -0700215 cr.move_to(*p5)
216 cr.line_to(*second_tangent)
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800217
Ravago Jonesb170ed32022-06-01 21:16:15 -0700218 cr.move_to(*second_tangent)
219 cr.line_to(*p3)
John Park91e69732019-03-03 13:12:43 -0800220
221 cr.stroke()
Ravago Jones54dafeb2022-03-02 20:41:47 -0800222 cr.set_line_width(self.pxToM(2))
John Park91e69732019-03-03 13:12:43 -0800223 set_color(cr, palette["WHITE"])
224
225 cr.paint_with_alpha(0.2)
226
Henry Speiser51be5c62022-03-13 23:14:36 -0700227 draw_px_cross(cr, self.mousex, self.mousey, self.pxToM(2))
James Kuszmaul1c933e02020-03-07 16:17:51 -0800228 cr.restore()
Ravago Jones6d460fe2021-07-03 16:59:55 -0700229
John Park91e69732019-03-03 13:12:43 -0800230 def draw_splines(self, cr):
Ravago Jonesfa8da562022-07-02 18:10:22 -0700231 for multispline in self.multisplines:
232 for i, spline in enumerate(multispline.getLibsplines()):
233 # draw lots of really small line segments to
234 # approximate the shape of the spline
235 for k in np.linspace(0.02, 1, 200):
236 cr.move_to(*spline.Point(k - 0.008))
237 cr.line_to(*spline.Point(k))
238 cr.stroke()
239
240 if i == 0:
241 self.draw_robot_at_point(cr, spline, 0)
242 self.draw_robot_at_point(cr, spline, 1)
Ravago Jonesb170ed32022-06-01 21:16:15 -0700243
244 mouse = np.array((self.mousex, self.mousey))
245
Ravago Jonesfa8da562022-07-02 18:10:22 -0700246 multispline, result = Multispline.nearest_distance(
247 self.multisplines, mouse)
Ravago Jonesb170ed32022-06-01 21:16:15 -0700248
Ravago Jonesfa8da562022-07-02 18:10:22 -0700249 # if the mouse is close enough, draw the robot
Ravago Jones291f5592022-07-07 20:40:37 -0700250 if result is not None and result.fun < 2:
Ravago Jonesfa8da562022-07-02 18:10:22 -0700251 distance_spline = DistanceSpline(multispline.getLibsplines())
252 x = result.x[0]
253
254 # draw the robot to show its width
Ravago Jones0a1d4092022-06-03 12:47:32 -0700255 self.draw_robot_at_point(cr, distance_spline, x)
256
Ravago Jones291f5592022-07-07 20:40:37 -0700257 multispline_index = self.multisplines.index(multispline)
258 self.graph.place_cursor(multispline_index, distance=result.x[0])
259 elif self.graph.cursor is not None:
260 multispline_index, x = self.graph.find_cursor()
Ravago Jonesfa8da562022-07-02 18:10:22 -0700261 distance_spline = DistanceSpline(
Ravago Jones291f5592022-07-07 20:40:37 -0700262 self.multisplines[multispline_index].getLibsplines())
Ravago Jonesfa8da562022-07-02 18:10:22 -0700263
264 self.draw_robot_at_point(cr, distance_spline, x)
265
266 # clear the cursor each draw so it doesn't persist
Ravago Jones0a1d4092022-06-03 12:47:32 -0700267 # after you move off the spline
268 self.graph.cursor = None
John Park91e69732019-03-03 13:12:43 -0800269
John Park909c0392020-03-05 23:56:30 -0800270 def export_json(self, file_name):
Ravago Jones09f59722021-03-03 21:11:41 -0800271 self.path_to_export = os.path.join(
272 self.module_path, # position of the python
273 "../../..", # root of the repository
Ravago Jones086a8872021-08-07 15:49:40 -0700274 get_json_folder(self.field), # path from the root
Ravago Jones09f59722021-03-03 21:11:41 -0800275 file_name # selected file
276 )
Ravago Jones3b92afa2021-02-05 14:27:32 -0800277
Ravago Jones6d460fe2021-07-03 16:59:55 -0700278 # Will export to json file
Ravago Jonesfa8da562022-07-02 18:10:22 -0700279 multisplines_object = [
280 multispline.toJsonObject() for multispline in self.multisplines
281 ]
282 print(multisplines_object)
Ravago Jones6d460fe2021-07-03 16:59:55 -0700283 with open(self.path_to_export, mode='w') as points_file:
Ravago Jonesfa8da562022-07-02 18:10:22 -0700284 json.dump(multisplines_object, points_file)
John Park909c0392020-03-05 23:56:30 -0800285
286 def import_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 )
293
Ravago Jones6d460fe2021-07-03 16:59:55 -0700294 # import from json file
295 print("LOADING LOAD FROM " + file_name) # Load takes a few seconds
296 with open(self.path_to_export) as points_file:
Ravago Jonesfa8da562022-07-02 18:10:22 -0700297 multisplines_object = json.load(points_file)
298
299 self.attempt_append_multisplines()
300
301 # TODO: Export multisplines in different files
302 if type(multisplines_object) is dict:
303 multisplines_object = [multisplines_object]
304 else:
305 self.multisplines = []
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
Ravago Jonesfa8da562022-07-02 18:10:22 -0700310 for multispline_object in multisplines_object:
311 print(multispline_object)
312 try: # try to salvage as many segments of the spline as possible
313 self.multisplines.append(
314 Multispline.fromJsonObject(multispline_object))
315 except IndexError:
316 # check if they're both 6+5*(k-1) long
317 expected_length = 6 + 5 * (multispline_object["spline_count"] -
318 1)
319 x_len = len(multispline_object["spline_x"])
320 y_len = len(multispline_object["spline_x"])
321 if x_len is not expected_length:
322 print(
323 "Error: spline x values were not the expected length; expected {} got {}"
324 .format(expected_length, x_len))
325 elif y_len is not expected_length:
326 print(
327 "Error: spline y values were not the expected length; expected {} got {}"
328 .format(expected_length, y_len))
Ravago Jones3b92afa2021-02-05 14:27:32 -0800329
Ravago Jones6d460fe2021-07-03 16:59:55 -0700330 print("SPLINES LOADED")
331 self.mode = Mode.kEditing
Ravago Jones76ecec82021-08-07 14:37:08 -0700332 self.queue_draw()
Ravago Jonesfa8da562022-07-02 18:10:22 -0700333 self.graph.schedule_recalculate(self.multisplines)
John Park909c0392020-03-05 23:56:30 -0800334
Ravago Jonesfa8da562022-07-02 18:10:22 -0700335 def attempt_append_multisplines(self):
336 if len(self.undo_history
337 ) == 0 or self.multisplines != self.undo_history[-1]:
338 self.undo_history.append(copy.deepcopy(self.multisplines))
Ryan Yind8be3882021-10-13 20:59:41 -0700339
Ravago Jonesfa8da562022-07-02 18:10:22 -0700340 def clear(self, should_attempt_append=True):
Ryan Yind8be3882021-10-13 20:59:41 -0700341 if should_attempt_append:
Ravago Jonesfa8da562022-07-02 18:10:22 -0700342 self.attempt_append_multisplines()
343 self.multisplines = []
Ryan Yin85f861f2021-09-16 17:55:11 -0700344 #recalulate graph using new points
345 self.graph.axis.clear()
346 self.graph.queue_draw()
347 #allow placing again
348 self.mode = Mode.kPlacing
349 #redraw entire graph
350 self.queue_draw()
Ryan Yind8be3882021-10-13 20:59:41 -0700351
Ryan Yind8be3882021-10-13 20:59:41 -0700352 def undo(self):
353 try:
Ravago Jonesfa8da562022-07-02 18:10:22 -0700354 self.undo_history.pop()
Ryan Yind8be3882021-10-13 20:59:41 -0700355 except IndexError:
356 return
Ravago Jonesfa8da562022-07-02 18:10:22 -0700357 if len(self.undo_history) == 0:
358 self.clear(should_attempt_append=False) #clear, don't do anything
Ryan Yind8be3882021-10-13 20:59:41 -0700359 return
Ravago Jonesfa8da562022-07-02 18:10:22 -0700360 if len(self.multisplines) > 0 and not any(
361 multispline.staged_points
362 for multispline in self.multisplines):
Ravago Jones8da89c42022-07-17 19:34:06 -0700363 self.mode = Mode.kEditing
Ryan Yind8be3882021-10-13 20:59:41 -0700364 else:
365 self.mode = Mode.kPlacing
Ravago Jonesfa8da562022-07-02 18:10:22 -0700366 self.clear(should_attempt_append=False)
367 self.multisplines = copy.deepcopy(self.undo_history[-1])
Ryan Yind8be3882021-10-13 20:59:41 -0700368 self.queue_draw()
369
Ravago Jones76ecec82021-08-07 14:37:08 -0700370 def do_key_press_event(self, event):
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800371 keyval = Gdk.keyval_to_lower(event.keyval)
Ryan Yind8be3882021-10-13 20:59:41 -0700372 if keyval == Gdk.KEY_z and event.state & Gdk.ModifierType.CONTROL_MASK:
373 self.undo()
Ravago Jones6d460fe2021-07-03 16:59:55 -0700374 # TODO: This should be a button
Andrew Runke6842bf92019-01-26 15:38:25 -0800375 if keyval == Gdk.KEY_p:
376 self.mode = Mode.kPlacing
377 # F0 = A1
378 # B1 = 2F0 - E0
379 # C1= d0 + 4F0 - 4E0
Ravago Jonesfa8da562022-07-02 18:10:22 -0700380 multispline = self.active_multispline
381 multispline.extrapolate()
382 self.queue_draw()
383 elif keyval == Gdk.KEY_m:
384 self.multisplines.append(Multispline())
385 self.active_spline_index = len(self.multisplines) - 1
386 self.mode = Mode.kPlacing
387
388 multispline = self.multisplines[-2]
389 #multispline.extrapolate()
Ravago Jones128fb992021-07-31 13:56:58 -0700390 self.queue_draw()
Tabitha Jarvis1007a132018-12-12 21:47:54 -0800391
Ravago Jones76ecec82021-08-07 14:37:08 -0700392 def do_button_release_event(self, event):
Ravago Jonesfa8da562022-07-02 18:10:22 -0700393 self.attempt_append_multisplines()
Ravago Jones76ecec82021-08-07 14:37:08 -0700394 self.mousex, self.mousey = self.input_transform.transform_point(
395 event.x, event.y)
396 if self.mode == Mode.kEditing:
Ravago Jonesfa8da562022-07-02 18:10:22 -0700397 if self.control_point_index != None:
398 multispline = self.multisplines[
399 self.control_point_index.multispline_index]
Ravago Jones76ecec82021-08-07 14:37:08 -0700400
Ravago Jonesfa8da562022-07-02 18:10:22 -0700401 multispline.setControlPoint(self.control_point_index,
402 self.mousex, self.mousey)
Ravago Jones76ecec82021-08-07 14:37:08 -0700403
Ravago Jonesfa8da562022-07-02 18:10:22 -0700404 multispline.splineExtrapolate(
405 self.control_point_index.spline_index)
Ravago Jones76ecec82021-08-07 14:37:08 -0700406
Ravago Jonesfa8da562022-07-02 18:10:22 -0700407 multispline.update_lib_spline()
408 self.graph.schedule_recalculate(self.multisplines)
409
410 self.control_point_index = None
Ravago Jones76ecec82021-08-07 14:37:08 -0700411
412 def do_button_press_event(self, event):
Ravago Jones797c49c2021-07-31 14:51:59 -0700413 self.mousex, self.mousey = self.input_transform.transform_point(
414 event.x, event.y)
Ravago Jones6d460fe2021-07-03 16:59:55 -0700415
Andrew Runke6842bf92019-01-26 15:38:25 -0800416 if self.mode == Mode.kPlacing:
Ravago Jonesfa8da562022-07-02 18:10:22 -0700417 if self.active_multispline.addPoint(self.mousex, self.mousey):
John Park91e69732019-03-03 13:12:43 -0800418 self.mode = Mode.kEditing
Ravago Jonesfa8da562022-07-02 18:10:22 -0700419 self.graph.schedule_recalculate(self.multisplines)
Andrew Runke6842bf92019-01-26 15:38:25 -0800420 elif self.mode == Mode.kEditing:
Ravago Jonesfa8da562022-07-02 18:10:22 -0700421 # Now after we have no control point index,
422 # the user can click for new point
423 if self.control_point_index == None:
Andrew Runke6842bf92019-01-26 15:38:25 -0800424 # Get clicked point
425 # Find nearest
426 # Move nearest to clicked
Ravago Jones54dafeb2022-03-02 20:41:47 -0800427 cur_p = [self.mousex, self.mousey]
Andrew Runke6842bf92019-01-26 15:38:25 -0800428 # Get the distance between each for x and y
429 # Save the index of the point closest
John Park13d3e282019-01-26 20:16:48 -0800430 nearest = 1 # Max distance away a the selected point can be in meters
Andrew Runke6842bf92019-01-26 15:38:25 -0800431 index_of_closest = 0
Ravago Jonesfa8da562022-07-02 18:10:22 -0700432 for index_multisplines, multispline in enumerate(
433 self.multisplines):
434 for index_splines, points in enumerate(
435 multispline.getSplines()):
436 for index_points, val in enumerate(points):
437 distance = np.sqrt((cur_p[0] - val[0])**2 +
438 (cur_p[1] - val[1])**2)
439 if distance < nearest:
440 nearest = distance
441 index_of_closest = index_points
442 self.control_point_index = ControlPointIndex(
443 index_multisplines, index_splines,
444 index_points)
Ravago Jones128fb992021-07-31 13:56:58 -0700445 self.queue_draw()
446
Ravago Jones76ecec82021-08-07 14:37:08 -0700447 def do_motion_notify_event(self, event):
448 old_x = self.mousex
449 old_y = self.mousey
Ravago Jones797c49c2021-07-31 14:51:59 -0700450 self.mousex, self.mousey = self.input_transform.transform_point(
451 event.x, event.y)
Ravago Jones76ecec82021-08-07 14:37:08 -0700452 dif_x = self.mousex - old_x
453 dif_y = self.mousey - old_y
Ravago Jones54dafeb2022-03-02 20:41:47 -0800454 difs = np.array([dif_x, dif_y])
Ravago Jones128fb992021-07-31 13:56:58 -0700455
Ravago Jonesfa8da562022-07-02 18:10:22 -0700456 if self.mode == Mode.kEditing and self.control_point_index != None:
457 multispline = self.multisplines[
458 self.control_point_index.multispline_index]
459 multispline.updates_for_mouse_move(
460 self.control_point_index.control_point_index,
461 self.control_point_index.spline_index, self.mousex,
462 self.mousey, difs)
Ravago Jones128fb992021-07-31 13:56:58 -0700463
Ravago Jonesfa8da562022-07-02 18:10:22 -0700464 multispline.update_lib_spline()
465 self.graph.schedule_recalculate(self.multisplines)
Ravago Jones76ecec82021-08-07 14:37:08 -0700466 self.queue_draw()
Ravago Jones128fb992021-07-31 13:56:58 -0700467
Ravago Jones76ecec82021-08-07 14:37:08 -0700468 def do_scroll_event(self, event):
Ravago Jones54dafeb2022-03-02 20:41:47 -0800469
Ravago Jones01781202021-08-01 15:25:11 -0700470 self.mousex, self.mousey = self.input_transform.transform_point(
471 event.x, event.y)
472
Ravago Jones54dafeb2022-03-02 20:41:47 -0800473 step_size = self.pxToM(20) # px
Ravago Jones01781202021-08-01 15:25:11 -0700474
475 if event.direction == Gdk.ScrollDirection.UP:
476 # zoom out
477 scale_by = step_size
478 elif event.direction == Gdk.ScrollDirection.DOWN:
479 # zoom in
480 scale_by = -step_size
481 else:
482 return
483
Ravago Jones54dafeb2022-03-02 20:41:47 -0800484 scale = (self.field.width + scale_by) / self.field.width
Ravago Jones01781202021-08-01 15:25:11 -0700485
Ribhav Kaulfa68ba82021-09-11 16:26:57 -0700486 # This restricts the amount it can be scaled.
Ravago Jones54dafeb2022-03-02 20:41:47 -0800487 if self.zoom_transform.xx <= 0.5:
Ribhav Kaulfa68ba82021-09-11 16:26:57 -0700488 scale = max(scale, 1)
Ravago Jones54dafeb2022-03-02 20:41:47 -0800489 elif self.zoom_transform.xx >= 16:
Ribhav Kaulfa68ba82021-09-11 16:26:57 -0700490 scale = min(scale, 1)
491
Ravago Jonesde18dfe2022-03-16 20:57:19 -0700492 # undo the scaled translation that the old zoom transform did
Ravago Jones8da89c42022-07-17 19:34:06 -0700493 x, y = self.invert(self.zoom_transform).transform_point(
494 event.x, event.y)
Ravago Jonesde18dfe2022-03-16 20:57:19 -0700495
Ravago Jones01781202021-08-01 15:25:11 -0700496 # move the origin to point
Ravago Jonesde18dfe2022-03-16 20:57:19 -0700497 self.zoom_transform.translate(x, y)
Ravago Jones01781202021-08-01 15:25:11 -0700498
499 # scale from new origin
Ravago Jones54dafeb2022-03-02 20:41:47 -0800500 self.zoom_transform.scale(scale, scale)
Ravago Jones01781202021-08-01 15:25:11 -0700501
502 # move back
Ravago Jonesde18dfe2022-03-16 20:57:19 -0700503 self.zoom_transform.translate(-x, -y)
Ravago Jones01781202021-08-01 15:25:11 -0700504
505 self.queue_draw()