diff --git a/scratch/render_icon.py b/scratch/render_icon.py new file mode 100644 index 000000000..a4d4d6fc6 --- /dev/null +++ b/scratch/render_icon.py @@ -0,0 +1,31 @@ +import sys +import os +import pyray as rl + +# Add selfdrive's parent to path to support openpilot imports +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) + +from selfdrive.ui.layouts.settings.starpilot.scribble import draw_custom_icon + +def main(): + rl.init_window(200, 200, "Icon Render") + rl.set_target_fps(60) + + # Run for a few frames to make sure rendering is ready + for _ in range(10): + rl.begin_drawing() + rl.clear_background(rl.Color(15, 12, 30, 255)) # Dark background matching Driving Model tile + + # Draw aicar icon centered + # x_c and y_c will be at x + 30*s, y + 30*s. + # Let's draw it at x=70, y=70, scale s=1.0. Centered at 100, 100. + draw_custom_icon("aicar", 70.0, 70.0, 1.0, rl.WHITE) + + rl.end_drawing() + + rl.take_screenshot("scratch/aicar_preview.png") + rl.close_window() + print("Screenshot saved to scratch/aicar_preview.png") + +if __name__ == "__main__": + main() diff --git a/selfdrive/ui/layouts/settings/starpilot/aethergrid.py b/selfdrive/ui/layouts/settings/starpilot/aethergrid.py index 400a1b87b..dba73b892 100644 --- a/selfdrive/ui/layouts/settings/starpilot/aethergrid.py +++ b/selfdrive/ui/layouts/settings/starpilot/aethergrid.py @@ -3519,7 +3519,7 @@ class HubTile(AetherTile): self.get_status = get_status self.title = title self.desc = desc - self.custom_icon_key = icon_key if icon_key in ("sound", "steering", "navigate", "system", "display", "vehicle") else None + self.custom_icon_key = icon_key if icon_key in ("sound", "steering", "navigate", "system", "display", "vehicle", "road", "aicar") else None self._icon = None self._font_title = gui_app.font(FontWeight.BOLD) self._font_desc = gui_app.font(FontWeight.MEDIUM) diff --git a/selfdrive/ui/layouts/settings/starpilot/main_panel.py b/selfdrive/ui/layouts/settings/starpilot/main_panel.py index 50a28d616..195ea2b90 100644 --- a/selfdrive/ui/layouts/settings/starpilot/main_panel.py +++ b/selfdrive/ui/layouts/settings/starpilot/main_panel.py @@ -29,7 +29,7 @@ class StarPilotLayout(Widget): { "title": "Driving Controls", "icon": "steering", - "buttons": [("DRIVING MODEL", "DRIVING_MODEL"), ("GAS / BRAKE", "LONGITUDINAL"), ("STEERING", "LATERAL")], + "buttons": [("DRIVING MODEL", "DRIVING_MODEL", "aicar"), ("GAS / BRAKE", "LONGITUDINAL", "road"), ("STEERING", "LATERAL", "steering")], }, { "title": "Map Data", @@ -203,7 +203,13 @@ class StarPilotLayout(Widget): cat = self.CATEGORIES[self._current_category_idx] visible_buttons = cat["buttons"] - for label, panel_key in visible_buttons: + for button_info in visible_buttons: + if len(button_info) == 3: + label, panel_key, btn_icon = button_info + else: + label, panel_key = button_info + btn_icon = cat["icon"] + p_type = panel_type_map[panel_key] def on_btn_click(p=p_type): self._set_current_panel(p) @@ -211,7 +217,7 @@ class StarPilotLayout(Widget): tile = HubTile( title=tr(label), desc="", - icon_key=cat["icon"], + icon_key=btn_icon, on_click=on_btn_click, bg_color=cat.get("color") ) diff --git a/selfdrive/ui/layouts/settings/starpilot/scribble.py b/selfdrive/ui/layouts/settings/starpilot/scribble.py index 827f4c2ff..9d5d9cfd8 100644 --- a/selfdrive/ui/layouts/settings/starpilot/scribble.py +++ b/selfdrive/ui/layouts/settings/starpilot/scribble.py @@ -219,6 +219,178 @@ def draw_custom_icon(key: str, x: float, y: float, s: float, color: rl.Color): rl.draw_line_ex(rl.Vector2(x + 18.0 * s, y + 25.0 * s), rl.Vector2(x + 36.0 * s, y + 25.0 * s), 1.5 * s, color) rl.draw_line_ex(rl.Vector2(x + 36.0 * s, y + 25.0 * s), rl.Vector2(x + 34.0 * s, y + 20.0 * s), 1.5 * s, color) + elif key == "road": + # Curvy Road: Vertical perspective road with dashed center line + x_c = x + 30.0 * s + y_c = y + 30.0 * s + + def draw_cubic_bezier(p0: rl.Vector2, p1: rl.Vector2, p2: rl.Vector2, p3: rl.Vector2, thick: float): + segments = 64 + for i in range(segments): + t1 = i / segments + t2 = (i + 1) / segments + + t1_3 = (1 - t1)**3 + t1_2_t = 3 * (1 - t1)**2 * t1 + t_t1_2 = 3 * (1 - t1) * t1**2 + t1_cube = t1**3 + + x1_val = t1_3 * p0.x + t1_2_t * p1.x + t_t1_2 * p2.x + t1_cube * p3.x + y1_val = t1_3 * p0.y + t1_2_t * p1.y + t_t1_2 * p2.y + t1_cube * p3.y + + t2_3 = (1 - t2)**3 + t2_2_t = 3 * (1 - t2)**2 * t2 + t_t2_2 = 3 * (1 - t2) * t2**2 + t2_cube = t2**3 + + x2_val = t2_3 * p0.x + t2_2_t * p1.x + t_t2_2 * p2.x + t2_cube * p3.x + y2_val = t2_3 * p0.y + t2_2_t * p1.y + t_t2_2 * p2.y + t2_cube * p3.y + + rl.draw_line_ex(rl.Vector2(x1_val, y1_val), rl.Vector2(x2_val, y2_val), thick, color) + + # Center reference points for S-curve + p0_c = rl.Vector2(x_c, y_c + 24.0 * s) + p1_c = rl.Vector2(x_c - 16.0 * s, y_c + 9.0 * s) + p2_c = rl.Vector2(x_c + 16.0 * s, y_c - 5.0 * s) + p3_c = rl.Vector2(x_c, y_c - 19.0 * s) + + # Left edge points (widest at bottom, tapering at top) + p0_l = rl.Vector2(p0_c.x - 17.0 * s, p0_c.y) + p1_l = rl.Vector2(p1_c.x - 12.0 * s, p1_c.y) + p2_l = rl.Vector2(p2_c.x - 8.0 * s, p2_c.y) + p3_l = rl.Vector2(p3_c.x - 5.0 * s, p3_c.y) + draw_cubic_bezier(p0_l, p1_l, p2_l, p3_l, 3.0 * s) + + # Right edge points (widest at bottom, tapering at top) + p0_r = rl.Vector2(p0_c.x + 17.0 * s, p0_c.y) + p1_r = rl.Vector2(p1_c.x + 12.0 * s, p1_c.y) + p2_r = rl.Vector2(p2_c.x + 8.0 * s, p2_c.y) + p3_r = rl.Vector2(p3_c.x + 5.0 * s, p3_c.y) + draw_cubic_bezier(p0_r, p1_r, p2_r, p3_r, 3.0 * s) + + # Center dashed line + dash_count = 4 + for i in range(dash_count): + t1 = (i + 0.15) / dash_count + t2 = (i + 0.65) / dash_count + + t1_3 = (1 - t1)**3 + t1_2_t = 3 * (1 - t1)**2 * t1 + t_t1_2 = 3 * (1 - t1) * t1**2 + t1_cube = t1**3 + + x1_val = t1_3 * p0_c.x + t1_2_t * p1_c.x + t_t1_2 * p2_c.x + t1_cube * p3_c.x + y1_val = t1_3 * p0_c.y + t1_2_t * p1_c.y + t_t1_2 * p2_c.y + t1_cube * p3_c.y + + t2_3 = (1 - t2)**3 + t2_2_t = 3 * (1 - t2)**2 * t2 + t_t2_2 = 3 * (1 - t2) * t2**2 + t2_cube = t2**3 + + x2_val = t2_3 * p0_c.x + t2_2_t * p1_c.x + t_t2_2 * p2_c.x + t2_cube * p3_c.x + y2_val = t2_3 * p0_c.y + t2_2_t * p1_c.y + t_t2_2 * p2_c.y + t2_cube * p3_c.y + + rl.draw_line_ex(rl.Vector2(x1_val, y1_val), rl.Vector2(x2_val, y2_val), 2.5 * s, color) + + elif key == "aicar": + # AI Car: Half car silhouette on the left (Sedan), circuit traces with contact pads on the right + x_c = x + 30.0 * s + y_c = y + 30.0 * s + + def draw_pad(cx: float, cy: float, size: float): + half = size / 2.0 + rl.draw_line_ex(rl.Vector2(cx - half, cy - half), rl.Vector2(cx + half, cy - half), 1.5 * s, color) + rl.draw_line_ex(rl.Vector2(cx + half, cy - half), rl.Vector2(cx + half, cy + half), 1.5 * s, color) + rl.draw_line_ex(rl.Vector2(cx + half, cy + half), rl.Vector2(cx - half, cy + half), 1.5 * s, color) + rl.draw_line_ex(rl.Vector2(cx - half, cy + half), rl.Vector2(cx - half, cy - half), 1.5 * s, color) + dot_half = size / 4.0 + rl.draw_rectangle_rec(rl.Rectangle(cx - dot_half, cy - dot_half, size / 2.0, size / 2.0), color) + + # 1. Soft Dashed Vertical Splitter (Blending/Transition Effect) + r = color.r if hasattr(color, "r") else color[0] + g = color.g if hasattr(color, "g") else color[1] + b = color.b if hasattr(color, "b") else color[2] + divider_color = rl.Color(r, g, b, 60) # ~24% opacity + dash_len = 1.0 * s + gap_len = 2.0 * s + curr_y = y_c - 16.0 * s + end_y = y_c + 12.0 * s + while curr_y < end_y: + rl.draw_line_ex(rl.Vector2(x_c, curr_y), rl.Vector2(x_c, min(curr_y + dash_len, end_y)), 1.5 * s, divider_color) + curr_y += dash_len + gap_len + + # 2. Right Side (AI Circuit Traces & Pads) + # Trace 1 (Top - Aligned with roofline) + rl.draw_line_ex(rl.Vector2(x_c, y_c - 16.0 * s), rl.Vector2(x_c + 8.0 * s, y_c - 16.0 * s), 1.5 * s, color) + rl.draw_line_ex(rl.Vector2(x_c + 8.0 * s, y_c - 16.0 * s), rl.Vector2(x_c + 14.0 * s, y_c - 20.0 * s), 1.5 * s, color) + rl.draw_line_ex(rl.Vector2(x_c + 14.0 * s, y_c - 20.0 * s), rl.Vector2(x_c + 22.0 * s, y_c - 20.0 * s), 1.5 * s, color) + draw_pad(x_c + 24.0 * s, y_c - 20.0 * s, 3.5 * s) + + # Trace 2 (Aligned with window bottom / beltline) + rl.draw_line_ex(rl.Vector2(x_c, y_c - 5.0 * s), rl.Vector2(x_c + 8.0 * s, y_c - 5.0 * s), 1.5 * s, color) + rl.draw_line_ex(rl.Vector2(x_c + 8.0 * s, y_c - 5.0 * s), rl.Vector2(x_c + 13.0 * s, y_c - 9.0 * s), 1.5 * s, color) + rl.draw_line_ex(rl.Vector2(x_c + 13.0 * s, y_c - 9.0 * s), rl.Vector2(x_c + 18.0 * s, y_c - 9.0 * s), 1.5 * s, color) + draw_pad(x_c + 20.0 * s, y_c - 9.0 * s, 3.5 * s) + + # Trace 3 (Forked - Centered on door) + rl.draw_line_ex(rl.Vector2(x_c, y_c + 1.0 * s), rl.Vector2(x_c + 6.0 * s, y_c + 1.0 * s), 1.5 * s, color) + # Fork 3A (Up) + rl.draw_line_ex(rl.Vector2(x_c + 6.0 * s, y_c + 1.0 * s), rl.Vector2(x_c + 10.0 * s, y_c - 2.0 * s), 1.5 * s, color) + rl.draw_line_ex(rl.Vector2(x_c + 10.0 * s, y_c - 2.0 * s), rl.Vector2(x_c + 16.0 * s, y_c - 2.0 * s), 1.5 * s, color) + draw_pad(x_c + 18.0 * s, y_c - 2.0 * s, 3.5 * s) + # Fork 3B (Down) + rl.draw_line_ex(rl.Vector2(x_c + 6.0 * s, y_c + 1.0 * s), rl.Vector2(x_c + 10.0 * s, y_c + 4.0 * s), 1.5 * s, color) + rl.draw_line_ex(rl.Vector2(x_c + 10.0 * s, y_c + 4.0 * s), rl.Vector2(x_c + 16.0 * s, y_c + 4.0 * s), 1.5 * s, color) + draw_pad(x_c + 18.0 * s, y_c + 4.0 * s, 3.5 * s) + + # Trace 4 (Lower door) + rl.draw_line_ex(rl.Vector2(x_c, y_c + 7.0 * s), rl.Vector2(x_c + 7.0 * s, y_c + 7.0 * s), 1.5 * s, color) + rl.draw_line_ex(rl.Vector2(x_c + 7.0 * s, y_c + 7.0 * s), rl.Vector2(x_c + 11.0 * s, y_c + 10.0 * s), 1.5 * s, color) + rl.draw_line_ex(rl.Vector2(x_c + 11.0 * s, y_c + 10.0 * s), rl.Vector2(x_c + 15.0 * s, y_c + 10.0 * s), 1.5 * s, color) + draw_pad(x_c + 17.0 * s, y_c + 10.0 * s, 3.5 * s) + + # Trace 5 (Bottom - Aligned with rocker panel) + rl.draw_line_ex(rl.Vector2(x_c, y_c + 12.0 * s), rl.Vector2(x_c + 8.0 * s, y_c + 12.0 * s), 1.5 * s, color) + rl.draw_line_ex(rl.Vector2(x_c + 8.0 * s, y_c + 12.0 * s), rl.Vector2(x_c + 14.0 * s, y_c + 18.0 * s), 1.5 * s, color) + rl.draw_line_ex(rl.Vector2(x_c + 14.0 * s, y_c + 18.0 * s), rl.Vector2(x_c + 22.0 * s, y_c + 18.0 * s), 1.5 * s, color) + draw_pad(x_c + 24.0 * s, y_c + 18.0 * s, 3.5 * s) + + # 3. Left Side (Car Front silhouette - Sleek modern profile with long sweeping windshield) + # Roof & Windshield (continuous Bezier curve profile) + p0_roof = rl.Vector2(x_c, y_c - 16.0 * s) + p1_roof = rl.Vector2(x_c - 12.0 * s, y_c - 16.0 * s) + p2_roof = rl.Vector2(x_c - 22.0 * s, y_c - 5.0 * s) + draw_bezier(p0_roof, p1_roof, p2_roof, 2.0 * s) + + # Hood (short, streamlined) + p0_hood = rl.Vector2(x_c - 22.0 * s, y_c - 5.0 * s) + p1_hood = rl.Vector2(x_c - 25.0 * s, y_c - 4.5 * s) + p2_hood = rl.Vector2(x_c - 28.0 * s, y_c - 2.5 * s) + draw_bezier(p0_hood, p1_hood, p2_hood, 2.0 * s) + + # Nose & Bumper Curve + p0_nose = rl.Vector2(x_c - 28.0 * s, y_c - 2.5 * s) + p1_nose = rl.Vector2(x_c - 31.0 * s, y_c + 1.0 * s) + p2_nose = rl.Vector2(x_c - 28.0 * s, y_c + 12.0 * s) + draw_bezier(p0_nose, p1_nose, p2_nose, 2.0 * s) + + # Wheel Arch + draw_ellipse_arc(x_c - 21.0 * s, y_c + 12.0 * s, 7.0 * s, 7.0 * s, 0.0, 180.0, 360.0, 2.0 * s) + + # Underbody / Rocker panel (aligned with Trace 5) + rl.draw_line_ex(rl.Vector2(x_c - 14.0 * s, y_c + 12.0 * s), rl.Vector2(x_c, y_c + 12.0 * s), 2.0 * s, color) + + # Wheel (hollow ring matching the arch) + rl.draw_ring(rl.Vector2(x_c - 21.0 * s, y_c + 12.0 * s), 3.5 * s, 5.0 * s, 0.0, 360.0, 24, color) + + # Side Window Details (Parallel sloped wedge, open to the dashed divider at the rear to blend perfectly) + p0_win = rl.Vector2(x_c, y_c - 13.0 * s) + p1_win = rl.Vector2(x_c - 10.0 * s, y_c - 13.0 * s) + p2_win = rl.Vector2(x_c - 18.0 * s, y_c - 5.0 * s) + draw_bezier(p0_win, p1_win, p2_win, 1.5 * s) + rl.draw_line_ex(rl.Vector2(x_c - 18.0 * s, y_c - 5.0 * s), rl.Vector2(x_c, y_c - 5.0 * s), 1.5 * s, color) + rl.draw_line_ex(rl.Vector2(x_c - 9.0 * s, y_c - 11.4 * s), rl.Vector2(x_c - 9.0 * s, y_c - 5.0 * s), 1.5 * s, color) + elif key == "first_aid": # First Aid Kit Symbol x_c = x + 30.0 * s