Add 2021 field to Spline UI

Change-Id: Ic2e86a0c42e76e4e61b5ee34c95dc743d409c8b8
diff --git a/frc971/control_loops/python/constants.py b/frc971/control_loops/python/constants.py
index 14c2afc..2c94169 100644
--- a/frc971/control_loops/python/constants.py
+++ b/frc971/control_loops/python/constants.py
@@ -1,4 +1,5 @@
 from gi.repository import Gtk
+from collections import namedtuple
 
 window = Gtk.Window()
 screen = window.get_screen()
@@ -17,23 +18,103 @@
 ROBOT_SIDE_TO_HATCH_PANEL = 0.1
 HATCH_PANEL_WIDTH = 0.4826
 
-FIELD = 2020
+FieldType = namedtuple(
+    'Field', ['name', 'tags', 'year', 'width', 'length', 'json_name'])
 
-if FIELD == 2019:
-    # Half Field
-    WIDTH_OF_FIELD_IN_METERS = 8.258302
-elif FIELD == 2020:
-    # Full Field
-    WIDTH_OF_FIELD_IN_METERS = 15.98295
-    LENGTH_OF_FIELD_IN_METERS = 8.21055
+GALACTIC_SEARCH = "Galactic Search"
+ARED = "A Red"
+BRED = "B Red"
+ABLUE = "A Blue"
+BBLUE = "B Blue"
+AUTONAV = "AutoNav"
+BOUNCE = "Bounce"
+SLALOM = "Slalom"
+BARREL = "Barrel"
+
+FIELDS = {
+    "2019 Field":
+    FieldType(
+        "2019 Field",
+        tags=[],
+        year=2019,
+        width=8.258302,
+        length=8.258302,
+        json_name="spline_2019.json"),
+    "2020 Field":
+    FieldType(
+        "2020 Field",
+        tags=[],
+        year=2020,
+        width=15.98295,
+        length=8.21055,
+        json_name="spline_2020.json"),
+    "2021 Galactic Search BRed":
+    FieldType(
+        "2021 Galactic Search BRed",
+        tags=[GALACTIC_SEARCH, BRED],
+        year=2021,
+        width=9.144,
+        length=4.572,
+        json_name="spline_red_a.json"),
+    "2021 Galactic Search ARed":
+    FieldType(
+        "2021 Galactic Search ARed",
+        tags=[GALACTIC_SEARCH, ARED],
+        year=2021,
+        width=9.144,
+        length=4.572,
+        json_name="spline_red_b.json"),
+    "2021 Galactic Search BBlue":
+    FieldType(
+        "2021 Galactic Search BBlue",
+        tags=[GALACTIC_SEARCH, BBLUE],
+        year=2021,
+        width=9.144,
+        length=4.572,
+        json_name="spline_blue_b.json"),
+    "2021 Galactic Search ABlue":
+    FieldType(
+        "2021 Galactic Search ABlue",
+        tags=[GALACTIC_SEARCH, ABLUE],
+        year=2021,
+        width=9.144,
+        length=4.572,
+        json_name="spline_blue_a.json"),
+    "2021 AutoNav Barrel":
+    FieldType(
+        "2021 AutoNav Barrel",
+        tags=[AUTONAV, BARREL],
+        year=2021,
+        width=9.144,
+        length=4.572,
+        json_name="autonav_barrel.json"),
+    "2021 AutoNav Slalom":
+    FieldType(
+        "2021 AutoNav Slalom",
+        tags=[AUTONAV, SLALOM],
+        year=2021,
+        width=9.144,
+        length=4.572,
+        json_name="autonav_slalom.json"),
+    "2021 AutoNav Bounce":
+    FieldType(
+        "2021 AutoNav Bounce",
+        tags=[AUTONAV, BOUNCE],
+        year=2021,
+        width=9.144,
+        length=4.572,
+        json_name="autonav_bounce.json"),
+}
+
+FIELD = FIELDS["2021 Galactic Search BRed"]
 
 
 def pxToM(p):
-    return p * WIDTH_OF_FIELD_IN_METERS / SCREEN_SIZE
+    return p * FIELD.width / SCREEN_SIZE
 
 
 def mToPx(m):
-    return (m * SCREEN_SIZE / WIDTH_OF_FIELD_IN_METERS)
+    return (m * SCREEN_SIZE / FIELD.width)
 
 
 def inToM(i):
