mirror of
https://github.com/dw-0/kiauh.git
synced 2026-05-05 00:38:15 +05:00
feat(extensions): Klipper-Adaptive-Meshing-Purging (#785)
* feat(instance_manager): add interactive stopping of Klipper instances with user confirmation * fix(instance_utils): remove unnecessary 'self' parameter from stop_klipper_instances_interactively function * refactor(tmc_autotune): replace internal stop function with direct call to stop_klipper_instances_interactively * feat(klipper_adaptive_meshing_purging): add initial implementation of Klipper Adaptive Meshing and Purging extension * docs(klipper_adaptive_meshing_purging): update warning message with additional documentation note regarding max_extrude_cross_section * fix(klipper_adaptive_meshing_purging): update warning message formatting for clarity * fix(klipper_adaptive_meshing_purging): update warning message formatting for clarity * fix(_install_cfg): update symlink creation to point to Configuration directory * fix(license): update copyright information to include myself & upstream * fix(klipper_adaptive_meshing_purging): update moonraker updater name for consistency with upstream * style: add type annotations - Correct minor formatting issues in copyright headers. * fix: update import path for `simple_config_parser` * refactor: improve logging and configuration handling for KAMP installation and removal * style: clarify warning message for purge settings in KAMP extension --------- Co-authored-by: dw-0 <th33xitus@gmail.com>
This commit is contained in:
@@ -0,0 +1,21 @@
|
|||||||
|
# ======================================================================= #
|
||||||
|
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
|
||||||
|
# Copyright (C) 2026 Théo Gaillard <theo.gayar@gmail.com> #
|
||||||
|
# #
|
||||||
|
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||||
|
# https://github.com/dw-0/kiauh #
|
||||||
|
# #
|
||||||
|
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||||
|
# ======================================================================= #
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# repo
|
||||||
|
KAMP_REPO = "https://github.com/kyleisah/Klipper-Adaptive-Meshing-Purging"
|
||||||
|
|
||||||
|
# directories
|
||||||
|
MODULE_PATH = Path(__file__).resolve().parent
|
||||||
|
KAMP_DIR = Path.home().joinpath("Klipper-Adaptive-Meshing-Purging")
|
||||||
|
KLIPPER_DIR = Path.home().joinpath("klipper")
|
||||||
|
|
||||||
|
# names
|
||||||
|
KAMP_MOONRAKER_UPDATER_NAME = "update_manager Klipper-Adaptive-Meshing-Purging"
|
||||||
+357
@@ -0,0 +1,357 @@
|
|||||||
|
# ======================================================================= #
|
||||||
|
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
|
||||||
|
# Copyright (C) 2026 Théo Gaillard <theo.gayar@gmail.com> #
|
||||||
|
# #
|
||||||
|
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||||
|
# https://github.com/dw-0/kiauh #
|
||||||
|
# #
|
||||||
|
# It integrates Klipper Adaptive Meshing & Purging (KAMP): #
|
||||||
|
# https://github.com/kyleisah/Klipper-Adaptive-Meshing-Purging #
|
||||||
|
# #
|
||||||
|
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||||
|
# ======================================================================= #
|
||||||
|
|
||||||
|
import shutil
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from components.klipper.klipper import Klipper
|
||||||
|
from components.moonraker.moonraker import Moonraker
|
||||||
|
from core.instance_manager.instance_manager import InstanceManager
|
||||||
|
from core.logger import DialogType, Logger
|
||||||
|
from core.services.backup_service import BackupService
|
||||||
|
from core.simple_config_parser.simple_config_parser import (
|
||||||
|
SimpleConfigParser,
|
||||||
|
)
|
||||||
|
from extensions.base_extension import BaseExtension
|
||||||
|
from extensions.klipper_adaptive_meshing_purging import (
|
||||||
|
KAMP_DIR,
|
||||||
|
KAMP_MOONRAKER_UPDATER_NAME,
|
||||||
|
KAMP_REPO,
|
||||||
|
KLIPPER_DIR,
|
||||||
|
)
|
||||||
|
from utils.config_utils import add_config_section, remove_config_section
|
||||||
|
from utils.fs_utils import check_file_exist, create_symlink, run_remove_routines
|
||||||
|
from utils.git_utils import git_clone_wrapper, git_pull_wrapper
|
||||||
|
from utils.input_utils import get_confirm
|
||||||
|
from utils.instance_utils import get_instances, stop_klipper_instances_interactively
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyMethodMayBeStatic
|
||||||
|
class KlipperAdaptiveMeshingPurgingExtension(BaseExtension):
|
||||||
|
def install_extension(self, **kwargs) -> None:
|
||||||
|
Logger.print_status("Installing Klipper Adaptive Meshing Purging...")
|
||||||
|
|
||||||
|
klipper_dir_exists = check_file_exist(KLIPPER_DIR)
|
||||||
|
if not klipper_dir_exists:
|
||||||
|
Logger.print_warn(
|
||||||
|
"No Klipper directory found! Unable to install extension."
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
kl_instances: List[Klipper] = get_instances(Klipper)
|
||||||
|
|
||||||
|
kamp_exists = check_file_exist(KAMP_DIR) and self._check_cfg_exists(
|
||||||
|
kl_instances
|
||||||
|
)
|
||||||
|
|
||||||
|
overwrite = True
|
||||||
|
if kamp_exists:
|
||||||
|
overwrite = get_confirm(
|
||||||
|
question="Extension seems to be installed already. Overwrite?",
|
||||||
|
default_choice=True,
|
||||||
|
allow_go_back=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not overwrite:
|
||||||
|
Logger.print_warn("Installation aborted due to user request.")
|
||||||
|
return
|
||||||
|
|
||||||
|
add_moonraker_update_section = get_confirm(
|
||||||
|
question="Add Klipper Adaptive Meshing and Purging to Moonraker update manager(s)?",
|
||||||
|
default_choice=True,
|
||||||
|
allow_go_back=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not stop_klipper_instances_interactively(
|
||||||
|
kl_instances, "installation of KAMP"
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
git_clone_wrapper(KAMP_REPO, KAMP_DIR, force=True)
|
||||||
|
|
||||||
|
self._install_cfg(kl_instances)
|
||||||
|
|
||||||
|
if add_moonraker_update_section:
|
||||||
|
mr_instances: List[Moonraker] = get_instances(Moonraker)
|
||||||
|
self._add_moonraker_update_manager_section(mr_instances)
|
||||||
|
else:
|
||||||
|
Logger.print_info(
|
||||||
|
"Skipping update section creation as per user request."
|
||||||
|
)
|
||||||
|
Logger.print_warn(
|
||||||
|
"Make sure to create the corresponding section in your moonraker.conf in order to have it appear in your frontend update manager!"
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
Logger.print_error(
|
||||||
|
f"Error during Klipper Adaptive Meshing and Purging installation:\n{e}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if kl_instances:
|
||||||
|
InstanceManager.start_all(kl_instances)
|
||||||
|
return
|
||||||
|
|
||||||
|
if kl_instances:
|
||||||
|
InstanceManager.start_all(kl_instances)
|
||||||
|
|
||||||
|
Logger.print_dialog(
|
||||||
|
DialogType.ATTENTION,
|
||||||
|
[
|
||||||
|
"Basic configuration files were created per instance. You must edit them to enable the extension.",
|
||||||
|
"Documentation:",
|
||||||
|
f"{KAMP_REPO}",
|
||||||
|
"\n\n",
|
||||||
|
"IMPORTANT:",
|
||||||
|
"1. If you'd like to use adaptive meshing, Klipper already has built-in support. Just call BED_MESH_CALIBRATE ADAPTIVE=1 in your PRINT_START macro. DO NOT USE THE FEATURE FROM THE EXTENSION",
|
||||||
|
"2. You MUST be thoughtful when editing values for the purge settings, as there is a risk to break parts of your printer.",
|
||||||
|
"3. According to KAMP's documentation, you should define 'max_extrude_cross_section' in 'printer.cfg' according to your needs.",
|
||||||
|
],
|
||||||
|
margin_bottom=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
Logger.print_ok("Klipper Adaptive Meshing and Purging installed successfully!")
|
||||||
|
|
||||||
|
def update_extension(self, **kwargs) -> None:
|
||||||
|
extension_installed = check_file_exist(KAMP_DIR)
|
||||||
|
if not extension_installed:
|
||||||
|
Logger.print_info("Extension does not seem to be installed! Skipping ...")
|
||||||
|
return
|
||||||
|
|
||||||
|
backup_before_update = get_confirm(
|
||||||
|
question="Backup Klipper Adaptive Meshing and Purging directory before update?",
|
||||||
|
default_choice=True,
|
||||||
|
allow_go_back=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
kl_instances: List[Klipper] = get_instances(Klipper)
|
||||||
|
|
||||||
|
if not stop_klipper_instances_interactively(kl_instances, "update of KAMP"):
|
||||||
|
return
|
||||||
|
|
||||||
|
Logger.print_status("Updating Klipper Adaptive Meshing and Purging...")
|
||||||
|
try:
|
||||||
|
if backup_before_update:
|
||||||
|
Logger.print_status(
|
||||||
|
"Backing up Klipper Adaptive Meshing and Purging directory ..."
|
||||||
|
)
|
||||||
|
svc = BackupService()
|
||||||
|
svc.backup_directory(
|
||||||
|
source_path=KAMP_DIR,
|
||||||
|
backup_name="Klipper-Adaptive-Meshing-Purging",
|
||||||
|
)
|
||||||
|
Logger.print_ok("Backup completed successfully.")
|
||||||
|
|
||||||
|
git_pull_wrapper(KAMP_DIR)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
Logger.print_error(
|
||||||
|
f"Error during Klipper Adaptive Meshing and Purging update:\n{e}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if kl_instances:
|
||||||
|
InstanceManager.start_all(kl_instances)
|
||||||
|
return
|
||||||
|
|
||||||
|
if kl_instances:
|
||||||
|
InstanceManager.start_all(kl_instances)
|
||||||
|
|
||||||
|
Logger.print_ok(
|
||||||
|
"Klipper Adaptive Meshing and Purging updated successfully.", end="\n\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
def remove_extension(self, **kwargs) -> None:
|
||||||
|
extension_installed = check_file_exist(KAMP_DIR)
|
||||||
|
if not extension_installed:
|
||||||
|
Logger.print_info("Extension does not seem to be installed! Skipping ...")
|
||||||
|
return
|
||||||
|
|
||||||
|
kl_instances: List[Klipper] = get_instances(Klipper)
|
||||||
|
|
||||||
|
if not stop_klipper_instances_interactively(kl_instances, "removal of KAMP"):
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
run_remove_routines(KAMP_DIR)
|
||||||
|
self._remove_cfg(kl_instances)
|
||||||
|
|
||||||
|
mr_instances: List[Moonraker] = get_instances(Moonraker)
|
||||||
|
self._remove_moonraker_update_manager_section(mr_instances)
|
||||||
|
|
||||||
|
BackupService().backup_printer_cfg()
|
||||||
|
remove_config_section("include KAMP_Settings.cfg", kl_instances)
|
||||||
|
|
||||||
|
Logger.print_dialog(
|
||||||
|
DialogType.ATTENTION,
|
||||||
|
[
|
||||||
|
"You might want to remove [exclude_object] sections from 'printer.cfg', unless you use them for some other reason.",
|
||||||
|
"\n\n",
|
||||||
|
"You might also want to remove the [file_manager] sections from 'moonraker.conf', unless used otherwise.",
|
||||||
|
"\n\n",
|
||||||
|
"NOTE:",
|
||||||
|
"'KAMP_Settings.cfg' is NOT removed automatically. ",
|
||||||
|
"Please delete it manually if no longer needed.",
|
||||||
|
],
|
||||||
|
margin_bottom=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
Logger.print_error(f"Unable to remove extension:\n{e}")
|
||||||
|
|
||||||
|
if kl_instances:
|
||||||
|
InstanceManager.start_all(kl_instances)
|
||||||
|
return
|
||||||
|
|
||||||
|
if kl_instances:
|
||||||
|
InstanceManager.start_all(kl_instances)
|
||||||
|
|
||||||
|
Logger.print_ok("Klipper Adaptive Meshing and Purging removed successfully.")
|
||||||
|
|
||||||
|
def _install_cfg(self, kl_instances: List[Klipper]):
|
||||||
|
is_multi_instance = len(kl_instances) > 1
|
||||||
|
for instance in kl_instances:
|
||||||
|
cfg_dir = instance.base.cfg_dir
|
||||||
|
Logger.print_status(
|
||||||
|
f"Creating symlink for KAMP directory in '{cfg_dir}' ..."
|
||||||
|
)
|
||||||
|
create_symlink(KAMP_DIR.joinpath("Configuration"), cfg_dir.joinpath("KAMP"))
|
||||||
|
if is_multi_instance:
|
||||||
|
Logger.print_ok(f"Symlink successfully created for instance '{instance.suffix}'.")
|
||||||
|
else:
|
||||||
|
Logger.print_ok("Symlink successfully created.")
|
||||||
|
|
||||||
|
# We do not overwrite the existing config files ever
|
||||||
|
Logger.print_status(f"Creating KAMP_Settings.cfg in '{cfg_dir}' ...")
|
||||||
|
if check_file_exist(cfg_dir.joinpath("KAMP_Settings.cfg")):
|
||||||
|
Logger.print_info("File already exists! Skipping ...")
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
shutil.copy(
|
||||||
|
KAMP_DIR.joinpath("Configuration/KAMP_Settings.cfg"),
|
||||||
|
cfg_dir.joinpath("KAMP_Settings.cfg"),
|
||||||
|
)
|
||||||
|
if is_multi_instance:
|
||||||
|
Logger.print_ok(f"Config file successfully created for instance '{instance.suffix}'.")
|
||||||
|
else:
|
||||||
|
Logger.print_ok(f"Config file successfully created.")
|
||||||
|
except OSError as e:
|
||||||
|
Logger.print_error(f"Unable to create example config: {e}")
|
||||||
|
|
||||||
|
BackupService().backup_printer_cfg()
|
||||||
|
|
||||||
|
sections = ["include KAMP_Settings.cfg", "exclude_object"]
|
||||||
|
for instance in kl_instances:
|
||||||
|
cfg_file = instance.cfg_file
|
||||||
|
scp = SimpleConfigParser()
|
||||||
|
scp.read_file(cfg_file)
|
||||||
|
|
||||||
|
for section in sections:
|
||||||
|
if scp.has_section(section):
|
||||||
|
continue
|
||||||
|
Logger.print_status(f"Add '{section}' to '{cfg_file}' ...")
|
||||||
|
scp.add_section(section)
|
||||||
|
scp.write_file(cfg_file)
|
||||||
|
Logger.print_ok("Done!")
|
||||||
|
|
||||||
|
def _add_moonraker_update_manager_section(
|
||||||
|
self, mr_instances: List[Moonraker]
|
||||||
|
) -> None:
|
||||||
|
if not mr_instances:
|
||||||
|
Logger.print_dialog(
|
||||||
|
DialogType.WARNING,
|
||||||
|
[
|
||||||
|
"Moonraker not found! Klipper Adaptive Meshing and Purging update "
|
||||||
|
"manager support for Moonraker will not be added to moonraker.conf.",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
if not get_confirm(
|
||||||
|
"Continue Klipper Adaptive Meshing and Purging installation?",
|
||||||
|
default_choice=False,
|
||||||
|
allow_go_back=True,
|
||||||
|
):
|
||||||
|
Logger.print_info("Installation aborted due to user request.")
|
||||||
|
return
|
||||||
|
|
||||||
|
BackupService().backup_moonraker_conf()
|
||||||
|
|
||||||
|
add_config_section(
|
||||||
|
section=KAMP_MOONRAKER_UPDATER_NAME,
|
||||||
|
instances=mr_instances,
|
||||||
|
options=[
|
||||||
|
("type", "git_repo"),
|
||||||
|
("channel", "dev"),
|
||||||
|
("path", KAMP_DIR.as_posix()),
|
||||||
|
("origin", KAMP_REPO),
|
||||||
|
("managed_services", "klipper"),
|
||||||
|
("primary_branch", "main"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
add_config_section(
|
||||||
|
section="file_manager",
|
||||||
|
instances=mr_instances,
|
||||||
|
options=[
|
||||||
|
("enable_object_processing", "True"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
InstanceManager.restart_all(mr_instances)
|
||||||
|
|
||||||
|
Logger.print_ok(
|
||||||
|
"Klipper Adaptive Meshing and Purging successfully added to Moonraker update manager(s)!"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _remove_moonraker_update_manager_section(
|
||||||
|
self, mr_instances: List[Moonraker]
|
||||||
|
) -> None:
|
||||||
|
if not mr_instances:
|
||||||
|
Logger.print_dialog(
|
||||||
|
DialogType.WARNING,
|
||||||
|
[
|
||||||
|
"Moonraker not found! Klipper Adaptive Meshing and Purging update "
|
||||||
|
"manager support for Moonraker will not be removed from moonraker.conf.",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
BackupService().backup_moonraker_conf()
|
||||||
|
|
||||||
|
remove_config_section(KAMP_MOONRAKER_UPDATER_NAME, mr_instances)
|
||||||
|
InstanceManager.restart_all(mr_instances)
|
||||||
|
|
||||||
|
Logger.print_ok(
|
||||||
|
"Klipper Adaptive Meshing and Purging successfully removed from Moonraker update manager(s)!"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _check_cfg_exists(self, kl_instances: List[Klipper]) -> bool:
|
||||||
|
cfg_dirs = [instance.base.cfg_dir for instance in kl_instances]
|
||||||
|
|
||||||
|
for cfg_dir in cfg_dirs:
|
||||||
|
if (
|
||||||
|
check_file_exist(cfg_dir.joinpath("KAMP_Settings.cfg"))
|
||||||
|
and check_file_exist(cfg_dir.joinpath("KAMP/KAMP_Settings.cfg"))
|
||||||
|
and check_file_exist(cfg_dir.joinpath("KAMP/Adaptive_Meshing.cfg"))
|
||||||
|
and check_file_exist(cfg_dir.joinpath("KAMP/Line_Purge.cfg"))
|
||||||
|
and check_file_exist(cfg_dir.joinpath("KAMP/Smart_Park.cfg"))
|
||||||
|
and check_file_exist(cfg_dir.joinpath("KAMP/Voron_Purge.cfg"))
|
||||||
|
):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _remove_cfg(self, kl_instances: List[Klipper]) -> None:
|
||||||
|
cfg_dirs = [instance.base.cfg_dir for instance in kl_instances]
|
||||||
|
|
||||||
|
for cfg_dir in cfg_dirs:
|
||||||
|
Logger.print_status(f"Removing KAMP symlink in '{cfg_dir}' ...")
|
||||||
|
run_remove_routines(cfg_dir.joinpath("KAMP"))
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"index": 14,
|
||||||
|
"module": "klipper_adaptive_meshing_purging_extension",
|
||||||
|
"maintained_by": "theogayar",
|
||||||
|
"display_name": "Klipper Adaptive Meshing Purging",
|
||||||
|
"description": [
|
||||||
|
"Klipper extension for adaptive meshing and purging."
|
||||||
|
],
|
||||||
|
"repo": "https://github.com/kyleisah/Klipper-Adaptive-Meshing-Purging",
|
||||||
|
"updates": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
# ======================================================================= #
|
# ======================================================================= #
|
||||||
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
|
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
|
||||||
|
# Copyright (C) 2026 Théo Gaillard <theo.gayar@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||||
# https://github.com/dw-0/kiauh #
|
# https://github.com/dw-0/kiauh #
|
||||||
@@ -82,7 +83,7 @@ class TmcAutotuneExtension(BaseExtension):
|
|||||||
allow_go_back=False,
|
allow_go_back=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
kl_instances = get_instances(Klipper)
|
kl_instances: List[Klipper] = get_instances(Klipper)
|
||||||
|
|
||||||
if not stop_klipper_instances_interactively(
|
if not stop_klipper_instances_interactively(
|
||||||
kl_instances, "installation of TMC Autotune"
|
kl_instances, "installation of TMC Autotune"
|
||||||
@@ -120,7 +121,7 @@ class TmcAutotuneExtension(BaseExtension):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if add_moonraker_update_section:
|
if add_moonraker_update_section:
|
||||||
mr_instances = get_instances(Moonraker)
|
mr_instances: List[Moonraker] = get_instances(Moonraker)
|
||||||
self._add_moonraker_update_manager_section(mr_instances)
|
self._add_moonraker_update_manager_section(mr_instances)
|
||||||
else:
|
else:
|
||||||
Logger.print_info(
|
Logger.print_info(
|
||||||
@@ -170,7 +171,7 @@ class TmcAutotuneExtension(BaseExtension):
|
|||||||
allow_go_back=True,
|
allow_go_back=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
kl_instances = get_instances(Klipper)
|
kl_instances: List[Klipper] = get_instances(Klipper)
|
||||||
|
|
||||||
if not stop_klipper_instances_interactively(
|
if not stop_klipper_instances_interactively(
|
||||||
kl_instances, "update of TMC Autotune"
|
kl_instances, "update of TMC Autotune"
|
||||||
@@ -208,7 +209,7 @@ class TmcAutotuneExtension(BaseExtension):
|
|||||||
Logger.print_info("Extension does not seem to be installed! Skipping ...")
|
Logger.print_info("Extension does not seem to be installed! Skipping ...")
|
||||||
return
|
return
|
||||||
|
|
||||||
kl_instances = get_instances(Klipper)
|
kl_instances: List[Klipper] = get_instances(Klipper)
|
||||||
|
|
||||||
if not stop_klipper_instances_interactively(
|
if not stop_klipper_instances_interactively(
|
||||||
kl_instances, "removal of TMC Autotune"
|
kl_instances, "removal of TMC Autotune"
|
||||||
@@ -344,4 +345,3 @@ class TmcAutotuneExtension(BaseExtension):
|
|||||||
Logger.print_ok(
|
Logger.print_ok(
|
||||||
"Klipper TMC Autotune successfully removed from Moonraker update manager(s)!"
|
"Klipper TMC Autotune successfully removed from Moonraker update manager(s)!"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user