Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c12653e1f7 | ||
|
|
8cf81bcb44 | ||
|
|
92a651b6a6 | ||
|
|
6712506862 | ||
|
|
6e49c2c607 | ||
|
|
4a99e95882 | ||
|
|
f5a74c29e1 | ||
|
|
f87713eacd | ||
|
|
f045b8a49e | ||
|
|
37d0e39d84 | ||
|
|
50ed13ca59 | ||
|
|
90ed7aca3c | ||
|
|
69ad228356 | ||
|
|
b98d103a26 | ||
|
|
a9c7a8491b | ||
|
|
fb8e1ce98f | ||
|
|
8b0862a96a |
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
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 501 KiB After Width: | Height: | Size: 490 KiB |
@@ -36,9 +36,9 @@ axes_map: -z,y,x
|
|||||||
|
|
||||||
This plot shows the acceleration data over time for the X, Y, and Z axes after removing the gravity offset. Look for patterns in the acceleration data for each axis: you should have exactly 2 spikes for each subplot (for the start and stop of the motion) that break away from the global noise. This can help identify any anomalies or inconsistencies in your accelerometer behavior.
|
This plot shows the acceleration data over time for the X, Y, and Z axes after removing the gravity offset. Look for patterns in the acceleration data for each axis: you should have exactly 2 spikes for each subplot (for the start and stop of the motion) that break away from the global noise. This can help identify any anomalies or inconsistencies in your accelerometer behavior.
|
||||||
|
|
||||||
The detected gravity offset is printed in the legend to give some context to the readings and their scale: if it's too far from the standard 9.8-10 m/s², this means that your accelerometer is not working properly and should be fixed or calibrated.
|
The dynamic noise and background vibrations measured by the accelerometer are extracted from the signal (using wavelet transform decomposition) and printed in the legend. **Usually values below about 500mm/s² are ok**, but Shake&Tune will automatically add a note if too much noise is recorded. **Be careful because this value is very different from Klipper's `MEASURE_AXES_NOISE` command, as Shake&Tune measures everything during the motion**, such as accelerometer noise, but also vibrations and motor noise, axis and toolhead oscillations, etc. If you want to record your axes_map correctly, you may need to use about 10 times this value in the `ACCEL` parameter to get a good signal-to-noise ratio and allow Shake&Tune to correctly detect the toolhead acceleration and deceleration phases.
|
||||||
|
|
||||||
The average noise in the accelerometer measurement is calculated (using wavelet transform decomposition) and displayed at the top of the image. Usually values <500mm/s² are ok, but a note is automatically added by Shake&Tune in case your accelerometer has too much noise.
|
The detected gravity offset is printed in the legend to give some context to the readings and their scale: if it's too far from the standard 9.8-10 m/s², this means that your accelerometer is not working properly and should be fixed or calibrated.
|
||||||
|
|
||||||
### Estimated 3D movement path
|
### Estimated 3D movement path
|
||||||
|
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ Then, call the `AXES_SHAPER_CALIBRATION` macro and look for the graphs in the re
|
|||||||
|
|
||||||
| parameters | default value | description |
|
| parameters | default value | description |
|
||||||
|-----------:|---------------|-------------|
|
|-----------:|---------------|-------------|
|
||||||
|FREQ_START|5|starting excitation frequency|
|
|FREQ_START|None (default to `[resonance_tester]` value)|starting excitation frequency|
|
||||||
|FREQ_END|133|maximum excitation frequency|
|
|FREQ_END|None (default to `[resonance_tester]` value)|maximum excitation frequency|
|
||||||
|HZ_PER_SEC|1|number of Hz per seconds for the test|
|
|HZ_PER_SEC|1|number of Hz per seconds for the test|
|
||||||
|ACCEL_PER_HZ|None|accel per Hz value used for the test. If unset, it will use the value from your `[resonance_tester]` config section (75 is the default)|
|
|ACCEL_PER_HZ|None (default to `[resonance_tester]` value)|accel per Hz value used for the test|
|
||||||
|AXIS|"all"|axis you want to test in the list of "all", "X" or "Y"|
|
|AXIS|"all"|axis you want to test in the list of "all", "X" or "Y"|
|
||||||
|SCV|printer square corner velocity|square corner velocity you want to use to calculate shaper recommendations. Using higher SCV values usually results in more smoothing and lower maximum accelerations|
|
|SCV|printer square corner velocity|square corner velocity you want to use to calculate shaper recommendations. Using higher SCV values usually results in more smoothing and lower maximum accelerations|
|
||||||
|MAX_SMOOTHING|None|max smoothing allowed when calculating shaper recommendations|
|
|MAX_SMOOTHING|None|max smoothing allowed when calculating shaper recommendations|
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ Then, call the `COMPARE_BELTS_RESPONSES` macro and look for the graphs in the re
|
|||||||
|
|
||||||
| parameters | default value | description |
|
| parameters | default value | description |
|
||||||
|-----------:|---------------|-------------|
|
|-----------:|---------------|-------------|
|
||||||
|FREQ_START|5|starting excitation frequency|
|
|FREQ_START|None (default to `[resonance_tester]` value)|starting excitation frequency|
|
||||||
|FREQ_END|133|maximum excitation frequency|
|
|FREQ_END|None (default to `[resonance_tester]` value)|maximum excitation frequency|
|
||||||
|HZ_PER_SEC|1|number of Hz per seconds for the test|
|
|HZ_PER_SEC|1|number of Hz per seconds for the test|
|
||||||
|ACCEL_PER_HZ|None|accel per Hz value used for the test. If unset, it will use the value from your `[resonance_tester]` config section (75 is the default)|
|
|ACCEL_PER_HZ|None (default to `[resonance_tester]` value)|accel per Hz value used for the test|
|
||||||
|TRAVEL_SPEED|120|speed in mm/s used for all the travel movements (to go to the start position prior to the test)|
|
|TRAVEL_SPEED|120|speed in mm/s used for all the travel movements (to go to the start position prior to the test)|
|
||||||
|Z_HEIGHT|None|Z height wanted for the test. This value can be used if needed to override the Z value of the probe_point set in your `[resonance_tester]` config section|
|
|Z_HEIGHT|None|Z height wanted for the test. This value can be used if needed to override the Z value of the probe_point set in your `[resonance_tester]` config section|
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ Here are the parameters available:
|
|||||||
|CREATE_GRAPH|0|whether or not to record the accelerometer data and create an associated graph during the excitation|
|
|CREATE_GRAPH|0|whether or not to record the accelerometer data and create an associated graph during the excitation|
|
||||||
|FREQUENCY|25|excitation frequency (in Hz) that you want to maintain. Usually, it's the frequency of a peak on one of the graphs|
|
|FREQUENCY|25|excitation frequency (in Hz) that you want to maintain. Usually, it's the frequency of a peak on one of the graphs|
|
||||||
|DURATION|30|duration in second to maintain this excitation|
|
|DURATION|30|duration in second to maintain this excitation|
|
||||||
|ACCEL_PER_HZ|None|accel per Hz value used for the test. If unset, it will use the value from your `[resonance_tester]` config section (75 is the default)|
|
|ACCEL_PER_HZ|None (default to `[resonance_tester]` value)|accel per Hz value used for the test|
|
||||||
|AXIS|x|axis you want to excitate. Can be set to either "x", "y", "a", "b"|
|
|AXIS|x|axis you want to excitate. Can be set to either "x", "y", "a", "b"|
|
||||||
|TRAVEL_SPEED|120|speed in mm/s used for all the travel movements (to go to the start position prior to the test)|
|
|TRAVEL_SPEED|120|speed in mm/s used for all the travel movements (to go to the start position prior to the test)|
|
||||||
|Z_HEIGHT|None|Z height wanted for the test. This value can be used if needed to override the Z value of the probe_point set in your `[resonance_tester]` config section|
|
|Z_HEIGHT|None|Z height wanted for the test. This value can be used if needed to override the Z value of the probe_point set in your `[resonance_tester]` config section|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -9,15 +9,21 @@
|
|||||||
# accelerometer measurements and write the data to a file in a blocking manner.
|
# accelerometer measurements and write the data to a file in a blocking manner.
|
||||||
|
|
||||||
|
|
||||||
|
import os
|
||||||
import time
|
import time
|
||||||
|
from multiprocessing import Process, Queue
|
||||||
|
|
||||||
# from ..helpers.console_output import ConsoleOutput
|
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_processes = []
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def find_axis_accelerometer(printer, axis: str = 'xy'):
|
def find_axis_accelerometer(printer, axis: str = 'xy'):
|
||||||
@@ -32,7 +38,6 @@ class Accelerometer:
|
|||||||
def start_measurement(self):
|
def start_measurement(self):
|
||||||
if self._bg_client is None:
|
if self._bg_client is None:
|
||||||
self._bg_client = self._k_accelerometer.start_internal_client()
|
self._bg_client = self._k_accelerometer.start_internal_client()
|
||||||
# ConsoleOutput.print('Accelerometer measurements started')
|
|
||||||
else:
|
else:
|
||||||
raise ValueError('measurements already started!')
|
raise ValueError('measurements already started!')
|
||||||
|
|
||||||
@@ -54,12 +59,49 @@ class Accelerometer:
|
|||||||
bg_client.finish_measurements()
|
bg_client.finish_measurements()
|
||||||
|
|
||||||
filename = f'/tmp/shaketune-{name}.csv'
|
filename = f'/tmp/shaketune-{name}.csv'
|
||||||
self._write_to_file(bg_client, filename)
|
self._queue_file_write(bg_client, filename)
|
||||||
# ConsoleOutput.print(f'Accelerometer measurements stopped. Data written to {filename}')
|
|
||||||
|
def _queue_file_write(self, bg_client, filename):
|
||||||
|
self._write_queue.put(filename)
|
||||||
|
write_proc = Process(target=self._write_to_file, args=(bg_client, filename))
|
||||||
|
write_proc.daemon = True
|
||||||
|
write_proc.start()
|
||||||
|
self._write_processes.append(write_proc)
|
||||||
|
|
||||||
def _write_to_file(self, bg_client, filename):
|
def _write_to_file(self, bg_client, filename):
|
||||||
|
try:
|
||||||
|
os.nice(20)
|
||||||
|
except Exception:
|
||||||
|
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()
|
||||||
|
|
||||||
|
def wait_for_file_writes(self):
|
||||||
|
while not self._write_queue.empty():
|
||||||
|
eventtime = self._reactor.monotonic()
|
||||||
|
self._reactor.pause(eventtime + 0.1)
|
||||||
|
|
||||||
|
for proc in self._write_processes:
|
||||||
|
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 = []
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -82,14 +88,20 @@ def axes_map_calibration(gcmd, config, st_process: ShakeTuneProcess) -> None:
|
|||||||
toolhead.dwell(0.5)
|
toolhead.dwell(0.5)
|
||||||
accelerometer.stop_measurement('axesmap_Z', append_time=True)
|
accelerometer.stop_measurement('axesmap_Z', append_time=True)
|
||||||
|
|
||||||
|
accelerometer.wait_for_file_writes()
|
||||||
|
|
||||||
# Re-enable the input shaper if it was active
|
# Re-enable the input shaper if it was active
|
||||||
if input_shaper is not None:
|
if input_shaper is not 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
|
||||||
|
|||||||
@@ -17,14 +17,20 @@ from .accelerometer import Accelerometer
|
|||||||
|
|
||||||
|
|
||||||
def axes_shaper_calibration(gcmd, config, st_process: ShakeTuneProcess) -> None:
|
def axes_shaper_calibration(gcmd, config, st_process: ShakeTuneProcess) -> None:
|
||||||
min_freq = gcmd.get_float('FREQ_START', default=5, minval=1)
|
printer = config.get_printer()
|
||||||
max_freq = gcmd.get_float('FREQ_END', default=133.33, minval=1)
|
toolhead = printer.lookup_object('toolhead')
|
||||||
|
res_tester = printer.lookup_object('resonance_tester')
|
||||||
|
systime = printer.get_reactor().monotonic()
|
||||||
|
toolhead_info = toolhead.get_status(systime)
|
||||||
|
|
||||||
|
min_freq = gcmd.get_float('FREQ_START', default=res_tester.test.min_freq, minval=1)
|
||||||
|
max_freq = gcmd.get_float('FREQ_END', default=res_tester.test.max_freq, minval=1)
|
||||||
hz_per_sec = gcmd.get_float('HZ_PER_SEC', default=1, minval=1)
|
hz_per_sec = gcmd.get_float('HZ_PER_SEC', default=1, minval=1)
|
||||||
accel_per_hz = gcmd.get_float('ACCEL_PER_HZ', default=None)
|
accel_per_hz = gcmd.get_float('ACCEL_PER_HZ', default=None)
|
||||||
axis_input = gcmd.get('AXIS', default='all').lower()
|
axis_input = gcmd.get('AXIS', default='all').lower()
|
||||||
if axis_input not in {'x', 'y', 'all'}:
|
if axis_input not in {'x', 'y', 'all'}:
|
||||||
raise gcmd.error('AXIS selection invalid. Should be either x, y, or all!')
|
raise gcmd.error('AXIS selection invalid. Should be either x, y, or all!')
|
||||||
scv = gcmd.get_float('SCV', default=None, minval=0)
|
scv = gcmd.get_float('SCV', default=toolhead_info['square_corner_velocity'], minval=0)
|
||||||
max_sm = gcmd.get_float('MAX_SMOOTHING', default=None, minval=0)
|
max_sm = gcmd.get_float('MAX_SMOOTHING', default=None, minval=0)
|
||||||
feedrate_travel = gcmd.get_float('TRAVEL_SPEED', default=120.0, minval=20.0)
|
feedrate_travel = gcmd.get_float('TRAVEL_SPEED', default=120.0, minval=20.0)
|
||||||
z_height = gcmd.get_float('Z_HEIGHT', default=None, minval=1)
|
z_height = gcmd.get_float('Z_HEIGHT', default=None, minval=1)
|
||||||
@@ -32,18 +38,11 @@ def axes_shaper_calibration(gcmd, config, st_process: ShakeTuneProcess) -> None:
|
|||||||
if accel_per_hz == '':
|
if accel_per_hz == '':
|
||||||
accel_per_hz = None
|
accel_per_hz = None
|
||||||
|
|
||||||
printer = config.get_printer()
|
|
||||||
gcode = printer.lookup_object('gcode')
|
|
||||||
toolhead = printer.lookup_object('toolhead')
|
|
||||||
res_tester = printer.lookup_object('resonance_tester')
|
|
||||||
systime = printer.get_reactor().monotonic()
|
|
||||||
|
|
||||||
if scv is None:
|
|
||||||
toolhead_info = toolhead.get_status(systime)
|
|
||||||
scv = toolhead_info['square_corner_velocity']
|
|
||||||
|
|
||||||
if accel_per_hz is None:
|
if accel_per_hz is None:
|
||||||
accel_per_hz = res_tester.test.accel_per_hz
|
accel_per_hz = res_tester.test.accel_per_hz
|
||||||
|
|
||||||
|
gcode = printer.lookup_object('gcode')
|
||||||
|
|
||||||
max_accel = max_freq * accel_per_hz
|
max_accel = max_freq * accel_per_hz
|
||||||
|
|
||||||
# Move to the starting point
|
# Move to the starting point
|
||||||
@@ -77,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)
|
||||||
@@ -96,13 +99,15 @@ 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()
|
||||||
vibrate_axis(toolhead, gcode, config['direction'], min_freq, max_freq, hz_per_sec, accel_per_hz)
|
vibrate_axis(toolhead, gcode, config['direction'], min_freq, max_freq, hz_per_sec, accel_per_hz)
|
||||||
accelerometer.stop_measurement(config['label'], append_time=True)
|
accelerometer.stop_measurement(config['label'], append_time=True)
|
||||||
|
|
||||||
|
accelerometer.wait_for_file_writes()
|
||||||
|
|
||||||
# And finally generate the graph for each measured axis
|
# And finally generate the graph for each measured axis
|
||||||
ConsoleOutput.print(f'{config["axis"].upper()} axis frequency profile generation...')
|
ConsoleOutput.print(f'{config["axis"].upper()} axis frequency profile generation...')
|
||||||
ConsoleOutput.print('This may take some time (1-3min)')
|
ConsoleOutput.print('This may take some time (1-3min)')
|
||||||
@@ -116,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}')
|
||||||
|
|||||||
@@ -18,9 +18,14 @@ from .accelerometer import Accelerometer
|
|||||||
|
|
||||||
|
|
||||||
def compare_belts_responses(gcmd, config, st_process: ShakeTuneProcess) -> None:
|
def compare_belts_responses(gcmd, config, st_process: ShakeTuneProcess) -> None:
|
||||||
min_freq = gcmd.get_float('FREQ_START', default=5.0, minval=1)
|
printer = config.get_printer()
|
||||||
max_freq = gcmd.get_float('FREQ_END', default=133.33, minval=1)
|
toolhead = printer.lookup_object('toolhead')
|
||||||
hz_per_sec = gcmd.get_float('HZ_PER_SEC', default=1.0, minval=1)
|
res_tester = printer.lookup_object('resonance_tester')
|
||||||
|
systime = printer.get_reactor().monotonic()
|
||||||
|
|
||||||
|
min_freq = gcmd.get_float('FREQ_START', default=res_tester.test.min_freq, minval=1)
|
||||||
|
max_freq = gcmd.get_float('FREQ_END', default=res_tester.test.max_freq, minval=1)
|
||||||
|
hz_per_sec = gcmd.get_float('HZ_PER_SEC', default=1, minval=1)
|
||||||
accel_per_hz = gcmd.get_float('ACCEL_PER_HZ', default=None)
|
accel_per_hz = gcmd.get_float('ACCEL_PER_HZ', default=None)
|
||||||
feedrate_travel = gcmd.get_float('TRAVEL_SPEED', default=120.0, minval=20.0)
|
feedrate_travel = gcmd.get_float('TRAVEL_SPEED', default=120.0, minval=20.0)
|
||||||
z_height = gcmd.get_float('Z_HEIGHT', default=None, minval=1)
|
z_height = gcmd.get_float('Z_HEIGHT', default=None, minval=1)
|
||||||
@@ -28,14 +33,11 @@ def compare_belts_responses(gcmd, config, st_process: ShakeTuneProcess) -> None:
|
|||||||
if accel_per_hz == '':
|
if accel_per_hz == '':
|
||||||
accel_per_hz = None
|
accel_per_hz = None
|
||||||
|
|
||||||
printer = config.get_printer()
|
|
||||||
gcode = printer.lookup_object('gcode')
|
|
||||||
toolhead = printer.lookup_object('toolhead')
|
|
||||||
res_tester = printer.lookup_object('resonance_tester')
|
|
||||||
systime = printer.get_reactor().monotonic()
|
|
||||||
|
|
||||||
if accel_per_hz is None:
|
if accel_per_hz is None:
|
||||||
accel_per_hz = res_tester.test.accel_per_hz
|
accel_per_hz = res_tester.test.accel_per_hz
|
||||||
|
|
||||||
|
gcode = printer.lookup_object('gcode')
|
||||||
|
|
||||||
max_accel = max_freq * accel_per_hz
|
max_accel = max_freq * accel_per_hz
|
||||||
|
|
||||||
# Configure the graph creator
|
# Configure the graph creator
|
||||||
@@ -58,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()
|
||||||
@@ -87,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)
|
||||||
@@ -103,12 +109,17 @@ def compare_belts_responses(gcmd, config, st_process: ShakeTuneProcess) -> None:
|
|||||||
vibrate_axis(toolhead, gcode, config['direction'], min_freq, max_freq, hz_per_sec, accel_per_hz)
|
vibrate_axis(toolhead, gcode, config['direction'], min_freq, max_freq, hz_per_sec, accel_per_hz)
|
||||||
accelerometer.stop_measurement(config['label'], append_time=True)
|
accelerometer.stop_measurement(config['label'], append_time=True)
|
||||||
|
|
||||||
|
accelerometer.wait_for_file_writes()
|
||||||
|
|
||||||
# Re-enable the input shaper if it was active
|
# Re-enable the input shaper if it was active
|
||||||
if input_shaper is not None:
|
if input_shaper is not 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
|
||||||
@@ -90,8 +96,8 @@ def create_vibrations_profile(gcmd, config, st_process: ShakeTuneProcess) -> Non
|
|||||||
k_accelerometer = printer.lookup_object(current_accel_chip, None)
|
k_accelerometer = printer.lookup_object(current_accel_chip, None)
|
||||||
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!')
|
||||||
accelerometer = Accelerometer(k_accelerometer)
|
|
||||||
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(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):
|
||||||
@@ -131,10 +137,15 @@ def create_vibrations_profile(gcmd, config, st_process: ShakeTuneProcess) -> Non
|
|||||||
toolhead.dwell(0.3)
|
toolhead.dwell(0.3)
|
||||||
toolhead.wait_moves()
|
toolhead.wait_moves()
|
||||||
|
|
||||||
|
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')
|
||||||
|
|
||||||
@@ -100,6 +100,7 @@ def excitate_axis_at_freq(gcmd, config, st_process: ShakeTuneProcess) -> None:
|
|||||||
# If the user wanted to create a graph, we stop the recording and generate it
|
# If the user wanted to create a graph, we stop the recording and generate it
|
||||||
if create_graph:
|
if create_graph:
|
||||||
accelerometer.stop_measurement(f'staticfreq_{axis.upper()}', append_time=True)
|
accelerometer.stop_measurement(f'staticfreq_{axis.upper()}', append_time=True)
|
||||||
|
accelerometer.wait_for_file_writes()
|
||||||
|
|
||||||
creator = st_process.get_graph_creator()
|
creator = st_process.get_graph_creator()
|
||||||
creator.configure(freq, duration, accel_per_hz)
|
creator.configure(freq, duration, accel_per_hz)
|
||||||
|
|||||||
@@ -45,15 +45,15 @@ gcode:
|
|||||||
[gcode_macro COMPARE_BELTS_RESPONSES]
|
[gcode_macro COMPARE_BELTS_RESPONSES]
|
||||||
description: dummy
|
description: dummy
|
||||||
gcode:
|
gcode:
|
||||||
{% set freq_start = params.FREQ_START|default(5) %}
|
{% set freq_start = params.FREQ_START %}
|
||||||
{% set freq_end = params.FREQ_END|default(133.33) %}
|
{% set freq_end = params.FREQ_END %}
|
||||||
{% set hz_per_sec = params.HZ_PER_SEC|default(1) %}
|
{% set hz_per_sec = params.HZ_PER_SEC|default(1) %}
|
||||||
{% set accel_per_hz = params.ACCEL_PER_HZ %}
|
{% set accel_per_hz = params.ACCEL_PER_HZ %}
|
||||||
{% set travel_speed = params.TRAVEL_SPEED|default(120) %}
|
{% set travel_speed = params.TRAVEL_SPEED|default(120) %}
|
||||||
{% set z_height = params.Z_HEIGHT %}
|
{% set z_height = params.Z_HEIGHT %}
|
||||||
{% set params_filtered = {
|
{% set params_filtered = {
|
||||||
"FREQ_START": freq_start,
|
"FREQ_START": freq_start if freq_start is not none else '',
|
||||||
"FREQ_END": freq_end,
|
"FREQ_END": freq_end if freq_end is not none else '',
|
||||||
"HZ_PER_SEC": hz_per_sec,
|
"HZ_PER_SEC": hz_per_sec,
|
||||||
"ACCEL_PER_HZ": accel_per_hz if accel_per_hz is not none else '',
|
"ACCEL_PER_HZ": accel_per_hz if accel_per_hz is not none else '',
|
||||||
"TRAVEL_SPEED": travel_speed,
|
"TRAVEL_SPEED": travel_speed,
|
||||||
@@ -65,8 +65,8 @@ gcode:
|
|||||||
[gcode_macro AXES_SHAPER_CALIBRATION]
|
[gcode_macro AXES_SHAPER_CALIBRATION]
|
||||||
description: dummy
|
description: dummy
|
||||||
gcode:
|
gcode:
|
||||||
{% set freq_start = params.FREQ_START|default(5) %}
|
{% set freq_start = params.FREQ_START %}
|
||||||
{% set freq_end = params.FREQ_END|default(133.33) %}
|
{% set freq_end = params.FREQ_END %}
|
||||||
{% set hz_per_sec = params.HZ_PER_SEC|default(1) %}
|
{% set hz_per_sec = params.HZ_PER_SEC|default(1) %}
|
||||||
{% set accel_per_hz = params.ACCEL_PER_HZ %}
|
{% set accel_per_hz = params.ACCEL_PER_HZ %}
|
||||||
{% set axis = params.AXIS|default('all') %}
|
{% set axis = params.AXIS|default('all') %}
|
||||||
@@ -75,8 +75,8 @@ gcode:
|
|||||||
{% set travel_speed = params.TRAVEL_SPEED|default(120) %}
|
{% set travel_speed = params.TRAVEL_SPEED|default(120) %}
|
||||||
{% set z_height = params.Z_HEIGHT %}
|
{% set z_height = params.Z_HEIGHT %}
|
||||||
{% set params_filtered = {
|
{% set params_filtered = {
|
||||||
"FREQ_START": freq_start,
|
"FREQ_START": freq_start if freq_start is not none else '',
|
||||||
"FREQ_END": freq_end,
|
"FREQ_END": freq_end if freq_end is not none else '',
|
||||||
"HZ_PER_SEC": hz_per_sec,
|
"HZ_PER_SEC": hz_per_sec,
|
||||||
"ACCEL_PER_HZ": accel_per_hz if accel_per_hz is not none else '',
|
"ACCEL_PER_HZ": accel_per_hz if accel_per_hz is not none else '',
|
||||||
"AXIS": axis,
|
"AXIS": axis,
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
# Description: Implements the axes map detection script for Shake&Tune, including
|
# Description: Implements the axes map detection script for Shake&Tune, including
|
||||||
# calibration tools and graph creation for 3D printer vibration analysis.
|
# calibration tools and graph creation for 3D printer vibration analysis.
|
||||||
|
|
||||||
|
|
||||||
import optparse
|
import optparse
|
||||||
import os
|
import os
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
@@ -194,9 +193,14 @@ def linear_regression_direction(
|
|||||||
|
|
||||||
|
|
||||||
def plot_compare_frequency(
|
def plot_compare_frequency(
|
||||||
ax: plt.Axes, time: np.ndarray, accel_x: np.ndarray, accel_y: np.ndarray, accel_z: np.ndarray, offset: float, i: int
|
ax: plt.Axes,
|
||||||
|
time_data: List[np.ndarray],
|
||||||
|
accel_data: List[Tuple[np.ndarray, np.ndarray, np.ndarray]],
|
||||||
|
offset: float,
|
||||||
|
noise_level: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
# Plot acceleration data
|
# Plot acceleration data
|
||||||
|
for i, (time, (accel_x, accel_y, accel_z)) in enumerate(zip(time_data, accel_data)):
|
||||||
ax.plot(
|
ax.plot(
|
||||||
time,
|
time,
|
||||||
accel_x,
|
accel_x,
|
||||||
@@ -222,7 +226,6 @@ def plot_compare_frequency(
|
|||||||
zorder=50 if i == 2 else 10,
|
zorder=50 if i == 2 else 10,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Setting axis parameters, grid and graph title
|
|
||||||
ax.set_xlabel('Time (s)')
|
ax.set_xlabel('Time (s)')
|
||||||
ax.set_ylabel('Acceleration (mm/s²)')
|
ax.set_ylabel('Acceleration (mm/s²)')
|
||||||
|
|
||||||
@@ -242,23 +245,24 @@ def plot_compare_frequency(
|
|||||||
|
|
||||||
ax.legend(loc='upper left', prop=fontP)
|
ax.legend(loc='upper left', prop=fontP)
|
||||||
|
|
||||||
# Add gravity offset to the graph
|
# Add the gravity and noise level to the graph legend
|
||||||
if i == 0:
|
ax2 = ax.twinx()
|
||||||
ax2 = ax.twinx() # To split the legends in two box
|
|
||||||
ax2.yaxis.set_visible(False)
|
ax2.yaxis.set_visible(False)
|
||||||
|
ax2.plot([], [], ' ', label=noise_level)
|
||||||
ax2.plot([], [], ' ', label=f'Measured gravity: {offset / 1000:0.3f} m/s²')
|
ax2.plot([], [], ' ', label=f'Measured gravity: {offset / 1000:0.3f} m/s²')
|
||||||
ax2.legend(loc='upper right', prop=fontP)
|
ax2.legend(loc='upper right', prop=fontP)
|
||||||
|
|
||||||
|
|
||||||
def plot_3d_path(
|
def plot_3d_path(
|
||||||
ax: plt.Axes,
|
ax: plt.Axes,
|
||||||
i: int,
|
position_data: List[Tuple[np.ndarray, np.ndarray, np.ndarray]],
|
||||||
position_x: np.ndarray,
|
direction_vectors: List[np.ndarray],
|
||||||
position_y: np.ndarray,
|
angle_errors: List[float],
|
||||||
position_z: np.ndarray,
|
|
||||||
average_direction_vector: np.ndarray,
|
|
||||||
angle_error: float,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
|
# Plot the 3D path of the movement
|
||||||
|
for i, ((position_x, position_y, position_z), average_direction_vector, angle_error) in enumerate(
|
||||||
|
zip(position_data, direction_vectors, angle_errors)
|
||||||
|
):
|
||||||
ax.plot(position_x, position_y, position_z, color=KLIPPAIN_COLORS['orange'], linestyle=':', linewidth=2)
|
ax.plot(position_x, position_y, position_z, color=KLIPPAIN_COLORS['orange'], linestyle=':', linewidth=2)
|
||||||
ax.scatter(position_x[0], position_y[0], position_z[0], color=KLIPPAIN_COLORS['red_pink'], zorder=10)
|
ax.scatter(position_x[0], position_y[0], position_z[0], color=KLIPPAIN_COLORS['red_pink'], zorder=10)
|
||||||
ax.text(
|
ax.text(
|
||||||
@@ -277,18 +281,16 @@ def plot_3d_path(
|
|||||||
end_position = start_position + average_direction_vector * np.linalg.norm(
|
end_position = start_position + average_direction_vector * np.linalg.norm(
|
||||||
[position_x[-1] - position_x[0], position_y[-1] - position_y[0], position_z[-1] - position_z[0]]
|
[position_x[-1] - position_x[0], position_y[-1] - position_y[0], position_z[-1] - position_z[0]]
|
||||||
)
|
)
|
||||||
axes = ['X', 'Y', 'Z']
|
|
||||||
ax.plot(
|
ax.plot(
|
||||||
[start_position[0], end_position[0]],
|
[start_position[0], end_position[0]],
|
||||||
[start_position[1], end_position[1]],
|
[start_position[1], end_position[1]],
|
||||||
[start_position[2], end_position[2]],
|
[start_position[2], end_position[2]],
|
||||||
label=f'{axes[i]} angle: {angle_error:0.2f}°',
|
label=f'{["X", "Y", "Z"][i]} angle: {angle_error:0.2f}°',
|
||||||
color=KLIPPAIN_COLORS['purple'],
|
color=KLIPPAIN_COLORS['purple'],
|
||||||
linestyle='-',
|
linestyle='-',
|
||||||
linewidth=2,
|
linewidth=2,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Setting axis parameters, grid and graph title
|
|
||||||
ax.set_xlabel('X Position (mm)')
|
ax.set_xlabel('X Position (mm)')
|
||||||
ax.set_ylabel('Y Position (mm)')
|
ax.set_ylabel('Y Position (mm)')
|
||||||
ax.set_zlabel('Z Position (mm)')
|
ax.set_zlabel('Z Position (mm)')
|
||||||
@@ -311,14 +313,24 @@ def plot_3d_path(
|
|||||||
|
|
||||||
def format_direction_vector(vectors: List[np.ndarray]) -> str:
|
def format_direction_vector(vectors: List[np.ndarray]) -> str:
|
||||||
formatted_vector = []
|
formatted_vector = []
|
||||||
|
axes_count = {'x': 0, 'y': 0, 'z': 0}
|
||||||
|
|
||||||
for vector in vectors:
|
for vector in vectors:
|
||||||
for i in range(len(vector)):
|
for i in range(len(vector)):
|
||||||
if vector[i] > 0:
|
if vector[i] > 0:
|
||||||
formatted_vector.append(MACHINE_AXES[i])
|
formatted_vector.append(MACHINE_AXES[i])
|
||||||
|
axes_count[MACHINE_AXES[i]] += 1
|
||||||
break
|
break
|
||||||
elif vector[i] < 0:
|
elif vector[i] < 0:
|
||||||
formatted_vector.append(f'-{MACHINE_AXES[i]}')
|
formatted_vector.append(f'-{MACHINE_AXES[i]}')
|
||||||
|
axes_count[MACHINE_AXES[i]] += 1
|
||||||
break
|
break
|
||||||
|
|
||||||
|
# Check if all axes are present in the axes_map and return an error message if not
|
||||||
|
for _, count in axes_count.items():
|
||||||
|
if count != 1:
|
||||||
|
return 'unable to determine it correctly!'
|
||||||
|
|
||||||
return ', '.join(formatted_vector)
|
return ', '.join(formatted_vector)
|
||||||
|
|
||||||
|
|
||||||
@@ -360,8 +372,12 @@ def axesmap_calibration(
|
|||||||
|
|
||||||
cumulative_start_position = np.array([0, 0, 0])
|
cumulative_start_position = np.array([0, 0, 0])
|
||||||
direction_vectors = []
|
direction_vectors = []
|
||||||
|
angle_errors = []
|
||||||
total_noise_intensity = 0.0
|
total_noise_intensity = 0.0
|
||||||
for i, machine_axis in enumerate(MACHINE_AXES):
|
acceleration_data = []
|
||||||
|
position_data = []
|
||||||
|
gravities = []
|
||||||
|
for _, machine_axis in enumerate(MACHINE_AXES):
|
||||||
if machine_axis not in raw_datas:
|
if machine_axis not in raw_datas:
|
||||||
raise ValueError(f'Missing CSV file for axis {machine_axis}')
|
raise ValueError(f'Missing CSV file for axis {machine_axis}')
|
||||||
|
|
||||||
@@ -388,15 +404,19 @@ def axesmap_calibration(
|
|||||||
f'Machine axis {machine_axis.upper()} -> nearest accelerometer direction vector: {direction_vector} (angle error: {angle_error:.2f}°)'
|
f'Machine axis {machine_axis.upper()} -> nearest accelerometer direction vector: {direction_vector} (angle error: {angle_error:.2f}°)'
|
||||||
)
|
)
|
||||||
direction_vectors.append(direction_vector)
|
direction_vectors.append(direction_vector)
|
||||||
|
angle_errors.append(angle_error)
|
||||||
|
|
||||||
total_noise_intensity += noise_intensity
|
total_noise_intensity += noise_intensity
|
||||||
|
|
||||||
plot_compare_frequency(ax1, time, accel_x, accel_y, accel_z, gravity, i)
|
acceleration_data.append((time, (accel_x, accel_y, accel_z)))
|
||||||
plot_3d_path(ax2, i, position_x, position_y, position_z, average_direction_vector, angle_error)
|
position_data.append((position_x, position_y, position_z))
|
||||||
|
gravities.append(gravity)
|
||||||
|
|
||||||
# Update the cumulative start position for the next segment
|
# Update the cumulative start position for the next segment
|
||||||
cumulative_start_position = np.array([position_x[-1], position_y[-1], position_z[-1]])
|
cumulative_start_position = np.array([position_x[-1], position_y[-1], position_z[-1]])
|
||||||
|
|
||||||
|
gravity = np.mean(gravities)
|
||||||
|
|
||||||
average_noise_intensity = total_noise_intensity / len(raw_datas)
|
average_noise_intensity = total_noise_intensity / len(raw_datas)
|
||||||
if average_noise_intensity <= 350:
|
if average_noise_intensity <= 350:
|
||||||
average_noise_intensity_text = '-> OK'
|
average_noise_intensity_text = '-> OK'
|
||||||
@@ -405,11 +425,25 @@ def axesmap_calibration(
|
|||||||
else:
|
else:
|
||||||
average_noise_intensity_text = '-> ERROR: accelerometer noise is too high!'
|
average_noise_intensity_text = '-> ERROR: accelerometer noise is too high!'
|
||||||
|
|
||||||
|
average_noise_intensity_label = (
|
||||||
|
f'Dynamic noise level: {average_noise_intensity:.2f} mm/s² {average_noise_intensity_text}'
|
||||||
|
)
|
||||||
|
ConsoleOutput.print(average_noise_intensity_label)
|
||||||
|
|
||||||
|
ConsoleOutput.print(f'--> Detected gravity: {gravity / 1000 :.2f} m/s²')
|
||||||
|
|
||||||
formatted_direction_vector = format_direction_vector(direction_vectors)
|
formatted_direction_vector = format_direction_vector(direction_vectors)
|
||||||
ConsoleOutput.print(f'--> Detected axes_map: {formatted_direction_vector}')
|
ConsoleOutput.print(f'--> Detected axes_map: {formatted_direction_vector}')
|
||||||
ConsoleOutput.print(
|
|
||||||
f'Average accelerometer noise level: {average_noise_intensity:.2f} mm/s² {average_noise_intensity_text}'
|
# Plot the differents graphs
|
||||||
|
plot_compare_frequency(
|
||||||
|
ax1,
|
||||||
|
[d[0] for d in acceleration_data],
|
||||||
|
[d[1] for d in acceleration_data],
|
||||||
|
gravity,
|
||||||
|
average_noise_intensity_label,
|
||||||
)
|
)
|
||||||
|
plot_3d_path(ax2, position_data, direction_vectors, angle_errors)
|
||||||
|
|
||||||
# Add title
|
# Add title
|
||||||
title_line1 = 'AXES MAP CALIBRATION TOOL'
|
title_line1 = 'AXES MAP CALIBRATION TOOL'
|
||||||
@@ -430,9 +464,7 @@ def axesmap_calibration(
|
|||||||
fig.text(0.060, 0.939, title_line2, ha='left', va='top', fontsize=16, color=KLIPPAIN_COLORS['dark_purple'])
|
fig.text(0.060, 0.939, title_line2, ha='left', va='top', fontsize=16, color=KLIPPAIN_COLORS['dark_purple'])
|
||||||
|
|
||||||
title_line3 = f'| Detected axes_map: {formatted_direction_vector}'
|
title_line3 = f'| Detected axes_map: {formatted_direction_vector}'
|
||||||
title_line4 = f'| Accelerometer noise level: {average_noise_intensity:.2f} mm/s² {average_noise_intensity_text}'
|
fig.text(0.50, 0.985, title_line3, ha='left', va='top', fontsize=16, color=KLIPPAIN_COLORS['dark_purple'])
|
||||||
fig.text(0.50, 0.985, title_line3, ha='left', va='top', fontsize=14, color=KLIPPAIN_COLORS['dark_purple'])
|
|
||||||
fig.text(0.50, 0.950, title_line4, ha='left', va='top', fontsize=11, color=KLIPPAIN_COLORS['dark_purple'])
|
|
||||||
|
|
||||||
# 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')
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|
||||||
@@ -296,7 +297,7 @@ def plot_compare_frequency(
|
|||||||
|
|
||||||
ax.xaxis.set_minor_locator(matplotlib.ticker.AutoMinorLocator())
|
ax.xaxis.set_minor_locator(matplotlib.ticker.AutoMinorLocator())
|
||||||
ax.yaxis.set_minor_locator(matplotlib.ticker.AutoMinorLocator())
|
ax.yaxis.set_minor_locator(matplotlib.ticker.AutoMinorLocator())
|
||||||
ax.ticklabel_format(axis='x', style='scientific', scilimits=(0, 0))
|
ax.ticklabel_format(axis='y', style='scientific', scilimits=(0, 0))
|
||||||
ax.grid(which='major', color='grey')
|
ax.grid(which='major', color='grey')
|
||||||
ax.grid(which='minor', color='lightgrey')
|
ax.grid(which='minor', color='lightgrey')
|
||||||
fontP = matplotlib.font_manager.FontProperties()
|
fontP = matplotlib.font_manager.FontProperties()
|
||||||
@@ -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',
|
||||||
@@ -459,7 +446,7 @@ def plot_versus_belts(
|
|||||||
|
|
||||||
ax.xaxis.set_minor_locator(matplotlib.ticker.AutoMinorLocator())
|
ax.xaxis.set_minor_locator(matplotlib.ticker.AutoMinorLocator())
|
||||||
ax.yaxis.set_minor_locator(matplotlib.ticker.AutoMinorLocator())
|
ax.yaxis.set_minor_locator(matplotlib.ticker.AutoMinorLocator())
|
||||||
ax.ticklabel_format(axis='y', style='scientific', scilimits=(0, 0))
|
ax.ticklabel_format(style='scientific', scilimits=(0, 0))
|
||||||
ax.grid(which='major', color='grey')
|
ax.grid(which='major', color='grey')
|
||||||
ax.grid(which='minor', color='lightgrey')
|
ax.grid(which='minor', color='lightgrey')
|
||||||
|
|
||||||
@@ -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
|
||||||
@@ -117,29 +131,54 @@ class ShakeTune:
|
|||||||
def cmd_EXCITATE_AXIS_AT_FREQ(self, gcmd) -> None:
|
def cmd_EXCITATE_AXIS_AT_FREQ(self, gcmd) -> None:
|
||||||
ConsoleOutput.print(f'Shake&Tune version: {ShakeTuneConfig.get_git_version()}')
|
ConsoleOutput.print(f'Shake&Tune version: {ShakeTuneConfig.get_git_version()}')
|
||||||
static_freq_graph_creator = StaticGraphCreator(self._config)
|
static_freq_graph_creator = StaticGraphCreator(self._config)
|
||||||
st_process = ShakeTuneProcess(self._config, static_freq_graph_creator, self.timeout)
|
st_process = ShakeTuneProcess(
|
||||||
|
self._config,
|
||||||
|
self._printer.get_reactor(),
|
||||||
|
static_freq_graph_creator,
|
||||||
|
self.timeout,
|
||||||
|
)
|
||||||
excitate_axis_at_freq(gcmd, self._pconfig, st_process)
|
excitate_axis_at_freq(gcmd, self._pconfig, st_process)
|
||||||
|
|
||||||
def cmd_AXES_MAP_CALIBRATION(self, gcmd) -> None:
|
def cmd_AXES_MAP_CALIBRATION(self, gcmd) -> None:
|
||||||
ConsoleOutput.print(f'Shake&Tune version: {ShakeTuneConfig.get_git_version()}')
|
ConsoleOutput.print(f'Shake&Tune version: {ShakeTuneConfig.get_git_version()}')
|
||||||
axes_map_graph_creator = AxesMapGraphCreator(self._config)
|
axes_map_graph_creator = AxesMapGraphCreator(self._config)
|
||||||
st_process = ShakeTuneProcess(self._config, axes_map_graph_creator, self.timeout)
|
st_process = ShakeTuneProcess(
|
||||||
|
self._config,
|
||||||
|
self._printer.get_reactor(),
|
||||||
|
axes_map_graph_creator,
|
||||||
|
self.timeout,
|
||||||
|
)
|
||||||
axes_map_calibration(gcmd, self._pconfig, st_process)
|
axes_map_calibration(gcmd, self._pconfig, st_process)
|
||||||
|
|
||||||
def cmd_COMPARE_BELTS_RESPONSES(self, gcmd) -> None:
|
def cmd_COMPARE_BELTS_RESPONSES(self, gcmd) -> None:
|
||||||
ConsoleOutput.print(f'Shake&Tune version: {ShakeTuneConfig.get_git_version()}')
|
ConsoleOutput.print(f'Shake&Tune version: {ShakeTuneConfig.get_git_version()}')
|
||||||
belt_graph_creator = BeltsGraphCreator(self._config)
|
belt_graph_creator = BeltsGraphCreator(self._config)
|
||||||
st_process = ShakeTuneProcess(self._config, belt_graph_creator, self.timeout)
|
st_process = ShakeTuneProcess(
|
||||||
|
self._config,
|
||||||
|
self._printer.get_reactor(),
|
||||||
|
belt_graph_creator,
|
||||||
|
self.timeout,
|
||||||
|
)
|
||||||
compare_belts_responses(gcmd, self._pconfig, st_process)
|
compare_belts_responses(gcmd, self._pconfig, st_process)
|
||||||
|
|
||||||
def cmd_AXES_SHAPER_CALIBRATION(self, gcmd) -> None:
|
def cmd_AXES_SHAPER_CALIBRATION(self, gcmd) -> None:
|
||||||
ConsoleOutput.print(f'Shake&Tune version: {ShakeTuneConfig.get_git_version()}')
|
ConsoleOutput.print(f'Shake&Tune version: {ShakeTuneConfig.get_git_version()}')
|
||||||
shaper_graph_creator = ShaperGraphCreator(self._config)
|
shaper_graph_creator = ShaperGraphCreator(self._config)
|
||||||
st_process = ShakeTuneProcess(self._config, shaper_graph_creator, self.timeout)
|
st_process = ShakeTuneProcess(
|
||||||
|
self._config,
|
||||||
|
self._printer.get_reactor(),
|
||||||
|
shaper_graph_creator,
|
||||||
|
self.timeout,
|
||||||
|
)
|
||||||
axes_shaper_calibration(gcmd, self._pconfig, st_process)
|
axes_shaper_calibration(gcmd, self._pconfig, st_process)
|
||||||
|
|
||||||
def cmd_CREATE_VIBRATIONS_PROFILE(self, gcmd) -> None:
|
def cmd_CREATE_VIBRATIONS_PROFILE(self, gcmd) -> None:
|
||||||
ConsoleOutput.print(f'Shake&Tune version: {ShakeTuneConfig.get_git_version()}')
|
ConsoleOutput.print(f'Shake&Tune version: {ShakeTuneConfig.get_git_version()}')
|
||||||
vibration_profile_creator = VibrationsGraphCreator(self._config)
|
vibration_profile_creator = VibrationsGraphCreator(self._config)
|
||||||
st_process = ShakeTuneProcess(self._config, vibration_profile_creator, self.timeout)
|
st_process = ShakeTuneProcess(
|
||||||
|
self._config,
|
||||||
|
self._printer.get_reactor(),
|
||||||
|
vibration_profile_creator,
|
||||||
|
self.timeout,
|
||||||
|
)
|
||||||
create_vibrations_profile(gcmd, self._pconfig, st_process)
|
create_vibrations_profile(gcmd, self._pconfig, st_process)
|
||||||
|
|||||||
@@ -8,10 +8,10 @@
|
|||||||
# vibration analysis processes in separate system processes.
|
# vibration analysis processes in separate system processes.
|
||||||
|
|
||||||
|
|
||||||
import multiprocessing
|
|
||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
import traceback
|
import traceback
|
||||||
|
from multiprocessing import Process
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from .helpers.console_output import ConsoleOutput
|
from .helpers.console_output import ConsoleOutput
|
||||||
@@ -19,11 +19,11 @@ from .shaketune_config import ShakeTuneConfig
|
|||||||
|
|
||||||
|
|
||||||
class ShakeTuneProcess:
|
class ShakeTuneProcess:
|
||||||
def __init__(self, config: ShakeTuneConfig, graph_creator, timeout: Optional[float] = None) -> None:
|
def __init__(self, st_config: ShakeTuneConfig, reactor, graph_creator, timeout: Optional[float] = None) -> None:
|
||||||
self._config = config
|
self._config = st_config
|
||||||
|
self._reactor = reactor
|
||||||
self.graph_creator = graph_creator
|
self.graph_creator = graph_creator
|
||||||
self._timeout = timeout
|
self._timeout = timeout
|
||||||
|
|
||||||
self._process = None
|
self._process = None
|
||||||
|
|
||||||
def get_graph_creator(self):
|
def get_graph_creator(self):
|
||||||
@@ -31,22 +31,32 @@ class ShakeTuneProcess:
|
|||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
# Start the target function in a new process (a thread is known to cause issues with Klipper and CANbus due to the GIL)
|
# Start the target function in a new process (a thread is known to cause issues with Klipper and CANbus due to the GIL)
|
||||||
self._process = multiprocessing.Process(
|
self._process = Process(target=self._shaketune_process_wrapper, args=(self.graph_creator, self._timeout))
|
||||||
target=self._shaketune_process_wrapper, args=(self.graph_creator, self._timeout)
|
|
||||||
)
|
|
||||||
self._process.start()
|
self._process.start()
|
||||||
|
|
||||||
def wait_for_completion(self) -> None:
|
def wait_for_completion(self) -> None:
|
||||||
if self._process is not None:
|
if self._process is None:
|
||||||
self._process.join()
|
return # Nothing to wait for
|
||||||
|
eventtime = self._reactor.monotonic()
|
||||||
|
endtime = eventtime + self._timeout
|
||||||
|
complete = False
|
||||||
|
while eventtime < endtime:
|
||||||
|
eventtime = self._reactor.pause(eventtime + 0.05)
|
||||||
|
if not self._process.is_alive():
|
||||||
|
complete = True
|
||||||
|
break
|
||||||
|
if not complete:
|
||||||
|
self._handle_timeout()
|
||||||
|
|
||||||
# This function is a simple wrapper to start the Shake&Tune process. It's needed in order to get the timeout
|
# This function is a simple wrapper to start the Shake&Tune process. It's needed in order to get the timeout
|
||||||
# as a Timer in a thread INSIDE the Shake&Tune child process to not interfere with the main Klipper process
|
# as a Timer in a thread INSIDE the Shake&Tune child process to not interfere with the main Klipper process
|
||||||
def _shaketune_process_wrapper(self, graph_creator, timeout) -> None:
|
def _shaketune_process_wrapper(self, graph_creator, timeout) -> None:
|
||||||
if timeout is not None:
|
if timeout is not None:
|
||||||
|
# Add 5 seconds to the timeout for safety. The goal is to avoid the Timer to finish before the
|
||||||
|
# Shake&Tune process is done in case we call the wait_for_completion() function that uses Klipper's reactor.
|
||||||
|
timeout += 5
|
||||||
timer = threading.Timer(timeout, self._handle_timeout)
|
timer = threading.Timer(timeout, self._handle_timeout)
|
||||||
timer.start()
|
timer.start()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._shaketune_process(graph_creator)
|
self._shaketune_process(graph_creator)
|
||||||
finally:
|
finally:
|
||||||
@@ -58,10 +68,12 @@ class ShakeTuneProcess:
|
|||||||
os._exit(1) # Forcefully exit the process
|
os._exit(1) # Forcefully exit the process
|
||||||
|
|
||||||
def _shaketune_process(self, graph_creator) -> None:
|
def _shaketune_process(self, graph_creator) -> None:
|
||||||
# Trying to reduce Shake&Tune process priority to avoid slowing down the main Klipper process
|
# Reducing Shake&Tune process priority by putting the scheduler into batch mode with low priority. This in order to avoid
|
||||||
# as this could lead to random "Timer too close" errors when already running CANbus, etc...
|
# slowing down the main Klipper process as this can lead to random "Timer too close" or "Move queue overflow" errors
|
||||||
|
# when also already running CANbus, neopixels and other consumming stuff in Klipper's main process.
|
||||||
try:
|
try:
|
||||||
os.nice(19)
|
param = os.sched_param(os.sched_get_priority_min(os.SCHED_BATCH))
|
||||||
|
os.sched_setscheduler(0, os.SCHED_BATCH, param)
|
||||||
except Exception:
|
except Exception:
|
||||||
ConsoleOutput.print('Warning: failed reducing Shake&Tune process priority, continuing...')
|
ConsoleOutput.print('Warning: failed reducing Shake&Tune process priority, continuing...')
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user