fixed items from code review

This commit is contained in:
Félix Boisselier
2024-06-11 21:26:15 +02:00
parent 6d1e53d4d1
commit ecd57ea3dc
21 changed files with 52 additions and 122 deletions

View File

@@ -1,5 +1,3 @@
#!/usr/bin/env python3
############################################ ############################################
###### INPUT SHAPER KLIPPAIN WORKFLOW ###### ###### INPUT SHAPER KLIPPAIN WORKFLOW ######
############################################ ############################################

View File

@@ -1,5 +1,3 @@
#!/usr/bin/env python3
from .axes_map_calibration import axes_map_calibration as axes_map_calibration from .axes_map_calibration import axes_map_calibration as axes_map_calibration
from .axes_shaper_calibration import axes_shaper_calibration as axes_shaper_calibration from .axes_shaper_calibration import axes_shaper_calibration as axes_shaper_calibration
from .compare_belts_responses import compare_belts_responses as compare_belts_responses from .compare_belts_responses import compare_belts_responses as compare_belts_responses

View File

@@ -1,5 +1,3 @@
#!/usr/bin/env python3
# This file provides a custom and internal Shake&Tune Accelerometer helper that is # This file provides a custom and internal Shake&Tune Accelerometer helper that is
# an interface to Klipper's own accelerometer classes. It is used to start and # an interface to Klipper's own accelerometer classes. It is used to start and
# stop accelerometer measurements and write the data to a file in a blocking manner. # stop accelerometer measurements and write the data to a file in a blocking manner.
@@ -18,7 +16,7 @@ class Accelerometer:
def find_axis_accelerometer(printer, axis: str = 'xy'): def find_axis_accelerometer(printer, axis: str = 'xy'):
accel_chip_names = printer.lookup_object('resonance_tester').accel_chip_names accel_chip_names = printer.lookup_object('resonance_tester').accel_chip_names
for chip_axis, chip_name in accel_chip_names: for chip_axis, chip_name in accel_chip_names:
if axis in ['x', 'y'] and chip_axis == 'xy': if axis in {'x', 'y'} and chip_axis == 'xy':
return chip_name return chip_name
elif chip_axis == axis: elif chip_axis == axis:
return chip_name return chip_name
@@ -57,4 +55,4 @@ class Accelerometer:
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('%.6f,%.6f,%.6f,%.6f\n' % (t, accel_x, accel_y, accel_z)) f.write(f'{t:.6f},{accel_x:.6f},{accel_y:.6f},{accel_z:.6f}\n')

View File

@@ -1,6 +1,3 @@
#!/usr/bin/env python3
from ..helpers.console_output import ConsoleOutput from ..helpers.console_output import ConsoleOutput
from ..shaketune_process import ShakeTuneProcess from ..shaketune_process import ShakeTuneProcess
from .accelerometer import Accelerometer from .accelerometer import Accelerometer

View File

@@ -1,6 +1,3 @@
#!/usr/bin/env python3
from ..helpers.common_func import AXIS_CONFIG from ..helpers.common_func import AXIS_CONFIG
from ..helpers.console_output import ConsoleOutput from ..helpers.console_output import ConsoleOutput
from ..helpers.resonance_test import vibrate_axis from ..helpers.resonance_test import vibrate_axis
@@ -14,7 +11,7 @@ def axes_shaper_calibration(gcmd, config, st_process: ShakeTuneProcess) -> None:
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=None, minval=0)
max_sm = gcmd.get_float('MAX_SMOOTHING', default=None, minval=0) max_sm = gcmd.get_float('MAX_SMOOTHING', default=None, minval=0)

View File

@@ -1,6 +1,3 @@
#!/usr/bin/env python3
from ..helpers.common_func import AXIS_CONFIG from ..helpers.common_func import AXIS_CONFIG
from ..helpers.console_output import ConsoleOutput from ..helpers.console_output import ConsoleOutput
from ..helpers.motors_config_parser import MotorsConfigParser from ..helpers.motors_config_parser import MotorsConfigParser

View File

