BigUI WIP: Adj Path Bugfixes

This commit is contained in:
firestarsdog
2026-04-03 09:57:23 -04:00
parent 44bd84448c
commit 65b81c5710
4 changed files with 506 additions and 9 deletions
@@ -212,6 +212,15 @@ class StarPilotVisualWidgetsLayout(StarPilotPanel):
"icon": "toggle_icons/icon_road.png",
"color": "#8B5CF6",
},
{
"title": tr_noop("Adjacent Lane Metrics"),
"type": "toggle",
"key": "AdjacentPathMetrics",
"get_state": lambda: self._params.get_bool("AdjacentPathMetrics"),
"set_state": lambda s: self._params.put_bool("AdjacentPathMetrics", s),
"icon": "toggle_icons/icon_road.png",
"color": "#8B5CF6",
},
{
"title": tr_noop("Blind Spot Path"),
"type": "toggle",
@@ -337,6 +346,15 @@ class StarPilotModelUILayout(StarPilotPanel):
"icon": "toggle_icons/icon_road.png",
"color": "#8B5CF6",
},
{
"title": tr_noop("Lane Line Color"),
"type": "value",
"key": "LaneLinesColor",
"get_value": lambda: self._get_color_display("LaneLinesColor"),
"on_click": lambda: self._show_color_selector("LaneLinesColor"),
"icon": "toggle_icons/icon_road.png",
"color": "#8B5CF6",
},
{
"title": tr_noop("Path Edge Width"),
"type": "value",
@@ -346,6 +364,15 @@ class StarPilotModelUILayout(StarPilotPanel):
"icon": "toggle_icons/icon_road.png",
"color": "#8B5CF6",
},
{
"title": tr_noop("Path Edge Color"),
"type": "value",
"key": "PathEdgesColor",
"get_value": lambda: self._get_color_display("PathEdgesColor"),
"on_click": lambda: self._show_color_selector("PathEdgesColor"),
"icon": "toggle_icons/icon_road.png",
"color": "#8B5CF6",
},
{
"title": tr_noop("Path Width"),
"type": "value",
@@ -355,6 +382,15 @@ class StarPilotModelUILayout(StarPilotPanel):
"icon": "toggle_icons/icon_road.png",
"color": "#8B5CF6",
},
{
"title": tr_noop("Path Color"),
"type": "value",
"key": "PathColor",
"get_value": lambda: self._get_color_display("PathColor"),
"on_click": lambda: self._show_color_selector("PathColor"),
"icon": "toggle_icons/icon_road.png",
"color": "#8B5CF6",
},
{
"title": tr_noop("Road Edge Width"),
"type": "value",
@@ -423,6 +459,26 @@ class StarPilotModelUILayout(StarPilotPanel):
gui_app.set_modal_overlay(AetherSliderDialog(tr(key), min_v, max_v, step, current, on_close, unit=unit, color="#8B5CF6"))
def _get_color_display(self, key):
val = self._params.get(key, encoding='utf-8') or ""
if not val:
return "Stock"
return val.upper()
def _show_color_selector(self, key):
presets = ["Stock", "#FFFFFF", "#178644", "#3B82F6", "#E63956", "#8B5CF6", "#F59E0B"]
current = self._params.get(key, encoding='utf-8') or "Stock"
def on_select(res, val):
if res == DialogResult.CONFIRM:
if val == "Stock":
self._params.remove(key)
else:
self._params.put(key, val)
self._rebuild_grid()
gui_app.set_modal_overlay(SelectionDialog(tr(key), presets, current, on_close=on_select))
class StarPilotNavigationVisualsLayout(StarPilotPanel):
def __init__(self):
+208 -9
View File
@@ -6,7 +6,7 @@ from dataclasses import dataclass, field
from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.common.params import Params
from openpilot.selfdrive.locationd.calibrationd import HEIGHT_INIT
from openpilot.selfdrive.ui.ui_state import ui_state
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
from openpilot.system.ui.widgets import Widget
@@ -53,6 +53,15 @@ class ModelRenderer(Widget):
self._lead_vehicles = [LeadVehicle(), LeadVehicle()]
self._path_offset_z = HEIGHT_INIT[0]
# Adjacent path vertices (left, right)
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()
self._lane_lines = [ModelPoints() for _ in range(4)]
@@ -72,7 +81,8 @@ class ModelRenderer(Widget):
)
# Get longitudinal control setting from car parameters
if car_params := Params().get("CarParams"):
self._params = Params()
if car_params := self._params.get("CarParams"):
cp = messaging.log_from_bytes(car_params, car.CarParams)
self._longitudinal_control = cp.openpilotLongitudinalControl
@@ -95,6 +105,7 @@ 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]
@@ -174,18 +185,44 @@ class ModelRenderer(Widget):
def _update_model(self, lead, path_x_array):
"""Update model visualization data based on model message"""
path_width = self._params.get_float('PathWidth') # stored in feet, convert to meters
if path_width <= 0:
path_width = 0.9 # default in meters
lane_line_width = self._params.get_int('LaneLinesWidth') # stored in inches, convert to meters
if lane_line_width <= 0:
lane_line_width = int(0.025 / 0.0254) # default ~1 inch
lane_line_width_m = lane_line_width * 0.0254
road_edge_width = self._params.get_int('RoadEdgesWidth') # stored in inches
if road_edge_width <= 0:
road_edge_width = int(0.025 / 0.0254)
road_edge_width_m = road_edge_width * 0.0254
path_edge_width_pct = self._params.get_int('PathEdgeWidth') / 100.0
# Dynamic path width
if self._params.get_bool('DynamicPathWidth'):
status = ui_state.status
if status == UIStatus.ENGAGED:
path_width *= 1.0
elif status == UIStatus.ALWAYS_ON_LATERAL:
path_width *= 0.75
else:
path_width *= 0.50
max_distance = np.clip(path_x_array[-1], MIN_DRAW_DISTANCE, MAX_DRAW_DISTANCE)
max_idx = self._get_path_length_idx(self._lane_lines[0].raw_points[:, 0], max_distance)
# Update lane lines using raw points
for i, lane_line in enumerate(self._lane_lines):
lane_line.projected_points = self._map_line_to_polygon(
lane_line.raw_points, 0.025 * self._lane_line_probs[i], 0.0, max_idx, max_distance
lane_line.raw_points, lane_line_width_m * self._lane_line_probs[i], 0.0, max_idx, max_distance
)
# Update road edges using raw points
for road_edge in self._road_edges:
road_edge.projected_points = self._map_line_to_polygon(road_edge.raw_points, 0.025, 0.0, max_idx, max_distance)
road_edge.projected_points = self._map_line_to_polygon(road_edge.raw_points, road_edge_width_m, 0.0, max_idx, max_distance)
# Update path using raw points
if lead and lead.status:
@@ -194,14 +231,25 @@ class ModelRenderer(Widget):
max_idx = self._get_path_length_idx(path_x_array, max_distance)
self._path.projected_points = self._map_line_to_polygon(
self._path.raw_points, 0.9, self._path_offset_z, max_idx, max_distance, allow_invert=False
self._path.raw_points, path_width * (1 - path_edge_width_pct), self._path_offset_z, max_idx, max_distance, allow_invert=False
)
# Compute track edge vertices (outer path polygon at full width)
self._track_edge_vertices = self._map_line_to_polygon(
self._path.raw_points, path_width, self._path_offset_z, max_idx, max_distance, allow_invert=False
)
# Compute adjacent path vertices
self._update_adjacent_paths(max_idx, max_distance)
self._update_experimental_gradient()
def _update_experimental_gradient(self):
"""Pre-calculate experimental mode gradient colors"""
if not self._experimental_mode:
use_acceleration = self._experimental_mode or self._params.get_bool('AccelerationPath')
use_rainbow = self._params.get_bool('RainbowPath') and not use_acceleration
if not use_acceleration and not use_rainbow:
return
max_len = min(len(self._path.projected_points) // 2, len(self._acceleration_x))
@@ -220,6 +268,21 @@ 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)
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)
@@ -270,12 +333,19 @@ class ModelRenderer(Widget):
def _draw_lane_lines(self):
"""Draw lane lines and road edges"""
color_scheme = self._params.get('ColorScheme', encoding='utf-8') or 'stock'
lane_lines_color = self._params.get('LaneLinesColor', encoding='utf-8')
for i, lane_line in enumerate(self._lane_lines):
if lane_line.projected_points.size == 0:
continue
alpha = np.clip(self._lane_line_probs[i], 0.0, 0.7)
color = rl.Color(255, 255, 255, int(alpha * 255))
if color_scheme != 'stock' and lane_lines_color:
base_color = self._hex_to_color(lane_lines_color)
color = rl.Color(base_color.r, base_color.g, base_color.b, int(alpha * 255))
else:
color = rl.Color(255, 255, 255, int(alpha * 255))
draw_polygon(self._rect, lane_line.projected_points, color)
for i, road_edge in enumerate(self._road_edges):
@@ -294,8 +364,8 @@ class ModelRenderer(Widget):
allow_throttle = sm['longitudinalPlan'].allowThrottle or not self._longitudinal_control
self._blend_filter.update(int(allow_throttle))
if self._experimental_mode:
# Draw with acceleration coloring
use_accel_path = self._params.get_bool('AccelerationPath')
if 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:
@@ -321,6 +391,135 @@ class ModelRenderer(Widget):
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))
def _update_adjacent_paths(self, max_idx: int, max_distance: float):
"""Compute adjacent lane path polygons by averaging lane line pairs."""
sm = ui_state.sm
if sm.recv_frame.get("starpilotPlan", 0) < ui_state.started_frame:
self._adjacent_path_vertices = [np.empty((0, 2), dtype=np.float32), np.empty((0, 2), dtype=np.float32)]
return
plan = sm["starpilotPlan"]
lane_width_left = float(plan.laneWidthLeft) if plan.laneWidthLeft > 0 else 0.0
lane_width_right = float(plan.laneWidthRight) if plan.laneWidthRight > 0 else 0.0
if lane_width_left <= 0 or lane_width_right <= 0:
self._adjacent_path_vertices = [np.empty((0, 2), dtype=np.float32), np.empty((0, 2), dtype=np.float32)]
return
# Left adjacent: average of lane_lines[0] and lane_lines[1]
self._adjacent_path_vertices[0] = self._map_averaged_line_to_polygon(
self._lane_lines[0].raw_points, self._lane_lines[1].raw_points,
lane_width_left / 2.0, 0.0, max_idx, max_distance
)
# Right adjacent: average of lane_lines[2] and lane_lines[3]
self._adjacent_path_vertices[1] = self._map_averaged_line_to_polygon(
self._lane_lines[2].raw_points, self._lane_lines[3].raw_points,
lane_width_right / 2.0, 0.0, max_idx, max_distance
)
def _map_averaged_line_to_polygon(self, line1: np.ndarray, line2: np.ndarray, y_off: float, z_off: float, max_idx: int, max_distance: float) -> np.ndarray:
"""Convert averaged 3D line pair to 2D polygon for adjacent path rendering.
Averages the Y coordinates of two lane lines, uses X from line1, Z from line1.
Then projects to screen space like _map_line_to_polygon.
Grounds the path to the bottom of the screen.
"""
if line1.shape[0] == 0 or line2.shape[0] == 0:
return np.empty((0, 2), dtype=np.float32)
# Use the shorter of the two lines
min_len = min(line1.shape[0], line2.shape[0], max_idx + 1)
if min_len == 0:
return np.empty((0, 2), dtype=np.float32)
# Average Y, use X from line1, Z from line1
avg_x = line1[:min_len, 0]
avg_y = (line1[:min_len, 1] + line2[:min_len, 1]) / 2.0
avg_z = line1[:min_len, 2]
# Filter non-negative x
valid = avg_x >= 0
if not np.any(valid):
return np.empty((0, 2), dtype=np.float32)
points = np.column_stack([avg_x[valid], avg_y[valid], avg_z[valid]])
N = points.shape[0]
# Generate left and right 3D points
offsets = np.array([[0, -y_off, z_off], [0, y_off, z_off]], dtype=np.float32)
points_3d = points[None, :, :] + offsets[:, None, :]
points_3d = points_3d.reshape(2 * N, 3)
# Transform
proj = self._car_space_transform @ points_3d.T
proj = proj.reshape(3, 2, N)
left_proj = proj[:, 0, :]
right_proj = proj[:, 1, :]
# Filter valid z
valid_proj = (np.abs(left_proj[2]) >= 1e-6) & (np.abs(right_proj[2]) >= 1e-6)
if not np.any(valid_proj):
return np.empty((0, 2), dtype=np.float32)
left_screen = left_proj[:2, valid_proj] / left_proj[2, valid_proj][None, :]
right_screen = right_proj[:2, valid_proj] / right_proj[2, valid_proj][None, :]
# Clip region filter
clip = self._clip_region
x_min, x_max = clip.x, clip.x + clip.width
y_min, y_max = clip.y, clip.y + clip.height
left_in_clip = (left_screen[0] >= x_min) & (left_screen[0] <= x_max) & (left_screen[1] >= y_min) & (left_screen[1] <= y_max)
right_in_clip = (right_screen[0] >= x_min) & (right_screen[0] <= x_max) & (right_screen[1] >= y_min) & (right_screen[1] <= y_max)
both_in_clip = left_in_clip & right_in_clip
if not np.any(both_in_clip):
return np.empty((0, 2), dtype=np.float32)
left_screen = left_screen[:, both_in_clip]
right_screen = right_screen[:, both_in_clip]
# No inversion check for adjacent paths (allow_invert=True equivalent)
polygon = np.vstack((left_screen.T, right_screen[:, ::-1].T)).astype(np.float32)
# Ground to bottom of screen
if polygon.shape[0] >= 4:
height = self._rect.y + self._rect.height
# Extend left_near (index 0) using slope from left_near → left_2nd
p0 = polygon[0]
p1 = polygon[1]
dy = p0[1] - p1[1]
if abs(dy) > 0.1:
slope = (p0[0] - p1[0]) / dy
p0[0] = p0[0] + (height - p0[1]) * slope
p0[1] = height
# Extend right_near (index -1) using slope from right_near → right_2nd
p0 = polygon[-1]
p1 = polygon[-2]
dy = p0[1] - p1[1]
if abs(dy) > 0.1:
slope = (p0[0] - p1[0]) / dy
p0[0] = p0[0] + (height - p0[1]) * slope
p0[1] = height
return polygon
@staticmethod
def _hex_to_color(hex_str: str) -> rl.Color:
"""Convert hex color string to rl.Color."""
hex_str = hex_str.lstrip('#')
if len(hex_str) == 6:
return rl.Color(
int(hex_str[0:2], 16),
int(hex_str[2:4], 16),
int(hex_str[4:6], 16),
255
)
return rl.Color(255, 255, 255, 255)
@staticmethod
def _get_path_length_idx(pos_x_array: np.ndarray, path_distance: float) -> int:
"""Get the index corresponding to the given path distance"""
+219
View File
@@ -0,0 +1,219 @@
from __future__ import annotations
import colorsys
import numpy as np
import pyray as rl
from openpilot.selfdrive.ui.lib.starpilot_state import starpilot_state
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.system.ui.lib.shader_polygon import draw_polygon, Gradient
_METRICS_FONT = None
_METRICS_FONT_SIZE = 45
def _get_metrics_font():
global _METRICS_FONT
if _METRICS_FONT is None or _METRICS_FONT.baseSize != _METRICS_FONT_SIZE:
if _METRICS_FONT is not None:
rl.unload_font(_METRICS_FONT)
_METRICS_FONT = rl.load_font_ex("fonts/Inter-SemiBold.ttf", _METRICS_FONT_SIZE, None, 256)
return _METRICS_FONT
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 _make_gradient(hue: float) -> Gradient:
return Gradient(
start=(0.0, 1.0),
end=(0.0, 0.0),
colors=[
_hsla_to_color(hue, 0.75, 0.5, 0.4),
_hsla_to_color(hue, 0.75, 0.5, 0.35),
_hsla_to_color(hue, 0.75, 0.5, 0.0),
],
stops=[0.0, 0.5, 1.0],
)
def _draw_text_with_outline(text: str, x: float, y: float, font, font_size: int):
pos = rl.Vector2(x, y)
rl.draw_text_ex(font, text, rl.Vector2(pos.x - 1, pos.y - 1), font_size, 0, rl.BLACK)
rl.draw_text_ex(font, text, rl.Vector2(pos.x + 1, pos.y - 1), font_size, 0, rl.BLACK)
rl.draw_text_ex(font, text, rl.Vector2(pos.x - 1, pos.y + 1), font_size, 0, rl.BLACK)
rl.draw_text_ex(font, text, rl.Vector2(pos.x + 1, pos.y + 1), font_size, 0, rl.BLACK)
rl.draw_text_ex(font, text, pos, font_size, 0, rl.WHITE)
def render_adjacent_paths(renderer) -> None:
"""Draw left and right adjacent lane paths with gradient coloring.
Qt reference: paintAdjacentPaths in starpilot_annotated_camera.cc:337-392
Gradient color is based on lane width ratio compared to LaneDetectionWidth toggle.
hue = (ratio^2) * (120/360), where ratio = laneWidth / lane_detection_width
Gradient: HSL(hue, 0.75, 0.5, 0.4) at bottom, 0.35 at mid, 0.0 at top.
If AdjacentPathMetrics is enabled, show lane width text on each path.
"""
sm = ui_state.sm
if sm.recv_frame["starpilotPlan"] < ui_state.started_frame:
return
plan = sm["starpilotPlan"]
lane_width_left = float(plan.laneWidthLeft)
lane_width_right = float(plan.laneWidthRight)
lane_detection_width = renderer._params.get_float("LaneDetectionWidth") if renderer._params.get("LaneDetectionWidth") else 3.5
vertices = renderer._adjacent_path_vertices
if not vertices or len(vertices) < 2:
return
show_metrics = renderer._params.get_bool("AdjacentPathMetrics")
rect = renderer._rect
distance_conversion = 3.28084 if not ui_state.is_metric else 1.0
unit = "ft" if not ui_state.is_metric else "m"
font = _get_metrics_font()
for i, (verts, lane_width) in enumerate(zip(vertices, [lane_width_left, lane_width_right], strict=True)):
if verts.size < 4 or lane_width == 0.0:
continue
ratio = np.clip(lane_width / lane_detection_width, 0.0, 1.0)
hue = (ratio * ratio) * (120.0 / 360.0)
gradient = _make_gradient(hue)
draw_polygon(rect, verts, gradient=gradient)
if show_metrics:
is_left = i == 0
mid_index = len(verts) // 2
anchor_idx = mid_index // 2 if is_left else mid_index + (len(verts) - mid_index) // 2
anchor = verts[anchor_idx]
text = f"{lane_width * distance_conversion:.2f}{unit}"
text_width = rl.measure_text_ex(font, text, _METRICS_FONT_SIZE, 0).x
text_height = rl.measure_text_ex(font, text, _METRICS_FONT_SIZE, 0).y
text_x = anchor[0] - text_width if is_left else anchor[0]
text_y = anchor[1] - text_height / 2 + text_height * 0.75
_draw_text_with_outline(text, text_x, text_y, font, _METRICS_FONT_SIZE)
def render_blind_spot_path(renderer) -> None:
"""Draw red gradient overlay on adjacent lanes when vehicle detected in blind spot.
Qt reference: paintBlindSpotPath in starpilot_annotated_camera.cc:394-411
Red gradient: HSL(0, 0.75, 0.5, 0.4) at bottom, 0.35 at mid, 0.0 at top.
Only draws on the side where blind spot is active.
"""
sm = ui_state.sm
if sm.recv_frame["carState"] < ui_state.started_frame:
return
if not renderer._params.get_bool("BlindSpotPath"):
return
car_state = sm["carState"]
blindspot_left = bool(car_state.leftBlindspot)
blindspot_right = bool(car_state.rightBlindspot)
if not blindspot_left and not blindspot_right:
return
vertices = renderer._adjacent_path_vertices
if not vertices or len(vertices) < 2:
return
gradient = _make_gradient(0.0)
rect = renderer._rect
if blindspot_left and vertices[0].size >= 4:
draw_polygon(rect, vertices[0], gradient=gradient)
if blindspot_right and vertices[1].size >= 4:
draw_polygon(rect, vertices[1], gradient=gradient)
def render_path_edges(renderer) -> None:
"""Draw colored edge strips along the driving path.
Qt reference: paintPathEdges in starpilot_annotated_camera.cc:732-769
Path edges are the area between track_edge_vertices (outer) and track_vertices (inner).
Color is based on current mode:
- If switchback_mode_enabled: use STATUS_SWITCHBACK_MODE_ENABLED color
- If always_on_lateral_active: use STATUS_ALWAYS_ON_LATERAL_ACTIVE color
- If conditional_status == 1: use STATUS_CEM_DISABLED color
- If experimental_mode: use STATUS_EXPERIMENTAL_MODE_ENABLED color
- If traffic_mode_enabled: use STATUS_TRAFFIC_MODE_ENABLED color
- If color_scheme != "stock" and path_edges_color is set: use that color
- Else: stock green gradient HSL(148/360, 0.94, 0.41, 0.4) → HSL(112/360, 1.0, 0.54, 0.35) → transparent
The path_edges_color param is a hex string like "#178644".
"""
outer = renderer._track_edge_vertices
inner = renderer._path.projected_points
if outer.size < 4 or inner.size < 4:
return
edge_strip = np.vstack([outer, inner[::-1]])
if ui_state.switchback_mode_enabled:
base_color = rl.Color(139, 108, 197, 241)
elif ui_state.always_on_lateral_active:
base_color = rl.Color(10, 186, 181, 241)
elif ui_state.conditional_status == 1:
base_color = rl.Color(255, 255, 0, 241)
elif renderer._experimental_mode:
base_color = rl.Color(218, 111, 37, 241)
elif ui_state.traffic_mode_enabled:
base_color = rl.Color(201, 34, 49, 241)
else:
color_scheme = renderer._params.get("ColorScheme")
if color_scheme and color_scheme.decode("utf-8") != "stock":
path_edges_color = renderer._params.get("PathEdgesColor")
if path_edges_color:
hex_str = path_edges_color.decode("utf-8").lstrip("#")
if len(hex_str) == 6:
r = int(hex_str[0:2], 16)
g = int(hex_str[2:4], 16)
b = int(hex_str[4:6], 16)
base_color = rl.Color(r, g, b, 241)
else:
base_color = rl.Color(23, 134, 68, 241)
else:
base_color = rl.Color(23, 134, 68, 241)
else:
base_color = None
if base_color is not None:
gradient = Gradient(
start=(0.0, 1.0),
end=(0.0, 0.0),
colors=[
rl.Color(base_color.r, base_color.g, base_color.b, int(255 * 0.4)),
rl.Color(base_color.r, base_color.g, base_color.b, int(255 * 0.35)),
rl.Color(base_color.r, base_color.g, base_color.b, 0),
],
stops=[0.0, 0.5, 1.0],
)
else:
gradient = Gradient(
start=(0.0, 1.0),
end=(0.0, 0.0),
colors=[
_hsla_to_color(148.0 / 360.0, 0.94, 0.41, 0.4),
_hsla_to_color(112.0 / 360.0, 1.0, 0.54, 0.35),
_hsla_to_color(112.0 / 360.0, 1.0, 0.54, 0.0),
],
stops=[0.0, 0.5, 1.0],
)
draw_polygon(renderer._rect, edge_strip, gradient=gradient)
@@ -4,6 +4,7 @@ from openpilot.common.params import Params
from openpilot.selfdrive.ui import UI_BORDER_SIZE
from openpilot.selfdrive.ui.onroad.augmented_road_view import AugmentedRoadView, BORDER_COLORS
from openpilot.selfdrive.ui.onroad.starpilot.curve_speed_border import render_glow, render_filament
from openpilot.selfdrive.ui.onroad.starpilot.path import render_adjacent_paths, render_blind_spot_path, render_path_edges
from openpilot.selfdrive.ui.onroad.starpilot.personality_button import PersonalityButton, BTN_SIZE
from openpilot.selfdrive.ui.onroad.starpilot.slc_speed_limit import (
render_speed_limit, handle_slc_click, SET_SPEED_X_OFFSET, SET_SPEED_Y_OFFSET,
@@ -30,6 +31,7 @@ class StarPilotOnroadView(AugmentedRoadView):
self._render_slc(rect)
self._render_overlays()
self._render_path_features(rect)
def _render_slc(self, rect: rl.Rectangle):
content_rect = rl.Rectangle(
@@ -47,6 +49,27 @@ class StarPilotOnroadView(AugmentedRoadView):
self._position_personality_button()
self._personality_button.render()
def _render_path_features(self, rect: rl.Rectangle):
"""Render path-related features (adjacent paths, blind spot, path edges)."""
mr = self.model_renderer
# Only render if we have path data
if not mr._path.projected_points.size:
return
# Path edges (always rendered if track_edge_vertices exist)
if mr._track_edge_vertices.size >= 4:
render_path_edges(mr)
# Adjacent paths or blind spot path (mutually exclusive, matching Qt behavior)
adjacent_enabled = self._params.get_bool("AdjacentPath")
blind_spot_enabled = self._params.get_bool("BlindSpotPath")
if adjacent_enabled and mr._adjacent_path_vertices[0].size >= 4:
render_adjacent_paths(mr)
elif blind_spot_enabled and mr._adjacent_path_vertices[0].size >= 4:
render_blind_spot_path(mr)
def _position_personality_button(self):
dm = self.driver_state_renderer
toggle_on = self._params.get_bool("OnroadDistanceButton")