IQ.Pilot Release Commit @ d23cc80
This commit is contained in:
@@ -214,10 +214,9 @@ class NoEntryCard(AlertCard):
|
||||
def __init__(self,
|
||||
alert_text_2: str,
|
||||
alert_text_1: str = "openpilot Unavailable",
|
||||
visual_alert: car.CarControl.HUDControl.VisualAlert = VisualAlert.none,
|
||||
priority: Tier = Tier.LOW):
|
||||
visual_alert: car.CarControl.HUDControl.VisualAlert = VisualAlert.none):
|
||||
primary, secondary, size = _mici_reframe(alert_text_1, alert_text_2)
|
||||
super().__init__(primary, secondary, AlertStatus.normal, size, priority, visual_alert, AudibleAlert.refuse, 3.0)
|
||||
super().__init__(primary, secondary, AlertStatus.normal, size, Tier.LOW, visual_alert, AudibleAlert.refuse, 3.0)
|
||||
|
||||
|
||||
class GentleDisableCard(AlertCard):
|
||||
|
||||
@@ -12,11 +12,11 @@ _ANGLE = _dbc.CarParams.SteerControlType.angle
|
||||
|
||||
# Port tunables surfaced to the fingerprint step, flat so the read is one pass.
|
||||
_TUNABLES = (
|
||||
"HyundaiLongitudinalTuning",
|
||||
"SubaruStopAndGo",
|
||||
"SubaruStopAndGoManualParkingBrake",
|
||||
"TeslaCoopSteering",
|
||||
"ToyotaEnforceStockLongitudinal",
|
||||
"IQHyundaiLongTune",
|
||||
"IQSubaruCreepAssist",
|
||||
"IQSubaruCreepAssistManualBrake",
|
||||
"IQTeslaTorqueBlend",
|
||||
"IQToyotaFactoryLong",
|
||||
"ToyotaSnGHack",
|
||||
)
|
||||
|
||||
|
||||
@@ -81,5 +81,5 @@ def _write(vehicles: dict[str, dict], basedir: str = BASEDIR) -> str:
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from iqdbc.lvbs.car.platform_list import get_car_list
|
||||
print("wrote", _write(get_car_list()))
|
||||
from iqdbc.lvbs.car.car_catalog import build_car_catalog
|
||||
print("wrote", _write(build_car_catalog()))
|
||||
|
||||
@@ -43,6 +43,7 @@ from openpilot.iqpilot.selfdrive.iqmodeld.models.helpers import (
|
||||
bundle_files_ready,
|
||||
get_active_bundle,
|
||||
get_runtime_bundle_upgrade,
|
||||
is_default_bundle,
|
||||
persist_active_bundle,
|
||||
)
|
||||
|
||||
@@ -56,6 +57,7 @@ class IQModelManager(_BaseIQModelManager):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._validated_active_key: tuple[tuple[str, str], ...] | None = None
|
||||
self._manifest_refresh_key: tuple[tuple[str, str], ...] | None = None
|
||||
|
||||
@staticmethod
|
||||
def _bundle_index(bundle) -> int | None:
|
||||
@@ -190,6 +192,58 @@ class IQModelManager(_BaseIQModelManager):
|
||||
if bundle_index is not None and self._download_index() is None:
|
||||
self.params.put(_DOWNLOAD_INDEX_KEY, bundle_index)
|
||||
|
||||
def _find_manifest_counterpart(self, target):
|
||||
# never match by index: indexes shift between manifest generations, and a
|
||||
# positional match could redownload a different model than the user selected
|
||||
for attr in ("ref", "internalName", "displayName"):
|
||||
value = getattr(target, attr, None)
|
||||
if not value:
|
||||
continue
|
||||
for bundle in self.available_models:
|
||||
if getattr(bundle, attr, None) == value:
|
||||
return bundle
|
||||
return None
|
||||
|
||||
def _queue_active_manifest_refresh(self) -> None:
|
||||
active = self.active_bundle
|
||||
if active is None or is_default_bundle(active):
|
||||
return
|
||||
if self._download_index() is not None:
|
||||
return
|
||||
|
||||
counterpart = self._find_manifest_counterpart(active)
|
||||
if counterpart is None:
|
||||
return
|
||||
counterpart_index = self._bundle_index(counterpart)
|
||||
if counterpart_index is None:
|
||||
return
|
||||
|
||||
active_files = dict(self._bundle_files(active))
|
||||
stale = False
|
||||
for filename, sha in self._bundle_files(counterpart):
|
||||
if not sha:
|
||||
continue
|
||||
active_sha = active_files.get(filename)
|
||||
# an empty recorded hash can't prove a mismatch, so it never triggers a redownload
|
||||
if active_sha is None or (active_sha and active_sha.lower() != sha.lower()):
|
||||
stale = True
|
||||
break
|
||||
if not stale:
|
||||
self._manifest_refresh_key = None
|
||||
return
|
||||
|
||||
# the manifest may be an expired offline cache, so keep the active bundle and its
|
||||
# files in place: the download flow replaces artifacts atomically and only persists
|
||||
# the counterpart as active once everything landed. One attempt per bundle per run
|
||||
# so a dead network doesn't turn the 1Hz loop into a download-retry storm.
|
||||
key = self._bundle_validation_key(active)
|
||||
if key == self._manifest_refresh_key:
|
||||
return
|
||||
self._manifest_refresh_key = key
|
||||
|
||||
cloudlog.warning(f"Active model {_display_bundle_name(active)} artifacts are stale vs current manifest; queueing redownload")
|
||||
self.params.put(_DOWNLOAD_INDEX_KEY, counterpart_index)
|
||||
|
||||
async def _download_file(self, url: str, path: str, model) -> None:
|
||||
temp_path = f"{path}.download"
|
||||
self._download_start_times[model.fileName] = time.monotonic()
|
||||
@@ -302,6 +356,7 @@ class IQModelManager(_BaseIQModelManager):
|
||||
self.active_bundle = get_active_bundle(self.params)
|
||||
self._queue_active_redownload_if_invalid()
|
||||
self._queue_tinygrad_upgrade()
|
||||
self._queue_active_manifest_refresh()
|
||||
|
||||
if (index_to_download := self._download_index()) is not None:
|
||||
if model_to_download := next((model for model in self.available_models if model.index == index_to_download), None):
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
"""
|
||||
Copyright (c) IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from openpilot.iqpilot.selfdrive.iqmodeld.models.manager import IQModelManager, _DOWNLOAD_INDEX_KEY
|
||||
|
||||
|
||||
@dataclass
|
||||
class _DownloadUri:
|
||||
sha256: str = ""
|
||||
uri: str = ""
|
||||
|
||||
|
||||
@dataclass
|
||||
class _Artifact:
|
||||
fileName: str = ""
|
||||
downloadUri: _DownloadUri = field(default_factory=_DownloadUri)
|
||||
|
||||
|
||||
@dataclass
|
||||
class _Model:
|
||||
artifact: _Artifact = field(default_factory=_Artifact)
|
||||
metadata: _Artifact | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class _Bundle:
|
||||
index: int = 0
|
||||
ref: str = ""
|
||||
internalName: str = ""
|
||||
displayName: str = ""
|
||||
models: list = field(default_factory=list)
|
||||
|
||||
|
||||
class _FakeParams:
|
||||
def __init__(self):
|
||||
self.store = {}
|
||||
|
||||
def get(self, key):
|
||||
return self.store.get(key)
|
||||
|
||||
def put(self, key, value):
|
||||
self.store[key] = value
|
||||
|
||||
def remove(self, key):
|
||||
self.store.pop(key, None)
|
||||
|
||||
|
||||
def _bundle(index, name, sha, filename="driving_vision_test_tinygrad.pkl"):
|
||||
return _Bundle(
|
||||
index=index,
|
||||
ref=f"ref-{name}",
|
||||
internalName=name,
|
||||
displayName=f"{name} display",
|
||||
models=[_Model(artifact=_Artifact(fileName=filename, downloadUri=_DownloadUri(sha256=sha)))],
|
||||
)
|
||||
|
||||
|
||||
def _manager(active, available):
|
||||
mgr = IQModelManager.__new__(IQModelManager)
|
||||
mgr.params = _FakeParams()
|
||||
mgr.active_bundle = active
|
||||
mgr.available_models = available
|
||||
mgr._validated_active_key = None
|
||||
mgr._manifest_refresh_key = None
|
||||
return mgr
|
||||
|
||||
|
||||
def test_stale_active_bundle_queues_redownload_at_current_index():
|
||||
active = _bundle(55, "WMIV12", "a" * 64)
|
||||
counterpart = _bundle(12, "WMIV12", "b" * 64)
|
||||
mgr = _manager(active, [counterpart])
|
||||
|
||||
mgr._queue_active_manifest_refresh()
|
||||
|
||||
assert mgr.params.get(_DOWNLOAD_INDEX_KEY) == 12
|
||||
assert mgr.active_bundle is active
|
||||
|
||||
|
||||
def test_matching_shas_do_not_queue():
|
||||
active = _bundle(55, "WMIV12", "a" * 64)
|
||||
counterpart = _bundle(12, "WMIV12", "A" * 64)
|
||||
mgr = _manager(active, [counterpart])
|
||||
|
||||
mgr._queue_active_manifest_refresh()
|
||||
|
||||
assert mgr.params.get(_DOWNLOAD_INDEX_KEY) is None
|
||||
|
||||
|
||||
def test_retired_bundle_is_left_alone():
|
||||
active = _bundle(55, "WMIV12", "a" * 64)
|
||||
mgr = _manager(active, [_bundle(12, "OtherModel", "b" * 64)])
|
||||
|
||||
mgr._queue_active_manifest_refresh()
|
||||
|
||||
assert mgr.params.get(_DOWNLOAD_INDEX_KEY) is None
|
||||
assert mgr.active_bundle is active
|
||||
|
||||
|
||||
def test_default_bundle_is_never_refreshed():
|
||||
active = _bundle(0, "Default", "a" * 64)
|
||||
active.ref = "default"
|
||||
mgr = _manager(active, [_bundle(0, "Default", "b" * 64)])
|
||||
|
||||
mgr._queue_active_manifest_refresh()
|
||||
|
||||
assert mgr.params.get(_DOWNLOAD_INDEX_KEY) is None
|
||||
|
||||
|
||||
def test_pending_download_blocks_refresh():
|
||||
active = _bundle(55, "WMIV12", "a" * 64)
|
||||
mgr = _manager(active, [_bundle(12, "WMIV12", "b" * 64)])
|
||||
mgr.params.put(_DOWNLOAD_INDEX_KEY, 3)
|
||||
|
||||
mgr._queue_active_manifest_refresh()
|
||||
|
||||
assert mgr.params.get(_DOWNLOAD_INDEX_KEY) == 3
|
||||
|
||||
|
||||
def test_empty_manifest_hash_never_triggers():
|
||||
active = _bundle(55, "WMIV12", "a" * 64)
|
||||
mgr = _manager(active, [_bundle(12, "WMIV12", "")])
|
||||
|
||||
mgr._queue_active_manifest_refresh()
|
||||
|
||||
assert mgr.params.get(_DOWNLOAD_INDEX_KEY) is None
|
||||
|
||||
|
||||
def test_refresh_queued_once_per_run():
|
||||
active = _bundle(55, "WMIV12", "a" * 64)
|
||||
mgr = _manager(active, [_bundle(12, "WMIV12", "b" * 64)])
|
||||
|
||||
mgr._queue_active_manifest_refresh()
|
||||
assert mgr.params.get(_DOWNLOAD_INDEX_KEY) == 12
|
||||
|
||||
mgr.params.remove(_DOWNLOAD_INDEX_KEY)
|
||||
mgr._queue_active_manifest_refresh()
|
||||
assert mgr.params.get(_DOWNLOAD_INDEX_KEY) is None
|
||||
|
||||
|
||||
def test_counterpart_matched_by_name_not_index():
|
||||
active = _bundle(55, "WMIV12", "a" * 64)
|
||||
imposter = _bundle(55, "OtherModel", "c" * 64)
|
||||
counterpart = _bundle(12, "WMIV12", "b" * 64)
|
||||
mgr = _manager(active, [imposter, counterpart])
|
||||
|
||||
mgr._queue_active_manifest_refresh()
|
||||
|
||||
assert mgr.params.get(_DOWNLOAD_INDEX_KEY) == 12
|
||||
@@ -337,6 +337,13 @@ _NOTICE_EVENTS: EVENTS_IQ_TYPE = {
|
||||
Priority.MID, VisualAlert.none, AudibleAlert.prompt, 3.),
|
||||
},
|
||||
|
||||
# outranks the generic processNotRunning alert so the driver sees why engagement is blocked
|
||||
EventNameIQ.modelUpdating: {
|
||||
ET.NO_ENTRY: NoEntryAlert("Update finishes while parked with internet",
|
||||
alert_text_1="Driving Model Updating",
|
||||
priority=Priority.MID),
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
EVENTS_IQ: EVENTS_IQ_TYPE = {**_GUIDANCE_EVENTS, **_ENGAGE_EVENTS, **_CABIN_BLOCK_EVENTS, **_NOTICE_EVENTS}
|
||||
|
||||
@@ -194,7 +194,7 @@ CHEVRON_INFO_DESCRIPTION = {
|
||||
|
||||
# param key -> (title fn, description fn)
|
||||
_HUD_TOGGLES = {
|
||||
"BlindSpot": (
|
||||
"IQBlindSpotAlerts": (
|
||||
lambda: tr("Blind Spot Alerts"),
|
||||
lambda: tr("Flashes a side warning whenever the car reports something sitting in your blind spot (BSM-equipped cars only)."),
|
||||
),
|
||||
@@ -202,20 +202,20 @@ _HUD_TOGGLES = {
|
||||
lambda: tr("Expanded Status Bar"),
|
||||
lambda: tr("Bring back the classic UI's wide offroad status strip: temperature, vehicle, and Konn3kt state at a glance."),
|
||||
),
|
||||
"TorqueBar": (
|
||||
"IQSteerEffortArc": (
|
||||
lambda: tr("Steering Effort Arc"),
|
||||
lambda: tr("Trace an arc over the road view showing how much steering IQ.Pilot is applying while lateral control runs."),
|
||||
),
|
||||
"RoadNameToggle": (
|
||||
"IQRoadNameOverlay": (
|
||||
lambda: tr("Road Name Overlay"),
|
||||
lambda: tr("Show the current road's name over the driving view."
|
||||
"<br>Requires offline map data for your region to be installed."),
|
||||
),
|
||||
"ShowTurnSignals": (
|
||||
"IQBlinkerIndicators": (
|
||||
lambda: tr("Blinker Indicators"),
|
||||
lambda: tr("Mirror the car's blinkers as arrows on the driving screen."),
|
||||
),
|
||||
"RocketFuel": (
|
||||
"IQAccelMeter": (
|
||||
lambda: tr("Acceleration Meter"),
|
||||
lambda: tr("Draw a bar along the left edge tracking measured acceleration and braking — what the car is actually "
|
||||
"doing right now, not the planner's request."),
|
||||
@@ -243,7 +243,7 @@ class VisualsLayout(Widget):
|
||||
title=lambda: tr("Lead Vehicle Readouts"),
|
||||
description="",
|
||||
buttons=[lambda: tr("Off"), lambda: tr("Distance"), lambda: tr("Speed"), lambda: tr("Time"), lambda: tr("All")],
|
||||
param="ChevronInfo",
|
||||
param="IQLeadReadouts",
|
||||
inline=False,
|
||||
)
|
||||
self._dev_ui_info = toggle_item(
|
||||
@@ -270,12 +270,12 @@ class VisualsLayout(Widget):
|
||||
def _sync_chevron_row(self):
|
||||
if ui_state.has_longitudinal_control:
|
||||
self._chevron_info.set_description(tr(CHEVRON_INFO_DESCRIPTION["enabled"]))
|
||||
self._chevron_info.action_item.set_selected_button(ui_state.params.get("ChevronInfo", return_default=True))
|
||||
self._chevron_info.action_item.set_selected_button(ui_state.params.get("IQLeadReadouts", return_default=True))
|
||||
self._chevron_info.action_item.set_enabled(True)
|
||||
else:
|
||||
self._chevron_info.set_description(tr(CHEVRON_INFO_DESCRIPTION["disabled"]))
|
||||
self._chevron_info.action_item.set_enabled(False)
|
||||
ui_state.params.put("ChevronInfo", 0)
|
||||
ui_state.params.put("IQLeadReadouts", 0)
|
||||
|
||||
def _update_state(self):
|
||||
super()._update_state()
|
||||
@@ -1388,7 +1388,7 @@ class IQDeviceLayout(DeviceLayout):
|
||||
DeviceLayout._initialize_items(self)
|
||||
|
||||
# Using dual button with no right button for better alignment
|
||||
self._always_offroad_btn = self._left_button(lambda: tr("Force Offroad Mode"), self._handle_always_offroad)
|
||||
self._always_offroad_btn = self._left_button(lambda: tr("Keep Device Offroad"), self._handle_always_offroad)
|
||||
self._force_onroad_btn = self._left_button(lambda: tr("Force On-Road (10 min)"), self._handle_force_onroad)
|
||||
|
||||
self._max_time_offroad = option_item(
|
||||
@@ -1592,7 +1592,7 @@ class IQDeviceLayout(DeviceLayout):
|
||||
force_onroad_active = force_onroad_until > now
|
||||
|
||||
# Text & Color
|
||||
offroad_mode_btn_text = tr("Exit Offroad Mode") if always_offroad else tr("Force Offroad Mode")
|
||||
offroad_mode_btn_text = tr("Exit Offroad Mode") if always_offroad else tr("Keep Device Offroad")
|
||||
offroad_mode_btn_style = ButtonStyle.PRIMARY if always_offroad else ButtonStyle.DANGER
|
||||
self._always_offroad_btn.action_item.left_button.set_text(offroad_mode_btn_text)
|
||||
self._always_offroad_btn.action_item.left_button.set_button_style(offroad_mode_btn_style)
|
||||
@@ -1957,12 +1957,12 @@ class ModelsLayout(Widget):
|
||||
return folders_list
|
||||
|
||||
def _handle_current_model_clicked(self):
|
||||
favs = ui_state.params.get("ModelManager_Favs")
|
||||
favs = ui_state.params.get("IQModelFavorites")
|
||||
favorites = set(favs.split(';')) if favs else set()
|
||||
folders_list = self._get_folders(favorites)
|
||||
|
||||
active_ref = self.model_manager.activeBundle.ref if self._has_active_bundle_param() and self.model_manager.activeBundle else "Default"
|
||||
self.model_dialog = PickerDialog(tr("Choose a Model"), folders_list, active_ref, "ModelManager_Favs",
|
||||
self.model_dialog = PickerDialog(tr("Choose a Model"), folders_list, active_ref, "IQModelFavorites",
|
||||
get_folders_fn=self._get_folders, on_exit=self._on_model_selected)
|
||||
gui_app.set_modal_overlay(self.model_dialog, callback=self._on_model_selected)
|
||||
|
||||
@@ -2191,8 +2191,8 @@ class HyundaiSettings(BrandPanel):
|
||||
self.longitudinal_tuning_item = multiple_button_item(
|
||||
tr("Longitudinal Tune Profile"), "",
|
||||
[tr("Off"), tr("Dynamic"), tr("Predictive")],
|
||||
button_width=300, param="HyundaiLongitudinalTuning", inline=False,
|
||||
callback=lambda index: ui_state.params.put("HyundaiLongitudinalTuning", index))
|
||||
button_width=300, param="IQHyundaiLongTune", inline=False,
|
||||
callback=lambda index: ui_state.params.put("IQHyundaiLongTune", index))
|
||||
self.items = [self.longitudinal_tuning_item]
|
||||
|
||||
def _alpha_long_supported(self) -> bool:
|
||||
@@ -2204,7 +2204,7 @@ class HyundaiSettings(BrandPanel):
|
||||
|
||||
def update_settings(self):
|
||||
self.alpha_long_available = self._alpha_long_supported()
|
||||
selected = int(ui_state.params.get("HyundaiLongitudinalTuning") or "0")
|
||||
selected = int(ui_state.params.get("IQHyundaiLongTune") or "0")
|
||||
|
||||
if not ui_state.is_offroad():
|
||||
desc, usable = tr("Unavailable while the car is onroad."), False
|
||||
@@ -2231,11 +2231,11 @@ class SubaruSettings(BrandPanel):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._supported = False
|
||||
self.stop_and_go_toggle = toggle_item(tr("Creep from Standstill (Beta)"), "", param="SubaruStopAndGo",
|
||||
self.stop_and_go_toggle = toggle_item(tr("Creep from Standstill (Beta)"), "", param="IQSubaruCreepAssist",
|
||||
callback=lambda _: self.update_settings())
|
||||
self.stop_and_go_manual_parking_brake_toggle = toggle_item(
|
||||
tr("Creep from Standstill — Manual Handbrake (Beta)"), "",
|
||||
param="SubaruStopAndGoManualParkingBrake", callback=lambda _: self.update_settings())
|
||||
param="IQSubaruCreepAssistManualBrake", callback=lambda _: self.update_settings())
|
||||
self.items = [self.stop_and_go_toggle, self.stop_and_go_manual_parking_brake_toggle]
|
||||
|
||||
def _platform_flags(self) -> int:
|
||||
@@ -2286,8 +2286,8 @@ def _speed_text(kmh: int) -> str:
|
||||
class TeslaSettings(BrandPanel):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.coop_steering_toggle = toggle_item(tr("VTB (Virtual Torque Blending)"), "", param="TeslaCoopSteering")
|
||||
self.items = [self.coop_steering_toggle]
|
||||
self.torque_blend_toggle = toggle_item(tr("VTB (Virtual Torque Blending)"), "", param="IQTeslaTorqueBlend")
|
||||
self.items = [self.torque_blend_toggle]
|
||||
|
||||
def update_settings(self):
|
||||
caution = tr("Warning: steering may oscillate in turns below {}; turn this off if you feel it.").format(
|
||||
@@ -2300,8 +2300,8 @@ class TeslaSettings(BrandPanel):
|
||||
blocker = tr("Flip on Always Offroad from the Device panel, or power the car down, to change this.")
|
||||
body = f"<b>{blocker}</b><br><br>{body}"
|
||||
|
||||
self.coop_steering_toggle.set_description(body)
|
||||
self.coop_steering_toggle.action_item.set_enabled(ui_state.is_offroad())
|
||||
self.torque_blend_toggle.set_description(body)
|
||||
self.torque_blend_toggle.action_item.set_enabled(ui_state.is_offroad())
|
||||
|
||||
|
||||
# ===== vehicle_brands_toyota =====
|
||||
@@ -2312,7 +2312,7 @@ class ToyotaSettings(BrandPanel):
|
||||
self.enforce_stock_longitudinal = toggle_item(
|
||||
lambda: tr("Keep Factory Gas and Brake"),
|
||||
description=lambda: tr("Keeps gas and brakes with the factory Toyota system; IQ.Pilot steers only."),
|
||||
initial_state=ui_state.params.get_bool("ToyotaEnforceStockLongitudinal"),
|
||||
initial_state=ui_state.params.get_bool("IQToyotaFactoryLong"),
|
||||
callback=self._on_toggled,
|
||||
enabled=lambda: not ui_state.engaged,
|
||||
)
|
||||
@@ -2320,7 +2320,7 @@ class ToyotaSettings(BrandPanel):
|
||||
|
||||
@staticmethod
|
||||
def _apply(enabled: bool):
|
||||
ui_state.params.put_bool("ToyotaEnforceStockLongitudinal", enabled)
|
||||
ui_state.params.put_bool("IQToyotaFactoryLong", enabled)
|
||||
if enabled and ui_state.params.get_bool("AlphaLongitudinalEnabled"):
|
||||
ui_state.params.put_bool("AlphaLongitudinalEnabled", False)
|
||||
ui_state.params.put_bool("OnroadCycleRequested", True)
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
from openpilot.iqpilot.ui.onroad.hud_overlays import ChevronMetrics
|
||||
from openpilot.iqpilot.ui.onroad.rainbow_path import RainbowPath
|
||||
|
||||
|
||||
class IQModelRenderer:
|
||||
def __init__(self):
|
||||
self.rainbow_path = RainbowPath()
|
||||
@@ -1,41 +0,0 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
import colorsys
|
||||
import time
|
||||
|
||||
import pyray as rl
|
||||
from openpilot.system.ui.lib.shader_polygon import draw_polygon, Gradient
|
||||
|
||||
# Scrolling spectrum along the driving path: a fixed set of stops from the
|
||||
# bottom (1.0) to the top (0.0) of the path, each a full-saturation swatch whose
|
||||
# hue advances with time and whose opacity thins toward the horizon.
|
||||
_SEGMENTS = 8
|
||||
_SCROLL_DEG_PER_S = 50.0
|
||||
_SATURATION = 0.9
|
||||
_LIGHTNESS = 0.6
|
||||
_ALPHA_NEAR = 0.8 # opacity at the bottom of the path
|
||||
_ALPHA_HORIZON_FRACTION = 0.3 # how much of that opacity is shed by the top
|
||||
|
||||
# Stop offsets are constant, so resolve them once.
|
||||
_STOP_OFFSETS = tuple(i / (_SEGMENTS - 1) for i in range(_SEGMENTS))
|
||||
|
||||
|
||||
def _swatch(hue_turns: float, alpha: float) -> rl.Color:
|
||||
r, g, b = colorsys.hls_to_rgb(hue_turns, _LIGHTNESS, _SATURATION)
|
||||
return rl.Color(int(r * 255), int(g * 255), int(b * 255), int(alpha * 255))
|
||||
|
||||
|
||||
def _spectrum_gradient() -> Gradient:
|
||||
scroll_deg = (time.monotonic() * _SCROLL_DEG_PER_S) % 360.0
|
||||
colors = []
|
||||
for offset in _STOP_OFFSETS:
|
||||
hue_deg = (scroll_deg + offset * 360.0) % 360.0
|
||||
alpha = _ALPHA_NEAR * (1.0 - offset * _ALPHA_HORIZON_FRACTION)
|
||||
colors.append(_swatch(hue_deg / 360.0, alpha))
|
||||
return Gradient(start=(0.0, 1.0), end=(0.0, 0.0), colors=colors, stops=list(_STOP_OFFSETS))
|
||||
|
||||
|
||||
class RainbowPath:
|
||||
def draw_rainbow_path(self, rect, path):
|
||||
draw_polygon(rect, path.projected_points, gradient=_spectrum_gradient())
|
||||
Reference in New Issue
Block a user