mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-06-28 18:12:05 +08:00
Mici customize?
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pyray as rl
|
||||
|
||||
from openpilot.common.basedir import BASEDIR
|
||||
|
||||
ACTIVE_THEME_COLORS_PATH = Path(BASEDIR) / "starpilot/assets/active_theme/colors/colors.json"
|
||||
STOCK_THEME_COLORS_PATH = Path(BASEDIR) / "starpilot/assets/stock_theme/colors/colors.json"
|
||||
|
||||
_FALLBACK_THEME_COLORS = {
|
||||
"LaneLines": (255, 255, 255, 178),
|
||||
"LeadMarker": (201, 34, 49, 255),
|
||||
"Path": (48, 255, 156, 255),
|
||||
"PathEdge": (38, 209, 125, 255),
|
||||
"Sidebar1": (255, 255, 255, 178),
|
||||
"Sidebar2": (255, 255, 255, 255),
|
||||
"Sidebar3": (255, 255, 255, 255),
|
||||
}
|
||||
|
||||
_THEME_COLOR_CACHE: dict[str, object] = {
|
||||
"stamp": None,
|
||||
"colors": None,
|
||||
}
|
||||
|
||||
|
||||
def _file_stamp(path: Path) -> tuple[str, int | None, int | None]:
|
||||
try:
|
||||
stat = path.stat()
|
||||
return (str(path), stat.st_mtime_ns, stat.st_size)
|
||||
except OSError:
|
||||
return (str(path), None, None)
|
||||
|
||||
|
||||
def _clamp_channel(value: object, fallback: int) -> int:
|
||||
try:
|
||||
return max(0, min(255, int(value)))
|
||||
except (TypeError, ValueError):
|
||||
return fallback
|
||||
|
||||
|
||||
def _load_color_map(path: Path) -> dict[str, dict[str, int]]:
|
||||
try:
|
||||
with path.open() as f:
|
||||
data = json.load(f)
|
||||
if isinstance(data, dict):
|
||||
return data
|
||||
except (OSError, ValueError, TypeError):
|
||||
pass
|
||||
return {}
|
||||
|
||||
|
||||
def _build_color(entry: object, fallback: tuple[int, int, int, int]) -> rl.Color:
|
||||
if isinstance(entry, dict):
|
||||
red, green, blue, alpha = fallback
|
||||
return rl.Color(
|
||||
_clamp_channel(entry.get("red"), red),
|
||||
_clamp_channel(entry.get("green"), green),
|
||||
_clamp_channel(entry.get("blue"), blue),
|
||||
_clamp_channel(entry.get("alpha"), alpha),
|
||||
)
|
||||
return rl.Color(*fallback)
|
||||
|
||||
|
||||
def _load_theme_colors() -> dict[str, rl.Color]:
|
||||
stamp = (_file_stamp(STOCK_THEME_COLORS_PATH), _file_stamp(ACTIVE_THEME_COLORS_PATH))
|
||||
if stamp == _THEME_COLOR_CACHE["stamp"] and _THEME_COLOR_CACHE["colors"] is not None:
|
||||
return _THEME_COLOR_CACHE["colors"] # type: ignore[return-value]
|
||||
|
||||
stock_colors = _load_color_map(STOCK_THEME_COLORS_PATH)
|
||||
active_colors = _load_color_map(ACTIVE_THEME_COLORS_PATH)
|
||||
|
||||
colors = {}
|
||||
for key, fallback in _FALLBACK_THEME_COLORS.items():
|
||||
colors[key] = _build_color(active_colors.get(key, stock_colors.get(key)), fallback)
|
||||
|
||||
_THEME_COLOR_CACHE["stamp"] = stamp
|
||||
_THEME_COLOR_CACHE["colors"] = colors
|
||||
return colors
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
def with_alpha(color: rl.Color, alpha: int) -> rl.Color:
|
||||
return rl.Color(color.r, color.g, color.b, max(0, min(255, int(alpha))))
|
||||
@@ -4,8 +4,10 @@ import pyray as rl
|
||||
from cereal import messaging, car
|
||||
from dataclasses import dataclass, field
|
||||
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.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
|
||||
@@ -79,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
|
||||
|
||||
@@ -184,21 +187,36 @@ class ModelRenderer(Widget):
|
||||
|
||||
def _update_model(self, lead, path_x_array):
|
||||
"""Update model visualization data based on model message"""
|
||||
model_ui_enabled = self._params.get_bool("ModelUI", default=True)
|
||||
if model_ui_enabled:
|
||||
path_width = self._path_width_to_half_m(self._params.get_float("PathWidth", return_default=True, default=6.1))
|
||||
lane_line_width = self._small_distance_to_half_m(self._params.get_float("LaneLinesWidth", return_default=True, default=4.0))
|
||||
road_edge_width = self._small_distance_to_half_m(self._params.get_float("RoadEdgesWidth", return_default=True, default=2.0))
|
||||
else:
|
||||
path_width = 0.9
|
||||
lane_line_width = 0.025
|
||||
road_edge_width = 0.025
|
||||
|
||||
if model_ui_enabled and self._params.get_bool("DynamicPathWidth", default=False):
|
||||
if ui_state.status == UIStatus.ENGAGED:
|
||||
path_width *= 1.0
|
||||
elif ui_state.always_on_lateral_active:
|
||||
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
|
||||
line_width_factor = 0.12
|
||||
for i, lane_line in enumerate(self._lane_lines):
|
||||
if i in (1, 2):
|
||||
line_width_factor = 0.16
|
||||
lane_line.projected_points = self._map_line_to_polygon(
|
||||
lane_line.raw_points, line_width_factor * self._lane_line_probs[i], 0.0, max_idx
|
||||
lane_line.raw_points, lane_line_width * self._lane_line_probs[i], 0.0, max_idx
|
||||
)
|
||||
|
||||
# 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, line_width_factor, 0.0, max_idx)
|
||||
road_edge.projected_points = self._map_line_to_polygon(road_edge.raw_points, road_edge_width, 0.0, max_idx)
|
||||
|
||||
# Update path using raw points
|
||||
if lead and lead.status:
|
||||
@@ -214,7 +232,7 @@ class ModelRenderer(Widget):
|
||||
high_pass_acceleration = self._acceleration_x_filter.x - self._acceleration_x_filter2.x
|
||||
y_off = np.interp(high_pass_acceleration, [-1, 0, 1], [0.9 * 2, 0.9, 0.9 / 2])
|
||||
else:
|
||||
y_off = 0.9
|
||||
y_off = path_width
|
||||
|
||||
max_idx = self._get_path_length_idx(path_x_array, max_distance)
|
||||
self._path.projected_points = self._map_line_to_polygon(
|
||||
@@ -225,7 +243,7 @@ class ModelRenderer(Widget):
|
||||
|
||||
def _update_experimental_gradient(self):
|
||||
"""Pre-calculate experimental mode gradient colors"""
|
||||
if not self._experimental_mode:
|
||||
if not (self._experimental_mode or self._params.get_bool("AccelerationPath", default=True)):
|
||||
return
|
||||
|
||||
max_len = min(len(self._path.projected_points) // 2, len(self._acceleration_x))
|
||||
@@ -304,7 +322,8 @@ class ModelRenderer(Widget):
|
||||
np.interp(abs(torque), [0.6, 0.8], [0.0, 1.0])
|
||||
)
|
||||
else:
|
||||
color = rl.Color(255, 255, 255, int(alpha * 255))
|
||||
lane_lines_color = get_theme_color("LaneLines", rl.WHITE)
|
||||
color = with_alpha(lane_lines_color, int(alpha * lane_lines_color.a))
|
||||
|
||||
return color
|
||||
|
||||
@@ -334,14 +353,28 @@ 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))
|
||||
custom_theme_selected = (self._params.get("ColorScheme", encoding="utf-8", default="stock") or "stock").lower() != "stock"
|
||||
|
||||
if self._experimental_mode:
|
||||
if self._experimental_mode or self._params.get_bool("AccelerationPath", default=True):
|
||||
# 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))
|
||||
gradient = Gradient(
|
||||
start=(0.0, 1.0),
|
||||
end=(0.0, 0.0),
|
||||
colors=[
|
||||
with_alpha(path_color, path_color.a),
|
||||
with_alpha(path_color, int(path_color.a * 0.55)),
|
||||
with_alpha(path_color, int(path_color.a * 0.10)),
|
||||
],
|
||||
stops=[0.0, 0.5, 1.0],
|
||||
)
|
||||
draw_polygon(self._rect, self._path.projected_points, gradient=gradient)
|
||||
else:
|
||||
# Blend throttle/no throttle colors based on transition
|
||||
blend_factor = round(self._blend_filter.x * 100) / 100
|
||||
@@ -479,3 +512,17 @@ class ModelRenderer(Widget):
|
||||
int(inv_t * start.b + t * end.b),
|
||||
int(inv_t * start.a + t * end.a)
|
||||
) for start, end in zip(begin_colors, end_colors, strict=True)]
|
||||
|
||||
def _small_distance_to_half_m(self, value: float) -> float:
|
||||
if value <= 0:
|
||||
return 0.0
|
||||
if self._params.get_bool("IsMetric"):
|
||||
return value / 200.0
|
||||
return value * (CV.INCH_TO_CM / 100.0) / 2.0
|
||||
|
||||
def _path_width_to_half_m(self, value: float) -> float:
|
||||
if value <= 0:
|
||||
return 0.0
|
||||
if self._params.get_bool("IsMetric"):
|
||||
return value / 2.0
|
||||
return value * CV.FOOT_TO_METER / 2.0
|
||||
|
||||
@@ -5,7 +5,9 @@ from cereal import messaging, car
|
||||
from dataclasses import dataclass, field
|
||||
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.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
|
||||
@@ -185,28 +187,24 @@ 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
|
||||
model_ui_enabled = self._params.get_bool('ModelUI', default=True)
|
||||
if model_ui_enabled:
|
||||
path_width = self._path_width_to_half_m(self._params.get_float('PathWidth', return_default=True, default=6.1))
|
||||
lane_line_width_m = self._small_distance_to_half_m(self._params.get_float('LaneLinesWidth', return_default=True, default=4.0))
|
||||
road_edge_width_m = self._small_distance_to_half_m(self._params.get_float('RoadEdgesWidth', return_default=True, default=2.0))
|
||||
path_edge_width_pct = np.clip(self._params.get_float('PathEdgeWidth', return_default=True, default=20.0) / 100.0, 0.0, 1.0)
|
||||
else:
|
||||
path_width = 0.9
|
||||
lane_line_width_m = 0.025
|
||||
road_edge_width_m = 0.025
|
||||
path_edge_width_pct = 0.0
|
||||
|
||||
# Dynamic path width
|
||||
if self._params.get_bool('DynamicPathWidth'):
|
||||
if model_ui_enabled and self._params.get_bool('DynamicPathWidth', default=False):
|
||||
status = ui_state.status
|
||||
if status == UIStatus.ENGAGED:
|
||||
path_width *= 1.0
|
||||
elif status == UIStatus.ALWAYS_ON_LATERAL:
|
||||
elif ui_state.always_on_lateral_active:
|
||||
path_width *= 0.75
|
||||
else:
|
||||
path_width *= 0.50
|
||||
@@ -246,7 +244,7 @@ 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')
|
||||
use_acceleration = self._experimental_mode or self._params.get_bool('AccelerationPath', default=True)
|
||||
use_rainbow = self._params.get_bool('RainbowPath') and not use_acceleration
|
||||
|
||||
if not use_acceleration and not use_rainbow:
|
||||
@@ -333,19 +331,14 @@ 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')
|
||||
lane_lines_color = get_theme_color("LaneLines", rl.WHITE)
|
||||
|
||||
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)
|
||||
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))
|
||||
color = with_alpha(lane_lines_color, int(alpha * lane_lines_color.a))
|
||||
draw_polygon(self._rect, lane_line.projected_points, color)
|
||||
|
||||
for i, road_edge in enumerate(self._road_edges):
|
||||
@@ -364,12 +357,26 @@ 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')
|
||||
use_accel_path = self._params.get_bool('AccelerationPath', default=True)
|
||||
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 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))
|
||||
gradient = Gradient(
|
||||
start=(0.0, 1.0),
|
||||
end=(0.0, 0.0),
|
||||
colors=[
|
||||
with_alpha(path_color, path_color.a),
|
||||
with_alpha(path_color, int(path_color.a * 0.55)),
|
||||
with_alpha(path_color, int(path_color.a * 0.10)),
|
||||
],
|
||||
stops=[0.0, 0.5, 1.0],
|
||||
)
|
||||
draw_polygon(self._rect, self._path.projected_points, gradient=gradient)
|
||||
else:
|
||||
# Blend throttle/no throttle colors based on transition
|
||||
blend_factor = round(self._blend_filter.x * 100) / 100
|
||||
@@ -384,12 +391,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))
|
||||
|
||||
def _update_adjacent_paths(self, max_idx: int, max_distance: float):
|
||||
"""Compute adjacent lane path polygons by averaging lane line pairs."""
|
||||
@@ -631,6 +639,20 @@ class ModelRenderer(Widget):
|
||||
int(a * 255)
|
||||
)
|
||||
|
||||
def _small_distance_to_half_m(self, value: float) -> float:
|
||||
if value <= 0:
|
||||
return 0.0
|
||||
if self._params.get_bool("IsMetric"):
|
||||
return value / 200.0
|
||||
return value * (CV.INCH_TO_CM / 100.0) / 2.0
|
||||
|
||||
def _path_width_to_half_m(self, value: float) -> float:
|
||||
if value <= 0:
|
||||
return 0.0
|
||||
if self._params.get_bool("IsMetric"):
|
||||
return value / 2.0
|
||||
return value * CV.FOOT_TO_METER / 2.0
|
||||
|
||||
@staticmethod
|
||||
def _blend_colors(begin_colors, end_colors, t):
|
||||
if t >= 1.0:
|
||||
|
||||
@@ -6,6 +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.ui_state import ui_state
|
||||
from openpilot.system.ui.lib.shader_polygon import draw_polygon, Gradient
|
||||
|
||||
@@ -176,20 +177,10 @@ def render_path_edges(renderer) -> None:
|
||||
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)
|
||||
color_scheme = renderer._params.get("ColorScheme", encoding="utf-8", default="stock")
|
||||
if 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:
|
||||
base_color = None
|
||||
|
||||
@@ -198,9 +189,9 @@ def render_path_edges(renderer) -> None:
|
||||
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),
|
||||
with_alpha(base_color, int(255 * 0.4)),
|
||||
with_alpha(base_color, int(255 * 0.35)),
|
||||
with_alpha(base_color, 0),
|
||||
],
|
||||
stops=[0.0, 0.5, 1.0],
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user