diff --git a/frc971/control_loops/python/drawing_constants.py b/frc971/control_loops/python/drawing_constants.py
index a364e40..8548b54 100644
--- a/frc971/control_loops/python/drawing_constants.py
+++ b/frc971/control_loops/python/drawing_constants.py
@@ -60,6 +60,91 @@
     cr.scale(widthb, -heightb)
 
 
+def draw_at_home_grid(cr):
+    field = np.zeros(shape=(5, 11), dtype=bool)
+    # field[row from bottom][column from left]
+
+    if GALACTIC_SEARCH in FIELD.tags:
+        # Galactic search start zone
+        field[1][0] = True
+        field[3][0] = True
+
+        # Galactic search end zone
+        field[1][10] = True
+        field[3][10] = True
+
+        if ARED in FIELD.tags:
+            field[4][5] = True
+            field[2][2] = True
+            field[1][4] = True
+        elif ABLUE in FIELD.tags:
+            field[0][5] = True
+            field[3][6] = True
+            field[2][8] = True
+        elif BRED in FIELD.tags:
+            field[3][2] = True
+            field[1][4] = True
+            field[3][6] = True
+        elif BBLUE in FIELD.tags:
+            field[1][5] = True
+            field[3][7] = True
+            field[1][9] = True
+    elif AUTONAV in FIELD.tags:
+        # start/end zone
+        field[1][0] = True
+        field[1][1] = True
+        field[3][0] = True
+        field[3][1] = True
+
+        if BAREL in FIELD.tags:
+            # barrels
+            field[1][4] = True
+            field[3][8] = True
+            field[1][10] = True
+        if BARREL in FIELD.tags:
+            field[1][3:8] = True  # 3 to 7 inclusive
+            field[1][9] = True
+        if BOUNCE in FIELD.tags:
+            # turn on two rows
+            field[1][:11] = True
+            field[3][:11] = True
+
+            # turn off parts of rows
+            field[3][2] = False
+            field[3][5] = False
+            field[3][8] = False
+
+            field[1][3] = False
+            field[1][5] = False
+            field[1][8] = False
+
+            # markers to hit
+            field[4][2] = True
+            field[4][5] = True
+            field[4][8] = True
+
+    # Move origin to bottom left
+    xorigin = -mToPx(FIELD.width) / 2.0
+    yorigin = -mToPx(FIELD.length) / 2.0
+
+    color = palette["BLACK"]
+    # markers are at least 6.35 x 6.35 cm
+    marker_length = mToPx(0.0635)
+
+    for row, row_array in enumerate(field):
+        for column, has_marker in enumerate(row_array):
+            one_indexed_row = row + 1
+            one_indexed_column = column + 1
+
+            # 76.2 cm increments
+            pos_y = one_indexed_row * mToPx(0.762)
+            pos_x = one_indexed_column * mToPx(0.762)
+
+            if has_marker:
+                draw_px_x(cr, xorigin + pos_x, yorigin + pos_y, marker_length,
+                          color)
+
+
 def markers(cr):
     SHOW_MARKERS = False
     if SHOW_MARKERS:
@@ -111,9 +196,9 @@
 
 def draw_init_lines(cr):
     set_color(cr, palette["RED"])
-    init_line_x = WIDTH_OF_FIELD_IN_METERS / 2.0 - inToM(120)
-    init_start_y = -LENGTH_OF_FIELD_IN_METERS / 2.0
-    init_end_y = LENGTH_OF_FIELD_IN_METERS / 2.0
+    init_line_x = FIELD.width / 2.0 - inToM(120)
+    init_start_y = -FIELD.length / 2.0
+    init_end_y = FIELD.length / 2.0
     cr.move_to(mToPx(init_line_x), mToPx(init_start_y))
     cr.line_to(mToPx(init_line_x), mToPx(init_end_y))
 
@@ -124,7 +209,7 @@
 
 
 def draw_trench_run(cr):
-    edge_of_field_y = LENGTH_OF_FIELD_IN_METERS / 2.0
+    edge_of_field_y = FIELD.length / 2.0
     edge_of_trench_y = edge_of_field_y - inToM(55.5)
     trench_start_x = inToM(-108.0)
     trench_length_x = inToM(216.0)
