This commit is contained in:
firestar5683
2026-04-21 16:14:02 -05:00
parent 5e3ceafa5b
commit fd1ea78a62
6 changed files with 175 additions and 58 deletions
+7
View File
@@ -137,6 +137,13 @@ def get_param_color(params, key: str, fallback_alpha: int = 255) -> rl.Color | N
return rl.Color(red, green, blue, alpha)
def is_stock_color_scheme(params) -> bool:
if params is None:
return True
scheme = params.get("ColorScheme", encoding="utf-8", default="stock")
return (scheme or "stock").lower() == "stock"
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)
+12 -5
View File
@@ -7,11 +7,11 @@ 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_param_color, get_theme_color, get_visual_color, with_alpha
from openpilot.selfdrive.ui.lib.starpilot_theme import get_param_color, get_theme_color, get_visual_color, is_stock_color_scheme, 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
from openpilot.selfdrive.ui.mici.onroad.starpilot_status import get_border_color
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
@@ -21,6 +21,7 @@ MIN_DRAW_DISTANCE = 10.0
MAX_DRAW_DISTANCE = 100.0
RAINBOW_GRADIENT_COLOR_COUNT = 19
RAINBOW_SCROLL_SPEED_DEG_PER_SEC = 60.0
STOCK_LINE_GREEN = rl.Color(0, 255, 0, 255)
THROTTLE_COLORS = [
rl.Color(13, 248, 122, 102), # HSLF(148/360, 0.94, 0.51, 0.4)
@@ -370,8 +371,8 @@ class ModelRenderer(Widget):
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))
base_color = STOCK_LINE_GREEN if is_stock_color_scheme(self._params) else get_theme_color("PathEdge", STOCK_LINE_GREEN)
color = with_alpha(base_color, int(alpha * base_color.a))
# turn adjacent lls orange if torque is high
torque = self._torque_filter.x
@@ -383,7 +384,13 @@ class ModelRenderer(Widget):
np.interp(abs(torque), [0.6, 0.8], [0.0, 1.0])
)
else:
lane_lines_color = get_visual_color(self._params, "LaneLinesColor", "LaneLines", rl.WHITE)
lane_lines_override = get_param_color(self._params, "LaneLinesColor", STOCK_LINE_GREEN.a)
if lane_lines_override is not None:
lane_lines_color = lane_lines_override
elif is_stock_color_scheme(self._params):
lane_lines_color = STOCK_LINE_GREEN
else:
lane_lines_color = get_theme_color("LaneLines", STOCK_LINE_GREEN)
color = with_alpha(lane_lines_color, int(alpha * lane_lines_color.a))
return color
+9 -2
View File
@@ -7,7 +7,7 @@ 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_param_color, get_theme_color, get_visual_color, with_alpha
from openpilot.selfdrive.ui.lib.starpilot_theme import get_param_color, get_theme_color, get_visual_color, is_stock_color_scheme, 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
@@ -19,6 +19,7 @@ MIN_DRAW_DISTANCE = 10.0
MAX_DRAW_DISTANCE = 100.0
RAINBOW_GRADIENT_COLOR_COUNT = 19
RAINBOW_SCROLL_SPEED_DEG_PER_SEC = 60.0
STOCK_LINE_GREEN = rl.Color(0, 255, 0, 255)
THROTTLE_COLORS = [
rl.Color(13, 248, 122, 102), # HSLF(148/360, 0.94, 0.51, 0.4)
@@ -363,7 +364,13 @@ class ModelRenderer(Widget):
def _draw_lane_lines(self):
"""Draw lane lines and road edges"""
lane_lines_color = get_visual_color(self._params, "LaneLinesColor", "LaneLines", rl.WHITE)
lane_lines_override = get_param_color(self._params, "LaneLinesColor", STOCK_LINE_GREEN.a)
if lane_lines_override is not None:
lane_lines_color = lane_lines_override
elif is_stock_color_scheme(self._params):
lane_lines_color = STOCK_LINE_GREEN
else:
lane_lines_color = get_theme_color("LaneLines", STOCK_LINE_GREEN)
for i, lane_line in enumerate(self._lane_lines):
if lane_line.projected_points.size == 0:
+23 -50
View File
@@ -6,12 +6,13 @@ 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_param_color, get_theme_color, with_alpha
from openpilot.selfdrive.ui.lib.starpilot_theme import get_param_color, get_theme_color, is_stock_color_scheme, with_alpha
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
_STOCK_LINE_GREEN = rl.Color(0, 255, 0, 241)
def _get_metrics_font():
@@ -147,14 +148,10 @@ def render_path_edges(renderer) -> None:
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
Color selection on Python UIs:
- If path_edges_color is set: use that color
- Else if color_scheme != "stock": use the active theme color
- Else: use a fixed stock-style green so border color carries engagement status
The path_edges_color param is a hex string like "#178644".
"""
@@ -166,48 +163,24 @@ def render_path_edges(renderer) -> None:
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)
override = get_param_color(renderer._params, "PathEdgesColor", 241)
if override is not None:
base_color = rl.Color(override.r, override.g, override.b, 241)
elif is_stock_color_scheme(renderer._params):
base_color = _STOCK_LINE_GREEN
else:
override = get_param_color(renderer._params, "PathEdgesColor", 241)
color_scheme = renderer._params.get("ColorScheme", encoding="utf-8", default="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:
base_color = None
theme_color = get_theme_color("PathEdge", _STOCK_LINE_GREEN)
base_color = rl.Color(theme_color.r, theme_color.g, theme_color.b, 241)
if base_color is not None:
gradient = Gradient(
start=(0.0, 1.0),
end=(0.0, 0.0),
colors=[
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],
)
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],
)
gradient = Gradient(
start=(0.0, 1.0),
end=(0.0, 0.0),
colors=[
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],
)
draw_polygon(renderer._rect, edge_strip, gradient=gradient)
@@ -2,6 +2,11 @@ import { html, reactive } from "/assets/vendor/arrow-core.js"
const endpointOptionsCache = {}
const endpointOptionsInflight = {}
const COLOR_UI_DEFAULTS = {
LaneLinesColor: "#00ff00",
PathEdgesColor: "#00ff00",
PathColor: "#30ff9c",
}
// Plain variables — scheduling/routing flags that must NOT be reactive
let syncScheduled = false
@@ -42,6 +47,34 @@ function toSelectValue(value) {
return value === null || value === undefined ? "" : String(value)
}
function normalizeHexColor(rawValue) {
const value = String(rawValue || "").trim()
if (!value || value.toLowerCase() === "stock") return ""
const stripped = value.startsWith("#") ? value.slice(1) : value
if (!/^[0-9a-fA-F]{6}([0-9a-fA-F]{2})?$/.test(stripped)) return ""
return `#${stripped.slice(0, 6).toLowerCase()}`
}
function getColorDefault(param) {
const candidate = normalizeHexColor(param?.default_color)
if (candidate) return candidate
return COLOR_UI_DEFAULTS[param?.key] || "#ffffff"
}
function resolveColorInputValue(param, rawValue = undefined) {
return normalizeHexColor(rawValue ?? state.values[param?.key]) || getColorDefault(param)
}
function formatColorDisplayValue(param, rawValue = undefined) {
const value = normalizeHexColor(rawValue ?? state.values[param?.key])
return value ? value.toUpperCase() : "Stock"
}
function isStockColorValue(rawValue) {
return normalizeHexColor(rawValue) === ""
}
function resolveEndpointTemplate(template) {
if (!template) return ""
return String(template).replace(/\{([A-Za-z0-9_]+)\}/g, (_, key) => {
@@ -126,6 +159,14 @@ function syncInputs() {
el.checked = !!state.values[el.id.slice(3)]
}
// Sync color inputs — map unset/"stock" values to the picker fallback color.
for (const el of document.querySelectorAll("input[type='color'].ds-color[id^='ds-']")) {
const key = el.id.slice(3)
const param = state.paramMetaByKey[key]
if (!param) continue
el.value = resolveColorInputValue(param)
}
// Sync selects — hydrate options + set value
for (const el of document.querySelectorAll("select.ds-select[id^='ds-']")) {
const key = el.id.slice(3)
@@ -544,6 +585,8 @@ async function updateParam(key, elType) {
} else if (elType === "dropdown") {
formattedVal = coerceValueByType(el.value, param.data_type)
selectedLabel = el.options?.[el.selectedIndex]?.textContent || ""
} else if (elType === "color") {
formattedVal = normalizeHexColor(el.value) || getColorDefault(param)
} else {
formattedVal = coerceValueByType(el.value, param.data_type)
}
@@ -589,9 +632,46 @@ function revertInput(key, current, elType) {
return
}
if (elType === "color") {
const param = state.paramMetaByKey[key]
if (!param) return
el.value = resolveColorInputValue(param, current)
return
}
el.value = current
}
async function resetColorParam(param) {
const key = param?.key
if (!key) return
const current = state.values[key]
if (isStockColorValue(current)) return
try {
const res = await fetch("/api/params", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ key, value: "stock" }),
})
const data = await res.json()
if (res.ok) {
const updated = (data.updated && typeof data.updated === "object") ? data.updated : {}
state.values = { ...state.values, [key]: "stock", ...updated }
showParamSnackbar(data.message || `Parameter '${key}' reset to stock.`)
scheduleSyncInputs()
} else {
showParamSnackbar(data.error || "Failed to reset parameter", "error")
revertInput(key, current, "color")
}
} catch (e) {
showParamSnackbar("Network error — is the device reachable?", "error")
revertInput(key, current, "color")
}
}
function toggleManage(key) {
state.expanded = { ...state.expanded, [key]: !state.expanded[key] }
scheduleSyncInputs()
@@ -640,6 +720,7 @@ function renderSettingRow(p) {
}
const isNumeric = p.ui_type === "numeric"
const isColor = p.ui_type === "color"
const isChild = p.parent_key ? "ds-child-modifier" : ""
const lockReason = getSettingLockReason(p)
const isLocked = lockReason !== ""
@@ -659,7 +740,8 @@ function renderSettingRow(p) {
</div>
` : ""}
</div>
${isNumeric ? html`<span class="ds-row-value" id="ds-display-${p.key}">${() => {
${(isNumeric || isColor) ? html`<span class="ds-row-value" id="ds-display-${p.key}">${() => {
if (isColor) return formatColorDisplayValue(p)
const currentValue = state.values[p.key]
const bounds = numericBounds(p)
return currentValue !== undefined ? formatSliderValue(currentValue, String(bounds.step), p.precision, p.key) : ".."
@@ -733,6 +815,20 @@ function renderSettingRow(p) {
@change="${() => updateParam(p.key, "dropdown")}">
<option value="">Loading...</option>
</select>
` : p.ui_type === "color" ? html`
<div style="display:flex; align-items:center; gap:0.75rem;">
<input
type="color"
class="ds-color"
id="ds-${p.key}"
?disabled="${isLocked}"
value="${() => resolveColorInputValue(p)}"
@change="${() => updateParam(p.key, "color")}" />
<button
class="ds-reset-btn"
?disabled="${() => isLocked || isStockColorValue(state.values[p.key])}"
@click="${() => resetColorParam(p)}">Stock</button>
</div>
` : html`
<input
type="checkbox"
@@ -1698,6 +1698,15 @@
"max": 24.0,
"parent_key": "ModelUI"
},
{
"key": "LaneLinesColor",
"label": "Lane Line Color",
"description": "Set a custom lane-line color for the Python UIs.\n\nUse Stock to follow the default line color behavior.",
"data_type": "string",
"ui_type": "color",
"default_color": "#00FF00",
"parent_key": "ModelUI"
},
{
"key": "BorderWidth",
"label": "Border Width",
@@ -1719,6 +1728,15 @@
"max": 100.0,
"parent_key": "ModelUI"
},
{
"key": "PathEdgesColor",
"label": "Path Edge Color",
"description": "Set a custom path-edge color for the Python UIs.\n\nUse Stock to follow the default line color behavior.",
"data_type": "string",
"ui_type": "color",
"default_color": "#00FF00",
"parent_key": "ModelUI"
},
{
"key": "PathWidth",
"label": "Path Width",
@@ -1730,6 +1748,15 @@
"step": 0.1,
"parent_key": "ModelUI"
},
{
"key": "PathColor",
"label": "Path Color",
"description": "Set a custom filled-path color for the Python UIs.\n\nUse Stock to follow the default path color behavior.",
"data_type": "string",
"ui_type": "color",
"default_color": "#30FF9C",
"parent_key": "ModelUI"
},
{
"key": "RoadEdgesWidth",
"label": "Road Edges Width",