diff --git a/kiauh/components/klipper/klipper_utils.py b/kiauh/components/klipper/klipper_utils.py index 918d0f1..6c75c68 100644 --- a/kiauh/components/klipper/klipper_utils.py +++ b/kiauh/components/klipper/klipper_utils.py @@ -43,7 +43,11 @@ from utils.common import check_install_dependencies, get_install_status from utils.fs_utils import check_file_exist from utils.input_utils import get_confirm, get_number_input, get_string_input from utils.instance_utils import get_instances -from utils.sys_utils import cmd_sysctl_service, parse_packages_from_file +from utils.sys_utils import ( + cmd_sysctl_service, + install_python_packages, + parse_packages_from_file, +) def get_klipper_status() -> ComponentStatus: @@ -211,3 +215,42 @@ def install_klipper_packages() -> None: packages.append("dbus") check_install_dependencies({*packages}) + + +def install_input_shaper_deps() -> None: + if not KLIPPER_ENV_DIR.exists(): + Logger.print_warn("Required Klipper python environment not found!") + return + + Logger.print_dialog( + DialogType.CUSTOM, + [ + "Resonance measurements and shaper auto-calibration require additional " + "software dependencies which are not installed by default. " + "If you agree, the following additional system packages will be installed:", + "● python3-numpy", + "● python3-matplotlib", + "● libatlas-base-dev", + "● libopenblas-dev", + "\n\n", + "Also, the following Python package will be installed:", + "● numpy", + ], + custom_title="Install Input Shaper Dependencies", + ) + if not get_confirm( + "Do you want to install the required packages?", default_choice=False + ): + return + + apt_deps = ( + "python3-numpy", + "python3-matplotlib", + "libatlas-base-dev", + "libopenblas-dev", + ) + check_install_dependencies({*apt_deps}) + + py_deps = ("numpy",) + + install_python_packages(KLIPPER_ENV_DIR, {*py_deps}) diff --git a/kiauh/core/menus/advanced_menu.py b/kiauh/core/menus/advanced_menu.py index 543d7b4..7311d39 100644 --- a/kiauh/core/menus/advanced_menu.py +++ b/kiauh/core/menus/advanced_menu.py @@ -13,6 +13,7 @@ from typing import Type from components.klipper import KLIPPER_DIR from components.klipper.klipper import Klipper +from components.klipper.klipper_utils import install_input_shaper_deps from components.klipper_firmware.menus.klipper_build_menu import ( KlipperBuildFirmwareMenu, KlipperKConfigMenu, @@ -50,9 +51,10 @@ class AdvancedMenu(BaseMenu): "2": Option(method=self.flash), "3": Option(method=self.build_flash), "4": Option(method=self.get_id), - "5": Option(method=self.klipper_rollback), - "6": Option(method=self.moonraker_rollback), - "7": Option(method=self.change_hostname), + "5": Option(method=self.input_shaper), + "6": Option(method=self.klipper_rollback), + "7": Option(method=self.moonraker_rollback), + "8": Option(method=self.change_hostname), } def print_menu(self) -> None: @@ -60,11 +62,13 @@ class AdvancedMenu(BaseMenu): """ ╟───────────────────────────┬───────────────────────────╢ ║ Klipper Firmware: │ Repository Rollback: ║ - ║ 1) [Build] │ 5) [Klipper] ║ - ║ 2) [Flash] │ 6) [Moonraker] ║ + ║ 1) [Build] │ 6) [Klipper] ║ + ║ 2) [Flash] │ 7) [Moonraker] ║ ║ 3) [Build + Flash] │ ║ ║ 4) [Get MCU ID] │ System: ║ - ║ │ 7) [Change hostname] ║ + ║ │ 8) [Change hostname] ║ + ║ Extra Dependencies: │ ║ + ║ 5) [Input Shaper] │ ║ ╟───────────────────────────┴───────────────────────────╢ """ )[1:] @@ -97,3 +101,6 @@ class AdvancedMenu(BaseMenu): def change_hostname(self, **kwargs) -> None: change_system_hostname() + + def input_shaper(self, **kwargs) -> None: + install_input_shaper_deps() diff --git a/kiauh/utils/sys_utils.py b/kiauh/utils/sys_utils.py index 5a83933..f2bcf70 100644 --- a/kiauh/utils/sys_utils.py +++ b/kiauh/utils/sys_utils.py @@ -197,6 +197,38 @@ def install_python_requirements(target: Path, requirements: Path) -> None: raise VenvCreationFailedException(log) +def install_python_packages(target: Path, packages: List[str]) -> None: + """ + Installs the python packages based on a provided packages list | + :param target: Path of the virtualenv + :param packages: str list of required packages + :return: None + """ + try: + # always update pip before installing requirements + update_python_pip(target) + + Logger.print_status("Installing Python requirements ...") + command = [ + target.joinpath("bin/pip").as_posix(), + "install", + ] + for pkg in packages: + command.append(pkg) + result = run(command, stderr=PIPE, text=True) + + if result.returncode != 0 or result.stderr: + Logger.print_error(f"{result.stderr}", False) + raise VenvCreationFailedException("Installing Python requirements failed!") + + Logger.print_ok("Installing Python requirements successful!") + + except Exception as e: + log = f"Error installing Python requirements: {e}" + Logger.print_error(log) + raise VenvCreationFailedException(log) + + def update_system_package_lists(silent: bool, rls_info_change=False) -> None: """ Updates the systems package list |