From f42dbf0c3489ea040963fef7a57b5b83891175ea Mon Sep 17 00:00:00 2001 From: Kumar <36933347+rav4kumar@users.noreply.github.com> Date: Fri, 19 Dec 2025 15:36:43 -0700 Subject: [PATCH] [TIZI/TICI] ui: rainbow path (#1486) * rainbow * use monotonic * sp dir * lint * decouple from stock model renderer * call in ui state directly * it's a boolean * too long * nope --------- Co-authored-by: Jason Wen --- selfdrive/ui/onroad/model_renderer.py | 11 ++- .../ui/sunnypilot/onroad/model_renderer.py | 12 +++ .../ui/sunnypilot/onroad/rainbow_path.py | 78 +++++++++++++++++++ selfdrive/ui/sunnypilot/ui_state.py | 1 + 4 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 selfdrive/ui/sunnypilot/onroad/model_renderer.py create mode 100644 selfdrive/ui/sunnypilot/onroad/rainbow_path.py diff --git a/selfdrive/ui/onroad/model_renderer.py b/selfdrive/ui/onroad/model_renderer.py index b9f601f8f..bf38f8e55 100644 --- a/selfdrive/ui/onroad/model_renderer.py +++ b/selfdrive/ui/onroad/model_renderer.py @@ -11,6 +11,8 @@ from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.shader_polygon import draw_polygon, Gradient from openpilot.system.ui.widgets import Widget +from openpilot.selfdrive.ui.sunnypilot.onroad.model_renderer import ModelRendererSP + CLIP_MARGIN = 500 MIN_DRAW_DISTANCE = 10.0 MAX_DRAW_DISTANCE = 100.0 @@ -41,9 +43,10 @@ class LeadVehicle: fill_alpha: int = 0 -class ModelRenderer(Widget): +class ModelRenderer(Widget, ModelRendererSP): def __init__(self): - super().__init__() + Widget.__init__(self) + ModelRendererSP.__init__(self) self._longitudinal_control = False self._experimental_mode = False self._blend_filter = FirstOrderFilter(1.0, 0.25, 1 / gui_app.target_fps) @@ -281,6 +284,10 @@ class ModelRenderer(Widget): allow_throttle = sm['longitudinalPlan'].allowThrottle or not self._longitudinal_control self._blend_filter.update(int(allow_throttle)) + if ui_state.rainbow_path: + self.rainbow_path.draw_rainbow_path(self._rect, self._path) + return + if self._experimental_mode: # Draw with acceleration coloring if len(self._exp_gradient.colors) > 1: diff --git a/selfdrive/ui/sunnypilot/onroad/model_renderer.py b/selfdrive/ui/sunnypilot/onroad/model_renderer.py new file mode 100644 index 000000000..214a855fb --- /dev/null +++ b/selfdrive/ui/sunnypilot/onroad/model_renderer.py @@ -0,0 +1,12 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from openpilot.selfdrive.ui.sunnypilot.onroad.rainbow_path import RainbowPath + + +class ModelRendererSP: + def __init__(self): + self.rainbow_path = RainbowPath() diff --git a/selfdrive/ui/sunnypilot/onroad/rainbow_path.py b/selfdrive/ui/sunnypilot/onroad/rainbow_path.py new file mode 100644 index 000000000..cd76261f8 --- /dev/null +++ b/selfdrive/ui/sunnypilot/onroad/rainbow_path.py @@ -0,0 +1,78 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +import time +import colorsys +import pyray as rl +from openpilot.system.ui.lib.shader_polygon import draw_polygon, Gradient + + +class RainbowPath: + DEFAULT_NUM_SEGMENTS = 8 + DEFAULT_SPEED = 50.0 # degrees per second + DEFAULT_SATURATION = 0.9 + DEFAULT_LIGHTNESS = 0.6 + BASE_ALPHA = 0.8 + ALPHA_FADE = 0.3 # Alpha reduction from bottom to top + + def __init__(self, num_segments: int = None, speed: float = None, saturation: float = None, lightness: float = None): + self.num_segments = num_segments if num_segments is not None else self.DEFAULT_NUM_SEGMENTS + self.speed = speed if speed is not None else self.DEFAULT_SPEED + self.saturation = saturation if saturation is not None else self.DEFAULT_SATURATION + self.lightness = lightness if lightness is not None else self.DEFAULT_LIGHTNESS + + def set_speed(self, speed: float): + self.speed = speed + + def set_num_segments(self, num_segments: int): + self.num_segments = num_segments + + def set_saturation(self, saturation: float): + self.saturation = max(0.0, min(1.0, saturation)) + + def set_lightness(self, lightness: float): + self.lightness = max(0.0, min(1.0, lightness)) + + def get_gradient(self) -> Gradient: + time_offset = time.monotonic() + hue_offset = (time_offset * self.speed) % 360.0 + + segment_colors = [] + gradient_stops = [] + + for i in range(self.num_segments): + position = i / (self.num_segments - 1) + hue = (hue_offset + position * 360.0) % 360.0 + alpha = self.BASE_ALPHA * (1.0 - position * self.ALPHA_FADE) + color = self._hsla_to_color( + hue / 360.0, + self.saturation, + self.lightness, + alpha + ) + gradient_stops.append(position) + segment_colors.append(color) + + return Gradient( + start=(0.0, 1.0), # Bottom of path + end=(0.0, 0.0), # Top of path + colors=segment_colors, + stops=gradient_stops, + ) + + @staticmethod + def _hsla_to_color(h: float, s: float, l: float, a: float) -> rl.Color: + rgb = colorsys.hls_to_rgb(h, l, s) + return rl.Color( + int(rgb[0] * 255), + int(rgb[1] * 255), + int(rgb[2] * 255), + int(a * 255) + ) + + def draw_rainbow_path(self, rect, path): + gradient = self.get_gradient() + draw_polygon(rect, path.projected_points, gradient=gradient) diff --git a/selfdrive/ui/sunnypilot/ui_state.py b/selfdrive/ui/sunnypilot/ui_state.py index aca0644c1..2e817090b 100644 --- a/selfdrive/ui/sunnypilot/ui_state.py +++ b/selfdrive/ui/sunnypilot/ui_state.py @@ -68,3 +68,4 @@ class UIStateSP: self.CP_SP = messaging.log_from_bytes(CP_SP_bytes, custom.CarParamsSP) self.sunnylink_enabled = self.params.get_bool("SunnylinkEnabled") self.developer_ui = self.params.get("DevUIInfo") + self.rainbow_path = self.params.get_bool("RainbowMode")