visionpulse

This commit is contained in:
firestar5683
2026-07-03 20:24:09 -05:00
parent 641f1dc238
commit ac4ffb4a7e
4 changed files with 87 additions and 9 deletions
+7 -1
View File
@@ -551,4 +551,10 @@ if [[ "${has_raylib}" == "1" ]]; then
launch_control_bar
fi
wait "${UI_PIDS[@]}"
ui_status=0
for pid in "${UI_PIDS[@]-}"; do
if [[ -n "${pid}" ]]; then
wait "${pid}" || ui_status=$?
fi
done
exit "${ui_status}"
+45 -5
View File
@@ -1,3 +1,5 @@
import math
import pyray as rl
from dataclasses import dataclass
from openpilot.common.constants import CV
@@ -30,6 +32,8 @@ SPEED_LIMIT_PROMPT_US_SIGN_WIDTH = 132
SPEED_LIMIT_PROMPT_US_SIGN_HEIGHT = 150
SPEED_LIMIT_PROMPT_EU_SIGN_SIZE = 148
SPEED_LIMIT_PROMPT_CENTER_OFFSET_X = -26
VISION_SPEED_LIMIT_PULSE_SECONDS = 1.0
VISION_SPEED_LIMIT_PULSE_COLOR = rl.Color(188, 132, 255, 255)
@dataclass(frozen=True)
@@ -124,6 +128,9 @@ class HudRenderer(Widget):
self._show_speed_limit_offset: bool = False
self._speed_limit_overridden: bool = False
self._pending_speed_limit: float = 0.0
self._vision_speed_limit_active: bool = False
self._last_vision_speed_limit: float = 0.0
self._vision_speed_limit_pulse_start: float = -VISION_SPEED_LIMIT_PULSE_SECONDS
self._prompt_visible: bool = False
self._prompt_card_rect: rl.Rectangle = rl.Rectangle(0, 0, 0, 0)
self._prompt_sign_rect: rl.Rectangle = rl.Rectangle(0, 0, 0, 0)
@@ -215,6 +222,13 @@ class HudRenderer(Widget):
primary_priority=primary_priority,
secondary_priority=secondary_priority,
)
vision_speed_limit_active = starpilot_plan.slcSpeedLimitSource == "Vision" and resolved_speed_limit > 0.0
vision_speed_limit_changed = abs(resolved_speed_limit - self._last_vision_speed_limit) >= 0.1
if vision_speed_limit_active and (not self._vision_speed_limit_active or vision_speed_limit_changed):
self._vision_speed_limit_pulse_start = rl.get_time()
self._vision_speed_limit_active = vision_speed_limit_active
self._last_vision_speed_limit = resolved_speed_limit if vision_speed_limit_active else 0.0
display_speed_limit = starpilot_plan.slcOverriddenSpeed if starpilot_plan.slcOverriddenSpeed > 0 else resolved_speed_limit
self._speed_limit = max(0.0, display_speed_limit * speed_conversion)
self._speed_limit_offset = starpilot_plan.slcSpeedLimitOffset * speed_conversion
@@ -228,6 +242,8 @@ class HudRenderer(Widget):
self._show_speed_limit_offset = False
self._speed_limit_overridden = False
self._pending_speed_limit = 0.0
self._vision_speed_limit_active = False
self._last_vision_speed_limit = 0.0
self._prompt_visible = self._pending_speed_limit > 0
else:
self._show_speed_limit = False
@@ -236,6 +252,8 @@ class HudRenderer(Widget):
self._show_speed_limit_offset = False
self._speed_limit_overridden = False
self._pending_speed_limit = 0.0
self._vision_speed_limit_active = False
self._last_vision_speed_limit = 0.0
self._prompt_visible = False
def prepare(self, rect: rl.Rectangle) -> None:
@@ -369,9 +387,11 @@ class HudRenderer(Widget):
footer_text: str = "",
footer_font_size: int = 0,
footer_top: float = 0.0,
border_color = None,
text_color = None,
) -> None:
border_color = rl.Color(255, 255, 255, sign_alpha)
text_color = rl.Color(255, 255, 255, sign_alpha)
border_color = border_color or rl.Color(255, 255, 255, sign_alpha)
text_color = text_color or rl.Color(255, 255, 255, sign_alpha)
inner_border_rect = rl.Rectangle(
sign_rect.x + 8,
@@ -412,6 +432,21 @@ class HudRenderer(Widget):
footer_pos = rl.Vector2(sign_rect.x + sign_rect.width / 2 - footer_size.x / 2, sign_rect.y + footer_top)
rl.draw_text_ex(self._font_semi_bold, footer_text, footer_pos, footer_font_size, 0, text_color)
def _speed_limit_pulse_color(self, base_color, alpha: int) -> rl.Color:
base = rl.Color(base_color.r, base_color.g, base_color.b, alpha)
elapsed = rl.get_time() - self._vision_speed_limit_pulse_start
if elapsed < 0.0 or elapsed >= VISION_SPEED_LIMIT_PULSE_SECONDS:
return base
progress = elapsed / VISION_SPEED_LIMIT_PULSE_SECONDS
pulse = math.sin(math.pi * progress)
return rl.Color(
round(base.r + (VISION_SPEED_LIMIT_PULSE_COLOR.r - base.r) * pulse),
round(base.g + (VISION_SPEED_LIMIT_PULSE_COLOR.g - base.g) * pulse),
round(base.b + (VISION_SPEED_LIMIT_PULSE_COLOR.b - base.b) * pulse),
alpha,
)
def _draw_speed_limit(self, rect: rl.Rectangle) -> None:
if not self._show_speed_limit:
return
@@ -433,11 +468,14 @@ class HudRenderer(Widget):
base_x = rect.x + rect.width - sign_width - 28
sign_x = base_x
sign_y = rect.y + (28 if use_vienna_speed_limit else 20)
widget_color = self._speed_limit_pulse_color(rl.Color(255, 255, 255, 255), sign_alpha)
if use_vienna_speed_limit:
center_x = sign_x + sign_width / 2
center_y = sign_y + sign_height / 2
radius = sign_width / 2
ring_color = self._speed_limit_pulse_color(rl.Color(201, 34, 49, 255), sign_alpha)
text_color = self._speed_limit_pulse_color(rl.Color(0, 0, 0, 255), sign_alpha)
rl.draw_circle(int(center_x), int(center_y), radius, rl.Color(255, 255, 255, sign_alpha))
rl.draw_ring(
@@ -447,18 +485,18 @@ class HudRenderer(Widget):
0,
360,
64,
rl.Color(201, 34, 49, sign_alpha),
ring_color,
)
font_size = 58 if len(speed_text) <= 2 else 48
text_size = measure_text_cached(self._font_bold, speed_text, font_size)
text_pos = rl.Vector2(center_x - text_size.x / 2, center_y - text_size.y / 2 + (-14 if offset_text else 4))
rl.draw_text_ex(self._font_bold, speed_text, text_pos, font_size, 0, rl.Color(0, 0, 0, sign_alpha))
rl.draw_text_ex(self._font_bold, speed_text, text_pos, font_size, 0, text_color)
if offset_text:
offset_font_size = 34
offset_size = measure_text_cached(self._font_semi_bold, offset_text, offset_font_size)
offset_pos = rl.Vector2(center_x - offset_size.x / 2, sign_y + sign_height - 42)
rl.draw_text_ex(self._font_semi_bold, offset_text, offset_pos, offset_font_size, 0, rl.Color(0, 0, 0, sign_alpha))
rl.draw_text_ex(self._font_semi_bold, offset_text, offset_pos, offset_font_size, 0, text_color)
else:
sign_rect = rl.Rectangle(sign_x, sign_y, sign_width, sign_height)
self._draw_us_speed_limit_sign(
@@ -474,6 +512,8 @@ class HudRenderer(Widget):
footer_text=offset_text,
footer_font_size=28 if offset_text else 0,
footer_top=100,
border_color=widget_color,
text_color=widget_color,
)
def _update_prompt_layout(self, rect: rl.Rectangle) -> None:
+33 -1
View File
@@ -28,7 +28,7 @@ class TestSpeedLimitUtils(unittest.TestCase):
self.assertEqual(speed_limit, 24.6)
def test_resolve_display_speed_limit_uses_active_source_when_display_only(self):
def test_resolve_display_speed_limit_ignores_unconfigured_active_source(self):
speed_limit = resolve_display_speed_limit_ms(
slc_speed_limit=0.0,
speed_limit_source="Dashboard",
@@ -42,6 +42,22 @@ class TestSpeedLimitUtils(unittest.TestCase):
secondary_priority="Vision",
)
self.assertEqual(speed_limit, 20.1)
def test_resolve_display_speed_limit_uses_configured_active_source_when_display_only(self):
speed_limit = resolve_display_speed_limit_ms(
slc_speed_limit=0.0,
speed_limit_source="Dashboard",
source_limits={
"Dashboard": 22.4,
"Map Data": 20.1,
"Vision": 0.0,
"Mapbox": 0.0,
},
primary_priority="Map Data",
secondary_priority="Dashboard",
)
self.assertEqual(speed_limit, 22.4)
def test_resolve_display_speed_limit_honors_priority_order(self):
@@ -75,3 +91,19 @@ class TestSpeedLimitUtils(unittest.TestCase):
)
self.assertEqual(speed_limit, 17.9)
def test_resolve_display_speed_limit_does_not_fallback_to_dashboard_when_vision_only(self):
speed_limit = resolve_display_speed_limit_ms(
slc_speed_limit=0.0,
speed_limit_source="None",
source_limits={
"Dashboard": 22.4,
"Map Data": 0.0,
"Vision": 0.0,
"Mapbox": 0.0,
},
primary_priority="Vision",
secondary_priority="None",
)
self.assertEqual(speed_limit, 0.0)
+2 -2
View File
@@ -872,8 +872,8 @@ class GuiApplication:
rl.clear_background(rl.BLACK)
self._mark_progress("gui_app.after_clear_background")
render_scale_x = self._scale * self._pixel_scale_x
render_scale_y = self._scale * self._pixel_scale_y
render_scale_x = self._scale * (self._pixel_scale_x if self._render_texture else 1.0)
render_scale_y = self._scale * (self._pixel_scale_y if self._render_texture else 1.0)
needs_render_scale = render_scale_x != 1.0 or render_scale_y != 1.0
if needs_render_scale:
rl.rl_push_matrix()