@@ -1,6 +1,3 @@
#!/usr/bin/env python3
import math import math
from ..helpers.console_output import ConsoleOutput from ..helpers.console_output import ConsoleOutput
@@ -39,7 +36,7 @@ def create_vibrations_profile(gcmd, config, st_process: ShakeTuneProcess) -> Non
raise gcmd.error('Input shaper is not configured! Please run the shaper calibration macro first.') raise gcmd.error('Input shaper is not configured! Please run the shaper calibration macro first.')
motors_config_parser = MotorsConfigParser(config, motors=['stepper_x', 'stepper_y']) motors_config_parser = MotorsConfigParser(config, motors=['stepper_x', 'stepper_y'])
if motors_config_parser.kinematics == 'cartesian' or motors_config_parser.kinematics == 'corexz': if motors_config_parser.kinematics in {'cartesian', 'corexz'}:
main_angles = [0, 90] # Cartesian motors are on X and Y axis directly, same for CoreXZ main_angles = [0, 90] # Cartesian motors are on X and Y axis directly, same for CoreXZ
elif motors_config_parser.kinematics == 'corexy': elif motors_config_parser.kinematics == 'corexy':
main_angles = [45, 135] # CoreXY motors are on A and B axis (45 and 135 degrees) main_angles = [45, 135] # CoreXY motors are on A and B axis (45 and 135 degrees)

View File

@@ -1,5 +1,3 @@
#!/usr/bin/env python3
from ..helpers.common_func import AXIS_CONFIG from ..helpers.common_func import AXIS_CONFIG
from ..helpers.console_output import ConsoleOutput from ..helpers.console_output import ConsoleOutput
from ..helpers.resonance_test import vibrate_axis_at_static_freq from ..helpers.resonance_test import vibrate_axis_at_static_freq
@@ -29,7 +27,7 @@ def excitate_axis_at_freq(gcmd, config, st_process: ShakeTuneProcess) -> None:
if create_graph: if create_graph:
printer = config.get_printer() printer = config.get_printer()
if accel_chip is None: if accel_chip is None:
accel_chip = Accelerometer.find_axis_accelerometer(printer, 'xy' if axis in ['a', 'b'] else axis) accel_chip = Accelerometer.find_axis_accelerometer(printer, 'xy' if axis in {'a', 'b'} else axis)
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!')

View File

@@ -1,5 +1,3 @@
#!/usr/bin/env python3
from .axes_map_graph_creator import AxesMapGraphCreator as AxesMapGraphCreator from .axes_map_graph_creator import AxesMapGraphCreator as AxesMapGraphCreator
from .belts_graph_creator import BeltsGraphCreator as BeltsGraphCreator from .belts_graph_creator import BeltsGraphCreator as BeltsGraphCreator
from .graph_creator import GraphCreator as GraphCreator from .graph_creator import GraphCreator as GraphCreator

View File

