diff --git a/.gitattributes b/.gitattributes index eda2505d0e..1384edb626 100644 --- a/.gitattributes +++ b/.gitattributes @@ -11,7 +11,6 @@ selfdrive/car/tests/test_models_segs.txt filter=lfs diff=lfs merge=lfs -text system/hardware/tici/updater filter=lfs diff=lfs merge=lfs -text -selfdrive/ui/qt/spinner_larch64 filter=lfs diff=lfs merge=lfs -text selfdrive/ui/qt/text_larch64 filter=lfs diff=lfs merge=lfs -text third_party/**/*.a filter=lfs diff=lfs merge=lfs -text third_party/**/*.so filter=lfs diff=lfs merge=lfs -text diff --git a/common/spinner.py b/common/spinner.py deleted file mode 100644 index dcf22641c4..0000000000 --- a/common/spinner.py +++ /dev/null @@ -1,52 +0,0 @@ -import os -import subprocess -from openpilot.common.basedir import BASEDIR - - -class Spinner: - def __init__(self): - try: - self.spinner_proc = subprocess.Popen(["./spinner"], - stdin=subprocess.PIPE, - cwd=os.path.join(BASEDIR, "selfdrive", "ui"), - close_fds=True) - except OSError: - self.spinner_proc = None - - def __enter__(self): - return self - - def update(self, spinner_text: str): - if self.spinner_proc is not None: - self.spinner_proc.stdin.write(spinner_text.encode('utf8') + b"\n") - try: - self.spinner_proc.stdin.flush() - except BrokenPipeError: - pass - - def update_progress(self, cur: float, total: float): - self.update(str(round(100 * cur / total))) - - def close(self): - if self.spinner_proc is not None: - self.spinner_proc.kill() - try: - self.spinner_proc.communicate(timeout=2.) - except subprocess.TimeoutExpired: - print("WARNING: failed to kill spinner") - self.spinner_proc = None - - def __del__(self): - self.close() - - def __exit__(self, exc_type, exc_value, traceback): - self.close() - - -if __name__ == "__main__": - import time - with Spinner() as s: - s.update("Spinner text") - time.sleep(5.0) - print("gone") - time.sleep(5.0) diff --git a/selfdrive/ui/.gitignore b/selfdrive/ui/.gitignore index f9724816da..d9176a3fd4 100644 --- a/selfdrive/ui/.gitignore +++ b/selfdrive/ui/.gitignore @@ -4,7 +4,6 @@ moc_* translations/main_test_en.* _text -_spinner ui mui diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 8fa4b55ea7..233addaa6a 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -66,9 +66,8 @@ if GetOption('extras'): qt_env.SharedLibrary("qt/python_helpers", ["qt/qt_window.cc"], LIBS=qt_libs) - # spinner and text window + # text window qt_env.Program("_text", ["qt/text.cc"], LIBS=qt_libs) - qt_env.Program("_spinner", ["qt/spinner.cc"], LIBS=qt_libs) # setup and factory resetter qt_env.Program("qt/setup/reset", ["qt/setup/reset.cc"], LIBS=qt_libs) diff --git a/selfdrive/ui/qt/spinner.cc b/selfdrive/ui/qt/spinner.cc deleted file mode 100644 index 2404efa668..0000000000 --- a/selfdrive/ui/qt/spinner.cc +++ /dev/null @@ -1,120 +0,0 @@ -#include "selfdrive/ui/qt/spinner.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "system/hardware/hw.h" -#include "selfdrive/ui/qt/qt_window.h" -#include "selfdrive/ui/qt/util.h" - -TrackWidget::TrackWidget(QWidget *parent) : QWidget(parent) { - setAttribute(Qt::WA_OpaquePaintEvent); - setFixedSize(spinner_size); - - // pre-compute all the track imgs. make this a gif instead? - QPixmap comma_img = loadPixmap("../assets/img_spinner_comma.png", spinner_size); - QPixmap track_img = loadPixmap("../assets/img_spinner_track.png", spinner_size); - - QTransform transform(1, 0, 0, 1, width() / 2, height() / 2); - QPixmap pm(spinner_size); - QPainter p(&pm); - p.setRenderHint(QPainter::SmoothPixmapTransform); - for (int i = 0; i < track_imgs.size(); ++i) { - p.resetTransform(); - p.fillRect(0, 0, spinner_size.width(), spinner_size.height(), Qt::black); - p.drawPixmap(0, 0, comma_img); - p.setTransform(transform.rotate(360 / spinner_fps)); - p.drawPixmap(-width() / 2, -height() / 2, track_img); - track_imgs[i] = pm.copy(); - } - - m_anim.setDuration(1000); - m_anim.setStartValue(0); - m_anim.setEndValue(int(track_imgs.size() -1)); - m_anim.setLoopCount(-1); - m_anim.start(); - connect(&m_anim, SIGNAL(valueChanged(QVariant)), SLOT(update())); -} - -void TrackWidget::paintEvent(QPaintEvent *event) { - QPainter painter(this); - painter.drawPixmap(0, 0, track_imgs[m_anim.currentValue().toInt()]); -} - -// Spinner - -Spinner::Spinner(QWidget *parent) : QWidget(parent) { - QGridLayout *main_layout = new QGridLayout(this); - main_layout->setSpacing(0); - main_layout->setMargin(200); - - main_layout->addWidget(new TrackWidget(this), 0, 0, Qt::AlignHCenter | Qt::AlignVCenter); - - text = new QLabel(); - text->setWordWrap(true); - text->setVisible(false); - text->setAlignment(Qt::AlignCenter); - main_layout->addWidget(text, 1, 0, Qt::AlignHCenter); - - progress_bar = new QProgressBar(); - progress_bar->setRange(5, 100); - progress_bar->setTextVisible(false); - progress_bar->setVisible(false); - progress_bar->setFixedHeight(20); - main_layout->addWidget(progress_bar, 1, 0, Qt::AlignHCenter); - - setStyleSheet(R"( - Spinner { - background-color: black; - } - QLabel { - color: white; - font-size: 80px; - background-color: transparent; - } - QProgressBar { - background-color: #373737; - width: 1000px; - border solid white; - border-radius: 10px; - } - QProgressBar::chunk { - border-radius: 10px; - background-color: white; - } - )"); - - notifier = new QSocketNotifier(fileno(stdin), QSocketNotifier::Read); - QObject::connect(notifier, &QSocketNotifier::activated, this, &Spinner::update); -} - -void Spinner::update(int n) { - std::string line; - std::getline(std::cin, line); - - if (line.length()) { - bool number = std::all_of(line.begin(), line.end(), ::isdigit); - text->setVisible(!number); - progress_bar->setVisible(number); - text->setText(QString::fromStdString(line)); - if (number) { - progress_bar->setValue(std::stoi(line)); - } - } -} - -int main(int argc, char *argv[]) { - initApp(argc, argv); - QApplication a(argc, argv); - Spinner spinner; - setMainWindow(&spinner); - return a.exec(); -} diff --git a/selfdrive/ui/qt/spinner.h b/selfdrive/ui/qt/spinner.h deleted file mode 100644 index 43d90a75b0..0000000000 --- a/selfdrive/ui/qt/spinner.h +++ /dev/null @@ -1,37 +0,0 @@ -#include - -#include -#include -#include -#include -#include -#include - -constexpr int spinner_fps = 30; -constexpr QSize spinner_size = QSize(360, 360); - -class TrackWidget : public QWidget { - Q_OBJECT -public: - TrackWidget(QWidget *parent = nullptr); - -private: - void paintEvent(QPaintEvent *event) override; - std::array track_imgs; - QVariantAnimation m_anim; -}; - -class Spinner : public QWidget { - Q_OBJECT - -public: - explicit Spinner(QWidget *parent = 0); - -private: - QLabel *text; - QProgressBar *progress_bar; - QSocketNotifier *notifier; - -public slots: - void update(int n); -}; diff --git a/selfdrive/ui/qt/spinner_larch64 b/selfdrive/ui/qt/spinner_larch64 deleted file mode 100755 index 74081a6925..0000000000 --- a/selfdrive/ui/qt/spinner_larch64 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:81d7073d16e8ddc40d4d81fc88f8fc11c434df241593b455e2787935371383e4 -size 3821728 diff --git a/selfdrive/ui/spinner b/selfdrive/ui/spinner deleted file mode 100755 index 965c8f581a..0000000000 --- a/selfdrive/ui/spinner +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh - -if [ -f /TICI ] && [ ! -f _spinner ]; then - cp qt/spinner_larch64 _spinner -fi - -exec ./_spinner "$1" diff --git a/system/athena/registration.py b/system/athena/registration.py index 964fbff51e..e48ae64aea 100755 --- a/system/athena/registration.py +++ b/system/athena/registration.py @@ -7,10 +7,10 @@ from pathlib import Path from datetime import datetime, timedelta, UTC from openpilot.common.api import api_get from openpilot.common.params import Params -from openpilot.common.spinner import Spinner from openpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert from openpilot.system.hardware import HARDWARE, PC from openpilot.system.hardware.hw import Paths +from openpilot.system.ui.spinner import Spinner from openpilot.common.swaglog import cloudlog diff --git a/system/manager/build.py b/system/manager/build.py index 93c0546c85..21eff2fecc 100755 --- a/system/manager/build.py +++ b/system/manager/build.py @@ -5,10 +5,10 @@ from pathlib import Path # NOTE: Do NOT import anything here that needs be built (e.g. params) from openpilot.common.basedir import BASEDIR -from openpilot.common.spinner import Spinner from openpilot.common.text_window import TextWindow -from openpilot.system.hardware import HARDWARE, AGNOS from openpilot.common.swaglog import cloudlog, add_file_handler +from openpilot.system.hardware import HARDWARE, AGNOS +from openpilot.system.ui.spinner import Spinner from openpilot.system.version import get_build_metadata MAX_CACHE_SIZE = 4e9 if "CI" in os.environ else 2e9 @@ -88,7 +88,7 @@ def build(spinner: Spinner, dirty: bool = False, minimal: bool = False) -> None: if __name__ == "__main__": - spinner = Spinner() - spinner.update_progress(0, 100) - build_metadata = get_build_metadata() - build(spinner, build_metadata.openpilot.is_dirty, minimal = AGNOS) + with Spinner() as spinner: + spinner.update_progress(0, 100) + build_metadata = get_build_metadata() + build(spinner, build_metadata.openpilot.is_dirty, minimal = AGNOS) diff --git a/system/ui/spinner.py b/system/ui/spinner.py index 9c1882cf5f..5da96c73f9 100755 --- a/system/ui/spinner.py +++ b/system/ui/spinner.py @@ -2,6 +2,7 @@ import pyray as rl import os import threading +import time from openpilot.common.basedir import BASEDIR from openpilot.system.ui.lib.application import gui_app @@ -13,8 +14,8 @@ PROGRESS_BAR_HEIGHT = 20 DEGREES_PER_SECOND = 360.0 # one full rotation per second MARGIN_H = 100 TEXTURE_SIZE = 360 -FONT_SIZE = 88 -LINE_HEIGHT = 96 +FONT_SIZE = 96 +LINE_HEIGHT = 104 DARKGRAY = (55, 55, 55, 255) @@ -22,7 +23,7 @@ def clamp(value, min_value, max_value): return max(min(value, max_value), min_value) -class Spinner: +class SpinnerRenderer: def __init__(self): self._comma_texture = gui_app.load_texture_from_image(os.path.join(BASEDIR, "selfdrive/assets/img_spinner_comma.png"), TEXTURE_SIZE, TEXTURE_SIZE) self._spinner_texture = gui_app.load_texture_from_image(os.path.join(BASEDIR, "selfdrive/assets/img_spinner_track.png"), TEXTURE_SIZE, TEXTURE_SIZE, @@ -70,7 +71,7 @@ class Spinner: spinner_origin, self._rotation, rl.WHITE) rl.draw_texture_v(self._comma_texture, comma_position, rl.WHITE) - # Display progress bar or text based on user input + # Display the progress bar or text based on user input if progress is not None: bar = rl.Rectangle(center.x - PROGRESS_BAR_WIDTH / 2.0, y_pos, PROGRESS_BAR_WIDTH, PROGRESS_BAR_HEIGHT) rl.draw_rectangle_rounded(bar, 1, 10, DARKGRAY) @@ -84,9 +85,55 @@ class Spinner: FONT_SIZE, 0.0, rl.WHITE) +class Spinner: + def __init__(self): + self._renderer: SpinnerRenderer | None = None + self._stop_event = threading.Event() + self._thread = threading.Thread(target=self._run) + self._thread.start() + + # wait for the renderer to be initialized + while self._renderer is None and self._thread.is_alive(): + time.sleep(0.01) + + def update(self, spinner_text: str): + if self._renderer is not None: + self._renderer.set_text(spinner_text) + + def update_progress(self, cur: float, total: float): + self.update(str(round(100 * cur / total))) + + def _run(self): + if os.getenv("CI") is not None: + return + gui_app.init_window("Spinner") + self._renderer = renderer = SpinnerRenderer() + try: + for _ in gui_app.render(): + if self._stop_event.is_set(): + break + renderer.render() + finally: + gui_app.close() + + def __enter__(self): + return self + + def close(self): + if self._thread.is_alive(): + self._stop_event.set() + self._thread.join(timeout=2.0) + if self._thread.is_alive(): + print("WARNING: failed to join spinner thread") + + def __del__(self): + self.close() + + def __exit__(self, exc_type, exc_val, exc_tb): + self.close() + + if __name__ == "__main__": - gui_app.init_window("Spinner") - spinner = Spinner() - spinner.set_text("Spinner text") - for _ in gui_app.render(): - spinner.render() + with Spinner() as s: + s.update("Spinner text") + time.sleep(5)