diff --git a/common/libcommon.a b/common/libcommon.a index d2b134531..51a941f94 100644 Binary files a/common/libcommon.a and b/common/libcommon.a differ diff --git a/common/params_keys.h b/common/params_keys.h index 7970a417b..4b1d431b9 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -316,11 +316,13 @@ inline static std::unordered_map keys = { {"LaneChanges", {PERSISTENT, BOOL, "1", "1", 0}}, {"LaneChangeTime", {PERSISTENT, FLOAT, "1.0", "0.0", 1}}, {"LaneDetectionWidth", {PERSISTENT, FLOAT, "0.0", "0.0", 1}}, + {"LaneLinesColor", {PERSISTENT, STRING, "", "", 2}}, {"LaneLinesWidth", {PERSISTENT, FLOAT, "4.0", "2.0", 2}}, {"LastMapsUpdate", {PERSISTENT, STRING, "", ""}}, {"LateralTune", {PERSISTENT, BOOL, "1", "0", 1}}, {"LeadDepartingAlert", {PERSISTENT, BOOL, "0", "0", 0}}, {"LeadDetectionThreshold", {PERSISTENT, INT, "35", "50", 3}}, + {"LeadIndicator", {PERSISTENT, BOOL, "1", "1", 2}}, {"LeadInfo", {PERSISTENT, BOOL, "1", "0", 3}}, {"LKASButtonControl", {PERSISTENT, INT, "5", "0", 2}}, {"LockDoors", {PERSISTENT, BOOL, "1", "0", 0}}, @@ -383,6 +385,8 @@ inline static std::unordered_map keys = { {"OnroadDistanceButtonPressed", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}}, {"openpilotMinutes", {PERSISTENT, INT, "0", "0", 0}}, {"OverpassRequests", {PERSISTENT, JSON, "{}", "{}"}}, + {"PathColor", {PERSISTENT, STRING, "", "", 2}}, + {"PathEdgesColor", {PERSISTENT, STRING, "", "", 2}}, {"PathEdgeWidth", {PERSISTENT, FLOAT, "20.0", "0.0", 2}}, {"PathWidth", {PERSISTENT, FLOAT, "6.1", "5.9", 2}}, {"PauseAOLOnBrake", {PERSISTENT, BOOL, "0", "0", 1}}, diff --git a/common/params_pyx.so b/common/params_pyx.so index c911fe202..9a9be8985 100755 Binary files a/common/params_pyx.so and b/common/params_pyx.so differ diff --git a/panda/board/obj/body_h7.bin.signed b/panda/board/obj/body_h7.bin.signed index 9aaa97141..84992a0f0 100644 Binary files a/panda/board/obj/body_h7.bin.signed and b/panda/board/obj/body_h7.bin.signed differ diff --git a/panda/board/obj/body_h7/bootstub.elf b/panda/board/obj/body_h7/bootstub.elf index 35b532f18..54f8c4f14 100755 Binary files a/panda/board/obj/body_h7/bootstub.elf and b/panda/board/obj/body_h7/bootstub.elf differ diff --git a/panda/board/obj/body_h7/main.bin b/panda/board/obj/body_h7/main.bin index bd2adb0ea..b5ce908ca 100755 Binary files a/panda/board/obj/body_h7/main.bin and b/panda/board/obj/body_h7/main.bin differ diff --git a/panda/board/obj/body_h7/main.elf b/panda/board/obj/body_h7/main.elf index 53b4944a4..9fa7d5f4c 100755 Binary files a/panda/board/obj/body_h7/main.elf and b/panda/board/obj/body_h7/main.elf differ diff --git a/panda/board/obj/bootstub.body_h7.bin b/panda/board/obj/bootstub.body_h7.bin index 38c4d55bc..d4959688e 100755 Binary files a/panda/board/obj/bootstub.body_h7.bin and b/panda/board/obj/bootstub.body_h7.bin differ diff --git a/panda/board/obj/bootstub.panda.bin b/panda/board/obj/bootstub.panda.bin index 30bd18feb..cd877c509 100755 Binary files a/panda/board/obj/bootstub.panda.bin and b/panda/board/obj/bootstub.panda.bin differ diff --git a/panda/board/obj/bootstub.panda_h7.bin b/panda/board/obj/bootstub.panda_h7.bin index 826022cc6..a3a5e532e 100755 Binary files a/panda/board/obj/bootstub.panda_h7.bin and b/panda/board/obj/bootstub.panda_h7.bin differ diff --git a/panda/board/obj/bootstub.panda_h7_remote.bin b/panda/board/obj/bootstub.panda_h7_remote.bin index 826022cc6..a3a5e532e 100755 Binary files a/panda/board/obj/bootstub.panda_h7_remote.bin and b/panda/board/obj/bootstub.panda_h7_remote.bin differ diff --git a/panda/board/obj/bootstub.panda_jungle_h7.bin b/panda/board/obj/bootstub.panda_jungle_h7.bin index ec3222276..bc15b7d93 100755 Binary files a/panda/board/obj/bootstub.panda_jungle_h7.bin and b/panda/board/obj/bootstub.panda_jungle_h7.bin differ diff --git a/panda/board/obj/bootstub.panda_remote.bin b/panda/board/obj/bootstub.panda_remote.bin index 30bd18feb..cd877c509 100755 Binary files a/panda/board/obj/bootstub.panda_remote.bin and b/panda/board/obj/bootstub.panda_remote.bin differ diff --git a/panda/board/obj/gitversion.h b/panda/board/obj/gitversion.h index 3daa0c4ed..91387f4d6 100644 --- a/panda/board/obj/gitversion.h +++ b/panda/board/obj/gitversion.h @@ -1,2 +1,2 @@ extern const uint8_t gitversion[19]; -const uint8_t gitversion[19] = "DEV-ee3f32ad-DEBUG"; +const uint8_t gitversion[19] = "DEV-392d38c5-DEBUG"; diff --git a/panda/board/obj/panda.bin.signed b/panda/board/obj/panda.bin.signed index e429591c5..9770fc36f 100644 Binary files a/panda/board/obj/panda.bin.signed and b/panda/board/obj/panda.bin.signed differ diff --git a/panda/board/obj/panda/bootstub.elf b/panda/board/obj/panda/bootstub.elf index 0bb710754..0b0574466 100755 Binary files a/panda/board/obj/panda/bootstub.elf and b/panda/board/obj/panda/bootstub.elf differ diff --git a/panda/board/obj/panda/main.bin b/panda/board/obj/panda/main.bin index b5edb7eda..a86f6da17 100755 Binary files a/panda/board/obj/panda/main.bin and b/panda/board/obj/panda/main.bin differ diff --git a/panda/board/obj/panda/main.elf b/panda/board/obj/panda/main.elf index 714526d9d..c7c3dc4eb 100755 Binary files a/panda/board/obj/panda/main.elf and b/panda/board/obj/panda/main.elf differ diff --git a/panda/board/obj/panda_h7.bin.signed b/panda/board/obj/panda_h7.bin.signed index c2c645709..5da05efab 100644 Binary files a/panda/board/obj/panda_h7.bin.signed and b/panda/board/obj/panda_h7.bin.signed differ diff --git a/panda/board/obj/panda_h7/bootstub.elf b/panda/board/obj/panda_h7/bootstub.elf index 3915c27b5..965590325 100755 Binary files a/panda/board/obj/panda_h7/bootstub.elf and b/panda/board/obj/panda_h7/bootstub.elf differ diff --git a/panda/board/obj/panda_h7/main.bin b/panda/board/obj/panda_h7/main.bin index 7a15b2d3a..e8c314805 100755 Binary files a/panda/board/obj/panda_h7/main.bin and b/panda/board/obj/panda_h7/main.bin differ diff --git a/panda/board/obj/panda_h7/main.elf b/panda/board/obj/panda_h7/main.elf index 26385f564..3750f4db8 100755 Binary files a/panda/board/obj/panda_h7/main.elf and b/panda/board/obj/panda_h7/main.elf differ diff --git a/panda/board/obj/panda_h7_remote.bin.signed b/panda/board/obj/panda_h7_remote.bin.signed index ff94af5c9..f5cded17b 100644 Binary files a/panda/board/obj/panda_h7_remote.bin.signed and b/panda/board/obj/panda_h7_remote.bin.signed differ diff --git a/panda/board/obj/panda_h7_remote/bootstub.elf b/panda/board/obj/panda_h7_remote/bootstub.elf index fd19f185b..72e4fb21f 100755 Binary files a/panda/board/obj/panda_h7_remote/bootstub.elf and b/panda/board/obj/panda_h7_remote/bootstub.elf differ diff --git a/panda/board/obj/panda_h7_remote/main.bin b/panda/board/obj/panda_h7_remote/main.bin index 3da981632..d809d53cb 100755 Binary files a/panda/board/obj/panda_h7_remote/main.bin and b/panda/board/obj/panda_h7_remote/main.bin differ diff --git a/panda/board/obj/panda_h7_remote/main.elf b/panda/board/obj/panda_h7_remote/main.elf index 31693a1d1..bd5b2f5a6 100755 Binary files a/panda/board/obj/panda_h7_remote/main.elf and b/panda/board/obj/panda_h7_remote/main.elf differ diff --git a/panda/board/obj/panda_jungle_h7.bin.signed b/panda/board/obj/panda_jungle_h7.bin.signed index dba7dacbf..c57111c44 100644 Binary files a/panda/board/obj/panda_jungle_h7.bin.signed and b/panda/board/obj/panda_jungle_h7.bin.signed differ diff --git a/panda/board/obj/panda_jungle_h7/bootstub.elf b/panda/board/obj/panda_jungle_h7/bootstub.elf index 527a9a10f..4b98ea945 100755 Binary files a/panda/board/obj/panda_jungle_h7/bootstub.elf and b/panda/board/obj/panda_jungle_h7/bootstub.elf differ diff --git a/panda/board/obj/panda_jungle_h7/main.bin b/panda/board/obj/panda_jungle_h7/main.bin index 9718c369a..c190b9e7a 100755 Binary files a/panda/board/obj/panda_jungle_h7/main.bin and b/panda/board/obj/panda_jungle_h7/main.bin differ diff --git a/panda/board/obj/panda_jungle_h7/main.elf b/panda/board/obj/panda_jungle_h7/main.elf index 9977cf9ef..dbc7e580f 100755 Binary files a/panda/board/obj/panda_jungle_h7/main.elf and b/panda/board/obj/panda_jungle_h7/main.elf differ diff --git a/panda/board/obj/panda_remote.bin.signed b/panda/board/obj/panda_remote.bin.signed index 418c0e02c..d0d8adbcf 100644 Binary files a/panda/board/obj/panda_remote.bin.signed and b/panda/board/obj/panda_remote.bin.signed differ diff --git a/panda/board/obj/panda_remote/bootstub.elf b/panda/board/obj/panda_remote/bootstub.elf index d51b563db..cd0c29522 100755 Binary files a/panda/board/obj/panda_remote/bootstub.elf and b/panda/board/obj/panda_remote/bootstub.elf differ diff --git a/panda/board/obj/panda_remote/main.bin b/panda/board/obj/panda_remote/main.bin index 913883a67..8ac6ec71e 100755 Binary files a/panda/board/obj/panda_remote/main.bin and b/panda/board/obj/panda_remote/main.bin differ diff --git a/panda/board/obj/panda_remote/main.elf b/panda/board/obj/panda_remote/main.elf index 168eaa045..3a667a3f9 100755 Binary files a/panda/board/obj/panda_remote/main.elf and b/panda/board/obj/panda_remote/main.elf differ diff --git a/panda/board/obj/version b/panda/board/obj/version index 91a116fbf..272535500 100644 --- a/panda/board/obj/version +++ b/panda/board/obj/version @@ -1 +1 @@ -DEV-ee3f32ad-DEBUG \ No newline at end of file +DEV-392d38c5-DEBUG \ No newline at end of file diff --git a/selfdrive/pandad/pandad b/selfdrive/pandad/pandad index 8e6450246..ba0708fe4 100755 Binary files a/selfdrive/pandad/pandad and b/selfdrive/pandad/pandad differ diff --git a/selfdrive/ui/lib/starpilot_theme.py b/selfdrive/ui/lib/starpilot_theme.py index b244b4803..0a07d463d 100644 --- a/selfdrive/ui/lib/starpilot_theme.py +++ b/selfdrive/ui/lib/starpilot_theme.py @@ -26,6 +26,30 @@ _THEME_COLOR_CACHE: dict[str, object] = { } +def _as_color(value: object, fallback: tuple[int, int, int, int] = (255, 255, 255, 255)) -> rl.Color: + if all(hasattr(value, channel) for channel in ("r", "g", "b", "a")): + return rl.Color( + _clamp_channel(getattr(value, "r"), fallback[0]), + _clamp_channel(getattr(value, "g"), fallback[1]), + _clamp_channel(getattr(value, "b"), fallback[2]), + _clamp_channel(getattr(value, "a"), fallback[3]), + ) + + if isinstance(value, (tuple, list)): + items = list(value) + if len(items) == 3: + items.append(fallback[3]) + if len(items) >= 4: + return rl.Color( + _clamp_channel(items[0], fallback[0]), + _clamp_channel(items[1], fallback[1]), + _clamp_channel(items[2], fallback[2]), + _clamp_channel(items[3], fallback[3]), + ) + + return rl.Color(*fallback) + + def _file_stamp(path: Path) -> tuple[str, int | None, int | None]: try: stat = path.stat() @@ -84,9 +108,43 @@ def _load_theme_colors() -> dict[str, rl.Color]: def get_theme_color(key: str, fallback: rl.Color | None = None) -> rl.Color: color = _load_theme_colors().get(key) if color is not None: - return color - return fallback if fallback is not None else rl.WHITE + return _as_color(color) + return _as_color(fallback if fallback is not None else rl.WHITE) + + +def get_param_color(params, key: str, fallback_alpha: int = 255) -> rl.Color | None: + value = params.get(key, encoding="utf-8") if params is not None else None + if not value: + return None + + color = value.strip() + if not color or color.lower() == "stock": + return None + if color.startswith("#"): + color = color[1:] + + if len(color) not in (6, 8): + return None + + try: + red = int(color[0:2], 16) + green = int(color[2:4], 16) + blue = int(color[4:6], 16) + alpha = int(color[6:8], 16) if len(color) == 8 else fallback_alpha + except ValueError: + return None + + return rl.Color(red, green, blue, alpha) + + +def get_visual_color(params, param_key: str, theme_key: str, fallback: rl.Color | None = None) -> rl.Color: + base = _as_color(fallback if fallback is not None else rl.WHITE) + override = get_param_color(params, param_key, base.a) + if override is not None: + return override + return get_theme_color(theme_key, base) def with_alpha(color: rl.Color, alpha: int) -> rl.Color: + color = _as_color(color) return rl.Color(color.r, color.g, color.b, max(0, min(255, int(alpha)))) diff --git a/selfdrive/ui/lib/starpilot_visuals.py b/selfdrive/ui/lib/starpilot_visuals.py index eba96c50d..5e186a22e 100644 --- a/selfdrive/ui/lib/starpilot_visuals.py +++ b/selfdrive/ui/lib/starpilot_visuals.py @@ -14,3 +14,13 @@ def get_border_width(base_width: int, params: Params | None = None) -> int: scale = min(250.0, max(25.0, scale)) return max(1, int(round(base_width * scale / 100.0))) + + +def lead_indicator_enabled(params: Params | None = None) -> bool: + active_params = params if params is not None else Params() + + enabled = not active_params.get_bool("HideLeadMarker", default=False) + if active_params.get("LeadIndicator") is not None: + enabled = enabled and active_params.get_bool("LeadIndicator", default=True) + + return enabled diff --git a/selfdrive/ui/mici/layouts/settings/visuals.py b/selfdrive/ui/mici/layouts/settings/visuals.py index b642dea17..264e6720f 100644 --- a/selfdrive/ui/mici/layouts/settings/visuals.py +++ b/selfdrive/ui/mici/layouts/settings/visuals.py @@ -1,5 +1,6 @@ from openpilot.common.params import Params -from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigParamControl +from openpilot.selfdrive.ui.lib.starpilot_visuals import lead_indicator_enabled +from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigParamControl, BigToggle from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigMultiOptionDialog from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.widgets.scroller import NavScroller @@ -38,12 +39,29 @@ class CameraViewBigButton(BigButton): gui_app.push_widget(dialog) +class LeadIndicatorBigButton(BigToggle): + def __init__(self): + super().__init__("lead indicator") + self.params = Params() + self.refresh() + + def _handle_mouse_release(self, mouse_pos): + super()._handle_mouse_release(mouse_pos) + self.params.put_bool("LeadIndicator", self._checked) + self.params.put_bool("HideLeadMarker", not self._checked) + + def refresh(self): + self.set_checked(lead_indicator_enabled(self.params)) + + class VisualsLayoutMici(NavScroller): def __init__(self): super().__init__() self._camera_view_btn = CameraViewBigButton() self._driver_camera_btn = BigParamControl("driver camera on reverse", "DriverCamera") self._stopped_timer_btn = BigParamControl("stopped timer", "StoppedTimer") + self._rainbow_path_btn = BigParamControl("rainbow road", "RainbowPath") + self._lead_indicator_btn = LeadIndicatorBigButton() self._speed_limit_signs_btn = BigParamControl("speed limit signs", "ShowSpeedLimits") self._slc_confirmation_btn = BigParamControl("confirm new speed limits", "SLCConfirmation") self._slc_confirmation_lower_btn = BigParamControl("confirm lower limits", "SLCConfirmationLower") @@ -53,6 +71,8 @@ class VisualsLayoutMici(NavScroller): self._camera_view_btn, self._driver_camera_btn, self._stopped_timer_btn, + self._rainbow_path_btn, + self._lead_indicator_btn, self._speed_limit_signs_btn, self._slc_confirmation_btn, self._slc_confirmation_lower_btn, @@ -69,6 +89,7 @@ class VisualsLayoutMici(NavScroller): def _refresh(self): self._camera_view_btn.refresh() + self._lead_indicator_btn.refresh() confirmation_enabled = self._slc_confirmation_btn.params.get_bool("SLCConfirmation") self._slc_confirmation_lower_btn.set_visible(confirmation_enabled) self._slc_confirmation_higher_btn.set_visible(confirmation_enabled) diff --git a/selfdrive/ui/mici/onroad/model_renderer.py b/selfdrive/ui/mici/onroad/model_renderer.py index 2f26d248b..84d72298f 100644 --- a/selfdrive/ui/mici/onroad/model_renderer.py +++ b/selfdrive/ui/mici/onroad/model_renderer.py @@ -7,7 +7,8 @@ from openpilot.common.params import Params from openpilot.common.constants import CV from openpilot.common.filter_simple import FirstOrderFilter from openpilot.selfdrive.locationd.calibrationd import HEIGHT_INIT -from openpilot.selfdrive.ui.lib.starpilot_theme import get_theme_color, with_alpha +from openpilot.selfdrive.ui.lib.starpilot_theme import get_param_color, get_theme_color, get_visual_color, with_alpha +from openpilot.selfdrive.ui.lib.starpilot_visuals import lead_indicator_enabled from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus from openpilot.selfdrive.ui.mici.onroad import blend_colors from openpilot.selfdrive.ui.mici.onroad.starpilot_status import get_border_color, get_path_edge_color @@ -18,6 +19,8 @@ from openpilot.system.ui.widgets import Widget CLIP_MARGIN = 500 MIN_DRAW_DISTANCE = 10.0 MAX_DRAW_DISTANCE = 100.0 +RAINBOW_GRADIENT_COLOR_COUNT = 19 +RAINBOW_SCROLL_SPEED_DEG_PER_SEC = 60.0 THROTTLE_COLORS = [ rl.Color(13, 248, 122, 102), # HSLF(148/360, 0.94, 0.51, 0.4) @@ -117,7 +120,7 @@ class ModelRenderer(Widget): model = sm['modelV2'] radar_state = sm['radarState'] if sm.valid['radarState'] else None lead_one = radar_state.leadOne if radar_state else None - render_lead_indicator = self._longitudinal_control and radar_state is not None + render_lead_indicator = self._longitudinal_control and radar_state is not None and lead_indicator_enabled(self._params) # Update model data when needed model_updated = sm.updated['modelV2'] @@ -137,8 +140,8 @@ class ModelRenderer(Widget): self._draw_lane_lines() self._draw_path(sm) - # if render_lead_indicator and radar_state: - # self._draw_lead_indicator() + if render_lead_indicator and radar_state: + self._draw_lead_indicator() def _update_raw_points(self, model): """Update raw 3D points from model data""" @@ -243,10 +246,22 @@ class ModelRenderer(Widget): def _update_experimental_gradient(self): """Pre-calculate experimental mode gradient colors""" - if not (self._experimental_mode or self._params.get_bool("AccelerationPath", default=True)): + use_rainbow = self._params.get_bool("RainbowPath", default=False) + use_acceleration = not use_rainbow and (self._experimental_mode or self._params.get_bool("AccelerationPath", default=True)) + + if not (use_rainbow or use_acceleration): return max_len = min(len(self._path.projected_points) // 2, len(self._acceleration_x)) + if use_rainbow: + gradient_bottom, gradient_top = self._get_visible_gradient_bounds() + self._exp_gradient = self._build_rainbow_gradient(gradient_bottom, gradient_top) + return + + custom_theme_selected = (self._params.get("ColorScheme", encoding="utf-8", default="stock") or "stock").lower() != "stock" + path_color = None + if custom_theme_selected or get_param_color(self._params, "PathColor", 255) is not None: + path_color = get_visual_color(self._params, "PathColor", "Path", rl.Color(48, 255, 156, 255)) segment_colors = [] gradient_stops = [] @@ -262,6 +277,14 @@ class ModelRenderer(Widget): # Calculate color based on acceleration (0 is bottom, 1 is top) lin_grad_point = 1 - (track_y - self._rect.y) / self._rect.height + if path_color is not None and abs(self._acceleration_x[i]) < 0.25: + alpha = np.interp(lin_grad_point, [0.0, 1.0], [path_color.a, path_color.a * 0.10]) + color = with_alpha(path_color, int(alpha)) + gradient_stops.append(lin_grad_point) + segment_colors.append(color) + i += 1 + (1 if (i + 2) < max_len else 0) + continue + # speed up: 120, slow down: 0 path_hue = np.clip(60 + self._acceleration_x[i] * 35, 0, 120) @@ -281,6 +304,40 @@ class ModelRenderer(Widget): # Store the gradient in the path object self._exp_gradient.colors = segment_colors self._exp_gradient.stops = gradient_stops + self._exp_gradient.start = (0.0, 1.0) + self._exp_gradient.end = (0.0, 0.0) + + def _get_visible_gradient_bounds(self) -> tuple[float, float]: + if self._path.projected_points.size == 0: + return 1.0, 0.0 + + polygon_y = self._path.projected_points[:, 1] + visible_track_y = polygon_y[(polygon_y >= self._rect.y) & (polygon_y <= (self._rect.y + self._rect.height))] + + if visible_track_y.size == 0: + return 1.0, 0.0 + + gradient_bottom = np.clip((float(np.max(visible_track_y)) - self._rect.y) / self._rect.height, 0.0, 1.0) + gradient_top = np.clip((float(np.min(visible_track_y)) - self._rect.y) / self._rect.height, 0.0, 1.0) + return float(gradient_bottom), float(gradient_top) + + def _build_rainbow_gradient(self, gradient_bottom: float, gradient_top: float) -> Gradient: + hue_offset = (rl.get_time() * RAINBOW_SCROLL_SPEED_DEG_PER_SEC) % 360.0 + stops = [i / (RAINBOW_GRADIENT_COLOR_COUNT - 1) for i in range(RAINBOW_GRADIENT_COLOR_COUNT)] + colors = [] + + for i, stop in enumerate(stops): + hue_progress = i / RAINBOW_GRADIENT_COLOR_COUNT + path_hue = (hue_progress * 360.0 - hue_offset) % 360.0 + alpha = np.interp(stop, [0.0, 1.0], [0.48, 0.18]) + colors.append(self._hsla_to_color(path_hue / 360.0, 1.0, 0.5, alpha)) + + return Gradient( + start=(0.0, gradient_bottom), + end=(0.0, gradient_top), + colors=colors, + stops=stops, + ) def _update_lead_vehicle(self, d_rel, v_rel, point, rect): speed_buff, lead_buff = 10.0, 40.0 @@ -309,8 +366,12 @@ class ModelRenderer(Widget): def _get_ll_color(self, prob: float, adjacent: bool, left: bool): alpha = np.clip(prob, 0.0, 0.7) if adjacent: - _base_color = get_path_edge_color(ui_state) - color = rl.Color(_base_color.r, _base_color.g, _base_color.b, int(alpha * 255)) + override = get_param_color(self._params, "PathEdgesColor", 255) + if override is not None: + color = with_alpha(override, int(alpha * override.a)) + else: + _base_color = get_path_edge_color(ui_state) + color = rl.Color(_base_color.r, _base_color.g, _base_color.b, int(alpha * 255)) # turn adjacent lls orange if torque is high torque = self._torque_filter.x @@ -322,7 +383,7 @@ class ModelRenderer(Widget): np.interp(abs(torque), [0.6, 0.8], [0.0, 1.0]) ) else: - lane_lines_color = get_theme_color("LaneLines", rl.WHITE) + lane_lines_color = get_visual_color(self._params, "LaneLinesColor", "LaneLines", rl.WHITE) color = with_alpha(lane_lines_color, int(alpha * lane_lines_color.a)) return color @@ -353,17 +414,20 @@ class ModelRenderer(Widget): lateral_ui_active = ui_state.status == UIStatus.ENGAGED or ui_state.always_on_lateral_active allow_throttle = sm['longitudinalPlan'].allowThrottle or not self._longitudinal_control or ui_state.always_on_lateral_active self._blend_filter.update(int(allow_throttle)) + use_rainbow = self._params.get_bool("RainbowPath", default=False) + use_accel_path = not use_rainbow and self._params.get_bool("AccelerationPath", default=True) + path_override = get_param_color(self._params, "PathColor", 255) custom_theme_selected = (self._params.get("ColorScheme", encoding="utf-8", default="stock") or "stock").lower() != "stock" - if self._experimental_mode or self._params.get_bool("AccelerationPath", default=True): + if use_rainbow or self._experimental_mode or use_accel_path: # Draw with acceleration coloring if len(self._exp_gradient.colors) > 1: draw_polygon(self._rect, self._path.projected_points, gradient=self._exp_gradient) else: fallback = get_border_color(ui_state) draw_polygon(self._rect, self._path.projected_points, rl.Color(fallback.r, fallback.g, fallback.b, 90)) - elif custom_theme_selected: - path_color = get_theme_color("Path", rl.Color(48, 255, 156, 255)) + elif path_override is not None or custom_theme_selected: + path_color = get_visual_color(self._params, "PathColor", "Path", rl.Color(48, 255, 156, 255)) gradient = Gradient( start=(0.0, 1.0), end=(0.0, 0.0), @@ -391,12 +455,13 @@ class ModelRenderer(Widget): def _draw_lead_indicator(self): # Draw lead vehicles if available + lead_color = get_theme_color("LeadMarker", rl.Color(201, 34, 49, 255)) for lead in self._lead_vehicles: if not lead.glow or not lead.chevron: continue rl.draw_triangle_fan(lead.glow, len(lead.glow), rl.Color(218, 202, 37, 255)) - rl.draw_triangle_fan(lead.chevron, len(lead.chevron), rl.Color(201, 34, 49, lead.fill_alpha)) + rl.draw_triangle_fan(lead.chevron, len(lead.chevron), with_alpha(lead_color, lead.fill_alpha)) @staticmethod def _get_path_length_idx(pos_x_array: np.ndarray, path_height: float) -> int: diff --git a/selfdrive/ui/onroad/model_renderer.py b/selfdrive/ui/onroad/model_renderer.py index 20fad2640..191ba086f 100644 --- a/selfdrive/ui/onroad/model_renderer.py +++ b/selfdrive/ui/onroad/model_renderer.py @@ -7,7 +7,8 @@ from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.params import Params from openpilot.common.constants import CV from openpilot.selfdrive.locationd.calibrationd import HEIGHT_INIT -from openpilot.selfdrive.ui.lib.starpilot_theme import get_theme_color, with_alpha +from openpilot.selfdrive.ui.lib.starpilot_theme import get_param_color, get_theme_color, get_visual_color, with_alpha +from openpilot.selfdrive.ui.lib.starpilot_visuals import lead_indicator_enabled from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.shader_polygon import draw_polygon, Gradient @@ -16,6 +17,8 @@ from openpilot.system.ui.widgets import Widget CLIP_MARGIN = 500 MIN_DRAW_DISTANCE = 10.0 MAX_DRAW_DISTANCE = 100.0 +RAINBOW_GRADIENT_COLOR_COUNT = 19 +RAINBOW_SCROLL_SPEED_DEG_PER_SEC = 60.0 THROTTLE_COLORS = [ rl.Color(13, 248, 122, 102), # HSLF(148/360, 0.94, 0.51, 0.4) @@ -59,10 +62,6 @@ class ModelRenderer(Widget): self._adjacent_path_vertices = [np.empty((0, 2), dtype=np.float32), np.empty((0, 2), dtype=np.float32)] # Outer path polygon for edge rendering self._track_edge_vertices = np.empty((0, 2), dtype=np.float32) - # Rainbow animation state - self._rainbow_hue_offset = 0.0 - # Cached speed for rainbow animation - self._speed = 0.0 # Initialize ModelPoints objects self._path = ModelPoints() @@ -107,7 +106,6 @@ class ModelRenderer(Widget): # Update state self._experimental_mode = sm['selfdriveState'].experimentalMode - self._speed = sm['carState'].vEgo live_calib = sm['liveCalibration'] self._path_offset_z = live_calib.height[0] if live_calib.height else HEIGHT_INIT[0] @@ -118,7 +116,7 @@ class ModelRenderer(Widget): model = sm['modelV2'] radar_state = sm['radarState'] if sm.valid['radarState'] else None lead_one = radar_state.leadOne if radar_state else None - render_lead_indicator = self._longitudinal_control and radar_state is not None + render_lead_indicator = self._longitudinal_control and radar_state is not None and lead_indicator_enabled(self._params) # Update model data when needed model_updated = sm.updated['modelV2'] @@ -244,13 +242,22 @@ class ModelRenderer(Widget): def _update_experimental_gradient(self): """Pre-calculate experimental mode gradient colors""" - use_acceleration = self._experimental_mode or self._params.get_bool('AccelerationPath', default=True) - use_rainbow = self._params.get_bool('RainbowPath') and not use_acceleration + use_rainbow = self._params.get_bool('RainbowPath', default=False) + use_acceleration = not use_rainbow and (self._experimental_mode or self._params.get_bool('AccelerationPath', default=True)) if not use_acceleration and not use_rainbow: return max_len = min(len(self._path.projected_points) // 2, len(self._acceleration_x)) + if use_rainbow: + gradient_bottom, gradient_top = self._get_visible_gradient_bounds() + self._exp_gradient = self._build_rainbow_gradient(gradient_bottom, gradient_top) + return + + custom_theme_selected = (self._params.get('ColorScheme', encoding='utf-8', default='stock') or 'stock').lower() != 'stock' + path_color = None + if custom_theme_selected or get_param_color(self._params, 'PathColor', 255) is not None: + path_color = get_visual_color(self._params, 'PathColor', 'Path', rl.Color(48, 255, 156, 255)) segment_colors = [] gradient_stops = [] @@ -266,16 +273,9 @@ class ModelRenderer(Widget): # Calculate color based on acceleration (0 is bottom, 1 is top) lin_grad_point = 1 - (track_y - self._rect.y) / self._rect.height - if use_rainbow: - # Rainbow: hue based on gradient position + animated offset - self._rainbow_hue_offset += self._speed * 0.02 - if self._rainbow_hue_offset >= 360.0: - self._rainbow_hue_offset = self._rainbow_hue_offset % 360.0 - path_hue = (lin_grad_point * 120.0 + self._rainbow_hue_offset) % 360.0 - saturation = 1.0 - lightness = 0.5 - alpha = np.interp(lin_grad_point, [0.0, 1.0], [0.5, 0.1]) - color = self._hsla_to_color(path_hue / 360.0, saturation, lightness, alpha) + if path_color is not None and abs(self._acceleration_x[i]) < 0.25: + alpha = np.interp(lin_grad_point, [0.0, 1.0], [path_color.a, path_color.a * 0.10]) + color = with_alpha(path_color, int(alpha)) gradient_stops.append(lin_grad_point) segment_colors.append(color) i += 1 + (1 if (i + 2) < max_len else 0) @@ -305,6 +305,38 @@ class ModelRenderer(Widget): stops=gradient_stops, ) + def _get_visible_gradient_bounds(self) -> tuple[float, float]: + if self._path.projected_points.size == 0: + return 1.0, 0.0 + + polygon_y = self._path.projected_points[:, 1] + visible_track_y = polygon_y[(polygon_y >= self._rect.y) & (polygon_y <= (self._rect.y + self._rect.height))] + + if visible_track_y.size == 0: + return 1.0, 0.0 + + gradient_bottom = np.clip((float(np.max(visible_track_y)) - self._rect.y) / self._rect.height, 0.0, 1.0) + gradient_top = np.clip((float(np.min(visible_track_y)) - self._rect.y) / self._rect.height, 0.0, 1.0) + return float(gradient_bottom), float(gradient_top) + + def _build_rainbow_gradient(self, gradient_bottom: float, gradient_top: float) -> Gradient: + hue_offset = (rl.get_time() * RAINBOW_SCROLL_SPEED_DEG_PER_SEC) % 360.0 + stops = [i / (RAINBOW_GRADIENT_COLOR_COUNT - 1) for i in range(RAINBOW_GRADIENT_COLOR_COUNT)] + colors = [] + + for i, stop in enumerate(stops): + hue_progress = i / RAINBOW_GRADIENT_COLOR_COUNT + path_hue = (hue_progress * 360.0 - hue_offset) % 360.0 + alpha = np.interp(stop, [0.0, 1.0], [0.48, 0.18]) + colors.append(self._hsla_to_color(path_hue / 360.0, 1.0, 0.5, alpha)) + + return Gradient( + start=(0.0, gradient_bottom), + end=(0.0, gradient_top), + colors=colors, + stops=stops, + ) + def _update_lead_vehicle(self, d_rel, v_rel, point, rect): speed_buff, lead_buff = 10.0, 40.0 @@ -331,7 +363,7 @@ class ModelRenderer(Widget): def _draw_lane_lines(self): """Draw lane lines and road edges""" - lane_lines_color = get_theme_color("LaneLines", rl.WHITE) + lane_lines_color = get_visual_color(self._params, "LaneLinesColor", "LaneLines", rl.WHITE) for i, lane_line in enumerate(self._lane_lines): if lane_line.projected_points.size == 0: @@ -357,15 +389,17 @@ class ModelRenderer(Widget): allow_throttle = sm['longitudinalPlan'].allowThrottle or not self._longitudinal_control self._blend_filter.update(int(allow_throttle)) - use_accel_path = self._params.get_bool('AccelerationPath', default=True) + use_rainbow = self._params.get_bool('RainbowPath', default=False) + use_accel_path = not use_rainbow and self._params.get_bool('AccelerationPath', default=True) + path_override = get_param_color(self._params, 'PathColor', 255) custom_theme_selected = (self._params.get('ColorScheme', encoding='utf-8', default='stock') or 'stock').lower() != 'stock' - if self._experimental_mode or use_accel_path: + if use_rainbow or self._experimental_mode or use_accel_path: if len(self._exp_gradient.colors) > 1: draw_polygon(self._rect, self._path.projected_points, gradient=self._exp_gradient) else: draw_polygon(self._rect, self._path.projected_points, rl.Color(255, 255, 255, 30)) - elif custom_theme_selected: - path_color = get_theme_color("Path", rl.Color(48, 255, 156, 255)) + elif path_override is not None or custom_theme_selected: + path_color = get_visual_color(self._params, "PathColor", "Path", rl.Color(48, 255, 156, 255)) gradient = Gradient( start=(0.0, 1.0), end=(0.0, 0.0), diff --git a/selfdrive/ui/onroad/starpilot/path.py b/selfdrive/ui/onroad/starpilot/path.py index dbb6a6e7d..528a6bb61 100644 --- a/selfdrive/ui/onroad/starpilot/path.py +++ b/selfdrive/ui/onroad/starpilot/path.py @@ -6,7 +6,7 @@ import numpy as np import pyray as rl from openpilot.selfdrive.ui.lib.starpilot_state import starpilot_state -from openpilot.selfdrive.ui.lib.starpilot_theme import get_theme_color, with_alpha +from openpilot.selfdrive.ui.lib.starpilot_theme import get_param_color, get_theme_color, with_alpha from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.ui.lib.shader_polygon import draw_polygon, Gradient @@ -177,8 +177,11 @@ def render_path_edges(renderer) -> None: elif ui_state.traffic_mode_enabled: base_color = rl.Color(201, 34, 49, 241) else: + override = get_param_color(renderer._params, "PathEdgesColor", 241) color_scheme = renderer._params.get("ColorScheme", encoding="utf-8", default="stock") - if color_scheme != "stock": + if override is not None: + base_color = rl.Color(override.r, override.g, override.b, 241) + elif color_scheme != "stock": theme_color = get_theme_color("PathEdge", rl.Color(23, 134, 68, 241)) base_color = rl.Color(theme_color.r, theme_color.g, theme_color.b, 241) else: diff --git a/selfdrive/ui/ui b/selfdrive/ui/ui index 892ffe775..dd7b23a6f 100755 Binary files a/selfdrive/ui/ui and b/selfdrive/ui/ui differ diff --git a/starpilot/system/the_pond/assets/components/tools/device_settings_layout.json b/starpilot/system/the_pond/assets/components/tools/device_settings_layout.json index 3542ebee4..63b80393c 100644 --- a/starpilot/system/the_pond/assets/components/tools/device_settings_layout.json +++ b/starpilot/system/the_pond/assets/components/tools/device_settings_layout.json @@ -1608,6 +1608,14 @@ "ui_type": "toggle", "parent_key": "CustomUI" }, + { + "key": "RainbowPath", + "label": "Rainbow Road", + "description": "Color the driving path like a rainbow road.\n\nOn the Python UIs this overrides acceleration and braking path coloring.", + "data_type": "bool", + "ui_type": "toggle", + "parent_key": "CustomUI" + }, { "key": "AdjacentPath", "label": "Adjacent Lanes", diff --git a/system/camerad/camerad b/system/camerad/camerad index b5fce3975..cf748cd03 100755 Binary files a/system/camerad/camerad and b/system/camerad/camerad differ diff --git a/system/loggerd/bootlog b/system/loggerd/bootlog index 74b27dd86..ab9faa056 100755 Binary files a/system/loggerd/bootlog and b/system/loggerd/bootlog differ diff --git a/system/loggerd/encoderd b/system/loggerd/encoderd index 1dbd0b41f..e480adb11 100755 Binary files a/system/loggerd/encoderd and b/system/loggerd/encoderd differ diff --git a/system/loggerd/loggerd b/system/loggerd/loggerd index 0eaafb33b..52b3947f2 100755 Binary files a/system/loggerd/loggerd and b/system/loggerd/loggerd differ diff --git a/tools/StarPilot/feasibleparams.txt b/tools/StarPilot/feasibleparams.txt index 145126842..857e0f32e 100644 --- a/tools/StarPilot/feasibleparams.txt +++ b/tools/StarPilot/feasibleparams.txt @@ -142,11 +142,13 @@ LKASButtonControl LaneChangeTime LaneChanges LaneDetectionWidth +LaneLinesColor LaneLinesWidth LanguageSetting LateralTune LeadDepartingAlert LeadDetectionThreshold +LeadIndicator LeadInfo LiveDelay LiveParameters @@ -197,6 +199,8 @@ OneLaneChange OnroadDistanceButton OpenpilotEnabledToggle OverpassRequests +PathColor +PathEdgesColor PathEdgeWidth PathWidth PauseAOLOnBrake