@@ -1,5 +1,3 @@
#!/usr/bin/env python3
###################################### ######################################
###### AXE_MAP DETECTION SCRIPT ###### ###### AXE_MAP DETECTION SCRIPT ######
###################################### ######################################
@@ -66,7 +64,7 @@ class AxesMapGraphCreator(GraphCreator):
return # No need to delete any files return # No need to delete any files
for old_file in files[keep_results:]: for old_file in files[keep_results:]:
file_date = '_'.join(old_file.stem.split('_')[1:3]) file_date = '_'.join(old_file.stem.split('_')[1:3])
for suffix in ['X', 'Y', 'Z']: for suffix in {'X', 'Y', 'Z'}:
csv_file = self._folder / f'axesmap_{file_date}_{suffix}.csv' csv_file = self._folder / f'axesmap_{file_date}_{suffix}.csv'
csv_file.unlink(missing_ok=True) csv_file.unlink(missing_ok=True)
old_file.unlink() old_file.unlink()
@@ -421,8 +419,7 @@ def axesmap_calibration(
title_line2 += f' -- at {accel:0.0f} mm/s²' title_line2 += f' -- at {accel:0.0f} mm/s²'
except Exception: except Exception:
ConsoleOutput.print( ConsoleOutput.print(
'Warning: CSV filenames look to be different than expected (%s , %s, %s)' f'Warning: CSV filenames look to be different than expected ({lognames[0]}, {lognames[1]}, {lognames[2]})'
% (lognames[0], lognames[1], lognames[2])
) )
title_line2 = lognames[0].split('/')[-1] + ' ...' title_line2 = lognames[0].split('/')[-1] + ' ...'
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'])

View File

@@ -1,5 +1,3 @@
#!/usr/bin/env python3
################################################# #################################################
######## CoreXY BELTS CALIBRATION SCRIPT ######## ######## CoreXY BELTS CALIBRATION SCRIPT ########
################################################# #################################################
@@ -88,7 +86,7 @@ class BeltsGraphCreator(GraphCreator):
return # No need to delete any files return # No need to delete any files
for old_file in files[keep_results:]: for old_file in files[keep_results:]:
file_date = '_'.join(old_file.stem.split('_')[1:3]) file_date = '_'.join(old_file.stem.split('_')[1:3])
for suffix in ['A', 'B']: for suffix in {'A', 'B'}:
csv_file = self._folder / f'beltscomparison_{file_date}_{suffix}.csv' csv_file = self._folder / f'beltscomparison_{file_date}_{suffix}.csv'
csv_file.unlink(missing_ok=True) csv_file.unlink(missing_ok=True)
old_file.unlink() old_file.unlink()
@@ -192,11 +190,10 @@ def mhi_lut(mhi: float) -> str:
(0, 15, 'Mechanical issue detected'), (0, 15, 'Mechanical issue detected'),
] ]
mhi = np.clip(mhi, 1, 100) mhi = np.clip(mhi, 1, 100)
for lower, upper, message in ranges: return next(
if lower < mhi <= upper: (message for lower, upper, message in ranges if lower < mhi <= upper),
return message 'Unknown mechanical health',
)
return 'Unknown mechanical health' # Should never happen
###################################################################### ######################################################################
@@ -220,9 +217,6 @@ def plot_compare_frequency(
for _, (peak1, peak2) in enumerate(signal1.paired_peaks): for _, (peak1, peak2) in enumerate(signal1.paired_peaks):
label = ALPHABET[paired_peak_count] label = ALPHABET[paired_peak_count]
# amplitude_offset = abs(
# ((signal2.psd[peak2[0]] - signal1.psd[peak1[0]]) / max(signal1.psd[peak1[0]], signal2.psd[peak2[0]])) * 100
# )
amplitude_offset = abs(((signal2.psd[peak2[0]] - signal1.psd[peak1[0]]) / psd_highest_max) * 100) amplitude_offset = abs(((signal2.psd[peak2[0]] - signal1.psd[peak1[0]]) / psd_highest_max) * 100)
frequency_offset = abs(signal2.freqs[peak2[0]] - signal1.freqs[peak1[0]]) frequency_offset = abs(signal2.freqs[peak2[0]] - signal1.freqs[peak1[0]])
offsets_table_data.append([f'Peaks {label}', f'{frequency_offset:.1f} Hz', f'{amplitude_offset:.1f} %']) offsets_table_data.append([f'Peaks {label}', f'{frequency_offset:.1f} Hz', f'{amplitude_offset:.1f} %'])
@@ -507,7 +501,7 @@ def belts_calibration(
# Parse data from the log files while ignoring CSV in the wrong format # Parse data from the log files while ignoring CSV in the wrong format
datas = [data for data in (parse_log(fn) for fn in lognames) if data is not None] datas = [data for data in (parse_log(fn) for fn in lognames) if data is not None]
if len(datas) > 2: if len(datas) != 2:
raise ValueError('Incorrect number of .csv files used (this function needs exactly two files to compare them)!') raise ValueError('Incorrect number of .csv files used (this function needs exactly two files to compare them)!')
# Get the belts name for the legend to avoid putting the full file name # Get the belts name for the legend to avoid putting the full file name
@@ -569,15 +563,13 @@ def belts_calibration(
if kinematics is not None: if kinematics is not None:
title_line2 += ' -- ' + kinematics.upper() + ' kinematics' title_line2 += ' -- ' + kinematics.upper() + ' kinematics'
except Exception: except Exception:
ConsoleOutput.print( ConsoleOutput.print(f'Warning: Unable to parse the date from the filename ({lognames[0]}, {lognames[1]})')
'Warning: CSV filenames look to be different than expected (%s , %s)' % (lognames[0], lognames[1])
)
title_line2 = lognames[0].split('/')[-1] + ' / ' + lognames[1].split('/')[-1] title_line2 = lognames[0].split('/')[-1] + ' / ' + lognames[1].split('/')[-1]
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'])
# We add the estimated similarity and the MHI value to the title only if the kinematics is CoreXY # We add the estimated similarity and the MHI value to the title only if the kinematics is CoreXY
# as it make no sense to compute these values for other kinematics that doesn't have paired belts # as it make no sense to compute these values for other kinematics that doesn't have paired belts
if kinematics in ['corexy', 'corexz']: if kinematics in {'corexy', 'corexz'}:
title_line3 = f'| Estimated similarity: {similarity_factor:.1f}%' title_line3 = f'| Estimated similarity: {similarity_factor:.1f}%'
title_line4 = f'| {mhi} (experimental)' title_line4 = f'| {mhi} (experimental)'
fig.text(0.55, 0.985, title_line3, ha='left', va='top', fontsize=14, color=KLIPPAIN_COLORS['dark_purple']) fig.text(0.55, 0.985, title_line3, ha='left', va='top', fontsize=14, color=KLIPPAIN_COLORS['dark_purple'])

View File

@@ -1,5 +1,3 @@
#!/usr/bin/env python3
import abc import abc
import shutil import shutil
from datetime import datetime from datetime import datetime

View File

@@ -1,5 +1,3 @@
#!/usr/bin/env python3
################################################# #################################################
######## INPUT SHAPER CALIBRATION SCRIPT ######## ######## INPUT SHAPER CALIBRATION SCRIPT ########
################################################# #################################################
@@ -131,8 +129,7 @@ def calibrate_shaper(datas: List[np.ndarray], max_smoothing: Optional[float], sc
shaper, all_shapers = helper.find_best_shaper(calibration_data, max_smoothing, ConsoleOutput.print) shaper, all_shapers = helper.find_best_shaper(calibration_data, max_smoothing, ConsoleOutput.print)
ConsoleOutput.print( ConsoleOutput.print(
'\n-> Recommended shaper is %s @ %.1f Hz (when using a square corner velocity of %.1f and a damping ratio of %.3f)' f'\n-> Recommended shaper is {shaper.name.upper()} @ {shaper.freq:.1f} Hz (when using a square corner velocity of {scv:.1f} and a damping ratio of {zeta:.3f})'
% (shaper.name.upper(), shaper.freq, scv, zeta)
) )
return shaper.name, all_shapers, calibration_data, fr, zeta, compat return shaper.name, all_shapers, calibration_data, fr, zeta, compat
@@ -190,13 +187,7 @@ def plot_freq_response(
perf_shaper_accel = 0 perf_shaper_accel = 0
for shaper in shapers: for shaper in shapers:
shaper_max_accel = round(shaper.max_accel / 100.0) * 100.0 shaper_max_accel = round(shaper.max_accel / 100.0) * 100.0
label = '%s (%.1f Hz, vibr=%.1f%%, sm~=%.2f, accel<=%.f)' % ( label = f'{shaper.name.upper()} ({shaper.freq:.1f} Hz, vibr={shaper.vibrs * 100.0:.1f}%, sm~={shaper.smoothing:.2f}, accel<={shaper_max_accel:.0f})'
shaper.name.upper(),
shaper.freq,
shaper.vibrs * 100.0,
shaper.smoothing,
shaper_max_accel,
)
ax2.plot(freqs, shaper.vals, label=label, linestyle='dotted') ax2.plot(freqs, shaper.vals, label=label, linestyle='dotted')
# Get the Klipper recommended shaper (usually it's a good low vibration compromise) # Get the Klipper recommended shaper (usually it's a good low vibration compromise)
@@ -226,40 +217,37 @@ def plot_freq_response(
[], [],
[], [],
' ', ' ',
label='Recommended performance shaper: %s @ %.1f Hz' % (perf_shaper_choice.upper(), perf_shaper_freq), label=f'Recommended performance shaper: {perf_shaper_choice.upper()} @ {perf_shaper_freq:.1f} Hz',
) )
ax.plot( ax.plot(
freqs, freqs,
psd * perf_shaper_vals, psd * perf_shaper_vals,
label='With %s applied' % (perf_shaper_choice.upper()), label=f'With {perf_shaper_choice.upper()} applied',
color='cyan', color='cyan',
) )
ax2.plot( ax2.plot(
[], [],
[], [],
' ', ' ',
label='Recommended low vibrations shaper: %s @ %.1f Hz' label=f'Recommended low vibrations shaper: {klipper_shaper_choice.upper()} @ {klipper_shaper_freq:.1f} Hz',
% (klipper_shaper_choice.upper(), klipper_shaper_freq),
)
ax.plot(
freqs, psd * klipper_shaper_vals, label='With %s applied' % (klipper_shaper_choice.upper()), color='lime'
) )
ax.plot(freqs, psd * klipper_shaper_vals, label=f'With {klipper_shaper_choice.upper()} applied', color='lime')
else: else:
ax2.plot( ax2.plot(
[], [],
[], [],
' ', ' ',
label='Recommended best shaper: %s @ %.1f Hz' % (klipper_shaper_choice.upper(), klipper_shaper_freq), label=f'Recommended performance shaper: {klipper_shaper_choice.upper()} @ {klipper_shaper_freq:.1f} Hz',
) )
ax.plot( ax.plot(
freqs, freqs,
psd * klipper_shaper_vals, psd * klipper_shaper_vals,
label='With %s applied' % (klipper_shaper_choice.upper()), label=f'With {klipper_shaper_choice.upper()} applied',
color='cyan', color='cyan',
) )
# And the estimated damping ratio is finally added at the end of the legend # And the estimated damping ratio is finally added at the end of the legend
ax2.plot([], [], ' ', label='Estimated damping ratio (ζ): %.3f' % (zeta)) ax2.plot([], [], ' ', label=f'Estimated damping ratio (ζ): {zeta:.3f}')
# Draw the detected peaks and name them # Draw the detected peaks and name them
# This also draw the detection threshold and warning threshold (aka "effect zone") # This also draw the detection threshold and warning threshold (aka "effect zone")
@@ -288,7 +276,7 @@ def plot_freq_response(
# Add the main resonant frequency and damping ratio of the axis to the graph title # Add the main resonant frequency and damping ratio of the axis to the graph title
ax.set_title( ax.set_title(
'Axis Frequency Profile (ω0=%.1fHz, ζ=%.3f)' % (fr, zeta), f'Axis Frequency Profile (ω0={fr:.1f}Hz, ζ={zeta:.3f})',
fontsize=14, fontsize=14,
color=KLIPPAIN_COLORS['dark_orange'], color=KLIPPAIN_COLORS['dark_orange'],
weight='bold', weight='bold',
@@ -368,6 +356,8 @@ def shaper_calibration(
# Parse data from the log files while ignoring CSV in the wrong format # Parse data from the log files while ignoring CSV in the wrong format
datas = [data for data in (parse_log(fn) for fn in lognames) if data is not None] datas = [data for data in (parse_log(fn) for fn in lognames) if data is not None]
if len(datas) == 0:
raise ValueError('No valid data found in the provided CSV files!')
if len(datas) > 1: if len(datas) > 1:
ConsoleOutput.print('Warning: incorrect number of .csv files detected. Only the first one will be used!') ConsoleOutput.print('Warning: incorrect number of .csv files detected. Only the first one will be used!')
@@ -397,8 +387,7 @@ def shaper_calibration(
peak_freqs_formated = ['{:.1f}'.format(f) for f in peaks_freqs] peak_freqs_formated = ['{:.1f}'.format(f) for f in peaks_freqs]
num_peaks_above_effect_threshold = np.sum(calibration_data.psd_sum[peaks] > peaks_threshold[1]) num_peaks_above_effect_threshold = np.sum(calibration_data.psd_sum[peaks] > peaks_threshold[1])
ConsoleOutput.print( ConsoleOutput.print(
'\nPeaks detected on the graph: %d @ %s Hz (%d above effect threshold)' f"\nPeaks detected on the graph: {num_peaks} @ {', '.join(map(str, peak_freqs_formated))} Hz ({num_peaks_above_effect_threshold} above effect threshold)"
% (num_peaks, ', '.join(map(str, peak_freqs_formated)), num_peaks_above_effect_threshold)
) )
# Create graph layout # Create graph layout
@@ -435,7 +424,7 @@ def shaper_calibration(
title_line4 = f'| Max allowed smoothing: {max_smoothing}' title_line4 = f'| Max allowed smoothing: {max_smoothing}'
title_line5 = f'| Accel per Hz used: {accel_per_hz} mm/s²/Hz' if accel_per_hz is not None else '' title_line5 = f'| Accel per Hz used: {accel_per_hz} mm/s²/Hz' if accel_per_hz is not None else ''
except Exception: except Exception:
ConsoleOutput.print('Warning: CSV filename look to be different than expected (%s)' % (lognames[0])) ConsoleOutput.print(f'Warning: CSV filename look to be different than expected ({lognames[0]})')
title_line2 = lognames[0].split('/')[-1] title_line2 = lognames[0].split('/')[-1]
title_line3 = '' title_line3 = ''
title_line4 = '' title_line4 = ''

View File

@@ -1,5 +1,3 @@
#!/usr/bin/env python3
import optparse import optparse
import os import os
from datetime import datetime from datetime import datetime
@@ -137,6 +135,8 @@ def static_frequency_tool(
raise ValueError('Error: missing frequency or duration parameters!') raise ValueError('Error: missing frequency or duration parameters!')
datas = [data for data in (parse_log(fn) for fn in lognames) if data is not None] datas = [data for data in (parse_log(fn) for fn in lognames) if data is not None]
if len(datas) == 0:
raise ValueError('No valid data found in the provided CSV files!')
if len(datas) > 1: if len(datas) > 1:
ConsoleOutput.print('Warning: incorrect number of .csv files detected. Only the first one will be used!') ConsoleOutput.print('Warning: incorrect number of .csv files detected. Only the first one will be used!')

View File

@@ -1,5 +1,3 @@
#!/usr/bin/env python3
################################################## ##################################################
#### DIRECTIONAL VIBRATIONS PLOTTING SCRIPT ###### #### DIRECTIONAL VIBRATIONS PLOTTING SCRIPT ######
################################################## ##################################################
@@ -32,7 +30,7 @@ from ..helpers.common_func import (
setup_klipper_import, setup_klipper_import,
) )
from ..helpers.console_output import ConsoleOutput from ..helpers.console_output import ConsoleOutput
from ..helpers.motors_config_parser import MotorsConfigParser from ..helpers.motors_config_parser import Motor, MotorsConfigParser
from ..shaketune_config import ShakeTuneConfig from ..shaketune_config import ShakeTuneConfig
from .graph_creator import GraphCreator from .graph_creator import GraphCreator
@@ -62,7 +60,7 @@ class VibrationsGraphCreator(GraphCreator):
def configure(self, kinematics: str, accel: float, motor_config_parser: MotorsConfigParser) -> None: def configure(self, kinematics: str, accel: float, motor_config_parser: MotorsConfigParser) -> None:
self._kinematics = kinematics self._kinematics = kinematics
self._accel = accel self._accel = accel
self._motors = motor_config_parser.get_motors() self._motors: List[Motor] = motor_config_parser.get_motors()
def _archive_files(self, lognames: List[Path]) -> None: def _archive_files(self, lognames: List[Path]) -> None:
tar_path = self._folder / f'{self._type}_{self._graph_date}.tar.gz' tar_path = self._folder / f'{self._type}_{self._graph_date}.tar.gz'
@@ -482,7 +480,7 @@ def plot_angular_speed_profiles(
ax.plot(speeds, spectrogram_data[idx], label=label, color=KLIPPAIN_COLORS[color], zorder=zorder) ax.plot(speeds, spectrogram_data[idx], label=label, color=KLIPPAIN_COLORS[color], zorder=zorder)
ax.set_xlim([speeds.min(), speeds.max()]) ax.set_xlim([speeds.min(), speeds.max()])
max_value = max(spectrogram_data[angle].max() for angle in [0, 45, 90, 135]) max_value = max(spectrogram_data[angle].max() for angle in {0, 45, 90, 135})
ax.set_ylim([0, max_value * 1.1]) ax.set_ylim([0, max_value * 1.1])
ax.xaxis.set_minor_locator(matplotlib.ticker.AutoMinorLocator()) ax.xaxis.set_minor_locator(matplotlib.ticker.AutoMinorLocator())
@@ -542,13 +540,11 @@ def plot_motor_profiles(
) )
if motor_zeta is not None: if motor_zeta is not None:
ConsoleOutput.print( ConsoleOutput.print(
'Motors have a main resonant frequency at %.1fHz with an estimated damping ratio of %.3f' f'Motors have a main resonant frequency at {motor_fr:.1f}Hz with an estimated damping ratio of {motor_zeta:.3f}'
% (motor_fr, motor_zeta)
) )
else: else:
ConsoleOutput.print( ConsoleOutput.print(
'Motors have a main resonant frequency at %.1fHz but it was impossible to estimate a damping ratio.' f'Motors have a main resonant frequency at {motor_fr:.1f}Hz but it was impossible to estimate a damping ratio.'
% (motor_fr)
) )
ax.plot(freqs[motor_res_idx], global_motor_profile[motor_res_idx], 'x', color='black', markersize=10) ax.plot(freqs[motor_res_idx], global_motor_profile[motor_res_idx], 'x', color='black', markersize=10)
@@ -563,9 +559,9 @@ def plot_motor_profiles(
weight='bold', weight='bold',
) )
ax2.plot([], [], ' ', label='Motor resonant frequency (ω0): %.1fHz' % (motor_fr)) ax2.plot([], [], ' ', label=f'Motor resonant frequency (ω0): {motor_fr:.1f}Hz')
if motor_zeta is not None: if motor_zeta is not None:
ax2.plot([], [], ' ', label='Motor damping ratio (ζ): %.3f' % (motor_zeta)) ax2.plot([], [], ' ', label=f'Motor damping ratio (ζ): {motor_zeta:.3f}')
else: else:
ax2.plot([], [], ' ', label='No damping ratio computed') ax2.plot([], [], ' ', label='No damping ratio computed')
@@ -792,8 +788,7 @@ def vibrations_profile(
) )
formated_peaks_speeds = ['{:.1f}'.format(pspeed) for pspeed in peaks_speeds] formated_peaks_speeds = ['{:.1f}'.format(pspeed) for pspeed in peaks_speeds]
ConsoleOutput.print( ConsoleOutput.print(
'Vibrations peaks detected: %d @ %s mm/s (avoid setting a speed near these values in your slicer print profile)' f"Vibrations peaks detected: {num_peaks} @ {', '.join(map(str, formated_peaks_speeds))} mm/s (avoid setting a speed near these values in your slicer print profile)"
% (num_peaks, ', '.join(map(str, formated_peaks_speeds)))
) )
good_speeds = identify_low_energy_zones(vibration_metric, SPEEDS_VALLEY_DETECTION_THRESHOLD) good_speeds = identify_low_energy_zones(vibration_metric, SPEEDS_VALLEY_DETECTION_THRESHOLD)
@@ -855,7 +850,7 @@ def vibrations_profile(
if accel is not None: if accel is not None:
title_line2 += ' at ' + str(accel) + ' mm/s² -- ' + kinematics.upper() + ' kinematics' title_line2 += ' at ' + str(accel) + ' mm/s² -- ' + kinematics.upper() + ' kinematics'
except Exception: except Exception:
ConsoleOutput.print('Warning: CSV filenames appear to be different than expected (%s)' % (lognames[0])) ConsoleOutput.print(f'Warning: CSV filenames appear to be different than expected ({lognames[0]})')
title_line2 = lognames[0].split('/')[-1] title_line2 = lognames[0].split('/')[-1]
fig.text(0.060, 0.957, title_line2, ha='left', va='top', fontsize=16, color=KLIPPAIN_COLORS['dark_purple']) fig.text(0.060, 0.957, title_line2, ha='left', va='top', fontsize=16, color=KLIPPAIN_COLORS['dark_purple'])
@@ -923,7 +918,7 @@ def main():
opts.error('No CSV file(s) to analyse') opts.error('No CSV file(s) to analyse')
if options.output is None: if options.output is None:
opts.error('You must specify an output file.png to use the script (option -o)') opts.error('You must specify an output file.png to use the script (option -o)')
if options.kinematics not in ['cartesian', 'corexy', 'corexz']: if options.kinematics not in {'cartesian', 'corexy', 'corexz'}:
opts.error('Only cartesian, corexy and corexz kinematics are supported by this tool at the moment!') opts.error('Only cartesian, corexy and corexz kinematics are supported by this tool at the moment!')
fig = vibrations_profile(args, options.klipperdir, options.kinematics, options.accel, options.max_freq) fig = vibrations_profile(args, options.klipperdir, options.kinematics, options.accel, options.max_freq)

View File

@@ -1,5 +1,3 @@
#!/usr/bin/env python3
# Common functions for the Shake&Tune package # Common functions for the Shake&Tune package
# Written by Frix_x#0161 # # Written by Frix_x#0161 #
@@ -35,9 +33,9 @@ def parse_log(logname):
# Check for a PSD file generated by Klipper and raise a warning # Check for a PSD file generated by Klipper and raise a warning
if cleaned_line.startswith('#freq,psd_x,psd_y,psd_z,psd_xyz'): if cleaned_line.startswith('#freq,psd_x,psd_y,psd_z,psd_xyz'):
ConsoleOutput.print( ConsoleOutput.print(
'Warning: %s does not contain raw accelerometer data. ' f'Warning: {logname} does not contain raw accelerometer data. '
'Please use the official Klipper script to process it instead. ' 'Please use the official Klipper script to process it instead. '
'It will be ignored by Shake&Tune!' % (logname,) 'It will be ignored by Shake&Tune!'
) )
return None return None
@@ -48,8 +46,8 @@ def parse_log(logname):
if not header: if not header:
ConsoleOutput.print( ConsoleOutput.print(
'Warning: file %s has an incorrect header and will be ignored by Shake&Tune!\n' f'Warning: file {logname} has an incorrect header and will be ignored by Shake&Tune!\n'
"Expected '#time,accel_x,accel_y,accel_z', but got '%s'." % (logname, header.strip()) f"Expected '#time,accel_x,accel_y,accel_z', but got '{header.strip()}'."
) )
return None return None
@@ -57,8 +55,8 @@ def parse_log(logname):
data = np.loadtxt(logname, comments='#', delimiter=',', skiprows=1) data = np.loadtxt(logname, comments='#', delimiter=',', skiprows=1)
if data.ndim == 1 or data.shape[1] != 4: if data.ndim == 1 or data.shape[1] != 4:
ConsoleOutput.print( ConsoleOutput.print(
'Warning: %s does not have the correct data format; expected 4 columns. ' f'Warning: {logname} does not have the correct data format; expected 4 columns. '
'It will be ignored by Shake&Tune!' % (logname,) 'It will be ignored by Shake&Tune!'
) )
return None return None

View File

@@ -1,5 +1,3 @@
#!/usr/bin/env python3
# Classes to retrieve a couple of motors infos and extract the relevant information # Classes to retrieve a couple of motors infos and extract the relevant information
# from the Klipper configuration and the TMC registers # from the Klipper configuration and the TMC registers
# Written by Frix_x#0161 # # Written by Frix_x#0161 #
@@ -41,7 +39,7 @@ class Motor:
value_dict = new_value_dict value_dict = new_value_dict
# Then gets merged all the thresholds into the same THRS virtual register # Then gets merged all the thresholds into the same THRS virtual register
if register in ['TPWMTHRS', 'TCOOLTHRS']: if register in {'TPWMTHRS', 'TCOOLTHRS'}:
existing_thrs = self._registers.get('THRS', {}) existing_thrs = self._registers.get('THRS', {})
merged_values = {**existing_thrs, **value_dict} merged_values = {**existing_thrs, **value_dict}
self._registers['THRS'] = merged_values self._registers['THRS'] = merged_values
@@ -97,10 +95,7 @@ class Motor:
if not differences['registers']: if not differences['registers']:
del differences['registers'] del differences['registers']
if not differences: return None if not differences else differences
return None
return differences
class MotorsConfigParser: class MotorsConfigParser:
@@ -180,10 +175,7 @@ class MotorsConfigParser:
# Find and return the motor by its name # Find and return the motor by its name
def get_motor(self, motor_name: str) -> Optional[Motor]: def get_motor(self, motor_name: str) -> Optional[Motor]:
for motor in self._motors: return next((motor for motor in self._motors if motor.name == motor_name), None)
if motor.name == motor_name:
return motor
return None
# Get all the motor list at once # Get all the motor list at once
def get_motors(self) -> List[Motor]: def get_motors(self) -> List[Motor]:

View File

@@ -1,5 +1,3 @@
#!/usr/bin/env python3
# The logic in this file was "extracted" from Klipper's orignal resonance_tester.py file # The logic in this file was "extracted" from Klipper's orignal resonance_tester.py file
# Courtesy of Dmitry Butyugin <dmbutyugin@google.com> for the original implementation # Courtesy of Dmitry Butyugin <dmbutyugin@google.com> for the original implementation

View File

@@ -1,6 +1,3 @@
#!/usr/bin/env python3
import os import os
from pathlib import Path from pathlib import Path
@@ -84,7 +81,7 @@ class ShakeTune:
try: try:
dummy_macros_cfg = pconfig.read_config(filename) dummy_macros_cfg = pconfig.read_config(filename)
except Exception as err: except Exception as err:
raise config.error("Cannot load Shake&Tune dummy macro '%s'" % (filename,)) from err raise config.error(f'Cannot load Shake&Tune dummy macro {filename}') from err
for gcode_macro in dummy_macros_cfg.get_prefix_sections('gcode_macro '): for gcode_macro in dummy_macros_cfg.get_prefix_sections('gcode_macro '):
gcode_macro_name = gcode_macro.get_name() gcode_macro_name = gcode_macro.get_name()

View File

@@ -1,5 +1,3 @@
#!/usr/bin/env python3
from pathlib import Path from pathlib import Path
from .helpers.console_output import ConsoleOutput from .helpers.console_output import ConsoleOutput

View File

@@ -1,7 +1,5 @@
#!/usr/bin/env python3
import os
import multiprocessing import multiprocessing
import os
import threading import threading
import traceback import traceback
from typing import Optional from typing import Optional