Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c12653e1f7 | ||
|
|
8cf81bcb44 | ||
|
|
92a651b6a6 | ||
|
|
6712506862 | ||
|
|
6e49c2c607 | ||
|
|
4a99e95882 | ||
|
|
f5a74c29e1 | ||
|
|
f87713eacd | ||
|
|
f045b8a49e | ||
|
|
37d0e39d84 | ||
|
|
50ed13ca59 | ||
|
|
90ed7aca3c |
69
.github/workflows/test.yml
vendored
Normal file
69
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
name: Smoke Tests
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
klippy_testing:
|
||||||
|
name: Klippy Tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
klipper_repo:
|
||||||
|
- klipper3d/klipper
|
||||||
|
- DangerKlippers/danger-klipper
|
||||||
|
steps:
|
||||||
|
- name: Checkout shaketune
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
path: shaketune
|
||||||
|
- name: Checkout Klipper
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
path: klipper
|
||||||
|
repository: ${{ matrix.klipper_repo }}
|
||||||
|
ref: master
|
||||||
|
- name: Install build dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y build-essential
|
||||||
|
- name: Build klipper dict
|
||||||
|
run: |
|
||||||
|
pushd klipper
|
||||||
|
cp ../shaketune/ci/smoke-test/klipper-smoketest.kconfig .config
|
||||||
|
make olddefconfig
|
||||||
|
make out/compile_time_request.o
|
||||||
|
popd
|
||||||
|
- name: Setup klippy env
|
||||||
|
run: |
|
||||||
|
python3 -m venv --prompt klippy klippy-env
|
||||||
|
./klippy-env/bin/python -m pip install -r klipper/scripts/klippy-requirements.txt
|
||||||
|
./klippy-env/bin/python -m pip install -r shaketune/requirements.txt
|
||||||
|
- name: Install shaketune
|
||||||
|
run: |
|
||||||
|
ln -s $PWD/shaketune/shaketune $PWD/klipper/klippy/extras/shaketune
|
||||||
|
- name: Klipper import test
|
||||||
|
run: |
|
||||||
|
./klippy-env/bin/python klipper/klippy/klippy.py --import-test
|
||||||
|
- name: Klipper integrated test
|
||||||
|
run: |
|
||||||
|
pushd klipper
|
||||||
|
mkdir ../dicts
|
||||||
|
cp ../klipper/out/klipper.dict ../dicts/linux_basic.dict
|
||||||
|
../klippy-env/bin/python scripts/test_klippy.py -d ../dicts ../shaketune/ci/smoke-test/klippy-tests/simple.test
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
cache: 'pip'
|
||||||
|
- name: install ruff
|
||||||
|
run: |
|
||||||
|
pip install ruff
|
||||||
|
- name: run ruff tests
|
||||||
|
run: |
|
||||||
|
ruff check
|
||||||
|
|
||||||
|
|
||||||
34
ci/smoke-test/klipper-smoketest.kconfig
Normal file
34
ci/smoke-test/klipper-smoketest.kconfig
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
CONFIG_LOW_LEVEL_OPTIONS=y
|
||||||
|
# CONFIG_MACH_AVR is not set
|
||||||
|
# CONFIG_MACH_ATSAM is not set
|
||||||
|
# CONFIG_MACH_ATSAMD is not set
|
||||||
|
# CONFIG_MACH_LPC176X is not set
|
||||||
|
# CONFIG_MACH_STM32 is not set
|
||||||
|
# CONFIG_MACH_HC32F460 is not set
|
||||||
|
# CONFIG_MACH_RP2040 is not set
|
||||||
|
# CONFIG_MACH_PRU is not set
|
||||||
|
# CONFIG_MACH_AR100 is not set
|
||||||
|
CONFIG_MACH_LINUX=y
|
||||||
|
# CONFIG_MACH_SIMU is not set
|
||||||
|
CONFIG_BOARD_DIRECTORY="linux"
|
||||||
|
CONFIG_CLOCK_FREQ=50000000
|
||||||
|
CONFIG_LINUX_SELECT=y
|
||||||
|
CONFIG_USB_VENDOR_ID=0x1d50
|
||||||
|
CONFIG_USB_DEVICE_ID=0x614e
|
||||||
|
CONFIG_USB_SERIAL_NUMBER="12345"
|
||||||
|
CONFIG_WANT_GPIO_BITBANGING=y
|
||||||
|
CONFIG_WANT_DISPLAYS=y
|
||||||
|
CONFIG_WANT_SENSORS=y
|
||||||
|
CONFIG_WANT_LIS2DW=y
|
||||||
|
CONFIG_WANT_LDC1612=y
|
||||||
|
CONFIG_WANT_SOFTWARE_I2C=y
|
||||||
|
CONFIG_WANT_SOFTWARE_SPI=y
|
||||||
|
CONFIG_NEED_SENSOR_BULK=y
|
||||||
|
CONFIG_CANBUS_FREQUENCY=1000000
|
||||||
|
CONFIG_INITIAL_PINS=""
|
||||||
|
CONFIG_HAVE_GPIO=y
|
||||||
|
CONFIG_HAVE_GPIO_ADC=y
|
||||||
|
CONFIG_HAVE_GPIO_SPI=y
|
||||||
|
CONFIG_HAVE_GPIO_I2C=y
|
||||||
|
CONFIG_HAVE_GPIO_HARD_PWM=y
|
||||||
|
CONFIG_INLINE_STEPPER_HACK=y
|
||||||
9
ci/smoke-test/klippy-tests/simple.cfg
Normal file
9
ci/smoke-test/klippy-tests/simple.cfg
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[mcu]
|
||||||
|
serial: /tmp/klipper_host_mcu
|
||||||
|
|
||||||
|
[printer]
|
||||||
|
kinematics: none
|
||||||
|
max_velocity: 300
|
||||||
|
max_accel: 300
|
||||||
|
|
||||||
|
[shaketune]
|
||||||
4
ci/smoke-test/klippy-tests/simple.test
Normal file
4
ci/smoke-test/klippy-tests/simple.test
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
DICTIONARY linux_basic.dict
|
||||||
|
CONFIG simple.cfg
|
||||||
|
|
||||||
|
G4 P1000
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "Shake&Tune"
|
name = "shake_n_tune"
|
||||||
description = "Klipper streamlined input shaper workflow and calibration tools"
|
description = "Klipper streamlined input shaper workflow and calibration tools"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">= 3.9"
|
requires-python = ">= 3.9"
|
||||||
|
|||||||
@@ -13,10 +13,13 @@ import os
|
|||||||
import time
|
import time
|
||||||
from multiprocessing import Process, Queue
|
from multiprocessing import Process, Queue
|
||||||
|
|
||||||
|
FILE_WRITE_TIMEOUT = 10 # seconds
|
||||||
|
|
||||||
|
|
||||||
class Accelerometer:
|
class Accelerometer:
|
||||||
def __init__(self, klipper_accelerometer):
|
def __init__(self, reactor, klipper_accelerometer):
|
||||||
self._k_accelerometer = klipper_accelerometer
|
self._k_accelerometer = klipper_accelerometer
|
||||||
|
self._reactor = reactor
|
||||||
|
|
||||||
self._bg_client = None
|
self._bg_client = None
|
||||||
self._write_queue = Queue()
|
self._write_queue = Queue()
|
||||||
@@ -70,16 +73,35 @@ class Accelerometer:
|
|||||||
os.nice(20)
|
os.nice(20)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
with open(filename, 'w') as f:
|
with open(filename, 'w') as f:
|
||||||
f.write('#time,accel_x,accel_y,accel_z\n')
|
f.write('#time,accel_x,accel_y,accel_z\n')
|
||||||
samples = bg_client.samples or bg_client.get_samples()
|
samples = bg_client.samples or bg_client.get_samples()
|
||||||
for t, accel_x, accel_y, accel_z in samples:
|
for t, accel_x, accel_y, accel_z in samples:
|
||||||
f.write(f'{t:.6f},{accel_x:.6f},{accel_y:.6f},{accel_z:.6f}\n')
|
f.write(f'{t:.6f},{accel_x:.6f},{accel_y:.6f},{accel_z:.6f}\n')
|
||||||
|
|
||||||
self._write_queue.get()
|
self._write_queue.get()
|
||||||
|
|
||||||
def wait_for_file_writes(self):
|
def wait_for_file_writes(self):
|
||||||
while not self._write_queue.empty():
|
while not self._write_queue.empty():
|
||||||
time.sleep(0.1)
|
eventtime = self._reactor.monotonic()
|
||||||
|
self._reactor.pause(eventtime + 0.1)
|
||||||
|
|
||||||
for proc in self._write_processes:
|
for proc in self._write_processes:
|
||||||
proc.join()
|
if proc is None:
|
||||||
|
continue
|
||||||
|
eventtime = self._reactor.monotonic()
|
||||||
|
endtime = eventtime + FILE_WRITE_TIMEOUT
|
||||||
|
complete = False
|
||||||
|
while eventtime < endtime:
|
||||||
|
eventtime = self._reactor.pause(eventtime + 0.05)
|
||||||
|
if not proc.is_alive():
|
||||||
|
complete = True
|
||||||
|
break
|
||||||
|
if not complete:
|
||||||
|
raise TimeoutError(
|
||||||
|
'Shake&Tune was not able to write the accelerometer data into the CSV file. '
|
||||||
|
'This might be due to a slow SD card or a busy or full filesystem.'
|
||||||
|
)
|
||||||
|
|
||||||
self._write_processes = []
|
self._write_processes = []
|
||||||
|
|||||||
@@ -37,15 +37,21 @@ def axes_map_calibration(gcmd, config, st_process: ShakeTuneProcess) -> None:
|
|||||||
raise gcmd.error(
|
raise gcmd.error(
|
||||||
f'The parameter axes_map is already set in your {accel_chip} configuration! Please remove it (or set it to "x,y,z")!'
|
f'The parameter axes_map is already set in your {accel_chip} configuration! Please remove it (or set it to "x,y,z")!'
|
||||||
)
|
)
|
||||||
accelerometer = Accelerometer(k_accelerometer)
|
accelerometer = Accelerometer(printer.get_reactor(), k_accelerometer)
|
||||||
|
|
||||||
toolhead_info = toolhead.get_status(systime)
|
toolhead_info = toolhead.get_status(systime)
|
||||||
old_accel = toolhead_info['max_accel']
|
old_accel = toolhead_info['max_accel']
|
||||||
old_mcr = toolhead_info['minimum_cruise_ratio']
|
|
||||||
old_sqv = toolhead_info['square_corner_velocity']
|
old_sqv = toolhead_info['square_corner_velocity']
|
||||||
|
|
||||||
# set the wanted acceleration values
|
# set the wanted acceleration values
|
||||||
gcode.run_script_from_command(f'SET_VELOCITY_LIMIT ACCEL={accel} MINIMUM_CRUISE_RATIO=0 SQUARE_CORNER_VELOCITY=5.0')
|
if 'minimum_cruise_ratio' in toolhead_info:
|
||||||
|
old_mcr = toolhead_info['minimum_cruise_ratio'] # minimum_cruise_ratio found: Klipper >= v0.12.0-239
|
||||||
|
gcode.run_script_from_command(
|
||||||
|
f'SET_VELOCITY_LIMIT ACCEL={accel} MINIMUM_CRUISE_RATIO=0 SQUARE_CORNER_VELOCITY=5.0'
|
||||||
|
)
|
||||||
|
else: # minimum_cruise_ratio not found: Klipper < v0.12.0-239
|
||||||
|
old_mcr = None
|
||||||
|
gcode.run_script_from_command(f'SET_VELOCITY_LIMIT ACCEL={accel} SQUARE_CORNER_VELOCITY=5.0')
|
||||||
|
|
||||||
# Deactivate input shaper if it is active to get raw movements
|
# Deactivate input shaper if it is active to get raw movements
|
||||||
input_shaper = printer.lookup_object('input_shaper', None)
|
input_shaper = printer.lookup_object('input_shaper', None)
|
||||||
@@ -89,9 +95,13 @@ def axes_map_calibration(gcmd, config, st_process: ShakeTuneProcess) -> None:
|
|||||||
input_shaper.enable_shaping()
|
input_shaper.enable_shaping()
|
||||||
|
|
||||||
# Restore the previous acceleration values
|
# Restore the previous acceleration values
|
||||||
|
if old_mcr is not None: # minimum_cruise_ratio found: Klipper >= v0.12.0-239
|
||||||
gcode.run_script_from_command(
|
gcode.run_script_from_command(
|
||||||
f'SET_VELOCITY_LIMIT ACCEL={old_accel} MINIMUM_CRUISE_RATIO={old_mcr} SQUARE_CORNER_VELOCITY={old_sqv}'
|
f'SET_VELOCITY_LIMIT ACCEL={old_accel} MINIMUM_CRUISE_RATIO={old_mcr} SQUARE_CORNER_VELOCITY={old_sqv}'
|
||||||
)
|
)
|
||||||
|
else: # minimum_cruise_ratio not found: Klipper < v0.12.0-239
|
||||||
|
gcode.run_script_from_command(f'SET_VELOCITY_LIMIT ACCEL={old_accel} SQUARE_CORNER_VELOCITY={old_sqv}')
|
||||||
|
|
||||||
toolhead.wait_moves()
|
toolhead.wait_moves()
|
||||||
|
|
||||||
# Run post-processing
|
# Run post-processing
|
||||||
|
|||||||
@@ -76,8 +76,12 @@ def axes_shaper_calibration(gcmd, config, st_process: ShakeTuneProcess) -> None:
|
|||||||
# set the needed acceleration values for the test
|
# set the needed acceleration values for the test
|
||||||
toolhead_info = toolhead.get_status(systime)
|
toolhead_info = toolhead.get_status(systime)
|
||||||
old_accel = toolhead_info['max_accel']
|
old_accel = toolhead_info['max_accel']
|
||||||
|
if 'minimum_cruise_ratio' in toolhead_info: # minimum_cruise_ratio found: Klipper >= v0.12.0-239
|
||||||
old_mcr = toolhead_info['minimum_cruise_ratio']
|
old_mcr = toolhead_info['minimum_cruise_ratio']
|
||||||
gcode.run_script_from_command(f'SET_VELOCITY_LIMIT ACCEL={max_accel} MINIMUM_CRUISE_RATIO=0')
|
gcode.run_script_from_command(f'SET_VELOCITY_LIMIT ACCEL={max_accel} MINIMUM_CRUISE_RATIO=0')
|
||||||
|
else: # minimum_cruise_ratio not found: Klipper < v0.12.0-239
|
||||||
|
old_mcr = None
|
||||||
|
gcode.run_script_from_command(f'SET_VELOCITY_LIMIT ACCEL={max_accel}')
|
||||||
|
|
||||||
# Deactivate input shaper if it is active to get raw movements
|
# Deactivate input shaper if it is active to get raw movements
|
||||||
input_shaper = printer.lookup_object('input_shaper', None)
|
input_shaper = printer.lookup_object('input_shaper', None)
|
||||||
@@ -95,7 +99,7 @@ def axes_shaper_calibration(gcmd, config, st_process: ShakeTuneProcess) -> None:
|
|||||||
accel_chip = Accelerometer.find_axis_accelerometer(printer, config['axis'])
|
accel_chip = Accelerometer.find_axis_accelerometer(printer, config['axis'])
|
||||||
if accel_chip is None:
|
if accel_chip is None:
|
||||||
raise gcmd.error('No suitable accelerometer found for measurement!')
|
raise gcmd.error('No suitable accelerometer found for measurement!')
|
||||||
accelerometer = Accelerometer(printer.lookup_object(accel_chip))
|
accelerometer = Accelerometer(printer.get_reactor(), printer.lookup_object(accel_chip))
|
||||||
|
|
||||||
# Then do the actual measurements
|
# Then do the actual measurements
|
||||||
accelerometer.start_measurement()
|
accelerometer.start_measurement()
|
||||||
@@ -117,4 +121,7 @@ def axes_shaper_calibration(gcmd, config, st_process: ShakeTuneProcess) -> None:
|
|||||||
input_shaper.enable_shaping()
|
input_shaper.enable_shaping()
|
||||||
|
|
||||||
# Restore the previous acceleration values
|
# Restore the previous acceleration values
|
||||||
|
if old_mcr is not None: # minimum_cruise_ratio found: Klipper >= v0.12.0-239
|
||||||
gcode.run_script_from_command(f'SET_VELOCITY_LIMIT ACCEL={old_accel} MINIMUM_CRUISE_RATIO={old_mcr}')
|
gcode.run_script_from_command(f'SET_VELOCITY_LIMIT ACCEL={old_accel} MINIMUM_CRUISE_RATIO={old_mcr}')
|
||||||
|
else: # minimum_cruise_ratio not found: Klipper < v0.12.0-239
|
||||||
|
gcode.run_script_from_command(f'SET_VELOCITY_LIMIT ACCEL={old_accel}')
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ def compare_belts_responses(gcmd, config, st_process: ShakeTuneProcess) -> None:
|
|||||||
raise gcmd.error(
|
raise gcmd.error(
|
||||||
'No suitable accelerometer found for measurement! Multi-accelerometer configurations are not supported for this macro.'
|
'No suitable accelerometer found for measurement! Multi-accelerometer configurations are not supported for this macro.'
|
||||||
)
|
)
|
||||||
accelerometer = Accelerometer(printer.lookup_object(accel_chip))
|
accelerometer = Accelerometer(printer.get_reactor(), printer.lookup_object(accel_chip))
|
||||||
|
|
||||||
# Move to the starting point
|
# Move to the starting point
|
||||||
test_points = res_tester.test.get_start_test_points()
|
test_points = res_tester.test.get_start_test_points()
|
||||||
@@ -89,8 +89,12 @@ def compare_belts_responses(gcmd, config, st_process: ShakeTuneProcess) -> None:
|
|||||||
# set the needed acceleration values for the test
|
# set the needed acceleration values for the test
|
||||||
toolhead_info = toolhead.get_status(systime)
|
toolhead_info = toolhead.get_status(systime)
|
||||||
old_accel = toolhead_info['max_accel']
|
old_accel = toolhead_info['max_accel']
|
||||||
|
if 'minimum_cruise_ratio' in toolhead_info: # minimum_cruise_ratio found: Klipper >= v0.12.0-239
|
||||||
old_mcr = toolhead_info['minimum_cruise_ratio']
|
old_mcr = toolhead_info['minimum_cruise_ratio']
|
||||||
gcode.run_script_from_command(f'SET_VELOCITY_LIMIT ACCEL={max_accel} MINIMUM_CRUISE_RATIO=0')
|
gcode.run_script_from_command(f'SET_VELOCITY_LIMIT ACCEL={max_accel} MINIMUM_CRUISE_RATIO=0')
|
||||||
|
else: # minimum_cruise_ratio not found: Klipper < v0.12.0-239
|
||||||
|
old_mcr = None
|
||||||
|
gcode.run_script_from_command(f'SET_VELOCITY_LIMIT ACCEL={max_accel}')
|
||||||
|
|
||||||
# Deactivate input shaper if it is active to get raw movements
|
# Deactivate input shaper if it is active to get raw movements
|
||||||
input_shaper = printer.lookup_object('input_shaper', None)
|
input_shaper = printer.lookup_object('input_shaper', None)
|
||||||
@@ -112,7 +116,10 @@ def compare_belts_responses(gcmd, config, st_process: ShakeTuneProcess) -> None:
|
|||||||
input_shaper.enable_shaping()
|
input_shaper.enable_shaping()
|
||||||
|
|
||||||
# Restore the previous acceleration values
|
# Restore the previous acceleration values
|
||||||
|
if old_mcr is not None: # minimum_cruise_ratio found: Klipper >= v0.12.0-239
|
||||||
gcode.run_script_from_command(f'SET_VELOCITY_LIMIT ACCEL={old_accel} MINIMUM_CRUISE_RATIO={old_mcr}')
|
gcode.run_script_from_command(f'SET_VELOCITY_LIMIT ACCEL={old_accel} MINIMUM_CRUISE_RATIO={old_mcr}')
|
||||||
|
else: # minimum_cruise_ratio not found: Klipper < v0.12.0-239
|
||||||
|
gcode.run_script_from_command(f'SET_VELOCITY_LIMIT ACCEL={old_accel}')
|
||||||
|
|
||||||
# Run post-processing
|
# Run post-processing
|
||||||
ConsoleOutput.print('Belts comparative frequency profile generation...')
|
ConsoleOutput.print('Belts comparative frequency profile generation...')
|
||||||
|
|||||||
@@ -59,11 +59,17 @@ def create_vibrations_profile(gcmd, config, st_process: ShakeTuneProcess) -> Non
|
|||||||
|
|
||||||
toolhead_info = toolhead.get_status(systime)
|
toolhead_info = toolhead.get_status(systime)
|
||||||
old_accel = toolhead_info['max_accel']
|
old_accel = toolhead_info['max_accel']
|
||||||
old_mcr = toolhead_info['minimum_cruise_ratio']
|
|
||||||
old_sqv = toolhead_info['square_corner_velocity']
|
old_sqv = toolhead_info['square_corner_velocity']
|
||||||
|
|
||||||
# set the wanted acceleration values
|
# set the wanted acceleration values
|
||||||
gcode.run_script_from_command(f'SET_VELOCITY_LIMIT ACCEL={accel} MINIMUM_CRUISE_RATIO=0 SQUARE_CORNER_VELOCITY=5.0')
|
if 'minimum_cruise_ratio' in toolhead_info: # minimum_cruise_ratio found: Klipper >= v0.12.0-239
|
||||||
|
old_mcr = toolhead_info['minimum_cruise_ratio']
|
||||||
|
gcode.run_script_from_command(
|
||||||
|
f'SET_VELOCITY_LIMIT ACCEL={accel} MINIMUM_CRUISE_RATIO=0 SQUARE_CORNER_VELOCITY=5.0'
|
||||||
|
)
|
||||||
|
else: # minimum_cruise_ratio not found: Klipper < v0.12.0-239
|
||||||
|
old_mcr = None
|
||||||
|
gcode.run_script_from_command(f'SET_VELOCITY_LIMIT ACCEL={accel} SQUARE_CORNER_VELOCITY=5.0')
|
||||||
|
|
||||||
kin_info = toolhead.kin.get_status(systime)
|
kin_info = toolhead.kin.get_status(systime)
|
||||||
mid_x = (kin_info['axis_minimum'].x + kin_info['axis_maximum'].x) / 2
|
mid_x = (kin_info['axis_minimum'].x + kin_info['axis_maximum'].x) / 2
|
||||||
@@ -91,7 +97,7 @@ def create_vibrations_profile(gcmd, config, st_process: ShakeTuneProcess) -> Non
|
|||||||
if k_accelerometer is None:
|
if k_accelerometer is None:
|
||||||
raise gcmd.error(f'Accelerometer [{current_accel_chip}] not found!')
|
raise gcmd.error(f'Accelerometer [{current_accel_chip}] not found!')
|
||||||
ConsoleOutput.print(f'Accelerometer chip used for this angle: [{current_accel_chip}]')
|
ConsoleOutput.print(f'Accelerometer chip used for this angle: [{current_accel_chip}]')
|
||||||
accelerometer = Accelerometer(k_accelerometer)
|
accelerometer = Accelerometer(printer.get_reactor(), k_accelerometer)
|
||||||
|
|
||||||
# Sweep the speed range to record the vibrations at different speeds
|
# Sweep the speed range to record the vibrations at different speeds
|
||||||
for curr_speed_sample in range(nb_speed_samples):
|
for curr_speed_sample in range(nb_speed_samples):
|
||||||
@@ -134,9 +140,12 @@ def create_vibrations_profile(gcmd, config, st_process: ShakeTuneProcess) -> Non
|
|||||||
accelerometer.wait_for_file_writes()
|
accelerometer.wait_for_file_writes()
|
||||||
|
|
||||||
# Restore the previous acceleration values
|
# Restore the previous acceleration values
|
||||||
|
if old_mcr is not None: # minimum_cruise_ratio found: Klipper >= v0.12.0-239
|
||||||
gcode.run_script_from_command(
|
gcode.run_script_from_command(
|
||||||
f'SET_VELOCITY_LIMIT ACCEL={old_accel} MINIMUM_CRUISE_RATIO={old_mcr} SQUARE_CORNER_VELOCITY={old_sqv}'
|
f'SET_VELOCITY_LIMIT ACCEL={old_accel} MINIMUM_CRUISE_RATIO={old_mcr} SQUARE_CORNER_VELOCITY={old_sqv}'
|
||||||
)
|
)
|
||||||
|
else: # minimum_cruise_ratio not found: Klipper < v0.12.0-239
|
||||||
|
gcode.run_script_from_command(f'SET_VELOCITY_LIMIT ACCEL={old_accel} SQUARE_CORNER_VELOCITY={old_sqv}')
|
||||||
toolhead.wait_moves()
|
toolhead.wait_moves()
|
||||||
|
|
||||||
# Run post-processing
|
# Run post-processing
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ def excitate_axis_at_freq(gcmd, config, st_process: ShakeTuneProcess) -> None:
|
|||||||
k_accelerometer = printer.lookup_object(accel_chip, None)
|
k_accelerometer = printer.lookup_object(accel_chip, None)
|
||||||
if k_accelerometer is None:
|
if k_accelerometer is None:
|
||||||
raise gcmd.error(f'Accelerometer chip [{accel_chip}] was not found!')
|
raise gcmd.error(f'Accelerometer chip [{accel_chip}] was not found!')
|
||||||
accelerometer = Accelerometer(k_accelerometer)
|
accelerometer = Accelerometer(printer.get_reactor(), k_accelerometer)
|
||||||
|
|
||||||
ConsoleOutput.print(f'Excitating {axis.upper()} axis at {freq}Hz for {duration} seconds')
|
ConsoleOutput.print(f'Excitating {axis.upper()} axis at {freq}Hz for {duration} seconds')
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import matplotlib.font_manager
|
|||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import matplotlib.ticker
|
import matplotlib.ticker
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
from scipy.stats import pearsonr
|
||||||
|
|
||||||
matplotlib.use('Agg')
|
matplotlib.use('Agg')
|
||||||
|
|
||||||
@@ -210,8 +211,8 @@ def plot_compare_frequency(
|
|||||||
ax: plt.Axes, signal1: SignalData, signal2: SignalData, signal1_belt: str, signal2_belt: str, max_freq: float
|
ax: plt.Axes, signal1: SignalData, signal2: SignalData, signal1_belt: str, signal2_belt: str, max_freq: float
|
||||||
) -> None:
|
) -> None:
|
||||||
# Plot the two belts PSD signals
|
# Plot the two belts PSD signals
|
||||||
ax.plot(signal1.freqs, signal1.psd, label='Belt ' + signal1_belt, color=KLIPPAIN_COLORS['purple'])
|
ax.plot(signal1.freqs, signal1.psd, label='Belt ' + signal1_belt, color=KLIPPAIN_COLORS['orange'])
|
||||||
ax.plot(signal2.freqs, signal2.psd, label='Belt ' + signal2_belt, color=KLIPPAIN_COLORS['orange'])
|
ax.plot(signal2.freqs, signal2.psd, label='Belt ' + signal2_belt, color=KLIPPAIN_COLORS['purple'])
|
||||||
|
|
||||||
psd_highest_max = max(signal1.psd.max(), signal2.psd.max())
|
psd_highest_max = max(signal1.psd.max(), signal2.psd.max())
|
||||||
|
|
||||||
@@ -343,14 +344,12 @@ def plot_versus_belts(
|
|||||||
common_freqs: np.ndarray,
|
common_freqs: np.ndarray,
|
||||||
signal1: SignalData,
|
signal1: SignalData,
|
||||||
signal2: SignalData,
|
signal2: SignalData,
|
||||||
interp_psd1: np.ndarray,
|
|
||||||
interp_psd2: np.ndarray,
|
|
||||||
signal1_belt: str,
|
signal1_belt: str,
|
||||||
signal2_belt: str,
|
signal2_belt: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
ax.set_title('Cross-belts comparison plot', fontsize=14, color=KLIPPAIN_COLORS['dark_orange'], weight='bold')
|
ax.set_title('Cross-belts comparison plot', fontsize=14, color=KLIPPAIN_COLORS['dark_orange'], weight='bold')
|
||||||
|
|
||||||
max_psd = max(np.max(interp_psd1), np.max(interp_psd2))
|
max_psd = max(np.max(signal1.psd), np.max(signal2.psd))
|
||||||
ideal_line = np.linspace(0, max_psd * 1.1, 500)
|
ideal_line = np.linspace(0, max_psd * 1.1, 500)
|
||||||
green_boundary = ideal_line + (0.35 * max_psd * np.exp(-ideal_line / (0.6 * max_psd)))
|
green_boundary = ideal_line + (0.35 * max_psd * np.exp(-ideal_line / (0.6 * max_psd)))
|
||||||
ax.fill_betweenx(ideal_line, ideal_line, green_boundary, color='green', alpha=0.15)
|
ax.fill_betweenx(ideal_line, ideal_line, green_boundary, color='green', alpha=0.15)
|
||||||
@@ -364,8 +363,8 @@ def plot_versus_belts(
|
|||||||
linewidth=2,
|
linewidth=2,
|
||||||
)
|
)
|
||||||
|
|
||||||
ax.plot(interp_psd1, interp_psd2, color='dimgrey', marker='o', markersize=1.5)
|
ax.plot(signal1.psd, signal2.psd, color='dimgrey', marker='o', markersize=1.5)
|
||||||
ax.fill_betweenx(interp_psd2, interp_psd1, color=KLIPPAIN_COLORS['red_pink'], alpha=0.1)
|
ax.fill_betweenx(signal2.psd, signal1.psd, color=KLIPPAIN_COLORS['red_pink'], alpha=0.1)
|
||||||
|
|
||||||
paired_peak_count = 0
|
paired_peak_count = 0
|
||||||
unpaired_peak_count = 0
|
unpaired_peak_count = 0
|
||||||
@@ -374,31 +373,27 @@ def plot_versus_belts(
|
|||||||
label = ALPHABET[paired_peak_count]
|
label = ALPHABET[paired_peak_count]
|
||||||
freq1 = signal1.freqs[peak1[0]]
|
freq1 = signal1.freqs[peak1[0]]
|
||||||
freq2 = signal2.freqs[peak2[0]]
|
freq2 = signal2.freqs[peak2[0]]
|
||||||
nearest_idx1 = np.argmin(np.abs(common_freqs - freq1))
|
|
||||||
nearest_idx2 = np.argmin(np.abs(common_freqs - freq2))
|
|
||||||
|
|
||||||
if nearest_idx1 == nearest_idx2:
|
if abs(freq1 - freq2) < 1:
|
||||||
psd1_peak_value = interp_psd1[nearest_idx1]
|
ax.plot(signal1.psd[peak1[0]], signal2.psd[peak2[0]], marker='o', color='black', markersize=7)
|
||||||
psd2_peak_value = interp_psd2[nearest_idx1]
|
|
||||||
ax.plot(psd1_peak_value, psd2_peak_value, marker='o', color='black', markersize=7)
|
|
||||||
ax.annotate(
|
ax.annotate(
|
||||||
f'{label}1/{label}2',
|
f'{label}1/{label}2',
|
||||||
(psd1_peak_value, psd2_peak_value),
|
(signal1.psd[peak1[0]], signal2.psd[peak2[0]]),
|
||||||
textcoords='offset points',
|
textcoords='offset points',
|
||||||
xytext=(-7, 7),
|
xytext=(-7, 7),
|
||||||
fontsize=13,
|
fontsize=13,
|
||||||
color='black',
|
color='black',
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
psd1_peak_value = interp_psd1[nearest_idx1]
|
ax.plot(
|
||||||
psd1_on_peak = interp_psd1[nearest_idx2]
|
signal1.psd[peak2[0]], signal2.psd[peak2[0]], marker='o', color=KLIPPAIN_COLORS['orange'], markersize=7
|
||||||
psd2_peak_value = interp_psd2[nearest_idx2]
|
)
|
||||||
psd2_on_peak = interp_psd2[nearest_idx1]
|
ax.plot(
|
||||||
ax.plot(psd1_on_peak, psd2_peak_value, marker='o', color=KLIPPAIN_COLORS['orange'], markersize=7)
|
signal1.psd[peak1[0]], signal2.psd[peak1[0]], marker='o', color=KLIPPAIN_COLORS['purple'], markersize=7
|
||||||
ax.plot(psd1_peak_value, psd2_on_peak, marker='o', color=KLIPPAIN_COLORS['purple'], markersize=7)
|
)
|
||||||
ax.annotate(
|
ax.annotate(
|
||||||
f'{label}1',
|
f'{label}1',
|
||||||
(psd1_peak_value, psd2_on_peak),
|
(signal1.psd[peak1[0]], signal2.psd[peak1[0]]),
|
||||||
textcoords='offset points',
|
textcoords='offset points',
|
||||||
xytext=(0, 7),
|
xytext=(0, 7),
|
||||||
fontsize=13,
|
fontsize=13,
|
||||||
@@ -406,7 +401,7 @@ def plot_versus_belts(
|
|||||||
)
|
)
|
||||||
ax.annotate(
|
ax.annotate(
|
||||||
f'{label}2',
|
f'{label}2',
|
||||||
(psd1_on_peak, psd2_peak_value),
|
(signal1.psd[peak2[0]], signal2.psd[peak2[0]]),
|
||||||
textcoords='offset points',
|
textcoords='offset points',
|
||||||
xytext=(0, 7),
|
xytext=(0, 7),
|
||||||
fontsize=13,
|
fontsize=13,
|
||||||
@@ -415,16 +410,12 @@ def plot_versus_belts(
|
|||||||
paired_peak_count += 1
|
paired_peak_count += 1
|
||||||
|
|
||||||
for _, peak_index in enumerate(signal1.unpaired_peaks):
|
for _, peak_index in enumerate(signal1.unpaired_peaks):
|
||||||
freq1 = signal1.freqs[peak_index]
|
ax.plot(
|
||||||
freq2 = signal2.freqs[peak_index]
|
signal1.psd[peak_index], signal2.psd[peak_index], marker='o', color=KLIPPAIN_COLORS['purple'], markersize=7
|
||||||
nearest_idx1 = np.argmin(np.abs(common_freqs - freq1))
|
)
|
||||||
nearest_idx2 = np.argmin(np.abs(common_freqs - freq2))
|
|
||||||
psd1_peak_value = interp_psd1[nearest_idx1]
|
|
||||||
psd2_peak_value = interp_psd2[nearest_idx1]
|
|
||||||
ax.plot(psd1_peak_value, psd2_peak_value, marker='o', color=KLIPPAIN_COLORS['purple'], markersize=7)
|
|
||||||
ax.annotate(
|
ax.annotate(
|
||||||
str(unpaired_peak_count + 1),
|
str(unpaired_peak_count + 1),
|
||||||
(psd1_peak_value, psd2_peak_value),
|
(signal1.psd[peak_index], signal2.psd[peak_index]),
|
||||||
textcoords='offset points',
|
textcoords='offset points',
|
||||||
fontsize=13,
|
fontsize=13,
|
||||||
weight='bold',
|
weight='bold',
|
||||||
@@ -434,16 +425,12 @@ def plot_versus_belts(
|
|||||||
unpaired_peak_count += 1
|
unpaired_peak_count += 1
|
||||||
|
|
||||||
for _, peak_index in enumerate(signal2.unpaired_peaks):
|
for _, peak_index in enumerate(signal2.unpaired_peaks):
|
||||||
freq1 = signal1.freqs[peak_index]
|
ax.plot(
|
||||||
freq2 = signal2.freqs[peak_index]
|
signal1.psd[peak_index], signal2.psd[peak_index], marker='o', color=KLIPPAIN_COLORS['orange'], markersize=7
|
||||||
nearest_idx1 = np.argmin(np.abs(common_freqs - freq1))
|
)
|
||||||
nearest_idx2 = np.argmin(np.abs(common_freqs - freq2))
|
|
||||||
psd1_peak_value = interp_psd1[nearest_idx1]
|
|
||||||
psd2_peak_value = interp_psd2[nearest_idx1]
|
|
||||||
ax.plot(psd1_peak_value, psd2_peak_value, marker='o', color=KLIPPAIN_COLORS['orange'], markersize=7)
|
|
||||||
ax.annotate(
|
ax.annotate(
|
||||||
str(unpaired_peak_count + 1),
|
str(unpaired_peak_count + 1),
|
||||||
(psd1_peak_value, psd2_peak_value),
|
(signal1.psd[peak_index], signal2.psd[peak_index]),
|
||||||
textcoords='offset points',
|
textcoords='offset points',
|
||||||
fontsize=13,
|
fontsize=13,
|
||||||
weight='bold',
|
weight='bold',
|
||||||
@@ -476,16 +463,21 @@ def plot_versus_belts(
|
|||||||
|
|
||||||
|
|
||||||
# Original Klipper function to get the PSD data of a raw accelerometer signal
|
# Original Klipper function to get the PSD data of a raw accelerometer signal
|
||||||
def compute_signal_data(data: np.ndarray, max_freq: float) -> SignalData:
|
def compute_signal_data(data: np.ndarray, common_freqs: np.ndarray, max_freq: float) -> SignalData:
|
||||||
helper = shaper_calibrate.ShaperCalibrate(printer=None)
|
helper = shaper_calibrate.ShaperCalibrate(printer=None)
|
||||||
calibration_data = helper.process_accelerometer_data(data)
|
calibration_data = helper.process_accelerometer_data(data)
|
||||||
|
|
||||||
freqs = calibration_data.freq_bins[calibration_data.freq_bins <= max_freq]
|
freqs = calibration_data.freq_bins[calibration_data.freq_bins <= max_freq]
|
||||||
psd = calibration_data.get_psd('all')[calibration_data.freq_bins <= max_freq]
|
psd = calibration_data.get_psd('all')[calibration_data.freq_bins <= max_freq]
|
||||||
|
|
||||||
_, peaks, _ = detect_peaks(psd, freqs, PEAKS_DETECTION_THRESHOLD * psd.max())
|
# Re-interpolate the PSD signal to a common frequency range to be able to plot them one against the other
|
||||||
|
interp_psd = np.interp(common_freqs, freqs, psd)
|
||||||
|
|
||||||
return SignalData(freqs=freqs, psd=psd, peaks=peaks)
|
_, peaks, _ = detect_peaks(
|
||||||
|
interp_psd, common_freqs, PEAKS_DETECTION_THRESHOLD * interp_psd.max(), window_size=20, vicinity=15
|
||||||
|
)
|
||||||
|
|
||||||
|
return SignalData(freqs=common_freqs, psd=interp_psd, peaks=peaks)
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
@@ -517,8 +509,9 @@ def belts_calibration(
|
|||||||
signal2_belt += belt_info.get(signal2_belt, '')
|
signal2_belt += belt_info.get(signal2_belt, '')
|
||||||
|
|
||||||
# Compute calibration data for the two datasets with automatic peaks detection
|
# Compute calibration data for the two datasets with automatic peaks detection
|
||||||
signal1 = compute_signal_data(datas[0], max_freq)
|
common_freqs = np.linspace(0, max_freq, 500)
|
||||||
signal2 = compute_signal_data(datas[1], max_freq)
|
signal1 = compute_signal_data(datas[0], common_freqs, max_freq)
|
||||||
|
signal2 = compute_signal_data(datas[1], common_freqs, max_freq)
|
||||||
del datas
|
del datas
|
||||||
|
|
||||||
# Pair the peaks across the two datasets
|
# Pair the peaks across the two datasets
|
||||||
@@ -526,18 +519,13 @@ def belts_calibration(
|
|||||||
signal1 = signal1._replace(paired_peaks=pairing_result.paired_peaks, unpaired_peaks=pairing_result.unpaired_peaks1)
|
signal1 = signal1._replace(paired_peaks=pairing_result.paired_peaks, unpaired_peaks=pairing_result.unpaired_peaks1)
|
||||||
signal2 = signal2._replace(paired_peaks=pairing_result.paired_peaks, unpaired_peaks=pairing_result.unpaired_peaks2)
|
signal2 = signal2._replace(paired_peaks=pairing_result.paired_peaks, unpaired_peaks=pairing_result.unpaired_peaks2)
|
||||||
|
|
||||||
# Re-interpolate the PSD signals to a common frequency range to be able to plot them one against the other point by point
|
# R² proved to be pretty instable to compute the similarity between the two belts
|
||||||
common_freqs = np.linspace(0, max_freq, 500)
|
# So now, we use the Pearson correlation coefficient to compute the similarity
|
||||||
interp_psd1 = np.interp(common_freqs, signal1.freqs, signal1.psd)
|
correlation, _ = pearsonr(signal1.psd, signal2.psd)
|
||||||
interp_psd2 = np.interp(common_freqs, signal2.freqs, signal2.psd)
|
similarity_factor = correlation * 100
|
||||||
|
similarity_factor = np.clip(similarity_factor, 0, 100)
|
||||||
# Calculating R^2 to y=x line to compute the similarity between the two belts
|
|
||||||
ss_res = np.sum((interp_psd2 - interp_psd1) ** 2)
|
|
||||||
ss_tot = np.sum((interp_psd2 - np.mean(interp_psd2)) ** 2)
|
|
||||||
similarity_factor = (1 - (ss_res / ss_tot)) * 100
|
|
||||||
ConsoleOutput.print(f'Belts estimated similarity: {similarity_factor:.1f}%')
|
ConsoleOutput.print(f'Belts estimated similarity: {similarity_factor:.1f}%')
|
||||||
|
|
||||||
# mhi = compute_mhi(similarity_factor, num_peaks, num_unpaired_peaks)
|
|
||||||
mhi = compute_mhi(similarity_factor, signal1, signal2)
|
mhi = compute_mhi(similarity_factor, signal1, signal2)
|
||||||
ConsoleOutput.print(f'[experimental] Mechanical health: {mhi}')
|
ConsoleOutput.print(f'[experimental] Mechanical health: {mhi}')
|
||||||
|
|
||||||
@@ -582,11 +570,11 @@ def belts_calibration(
|
|||||||
|
|
||||||
# Add the accel_per_hz value to the title
|
# Add the accel_per_hz value to the title
|
||||||
title_line5 = f'| Accel per Hz used: {accel_per_hz} mm/s²/Hz'
|
title_line5 = f'| Accel per Hz used: {accel_per_hz} mm/s²/Hz'
|
||||||
fig.text(0.55, 0.915, title_line5, ha='left', va='top', fontsize=14, color=KLIPPAIN_COLORS['dark_purple'])
|
fig.text(0.551, 0.915, title_line5, ha='left', va='top', fontsize=10, color=KLIPPAIN_COLORS['dark_purple'])
|
||||||
|
|
||||||
# Plot the graphs
|
# Plot the graphs
|
||||||
plot_compare_frequency(ax1, signal1, signal2, signal1_belt, signal2_belt, max_freq)
|
plot_compare_frequency(ax1, signal1, signal2, signal1_belt, signal2_belt, max_freq)
|
||||||
plot_versus_belts(ax3, common_freqs, signal1, signal2, interp_psd1, interp_psd2, signal1_belt, signal2_belt)
|
plot_versus_belts(ax3, common_freqs, signal1, signal2, signal1_belt, signal2_belt)
|
||||||
|
|
||||||
# Adding a small Klippain logo to the top left corner of the figure
|
# Adding a small Klippain logo to the top left corner of the figure
|
||||||
ax_logo = fig.add_axes([0.001, 0.894, 0.105, 0.105], anchor='NW')
|
ax_logo = fig.add_axes([0.001, 0.894, 0.105, 0.105], anchor='NW')
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ from .helpers.console_output import ConsoleOutput
|
|||||||
from .shaketune_config import ShakeTuneConfig
|
from .shaketune_config import ShakeTuneConfig
|
||||||
from .shaketune_process import ShakeTuneProcess
|
from .shaketune_process import ShakeTuneProcess
|
||||||
|
|
||||||
|
IN_DANGER = False
|
||||||
|
|
||||||
|
|
||||||
class ShakeTune:
|
class ShakeTune:
|
||||||
def __init__(self, config) -> None:
|
def __init__(self, config) -> None:
|
||||||
@@ -51,21 +53,31 @@ class ShakeTune:
|
|||||||
self._config = ShakeTuneConfig(result_folder_path, keep_n_results, keep_csv, dpi)
|
self._config = ShakeTuneConfig(result_folder_path, keep_n_results, keep_csv, dpi)
|
||||||
ConsoleOutput.register_output_callback(gcode.respond_info)
|
ConsoleOutput.register_output_callback(gcode.respond_info)
|
||||||
|
|
||||||
commands = [
|
# Register Shake&Tune's measurement commands
|
||||||
|
measurement_commands = [
|
||||||
(
|
(
|
||||||
'EXCITATE_AXIS_AT_FREQ',
|
'EXCITATE_AXIS_AT_FREQ',
|
||||||
self.cmd_EXCITATE_AXIS_AT_FREQ,
|
self.cmd_EXCITATE_AXIS_AT_FREQ,
|
||||||
'Maintain a specified excitation frequency for a period of time to diagnose and locate a source of vibration',
|
(
|
||||||
|
'Maintain a specified excitation frequency for a period '
|
||||||
|
'of time to diagnose and locate a source of vibrations'
|
||||||
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
'AXES_MAP_CALIBRATION',
|
'AXES_MAP_CALIBRATION',
|
||||||
self.cmd_AXES_MAP_CALIBRATION,
|
self.cmd_AXES_MAP_CALIBRATION,
|
||||||
'Perform a set of movements to measure the orientation of the accelerometer and help you set the best axes_map configuration for your printer',
|
(
|
||||||
|
'Perform a set of movements to measure the orientation of the accelerometer '
|
||||||
|
'and help you set the best axes_map configuration for your printer'
|
||||||
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
'COMPARE_BELTS_RESPONSES',
|
'COMPARE_BELTS_RESPONSES',
|
||||||
self.cmd_COMPARE_BELTS_RESPONSES,
|
self.cmd_COMPARE_BELTS_RESPONSES,
|
||||||
'Perform a custom half-axis test to analyze and compare the frequency profiles of individual belts on CoreXY printers',
|
(
|
||||||
|
'Perform a custom half-axis test to analyze and compare the '
|
||||||
|
'frequency profiles of individual belts on CoreXY or CoreXZ printers'
|
||||||
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
'AXES_SHAPER_CALIBRATION',
|
'AXES_SHAPER_CALIBRATION',
|
||||||
@@ -75,12 +87,14 @@ class ShakeTune:
|
|||||||
(
|
(
|
||||||
'CREATE_VIBRATIONS_PROFILE',
|
'CREATE_VIBRATIONS_PROFILE',
|
||||||
self.cmd_CREATE_VIBRATIONS_PROFILE,
|
self.cmd_CREATE_VIBRATIONS_PROFILE,
|
||||||
'Perform a set of movements to measure the orientation of the accelerometer and help you set the best axes_map configuration for your printer',
|
(
|
||||||
|
'Run a series of motions to find speed/angle ranges where the printer could be '
|
||||||
|
'exposed to VFAs to optimize your slicer speed profiles and TMC driver parameters'
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
command_descriptions = {name: desc for name, _, desc in commands}
|
command_descriptions = {name: desc for name, _, desc in measurement_commands}
|
||||||
|
for name, command, description in measurement_commands:
|
||||||
for name, command, description in commands:
|
|
||||||
gcode.register_command(f'_{name}' if show_macros else name, command, desc=description)
|
gcode.register_command(f'_{name}' if show_macros else name, command, desc=description)
|
||||||
|
|
||||||
# Load the dummy macros with their description in order to show them in the web interfaces
|
# Load the dummy macros with their description in order to show them in the web interfaces
|
||||||
|
|||||||
Reference in New Issue
Block a user