@@ -133,7 +218,7 @@
     ball_two_x = -inToM(36)
     ball_three_x = 0.0
     # The fourth/fifth balls are referenced off of the init line...
-    ball_fourfive_x = WIDTH_OF_FIELD_IN_METERS / 2.0 - inToM(120.0 + 130.36)
+    ball_fourfive_x = FIELD.width / 2.0 - inToM(120.0 + 130.36)
 
     for sign in [1.0, -1.0]:
         set_color(cr, palette["GREEN"])
@@ -167,9 +252,9 @@
 
 def draw_control_panel(cr):  # Base plates are not included
     set_color(cr, palette["LIGHT_GREY"])
-    edge_of_field_y = LENGTH_OF_FIELD_IN_METERS / 2.0
+    edge_of_field_y = FIELD.length / 2.0
     edge_of_trench_y = edge_of_field_y - inToM(55.5)
-    high_x = inToM(374.59) - WIDTH_OF_FIELD_IN_METERS / 2.0
+    high_x = inToM(374.59) - FIELD.width / 2.0
     low_x = high_x - inToM(30)
     for sign in [1.0, -1.0]:
         # Bottom Control Panel
diff --git a/frc971/control_loops/python/path_edit.py b/frc971/control_loops/python/path_edit.py
index f198299..7620ff5 100755
--- a/frc971/control_loops/python/path_edit.py
+++ b/frc971/control_loops/python/path_edit.py
@@ -96,17 +96,19 @@
         return self.all_controls[self.get_index_of_nearest_point()]
 
     def draw_field_elements(self, cr):
-        if FIELD == 2019:
+        if FIELD.year == 2019:
             draw_HAB(cr)
             draw_rockets(cr)
             draw_cargo_ship(cr)
-        elif FIELD == 2020:
+        elif FIELD.year == 2020:
             set_color(cr, palette["BLACK"])
             markers(cr)
             draw_shield_generator(cr)
             draw_trench_run(cr)
             draw_init_lines(cr)
             draw_control_panel(cr)
+        elif FIELD.year == 2021:
+            draw_at_home_grid(cr)
 
     def draw_robot_at_point(self, cr, i, p, spline):
         p1 = [mToPx(spline.Point(i)[0]), mToPx(spline.Point(i)[1])]
@@ -222,18 +224,18 @@
         cr.show_text('Press "i" to import')
 
         cr.save()
-        cr.translate(mToPx(WIDTH_OF_FIELD_IN_METERS) / 2.0, 0.0)
         set_color(cr, palette["BLACK"])
-        if FIELD == 2020:
-            cr.rectangle(-mToPx(WIDTH_OF_FIELD_IN_METERS) / 2.0,
-                         -mToPx(LENGTH_OF_FIELD_IN_METERS) / 2.0,
-                         mToPx(WIDTH_OF_FIELD_IN_METERS),
-                         mToPx(LENGTH_OF_FIELD_IN_METERS))
-        else:
+
+        if FIELD.year == 2019:  # half field
             cr.rectangle(0, -SCREEN_SIZE / 2, SCREEN_SIZE, SCREEN_SIZE)
+        else:  # full field
+            cr.translate(mToPx(FIELD.width) / 2.0, 0.0)
+            cr.rectangle(-mToPx(FIELD.width) / 2.0, -mToPx(FIELD.length) / 2.0,
+                         mToPx(FIELD.width), mToPx(FIELD.length))
         cr.set_line_join(cairo.LINE_JOIN_ROUND)
         cr.stroke()
         self.draw_field_elements(cr)
+
         y = 0
 
         # update everything
@@ -319,7 +321,7 @@
     def mouse_move(self, event):
         old_x = self.x
         old_y = self.y
-        self.x = event.x - mToPx(WIDTH_OF_FIELD_IN_METERS / 2.0)
+        self.x = event.x - mToPx(FIELD.width / 2.0)
         self.y = event.y
         dif_x = self.x - old_x
         dif_y = self.y - old_y
@@ -435,6 +437,6 @@
 
     def do_button_press(self, event):
         # Be consistent with the scaling in the drawing_area
-        self.x = event.x * 2 - mToPx(WIDTH_OF_FIELD_IN_METERS / 2.0)
+        self.x = event.x * 2 - mToPx(FIELD.width / 2.0)
         self.y = event.y * 2
         self.button_press_action()