-
Notifications
You must be signed in to change notification settings - Fork 0
/
main_screen.py
1095 lines (795 loc) · 51.4 KB
/
main_screen.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import os
import tkinter
from copy import deepcopy
from auto_components.drop_down_menu import DropDownMenu
from auto_components.required_points import RequiredPoint
from auto_features.data_validator import DataValidator
from miscellaneous import important_variables
from auto_components.grid_items import GridItems
from auto_components.input_field import InputField
from auto_components.movable_point import MovablePoint
from auto_components.titled_input_field import TitledInputField
from auto_features.json_file_loader import json_file_loader
from auto_components.point_alterable_fields_frame import PointAlterableFieldsFrame
from auto_components.path_modifying_point import PathModifyingPoint
from auto_components.path_action_point import PathActionPoint
from auto_features.json_file_writer import json_file_writer
from auto_features.path_creation import *
from tkinter import filedialog, messagebox
from tkinter import *
from auto_components.grid import Grid
from miscellaneous.utility_functions import *
from miscellaneous.important_variables import *
from miscellaneous.popup_variables import commands_frame_saver, commands_main_frame
class MainScreen:
"""The main screen of the application"""
font_size = 22
# Toolbar
draw_button = Button(WINDOW, compound=tkinter.CENTER, text="Draw", bg=pleasing_green, fg=white, font=TINY_FONT)
update_points_button = Button(WINDOW, compound=tkinter.CENTER, text="Update Points", bg=pleasing_green, fg=white, font=TINY_FONT)
clear_field_button = Button(WINDOW, compound=tkinter.CENTER, text="Clear Field", bg=pleasing_green, fg=white, font=TINY_FONT)
reset_input_fields_button = Button(WINDOW, compound=tkinter.CENTER, text="Reset Fields", bg=pleasing_green, fg=white, font=TINY_FONT)
save_file_button = Button(WINDOW, compound=tkinter.CENTER, text="Save File", bg=pleasing_green, fg=white, font=TINY_FONT)
toolbar_height = min(get_measurement(SCREEN_HEIGHT, 15), SCREEN_HEIGHT - FIELD_IMAGE_HEIGHT)
toolbar_length = get_measurement(SCREEN_LENGTH, 40)
toolbar_top_edge = SCREEN_HEIGHT - toolbar_height
popup_windows = []
# Switching Points Bar
switching_points_bar_height = get_measurement(SCREEN_HEIGHT, 5)
selected_point_field = InputField(WINDOW, SMALL_FONT, "1", True)
switched_point_field = InputField(WINDOW, SMALL_FONT, "2", True)
swap_points_button = Button(WINDOW, compound=tkinter.CENTER, text="Swap", bg=pleasing_green, fg=white, font=SMALL_FONT)
switching_points_bar_top_edge = SCREEN_HEIGHT - toolbar_height - switching_points_bar_height
# Point Action Bar
delete_button = Button(WINDOW, compound=tkinter.CENTER, text="Delete", bg=pleasing_green, fg=white, font=SMALL_FONT)
move_button = Button(WINDOW, compound=tkinter.CENTER, text="Move", bg=pleasing_green, fg=white, font=SMALL_FONT)
add_button = Button(WINDOW, compound=tkinter.CENTER, text="Add", bg=pleasing_green, fg=white, font=SMALL_FONT)
point_bar_length = SCREEN_LENGTH - FIELD_IMAGE_LENGTH
point_action_bar_height = get_measurement(SCREEN_HEIGHT, 5)
point_action_bar_top_edge = SCREEN_HEIGHT - toolbar_height - switching_points_bar_height - point_action_bar_height
point_action_bar_buttons = [delete_button, move_button, add_button]
# Miscellaneous
file_name = ""
selected_point = None
selected_input_field = None
# File Menu
menu = Menu(WINDOW)
file_menu = Menu(menu)
menu.add_cascade(label='File', menu=file_menu)
# Point Info:
path_action_points = []
path_modifying_points = []
required_points = []
current_points_altered_class = PathModifyingPoint
next_points_altered = {PathModifyingPoint: PathActionPoint, PathActionPoint: RequiredPoint,
RequiredPoint: PathModifyingPoint}
points_altered_to_point_list = {PathActionPoint: path_action_points, PathModifyingPoint: path_modifying_points, RequiredPoint: required_points}
points_altered_to_frame_button_color = {PathActionPoint: path_action_point_color, PathModifyingPoint: path_modifying_point_color, RequiredPoint: required_point_color}
points_altered_to_frame_name = {PathActionPoint: "Path Action Point", PathModifyingPoint: "Path Modifying Point",
RequiredPoint: "Required Point"}
# Point Alterable Field Frames:
path_modifying_point_alterable_fields_frame = PointAlterableFieldsFrame(path_modifying_points, ["Vx", "Vy", "x power"])
path_action_point_alterable_fields_frame = PointAlterableFieldsFrame(path_action_points, ["Speed", "tValue", "Command"])
required_point_alterable_fields_frame = PointAlterableFieldsFrame(required_points, ["tValue", "Angle"])
toggle_frame_button = Button(WINDOW, compound=tkinter.CENTER, text="Path Action Point", bg=path_action_point_color, fg=white, font=SMALL_FONT)
path_action_points_input_fields = []
path_modifying_points_input_fields = []
required_points_input_fields = []
points_altered_to_point_alterable_fields_frame = {PathActionPoint: path_action_point_alterable_fields_frame,
PathModifyingPoint: path_modifying_point_alterable_fields_frame,
RequiredPoint: required_point_alterable_fields_frame}
points_altered_to_points_input_fields = {PathActionPoint: path_action_points_input_fields,
PathModifyingPoint: path_modifying_points_input_fields,
RequiredPoint: required_points_input_fields}
toggle_frame_button_height = get_measurement(SCREEN_HEIGHT, 4)
point_alterable_fields_frames_height = point_action_bar_top_edge - toggle_frame_button_height # The Point frames should go down to the top of the Point action bar
# Commands Frame Dimensions
commands_frame_length = get_measurement(SCREEN_LENGTH, 15)
# Initial And End Conditions Frame
initial_conditions_tab_length = SCREEN_LENGTH - toolbar_length - commands_frame_length
initial_conditions_tab_left_edge = toolbar_length + commands_frame_length
initial_angle_field = TitledInputField(WINDOW, SMALL_FONT, "45", "Initial Angle", title_field_background_color=blue, title_field_text_color=white)
initial_speed_field = TitledInputField(WINDOW, SMALL_FONT, "1", "InitialSpeed", title_field_background_color=blue, title_field_text_color=white)
path_is_closed_drop_down_menu = DropDownMenu(WINDOW, 0, ["Path Is Closed", "Path Is Open"])
end_angle_field = TitledInputField(WINDOW, SMALL_FONT, "45", "End Angle", title_field_background_color=blue, title_field_text_color=white)
placement_angle_field = TitledInputField(WINDOW, SMALL_FONT, "0", "Placement Angle", title_field_background_color=blue, title_field_text_color=white)
initial_and_end_condition_fields = [initial_angle_field, initial_speed_field, end_angle_field, placement_angle_field]
# Field Image
right_field_image = tkinter.PhotoImage(file=right_field_image_path)
left_field_image = tkinter.PhotoImage(file=left_field_image_path)
current_field_image = left_field_image
field_image_bounds = [0, 0, SCREEN_LENGTH - point_bar_length, SCREEN_HEIGHT - toolbar_height]
image_left_edge = FIELD_IMAGE_LENGTH / 2
image_top_edge = FIELD_IMAGE_HEIGHT / 2
field_canvas = None
# States
class States:
DELETION = "DELETION"
MOVING = "MOVING"
ADD = "ADD"
INIT = "INIT"
point_editing_state = States.ADD
point_editing_state_to_point_button = {States.DELETION: delete_button, States.MOVING: move_button, States.ADD: add_button}
right_field_canvas = None
left_field_canvas = None
# Path Drawing
path_line_width = 8
path_modifying_point_line_width = 5
# Storing information for saving files
previous_file_name = ""
previous_file_path = ""
key_binding_to_function = {}
event_key_binding_to_function = {}
# Input Field Quick Transition Shortcuts
currently_selected_point_number = 1
current_input_field_number = 1
copied_point = None
def __init__(self):
"""Used for setting up the entire GUI"""
self.create_file_menu()
WINDOW.bind("<Button-1>", lambda e: self.run_mouse_click(e))
self.delete_button.configure(command=lambda: self.change_point_editing_state(self.States.DELETION, self.States.ADD))
self.move_button.configure(command=lambda: self.change_point_editing_state(self.States.MOVING, self.States.INIT))
self.add_button.configure(command=lambda: self.change_point_editing_state(self.States.ADD, self.States.DELETION))
self.toggle_frame_button.configure(command=self.toggle_points_alterable_fields_frame)
self.update_points_button.configure(command=self.update_points)
self.draw_button.configure(command=self.draw_path)
self.clear_field_button.configure(command=self.clear_field)
self.reset_input_fields_button.configure(command=self.reset_all_point_input_fields)
self.swap_points_button.configure(command=self.swap_points_function)
self.save_file_button.configure(command=self.quick_save_file)
commands_frame_saver.create_commands_frame(self.toolbar_length, self.toolbar_top_edge, self.commands_frame_length, self.toolbar_height)
commands_main_frame.default_show_items()
# Keyboard Shortcuts
self.add_all_key_binding_shortcuts()
self.set_button_colors()
self.display_everything()
self.initial_speed_field.error_message_function = DataValidator.get_float_error_message_function(-8, 8)
self.initial_angle_field.error_message_function = DataValidator.get_float_error_message_function(0, 360)
self.end_angle_field.error_message_function = DataValidator.get_float_error_message_function(0, 360)
self.placement_angle_field.error_message_function = DataValidator.get_float_error_message_function(0, 360)
points.set_points(self.path_modifying_points, self.path_action_points, self.required_points)
def toggle_points_alterable_fields_frame(self, new_point_class=None):
"""Toggles the PointsAlterableFieldsFrame, so it switches to being able to edit PathActionPoints and PathModifyingPoints"""
self.run_error_checking()
if not self.all_input_field_text_is_valid():
return
# Resetting the input field the GUI thinks is selected
self.currently_selected_point_number = 1
self.current_input_field_number = 1
self.unselect_input_fields()
last_frame = self.points_altered_to_point_alterable_fields_frame.get(self.current_points_altered_class)
last_frame.hide()
self.current_points_altered_class = self.next_points_altered.get(self.current_points_altered_class)
if new_point_class is not None:
self.current_points_altered_class = new_point_class
print(self.current_points_altered_class)
new_frame = self.points_altered_to_point_alterable_fields_frame.get(self.current_points_altered_class)
new_frame.show()
frame_name = self.points_altered_to_frame_name.get(self.current_points_altered_class)
frame_button_color = self.points_altered_to_frame_button_color.get(self.current_points_altered_class)
self.toggle_frame_button.configure(bg=frame_button_color, text=frame_name)
def change_point_editing_state(self, point_editing_state, state_after_double_click):
"""Changes the point editing state, so it can switch between adding, moving, deleting, and doing nothing with points"""
# If the button is a toggle then it should toggle between INIT (doing nothing) and that point_editing_state
if point_editing_state == self.point_editing_state:
self.point_editing_state = state_after_double_click
else:
self.point_editing_state = point_editing_state
self.set_button_colors()
def create_file_menu(self):
"""Creates the file menu system that allows the user to navigate between loading and saving files"""
self.file_menu.add_command(label="Load File", command=self.request_load_file)
self.file_menu.add_command(label="Save File As", command=self.save_file_as)
self.file_menu.add_command(label="Right Field Image", command=lambda: self.draw_field_image(self.right_field_image))
self.file_menu.add_command(label="Left Field Image", command=lambda: self.draw_field_image(self.left_field_image))
WINDOW.configure(menu=self.menu)
def create_bottom_bar(self):
"""Creates the button bar at the bottom of the screen (updating points, draw button, etc.)"""
grid = Grid([0, self.toolbar_top_edge, self.toolbar_length, self.toolbar_height], 1, None)
grid.turn_into_grid([self.draw_button, self.update_points_button, self.save_file_button,
self.clear_field_button, self.reset_input_fields_button], None, None)
def create_switch_points_bar(self):
"""Creates the bar that allows you to switch points around"""
grid = Grid([SCREEN_LENGTH - self.point_bar_length, self.switching_points_bar_top_edge, self.point_bar_length, self.switching_points_bar_height], 1, None)
grid.turn_into_grid([self.selected_point_field, self.switched_point_field, self.swap_points_button], None, None)
def create_point_action_bar(self):
"""Creates the bar that allows you to be able to add, delete, and move points"""
grid = Grid([SCREEN_LENGTH - self.point_bar_length, self.point_action_bar_top_edge, self.point_bar_length, self.point_action_bar_height], 1, None)
grid.turn_into_grid([self.add_button, self.delete_button, self.move_button], None, None)
def create_point_alterable_fields_frames(self):
"""Creates the bar that allows you to be able to modify the field's attributes like X, Y, Command, etc."""
grid = Grid([SCREEN_LENGTH - self.point_bar_length, self.toggle_frame_button_height, self.point_bar_length, self.point_alterable_fields_frames_height], None, 1)
# So they have the same dimensions
grid.turn_into_grid([self.path_modifying_point_alterable_fields_frame], None, None)
grid.turn_into_grid([self.path_action_point_alterable_fields_frame], None, None)
grid.turn_into_grid([self.required_point_alterable_fields_frame], None, None)
self.path_action_point_alterable_fields_frame.hide()
self.required_point_alterable_fields_frame.hide()
self.path_modifying_point_alterable_fields_frame.show()
self.toggle_frame_button.place(x=grid.left_edge, y=0, width=self.point_bar_length, height=self.toggle_frame_button_height)
frame_name = self.points_altered_to_frame_name.get(self.current_points_altered_class)
frame_button_color = self.points_altered_to_frame_button_color.get(self.current_points_altered_class)
self.toggle_frame_button.configure(bg=frame_button_color, text=frame_name)
def save_file_as(self):
"""Saves a new file with the contents of the GUI"""
self.run_error_checking()
if len(self.path_modifying_points) < 2:
messagebox.showerror("ERROR", "There must be at least 2 path modifying points")
return
if not self.all_input_field_text_is_valid():
return
file = filedialog.asksaveasfile(mode='w', defaultextension=".json")
if file is not None:
create_file("swerve_input.txt")
create_file("swerve_output.txt")
self.previous_file_name = get_file_name(file)
self.previous_file_path = file.name
self.save_file(get_file_name(file), file)
def quick_save_file(self):
""" Saves the file 'quickly.' The user only has to hit Ctrl + s or hit the save file button and the previous file
will be replaced with the new contents"""
if self.previous_file_path is not None and self.previous_file_name != "":
file = open(self.previous_file_path, "w")
self.save_file(self.previous_file_name, file)
else:
messagebox.showerror("ERROR", "No file has been previous selected. Either Load a File or Save a File as, so "
"I know where to save the files")
def save_file(self, file_name, file):
"""Saves the file with the contents of the GUI"""
create_file("swerve_input.txt")
create_file("swerve_output.txt")
self.run_error_checking()
if self.all_input_field_text_is_valid():
self._save_file(file_name, file)
def _save_file(self, file_name, file):
"""Saves the file with the contents of the GUI"""
if len(self.path_modifying_points) < 2:
messagebox.showerror("ERROR", "There must be at least 2 path modifying points")
return
start_all_json_contents = {
"Name": file_name,
"Closed": self.path_is_closed_drop_down_menu.get_selected_item() == "Path Is Closed",
"ClosedValue": self.path_is_closed_drop_down_menu.get_selected_item(),
"InitialAngle": self.initial_angle_field.get_text(),
"EndAngle": self.end_angle_field.get_text(),
"InitialSpeed": self.initial_speed_field.get_text(),
"offsetAngle": self.placement_angle_field.get_text()
}
initial_path_action_point, first_required_point, last_required_point = self.get_path_action_points_to_reflect_conditions()
path_action_points = copy_list(self.path_action_points)
path_action_points.append(initial_path_action_point)
json_file_writer.write_positions_to_file()
if len(points.path_action_points) <= 1:
first_path_action_point_coordinates = get_closest_path_point(path_action_points[0].get_field_left_edge(),
path_action_points[0].get_field_top_edge())
left_edge, top_edge = json_file_writer.get_next_path_action_point_coordinates(first_path_action_point_coordinates)
additional_path_action_point = PathActionPoint(None, None, False)
additional_path_action_point.set_field_left_edge(left_edge)
additional_path_action_point.set_field_top_edge(top_edge)
additional_path_action_point.set_speed(path_action_points[0].get_speed())
additional_path_action_point.set_command("none")
additional_path_action_point.is_needed = False
path_action_points.append(additional_path_action_point)
placement_angle = float(self.placement_angle_field.get_text())
json_file_writer.write_file(file, self.path_modifying_points,
path_action_points, start_all_json_contents, first_required_point,
last_required_point, placement_angle)
first_required_point.destroy()
last_required_point.destroy()
initial_path_action_point.destroy()
file.close()
def get_path_action_points_to_reflect_conditions(self):
"""
Returns:
list[MovablePoint]: {initial_path_action_point, first_required_point, last_required_point}; The updated control points
that reflect what was entered in the conditions tab + the first and last required point"""
last_path_modifying_point = self.path_modifying_points[len(self.path_modifying_points) - 1]
# The first point on the path must have a path modifying point, so the robot has the information to start the path
initial_required_point = self.get_required_point_at_path_modifying_point(self.path_modifying_points[0], float(self.initial_angle_field.get_text()), 0)
last_required_point = self.get_required_point_at_path_modifying_point(last_path_modifying_point, float(self.end_angle_field.get_text()), len(points.path_modifying_points) - 1)
initial_path_action_point: PathActionPoint = self.get_path_action_point_point_at_path_modifying_point(self.path_modifying_points[0], 0)
additional_path_action_points = [initial_path_action_point, initial_required_point, last_required_point]
for path_action_point in additional_path_action_points:
path_action_point.is_needed = False
return [initial_path_action_point, initial_required_point, last_required_point]
def get_required_point_at_path_modifying_point(self, path_modifying_point, angle, path_modifying_point_path_index):
"""
Returns:
PathActionPoint: a PathActionPoint that is at the same position of the 'path_modifying_point' provided"""
# None of these numbers matter because this PathActionPoint won't be on the screen
required_point = RequiredPoint(None, 0, is_on_screen=False)
required_point.set_field_left_edge(path_modifying_point.get_field_left_edge())
required_point.set_field_top_edge(path_modifying_point.get_field_top_edge())
required_point.set_angle(angle)
required_point.set_t_value(path_modifying_point_path_index)
return required_point
def get_path_action_point_point_at_path_modifying_point(self, path_modifying_point, path_modifying_point_path_index):
"""
Returns:
PathActionPoint: a PathActionPoint that is at the same position of the 'path_modifying_point' provided"""
# None of these numbers matter because this PathActionPoint won't be on the screen
path_action_point = PathActionPoint(None, 0, is_on_screen=False)
path_action_point.set_field_left_edge(path_modifying_point.get_field_left_edge())
path_action_point.set_field_top_edge(path_modifying_point.get_field_top_edge())
path_action_point.set_t_value(path_modifying_point_path_index)
path_action_point.set_command("none")
path_action_point.set_speed(float(self.initial_speed_field.get_text()))
return path_action_point
def display_everything(self):
"""Allows the user to be able to interact with the GUI"""
# Creating all the grids on the screen (the 'bars')
self.create_bottom_bar()
self.create_point_action_bar()
self.create_point_alterable_fields_frames()
self.create_switch_points_bar()
self.create_initial_conditions_bar()
# Creating the canvas that holds all the points and the field image
canvas_length = SCREEN_LENGTH - self.point_bar_length
canvas_height = SCREEN_HEIGHT - self.toolbar_height
self.field_canvas = Canvas(master=WINDOW, width=canvas_length,
height=canvas_height, bg=blue)
self.draw_field_image()
self.field_canvas.place(x=0, y=0)
def draw_field_image(self, field_image=None):
"""Displays the current field image"""
if field_image is not None and field_image == self.right_field_image:
important_variables.CENTER_OF_FIELD_HORIZONTAL_OFFSET = 0
elif field_image is not None:
important_variables.CENTER_OF_FIELD_HORIZONTAL_OFFSET = FIELD_IMAGE_LENGTH * PIXELS_TO_METERS_MULTIPLIER # pixels -> meters
if field_image is not None and self.current_field_image != field_image:
self.current_field_image = field_image
for point in self.required_points + self.path_action_points + self.path_modifying_points:
# Mirroring all the points along the y axis
left_edge = point.get_field_left_edge() * -1
point.set_field_left_edge(left_edge)
for points_list in [self.path_modifying_points, self.path_action_points, self.required_points]:
self.update_points(points_list)
self.field_canvas.create_image(self.image_left_edge, self.image_top_edge, image=self.current_field_image)
def create_initial_conditions_bar(self):
"""Creates the bar for the conditions"""
grid = Grid([self.initial_conditions_tab_left_edge, self.toolbar_top_edge, self.initial_conditions_tab_length, self.toolbar_height], 1, None)
grid.turn_into_grid(self.initial_and_end_condition_fields + [self.path_is_closed_drop_down_menu], None, None)
def create_point(self, mouse_left_edge, mouse_top_edge):
"""Puts a new point onto the screen"""
min_left_edge, min_top_edge, length, height = self.field_image_bounds
max_left_edge = min_left_edge + length
max_top_edge = min_top_edge + height
is_within_horizontal_bounds = mouse_left_edge >= min_left_edge and mouse_left_edge <= max_left_edge
is_within_vertical_bounds = mouse_top_edge >= min_top_edge and mouse_top_edge <= max_top_edge
if is_within_horizontal_bounds and is_within_vertical_bounds:
# Initializing the point
point = self.current_points_altered_class(self.point_click_function, len(self.points_list) + 1)
point_left_edge = mouse_left_edge - point.base_length / 2
point_top_edge = mouse_top_edge - point.base_height / 2
point.place(want_to_update_input_fields=True, x=point_left_edge, y=point_top_edge, width=point.base_length, height=point.base_height)
self.points_list.append(point)
point.set_order_position(len(self.points_list))
self.add_needed_point_creation_information(point)
self.point_alterable_fields_frame.update()
def unselect_input_fields(self, selected_input_field=None):
"""Makes all the points except the 'selected_input_field' become unselected"""
for input_field in self.points_input_fields:
if selected_input_field is None:
input_field.set_is_selected(False)
# Meaning we can now check if the input_field is the same as the selected_input_field because the input_field
# is not None
elif input_field != selected_input_field:
input_field.set_is_selected(False)
for point in self.points_list:
if selected_input_field is None:
point.unselect()
# Meaning we can now check if the input_field is the same as the selected_input_field because the input_field
# is not None
elif selected_input_field.belongs_to != point:
point.unselect()
self.selected_input_field = None
def update_input_fields(self):
"""So when a point is either added or deleted all the fields are recalculated to reflect the points"""
# So there are no more input fields; then all the input fields can be populated
# Creating a new variable, so the names don't conflict with the function name
points_input_fields = self.points_input_fields
points_input_fields[:] = []
for point in self.points_list:
points_input_fields += point.get_input_fields()
def handle_input_field_click(self, selected_input_field, want_error_checking=True):
"""Makes the input field become selected and the point that input field belongs to selected (all others are unselected)"""
if want_error_checking:
self.run_error_checking()
selected_point = selected_input_field.belongs_to
self.currently_selected_point_number = self.points_list.index(selected_point) + 1
self.current_input_field_number = selected_point.get_input_fields().index(selected_input_field) + 1
self.unselect_input_fields(selected_input_field)
# Once all the input field's are unselected then make the 'selected_input_field' selected
selected_input_field.set_is_selected(True)
selected_input_field.get_belongs_to().select()
self.selected_input_field = selected_input_field
# Click Functions
def swap_points_function(self):
"""Swaps the points"""
# Indexes and the point numbers are of a difference of 1
point_index = int(self.selected_point_field.get_text()) - 1
new_index = int(self.switched_point_field.get_text()) - 1
point_index_is_valid = point_index >= 0 and point_index < len(self.points_list)
new_index_is_valid = new_index >= 0 and new_index < len(self.points_list)
if point_index == new_index or not point_index_is_valid or not new_index_is_valid:
messagebox.showerror("ERROR", f"Can not swap points when invalid point order numbers are inputted. Values must be between 1 and {len(self.points_list)}")
else:
# Swaps the 'backend' position of the points
swap_list_items(self.points_list, point_index, new_index)
self.point_alterable_fields_frame.update()
def get_points_list(self, point):
"""
Returns:
MovablePoint[]: the points list that the point belongs to (PathModifyingPoint, PathActionPoint, etc.)"""
point_type = type(point)
return self.points_altered_to_point_list.get(point_type)
def get_index_of_point(self, point, points_list):
"""
Returns:
int: the index of the point within the points list gotten from get_points_list()"""
return points_list.index(point)
def point_click_function(self, point):
""" Runs different things depending on what point_editing_state the GUI is in when the point was clicked:
ADD: Adds a point
MOVING: Selects a point
DELETION: Deletes a point
"""
points_list = self.get_points_list(point)
index_of_point = self.get_index_of_point(point, points_list)
if self.point_editing_state == self.States.DELETION:
self.delete_point(index_of_point, points_list)
if self.point_editing_state == self.States.MOVING:
self.selected_point = point
self.selected_point.select()
def set_button_colors(self):
"""Sets the colors of the add, move, delete buttons; called upon point_editing_state change"""
for button in self.point_action_bar_buttons:
button.configure(bg=pleasing_green)
point_button = self.point_editing_state_to_point_button.get(self.point_editing_state)
# If the point_editing_state is in INIT then there will be no point button causing an error
if point_button is not None:
point_button.configure(bg=dark_green)
def run_mouse_click(self, event):
"""Creates a point if the point_editing_state is ADD and moves a point if the point_editing_state is MOVE and a point is selected"""
mouse_left_edge, mouse_top_edge = get_mouse_position()
if self.point_editing_state == self.States.ADD:
self.create_point(mouse_left_edge, mouse_top_edge)
if self.point_editing_state == self.States.MOVING and self.selected_point is not None:
self.selected_point.place(True, x=mouse_left_edge, y=mouse_top_edge)
self.selected_point.unselect()
self.selected_point = None
def all_input_field_text_is_valid(self):
"""
Returns:
bool: whether the text in the InputField's are valid"""
if not WANT_ERROR_CHECKING:
return True
return self.get_error_message() is None
def run_error_checking(self):
"""Runs the error checking for all the input fields"""
error_message = self.get_error_message()
if error_message is not None and WANT_ERROR_CHECKING:
messagebox.showerror("ERROR", error_message)
# raise ValueError("There was bad input! Stopping the program")
def get_error_message(self):
"""
Returns:
str: the error message of the input field's if the data was invalid (None if it is valid)"""
return_value = None
for point in self.points_list:
for input_field in point.get_input_fields():
error_message = input_field.get_error_message()
if error_message is not None:
return_value = error_message
input_field.get_error_message()
break
if return_value is not None:
break
for input_field in self.initial_and_end_condition_fields:
error_message = input_field.get_error_message()
if error_message is not None and return_value is None:
return_value = error_message
break
return return_value
def update_points(self, points_list=None):
"""Updates the points, so they reflect what the input field's have"""
self.run_error_checking()
if not self.all_input_field_text_is_valid():
return
if len(points.path_points) == 0:
messagebox.showerror("ERROR", "Make sure you have drawn the path before trying to update the points")
return
path_modifying_point_path_indexes = get_path_modifying_point_path_indexes()
update_path_action_and_required_point_location(points.path_action_points, points.required_points,
path_modifying_point_path_indexes, points.path_modifying_points)
points_list = self.points_list if points_list is None else points_list
for point in points_list:
point.default_update_coordinates()
def reset_point_input_fields(self, points):
"""Changes the input fields, so they reflect the points position on the screen"""
for point in points:
point.update_input_fields()
def reset_all_point_input_fields(self):
"""Changes all the point input fields, so they reflect the points position on the screen"""
self.reset_point_input_fields(self.path_action_points)
self.reset_point_input_fields(self.path_modifying_points)
self.reset_point_input_fields(self.required_points)
self.reset_point_input_fields(self.path_action_points)
self.reset_point_input_fields(self.path_modifying_points)
self.reset_point_input_fields(self.required_points)
def clear_field(self):
"""Clears the entire field of points and the path"""
for point in self.path_action_points + self.path_modifying_points + self.required_points:
point.destroy()
# So they don't reassigned to a new spot in memory messing up the pointer the frames have to the lists
self.path_action_points[:] = []
self.path_modifying_points[:] = []
self.required_points[:] = []
self.field_canvas.delete("all")
self.draw_field_image()
# Updates the frames, so they contain the points data
self.path_action_point_alterable_fields_frame.update()
self.path_modifying_point_alterable_fields_frame.update()
def change_point_order(self, is_up):
"""Moves the order of the currently selected point (1 -> 2)"""
if self.selected_input_field is not None:
point = self.selected_input_field.get_belongs_to()
point_index, new_index = self.get_point_indexes(point, is_up)
# Swaps the 'backend' position of the points
swap_list_items(self.points_list, point_index, new_index)
self.point_alterable_fields_frame.update()
def get_point_indexes(self, selected_point, is_up):
"""
Returns:
int: the new index of the point"""
point_index = selected_point.get_order_position() - 1
next_index = get_next_index(len(self.points_list) - 1, point_index)
previous_index = get_previous_index(len(self.points_list) - 1, point_index)
new_index = next_index if is_up else previous_index
return [point_index, new_index]
def draw_path(self):
"""Writes the data to the file, which calls AutoFollower.jar then it draws the points from AutoFollower.jar"""
self.run_error_checking()
if len(self.path_modifying_points) < 2:
messagebox.showerror("ERROR", "There must be at least 2 path modifying points")
return
if self.all_input_field_text_is_valid():
create_file("swerve_input.txt")
create_file("swerve_output.txt")
# So all the lines are deleted and the image is still on the canvas
self.field_canvas.delete("all")
self.draw_field_image()
json_file_writer.write_positions_to_file()
self.update_point_information()
draw_path_lines(self.field_canvas, self.path_modifying_point_line_width, self.path_line_width)
self.draw_robot_angle_lines()
self.update_points()
def update_point_information(self):
"""Updates all the point information, so drawing the path lines will work correctly"""
unused, first_required_point, last_required_point = self.get_path_action_points_to_reflect_conditions()
path_action_points = [first_required_point] + points.path_action_points + [last_required_point]
required_points = [first_required_point] + self.required_points + [last_required_point]
update_path_modifying_point_information(path_action_points=path_action_points, required_points=required_points)
first_required_point.destroy()
last_required_point.destroy()
def draw_robot_angle_lines(self):
"""Draws the robot angle at each control point"""
for path_modifying_point in self.path_modifying_points:
angle = path_modifying_point.get_angle_at_point()
point2_left_edge = path_modifying_point.get_left_edge() + math.cos(angle) * ROBOT_ANGLE_LINE_LENGTH
y_distance = math.sin(angle) * ROBOT_ANGLE_LINE_LENGTH
point2_top_edge = path_modifying_point.get_top_edge() - y_distance
self.field_canvas.create_line([path_modifying_point.get_left_edge(), path_modifying_point.get_top_edge()], [point2_left_edge, point2_top_edge],
fill=ROBOT_ANGLE_LINE_COLOR, width=ROBOT_ANGLE_LINE_WIDTH)
# Loading in from files
def request_load_file(self):
"""Loads a file onto the GUI"""
file = filedialog.askopenfile(mode='r')
if file is not None:
self.load_file(file)
def load_file(self, file):
"""Loads the file onto the GUI"""
json_contents = json.load(file)
self.previous_file_path = file.name
self.previous_file_name = get_file_name(file)
file.close()
# Setting the text field information to reflect the file
self.placement_angle_field.set_text(json_contents["offsetAngle"])
self.initial_speed_field.set_text(json_contents["InitialSpeed"])
self.end_angle_field.set_text(json_contents["EndAngle"])
self.initial_angle_field.set_text(json_contents["InitialAngle"])
self.path_is_closed_drop_down_menu.set_selected_item(json_contents["ClosedValue"])
# Updating the points to reflect the file
self.update_points_to_reflect_loaded_file(json_contents)
self.add_needed_point_creation_information_for_all_points()
def add_needed_point_creation_information_for_all_points(self):
""" Adds all the information the other parts of the code need for a point to be created for all the points (
for each point it calls add_needed_point_creation_information())"""
for point in self.path_modifying_points + self.path_action_points:
self.add_needed_point_creation_information(point)
self.path_action_point_alterable_fields_frame.update()
self.path_modifying_point_alterable_fields_frame.update()
def add_needed_point_creation_information(self, point):
"""Adds all the information the other parts of the code need for a point to be created"""
point.set_input_fields_command(self.handle_input_field_click)
self.update_input_fields()
def update_points_to_reflect_loaded_file(self, json_contents):
"""So the GUI reflects what is in the file (has to be delayed because it takes a while for the GUI to update and load the file)"""
self.clear_field()
json_file_loader.set_all_points_to_reflect_json_file(self.path_modifying_points, self.path_action_points,
self.required_points, json_contents, self.point_click_function)
# So the points change location based on what is in the input fields
self.update_points(self.path_action_points)
self.update_points(self.path_modifying_points)
self.update_points(self.required_points)
# Otherwise the frame information does not update correctly
self.toggle_points_alterable_fields_frame()
self.toggle_points_alterable_fields_frame()
self.toggle_points_alterable_fields_frame()
def change_input_field_selection(self, event):
"""Changes the selected input field depending on what key was pressed
Args:
event (str): the event name. Here are all the possible values:
'Up' -> Selects the next input field above the current one (belongs to the previous way point)
'Down' -> Selects the next input field below the current one (belongs to the next way point)
'Left' -> Selects the next input field to the right of the current one (belongs to the current way point)
'Right' -> Selects the previous input field to the left the current one (belongs to the current way point)
Returns:
None
"""
self.run_error_checking()
if not self.all_input_field_text_is_valid():
return
if self.currently_selected_point_number > len(self.points_list) or self.currently_selected_point_number < 1:
messagebox.showerror("ERROR", "Cannot Switch input fields because they do not exist!!!")
return
valid_event_types = ["Up", "Down", "Left", "Right", "Beginning", "End"]
if not valid_event_types.__contains__(event):
raise ValueError("Valid Data was not inputted!!!")
user_modifiable_fields = self.get_user_modifiable_fields(self.currently_selected_point_number)
# Modifying the numbers, so the selected input_field is not out of bounds of the input_fields on the screen
max_input_field_number = len(user_modifiable_fields)
if event == "Up":
self.currently_selected_point_number -= 1
if event == "Down":
self.currently_selected_point_number += 1
if event == "Left":
self.current_input_field_number -= 1
if event == "Right":
self.current_input_field_number += 1
if event == "Beginning":
self.current_input_field_number = 1
if event == "End":
self.current_input_field_number = max_input_field_number
if self.current_input_field_number > max_input_field_number:
self.current_input_field_number = 1
self.currently_selected_point_number += 1
if self.current_input_field_number <= 0:
self.current_input_field_number = max_input_field_number
self.currently_selected_point_number -= 1
self.make_input_field_selection_valid()
def make_input_field_selection_valid(self):
"""Makes sure where the GUI thinks the next input field selection is valid"""
# Making sure the currently selected points are cyclic 0 -> max_index -> 0 -> etc.
max_selected_point_number = len(self.points_list)
if self.currently_selected_point_number > max_selected_point_number:
self.currently_selected_point_number = 1
if self.currently_selected_point_number <= 0:
self.currently_selected_point_number = max_selected_point_number
self.focus_on_input_field(self.currently_selected_point_number, self.current_input_field_number)
def add_all_key_binding_shortcuts(self):
"""Adds all the key binding shortcuts to the Auto GUI"""
key_binding_to_function = {}
key_binding_to_function["<Control-a>"] = lambda event: self.toggle_points_alterable_fields_frame(PathModifyingPoint)
key_binding_to_function["<Control-c>"] = lambda event: self.copy_point()
key_binding_to_function["<Control-d>"] = lambda event: self.toggle_points_alterable_fields_frame(RequiredPoint)
key_binding_to_function["<KeyPress-d>"] = lambda event: self.draw_path()
key_binding_to_function["<KeyPress-e>"] = lambda event: self.change_point_editing_state(self.States.MOVING,
self.States.MOVING)
key_binding_to_function["<KeyPress-f>"] = lambda event: self.save_file_as()
key_binding_to_function["<KeyPress-g>"] = lambda event: self.request_load_file()
key_binding_to_function["<KeyPress-h>"] = lambda event: self.change_input_field_selection("Left")
key_binding_to_function["<KeyPress-i>"] = lambda event: self.change_input_field_selection("Beginning")
key_binding_to_function["<KeyPress-j>"] = lambda event: self.change_input_field_selection("Down")
key_binding_to_function["<KeyPress-k>"] = lambda event: self.change_input_field_selection("Up")
key_binding_to_function["<Control-l>"] = lambda event: self.request_load_file()
key_binding_to_function["<KeyPress-l>"] = lambda event: self.change_input_field_selection("Right")
key_binding_to_function["<KeyPress-o>"] = lambda event: self.change_input_field_selection("End")
key_binding_to_function["<KeyPress-q>"] = lambda event: self.change_point_editing_state(self.States.ADD,
self.States.ADD)
key_binding_to_function["<KeyPress-r>"] = lambda event: self.reset_all_point_input_fields()
key_binding_to_function["<KeyPress-s>"] = lambda event: self.quick_save_file()
key_binding_to_function["<Control-s>"] = lambda event: self.toggle_points_alterable_fields_frame(PathActionPoint)
key_binding_to_function["<Control-v>"] = lambda event: self.paste_point()
key_binding_to_function["<KeyPress-u>"] = lambda event: self.clear_field()
key_binding_to_function["<KeyPress-w>"] = lambda event: self.change_point_editing_state(self.States.DELETION,
self.States.DELETION)
key_binding_to_function["<KeyPress-z>"] = lambda event: self.swap_points_function()
key_binding_to_function["<KeyPress-Return>"] = lambda event: self.update_points(self.points_list)
key_binding_to_function["<Control-space>"] = lambda event: self.toggle_points_alterable_fields_frame()
key_binding_to_function["<Control-BackSpace>"] = lambda event: self.delete_point()
key_binding_to_function["<KeyPress-Up>"] = lambda event: self.change_point_order(False)
key_binding_to_function["<KeyPress-Down>"] = lambda event: self.change_point_order(True)
key_binding_to_function["<KeyPress-Tab>"] = lambda event: self.change_input_field_selection("Right")
for key_binding in key_binding_to_function.keys():
WINDOW.bind(key_binding, key_binding_to_function.get(key_binding))
# Finding the event_key_binding because the event_key_binding has different information than the key_binding.
# For instance, key_binding is '<KeyPress-u>' while event_key_binding is 'u'
first_dash_index = key_binding.index("-")
event_key_binding = key_binding[first_dash_index + 1:]
event_key_binding = event_key_binding[:-1] # The last character of the string is '>' which is not needed
important_variables.all_key_bindings.append(event_key_binding)
def focus_on_input_field(self, point_number, input_field_number):
"""Moves the mouse to the input field and the desired location"""
user_modifiable_field = self.get_user_modifiable_fields(point_number)[input_field_number - 1]
user_modifiable_field.focus_force()
self.handle_input_field_click(user_modifiable_field, want_error_checking=False)
def get_user_modifiable_fields(self, point_number):