fixed most of the bugs now as a Klipper plugin

This commit is contained in:
Félix Boisselier
2024-05-16 23:33:49 +02:00
parent dd08162616
commit 55895c1507
13 changed files with 116 additions and 106 deletions

View File

@@ -14,6 +14,14 @@ from scipy.signal import spectrogram
from .console_output import ConsoleOutput from .console_output import ConsoleOutput
# Constant used to define the standard axis direction and names
AXIS_CONFIG = [
{'axis': 'x', 'direction': (1, 0, 0), 'label': 'axis_X'},
{'axis': 'y', 'direction': (0, 1, 0), 'label': 'axis_Y'},
{'axis': 'a', 'direction': (1, -1, 0), 'label': 'belt_A'},
{'axis': 'b', 'direction': (1, 1, 0), 'label': 'belt_B'},
]
def parse_log(logname): def parse_log(logname):
try: try:

View File

@@ -5,20 +5,3 @@ from .axes_map import axes_map_calibration as axes_map_calibration
from .belts_comparison import compare_belts_responses as compare_belts_responses from .belts_comparison import compare_belts_responses as compare_belts_responses
from .static_freq import excitate_axis_at_freq as excitate_axis_at_freq from .static_freq import excitate_axis_at_freq as excitate_axis_at_freq
from .vibrations_profile import create_vibrations_profile as create_vibrations_profile from .vibrations_profile import create_vibrations_profile as create_vibrations_profile
AXIS_CONFIG = [
{'axis': 'x', 'direction': (1, 0, 0), 'label': 'axis_X'},
{'axis': 'y', 'direction': (0, 1, 0), 'label': 'axis_Y'},
{'axis': 'a', 'direction': (1, -1, 0), 'label': 'belt_A'},
{'axis': 'b', 'direction': (1, 1, 0), 'label': 'belt_B'},
]
# graph_creators = {
# 'axesmap': (AxesMapFinder, lambda gc: gc.configure(options.accel_used, options.chip_name)),
# 'belts': (BeltsGraphCreator, None),
# 'shaper': (ShaperGraphCreator, lambda gc: gc.configure(options.scv, options.max_smoothing)),
# 'vibrations': (
# VibrationsGraphCreator,
# lambda gc: gc.configure(options.kinematics, options.accel_used, options.chip_name, options.metadata),
# ),
# }

View File

@@ -1,14 +1,14 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from ..helpers.common_func import AXIS_CONFIG
from ..helpers.console_output import ConsoleOutput from ..helpers.console_output import ConsoleOutput
from ..shaketune_thread import ShakeTuneThread from ..shaketune_thread import ShakeTuneThread
from . import AXIS_CONFIG
from .accelerometer import Accelerometer from .accelerometer import Accelerometer
from .resonance_test import vibrate_axis from .resonance_test import vibrate_axis
def axes_shaper_calibration(gcmd, gcode, printer, st_thread: ShakeTuneThread) -> None: def axes_shaper_calibration(gcmd, config, st_thread: ShakeTuneThread) -> None:
min_freq = gcmd.get_float('FREQ_START', default=5, minval=1) min_freq = gcmd.get_float('FREQ_START', default=5, minval=1)
max_freq = gcmd.get_float('FREQ_END', default=133.33, minval=1) max_freq = gcmd.get_float('FREQ_END', default=133.33, 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)
@@ -21,9 +21,11 @@ def axes_shaper_calibration(gcmd, gcode, printer, st_thread: ShakeTuneThread) ->
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)
systime = printer.get_reactor().monotonic() printer = config.get_printer()
gcode = printer.lookup_object('gcode')
toolhead = printer.lookup_object('toolhead') toolhead = printer.lookup_object('toolhead')
res_tester = printer.lookup_object('resonance_tester') res_tester = printer.lookup_object('resonance_tester')
systime = printer.get_reactor().monotonic()
if scv is None: if scv is None:
toolhead_info = toolhead.get_status(systime) toolhead_info = toolhead.get_status(systime)
@@ -92,7 +94,7 @@ def axes_shaper_calibration(gcmd, gcode, printer, st_thread: ShakeTuneThread) ->
accelerometer.stop_measurement(config['label'], append_time=True) accelerometer.stop_measurement(config['label'], append_time=True)
# 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)')
st_thread.run() st_thread.run()

View File

@@ -6,13 +6,18 @@ from ..shaketune_thread import ShakeTuneThread
from .accelerometer import Accelerometer from .accelerometer import Accelerometer
def axes_map_calibration(gcmd, gcode, printer, st_thread: ShakeTuneThread) -> None: def axes_map_calibration(gcmd, config, st_thread: ShakeTuneThread) -> None:
z_height = gcmd.get_float('Z_HEIGHT', default=20.0) z_height = gcmd.get_float('Z_HEIGHT', default=20.0)
speed = gcmd.get_float('SPEED', default=80.0, minval=20.0) speed = gcmd.get_float('SPEED', default=80.0, minval=20.0)
accel = gcmd.get_int('ACCEL', default=1500, minval=100) accel = gcmd.get_int('ACCEL', default=1500, minval=100)
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)
accel_chip = gcmd.get('ACCEL_CHIP', default=None) accel_chip = gcmd.get('ACCEL_CHIP', default=None)
printer = config.get_printer()
gcode = printer.lookup_object('gcode')
toolhead = printer.lookup_object('toolhead')
systime = printer.get_reactor().monotonic()
if accel_chip is None: if accel_chip is None:
accel_chip = Accelerometer.find_axis_accelerometer(printer, 'xy') accel_chip = Accelerometer.find_axis_accelerometer(printer, 'xy')
if accel_chip is None: if accel_chip is None:
@@ -21,8 +26,6 @@ def axes_map_calibration(gcmd, gcode, printer, st_thread: ShakeTuneThread) -> No
) )
accelerometer = Accelerometer(printer.lookup_object(accel_chip)) accelerometer = Accelerometer(printer.lookup_object(accel_chip))
systime = printer.get_reactor().monotonic()
toolhead = printer.lookup_object('toolhead')
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_mcr = toolhead_info['minimum_cruise_ratio']

View File

@@ -1,14 +1,14 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from ..helpers.common_func import AXIS_CONFIG
from ..helpers.console_output import ConsoleOutput from ..helpers.console_output import ConsoleOutput
from ..shaketune_thread import ShakeTuneThread from ..shaketune_thread import ShakeTuneThread
from . import AXIS_CONFIG
from .accelerometer import Accelerometer from .accelerometer import Accelerometer
from .resonance_test import vibrate_axis from .resonance_test import vibrate_axis
def compare_belts_responses(gcmd, gcode, printer, st_thread: ShakeTuneThread) -> None: def compare_belts_responses(gcmd, config, st_thread: ShakeTuneThread) -> None:
min_freq = gcmd.get_float('FREQ_START', default=5.0, minval=1) min_freq = gcmd.get_float('FREQ_START', default=5.0, minval=1)
max_freq = gcmd.get_float('FREQ_END', default=133.33, minval=1) max_freq = gcmd.get_float('FREQ_END', default=133.33, minval=1)
hz_per_sec = gcmd.get_float('HZ_PER_SEC', default=1.0, minval=1) hz_per_sec = gcmd.get_float('HZ_PER_SEC', default=1.0, minval=1)
@@ -16,9 +16,11 @@ def compare_belts_responses(gcmd, gcode, printer, st_thread: ShakeTuneThread) ->
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)
systime = printer.get_reactor().monotonic() printer = config.get_printer()
gcode = printer.lookup_object('gcode')
toolhead = printer.lookup_object('toolhead') toolhead = printer.lookup_object('toolhead')
res_tester = printer.lookup_object('resonance_tester') res_tester = printer.lookup_object('resonance_tester')
systime = printer.get_reactor().monotonic()
accel_chip = Accelerometer.find_axis_accelerometer(printer, 'xy') accel_chip = Accelerometer.find_axis_accelerometer(printer, 'xy')
if accel_chip is None: if accel_chip is None:
@@ -68,7 +70,7 @@ def compare_belts_responses(gcmd, gcode, printer, st_thread: ShakeTuneThread) ->
input_shaper = None input_shaper = None
# Filter axis configurations to get the A and B axis only # Filter axis configurations to get the A and B axis only
filtered_config = [a for a in AXIS_CONFIG if a['axis'] in ('x', 'y')] filtered_config = [a for a in AXIS_CONFIG if a['axis'] in ('a', 'b')]
for config in filtered_config: for config in filtered_config:
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)

View File

@@ -0,0 +1,8 @@
# [gcode_macro AXES_MAP_CALIBRATION]
# gcode:
# {% set z_height = params.Z_HEIGHT|default(20)|int %} # z height to put the toolhead before starting the movements
# {% set speed = params.SPEED|default(80)|float * 60 %} # feedrate for the movements
# {% set accel = params.ACCEL|default(1500)|int %} # accel value used to move on the pattern
# {% set feedrate_travel = params.TRAVEL_SPEED|default(120)|int * 60 %} # travel feedrate between moves
# {% set accel_chip = params.ACCEL_CHIP|default("adxl345") %} # ADXL chip name in the config

View File

@@ -4,8 +4,7 @@
# 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 #
import re from typing import Any, Dict, List, Optional
from typing import Any, Dict, List, Optional, Tuple
TRINAMIC_DRIVERS = ['tmc2130', 'tmc2208', 'tmc2209', 'tmc2240', 'tmc2660', 'tmc5160'] TRINAMIC_DRIVERS = ['tmc2130', 'tmc2208', 'tmc2209', 'tmc2240', 'tmc2660', 'tmc5160']
MOTORS = ['stepper_x', 'stepper_y', 'stepper_x1', 'stepper_y1', 'stepper_z', 'stepper_z1', 'stepper_z2', 'stepper_z3'] MOTORS = ['stepper_x', 'stepper_y', 'stepper_x1', 'stepper_y1', 'stepper_z', 'stepper_z1', 'stepper_z2', 'stepper_z3']
@@ -17,25 +16,20 @@ class Motor:
self.name: str = name self.name: str = name
self._registers: Dict[str, Dict[str, Any]] = {} self._registers: Dict[str, Dict[str, Any]] = {}
self._config: Dict[str, Any] = {} self._config: Dict[str, Any] = {}
self._driver: Tuple[str, Any] = ('', None)
def set_driver(self, driver_name: str, tmc_object: Any) -> None:
self._driver = (driver_name, tmc_object)
def get_driver(self) -> Tuple[str, Any]:
return self._driver
def set_register(self, register: str, value_dict: dict) -> None: def set_register(self, register: str, value_dict: dict) -> None:
# First we filter out entries with a value of 0 to avoid having too much uneeded data
value_dict = {k: v for k, v in value_dict.items() if v != 0}
# Special parsing for CHOPCONF to extract meaningful values # Special parsing for CHOPCONF to extract meaningful values
if register == 'CHOPCONF': if register == 'CHOPCONF':
# Add intpol=0 if missing from the register dump # Add intpol=0 if missing from the register dump to force printing it as it's important
if 'intpol=' not in value_dict: if 'intpol' not in value_dict:
value_dict['intpol'] = '0' value_dict['intpol'] = '0'
# Simplify the microstep resolution format # Remove the microsteps entry as the format here is not easy to read and
# it's already read in the correct format directly from the Klipper config
if 'mres' in value_dict: if 'mres' in value_dict:
mres_match = re.search(r'(\d+)usteps', value_dict['mres']) del value_dict['mres']
if mres_match:
value_dict['mres'] = mres_match.group(1)
# Special parsing for CHOPCONF to avoid pwm_ before each values # Special parsing for CHOPCONF to avoid pwm_ before each values
if register == 'PWMCONF': if register == 'PWMCONF':
@@ -46,7 +40,7 @@ class Motor:
new_value_dict[key] = val new_value_dict[key] = val
value_dict = new_value_dict value_dict = new_value_dict
# Then fill the registers while merging 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}
@@ -110,35 +104,42 @@ class Motor:
class MotorsConfigParser: class MotorsConfigParser:
def __init__(self, printer, motors: List[str] = MOTORS, drivers: List[str] = TRINAMIC_DRIVERS): def __init__(self, config, motors: List[str] = MOTORS, drivers: List[str] = TRINAMIC_DRIVERS):
self._printer = config.get_printer()
self._motors: List[Motor] = [] self._motors: List[Motor] = []
self._printer = printer
for motor_name in motors: for motor_name in motors:
for driver in drivers: for driver in drivers:
tmc_object = printer.lookup_object(f'{driver} {motor_name}', None) tmc_object = self._printer.lookup_object(f'{driver} {motor_name}', None)
if tmc_object is None: if tmc_object is None:
continue continue
motor = self._create_motor(motor_name, driver, tmc_object) motor = self._create_motor(motor_name, driver, tmc_object)
self._motors.append(motor) self._motors.append(motor)
pconfig = self._printer.lookup_object('configfile')
self.kinematics = pconfig.status_raw_config['printer']['kinematics']
# Create a Motor object with the given name, driver and TMC object # Create a Motor object with the given name, driver and TMC object
# and fill it with the relevant configuration and registers # and fill it with the relevant configuration and registers
def _create_motor(self, motor_name: str, driver: str, tmc_object: Any) -> Motor: def _create_motor(self, motor_name: str, driver: str, tmc_object: Any) -> Motor:
motor = Motor(motor_name) motor = Motor(motor_name)
motor.set_driver(driver.upper(), tmc_object) motor.set_config('tmc', driver)
self._parse_klipper_config(motor, tmc_object) self._parse_klipper_config(motor, tmc_object)
self._parse_tmc_registers(motor, tmc_object) self._parse_tmc_registers(motor, tmc_object)
return motor return motor
def _parse_klipper_config(self, motor: Motor, tmc: Any) -> None: def _parse_klipper_config(self, motor: Motor, tmc_object: Any) -> None:
# The TMCCommandHelper isn't a direct member of the TMC object... but we can still get it this way # The TMCCommandHelper isn't a direct member of the TMC object... but we can still get it this way
tmc_cmdhelper = tmc.get_status.__self__ tmc_cmdhelper = tmc_object.get_status.__self__
motor_currents = tmc_cmdhelper.current_helper.get_current() motor_currents = tmc_cmdhelper.current_helper.get_current()
motor.set_config('run_current', motor_currents[0]) motor.set_config('run_current', motor_currents[0])
motor.set_config('hold_current', motor_currents[1]) motor.set_config('hold_current', motor_currents[1])
pconfig = self._printer.lookup_object('configfile')
motor.set_config('microsteps', int(pconfig.status_raw_config[motor.name]['microsteps']))
autotune_object = self._printer.lookup_object(f'autotune_tmc {motor.name}', None) autotune_object = self._printer.lookup_object(f'autotune_tmc {motor.name}', None)
if autotune_object is not None: if autotune_object is not None:
motor.set_config('autotune_enabled', True) motor.set_config('autotune_enabled', True)
@@ -147,24 +148,21 @@ class MotorsConfigParser:
else: else:
motor.set_config('autotune_enabled', False) motor.set_config('autotune_enabled', False)
def _parse_tmc_registers(self, motor: Motor, tmc: Any) -> None: def _parse_tmc_registers(self, motor: Motor, tmc_object: Any) -> None:
# The TMCCommandHelper isn't a direct member of the TMC object... but we can still get it this way # The TMCCommandHelper isn't a direct member of the TMC object... but we can still get it this way
tmc_cmdhelper = tmc.get_status.__self__ tmc_cmdhelper = tmc_object.get_status.__self__
for register in RELEVANT_TMC_REGISTERS: for register in RELEVANT_TMC_REGISTERS:
# value = tmc_cmdhelper.read_register(register)
# motor.set_register(register, value)
val = tmc_cmdhelper.fields.registers.get(register) val = tmc_cmdhelper.fields.registers.get(register)
if (val is not None) and (register not in tmc_cmdhelper.read_registers): if (val is not None) and (register not in tmc_cmdhelper.read_registers):
# write-only register # write-only register
fields_string = self._extract_register_values(register, val) fields_string = self._extract_register_values(tmc_cmdhelper, register, val)
elif register in tmc_cmdhelper.read_registers: elif register in tmc_cmdhelper.read_registers:
# readable register # readable register
val = tmc_cmdhelper.mcu_tmc.get_register(register) val = tmc_cmdhelper.mcu_tmc.get_register(register)
if tmc_cmdhelper.read_translate is not None: if tmc_cmdhelper.read_translate is not None:
register, val = tmc_cmdhelper.read_translate(register, val) register, val = tmc_cmdhelper.read_translate(register, val)
fields_string = self._extract_register_values(register, val) fields_string = self._extract_register_values(tmc_cmdhelper, register, val)
motor.set_register(register, fields_string) motor.set_register(register, fields_string)
@@ -173,7 +171,7 @@ class MotorsConfigParser:
reg_fields = tmc_cmdhelper.fields.all_fields.get(register, {}) reg_fields = tmc_cmdhelper.fields.all_fields.get(register, {})
reg_fields = sorted([(mask, name) for name, mask in reg_fields.items()]) reg_fields = sorted([(mask, name) for name, mask in reg_fields.items()])
fields = {} fields = {}
for mask, field_name in reg_fields: for _, field_name in reg_fields:
field_value = tmc_cmdhelper.fields.get_field(field_name, val, register) field_value = tmc_cmdhelper.fields.get_field(field_name, val, register)
fields[field_name] = field_value fields[field_name] = field_value
return fields return fields
@@ -181,7 +179,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: for motor in self._motors:
if motor._name == motor_name: if motor.name == motor_name:
return motor return motor
return None return None

View File

@@ -1,11 +1,11 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from ..helpers.common_func import AXIS_CONFIG
from ..helpers.console_output import ConsoleOutput from ..helpers.console_output import ConsoleOutput
from . import AXIS_CONFIG
from .resonance_test import vibrate_axis from .resonance_test import vibrate_axis
def excitate_axis_at_freq(gcmd, gcode, printer) -> None: def excitate_axis_at_freq(gcmd, config) -> None:
freq = gcmd.get_int('FREQUENCY', default=25, minval=1) freq = gcmd.get_int('FREQUENCY', default=25, minval=1)
duration = gcmd.get_int('DURATION', default=10, minval=1) duration = gcmd.get_int('DURATION', default=10, minval=1)
accel_per_hz = gcmd.get_float('ACCEL_PER_HZ', default=None) accel_per_hz = gcmd.get_float('ACCEL_PER_HZ', default=None)
@@ -19,9 +19,11 @@ def excitate_axis_at_freq(gcmd, gcode, printer) -> None:
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')
systime = printer.get_reactor().monotonic() printer = config.get_printer()
gcode = printer.lookup_object('gcode')
toolhead = printer.lookup_object('toolhead') toolhead = printer.lookup_object('toolhead')
res_tester = printer.lookup_object('resonance_tester') 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

View File

@@ -11,7 +11,7 @@ from .motorsconfigparser import MotorsConfigParser
MIN_SPEED = 2 # mm/s MIN_SPEED = 2 # mm/s
def create_vibrations_profile(gcmd, gcode, printer, st_thread: ShakeTuneThread) -> None: def create_vibrations_profile(gcmd, config, st_thread: ShakeTuneThread) -> None:
size = gcmd.get_float('SIZE', default=100.0, minval=50.0) size = gcmd.get_float('SIZE', default=100.0, minval=50.0)
z_height = gcmd.get_float('Z_HEIGHT', default=20.0) z_height = gcmd.get_float('Z_HEIGHT', default=20.0)
max_speed = gcmd.get_float('MAX_SPEED', default=200.0, minval=10.0) max_speed = gcmd.get_float('MAX_SPEED', default=200.0, minval=10.0)
@@ -23,29 +23,30 @@ def create_vibrations_profile(gcmd, gcode, printer, st_thread: ShakeTuneThread)
if (size / (max_speed / 60)) < 0.25: if (size / (max_speed / 60)) < 0.25:
gcmd.error('The size of the movement is too small for the given speed! Increase SIZE or decrease MAX_SPEED!') gcmd.error('The size of the movement is too small for the given speed! Increase SIZE or decrease MAX_SPEED!')
# Check that input shaper is already configured printer = config.get_printer()
gcode = printer.lookup_object('gcode')
toolhead = printer.lookup_object('toolhead')
input_shaper = printer.lookup_object('input_shaper', None) input_shaper = printer.lookup_object('input_shaper', None)
systime = printer.get_reactor().monotonic()
# Check that input shaper is already configured
if input_shaper is None: if input_shaper is None:
gcmd.error('Input shaper is not configured! Please run the shaper calibration macro first.') gcmd.error('Input shaper is not configured! Please run the shaper calibration macro first.')
# TODO: Add the kinematics check to define the main_angles motors_config_parser = MotorsConfigParser(config, motors=['stepper_x', 'stepper_y'])
# but this needs to retrieve it from the printer configuration
# {% if kinematics == "cartesian" %} if motors_config_parser.kinematics == 'cartesian' or motors_config_parser.kinematics == 'corexz':
# # Cartesian motors are on X and Y axis directly # Cartesian motors are on X and Y axis directly, same for CoreXZ
# RESPOND MSG="Cartesian kinematics mode"
# {% set main_angles = [0, 90] %}
# {% elif kinematics == "corexy" %}
# # CoreXY motors are on A and B axis (45 and 135 degrees)
# RESPOND MSG="CoreXY kinematics mode"
# {% set main_angles = [45, 135] %}
# {% else %}
# { action_raise_error("Only Cartesian and CoreXY kinematics are supported at the moment for the vibrations measurement tool!") }
# {% endif %}
kinematics = 'cartesian'
main_angles = [0, 90] main_angles = [0, 90]
elif motors_config_parser.kinematics == 'corexy':
# CoreXY motors are on A and B axis (45 and 135 degrees)
main_angles = [45, 135]
else:
gcmd.error(
'Only Cartesian and CoreXY kinematics are supported at the moment for the vibrations measurement tool!'
)
ConsoleOutput.print(f'{motors_config_parser.kinematics.upper()} kinematics mode')
systime = printer.get_reactor().monotonic()
toolhead = printer.lookup_object('toolhead')
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_mcr = toolhead_info['minimum_cruise_ratio']
@@ -66,6 +67,7 @@ def create_vibrations_profile(gcmd, gcode, printer, st_thread: ShakeTuneThread)
nb_speed_samples = int((max_speed - MIN_SPEED) / speed_increment + 1) nb_speed_samples = int((max_speed - MIN_SPEED) / speed_increment + 1)
for curr_angle in main_angles: for curr_angle in main_angles:
ConsoleOutput.print(f'-> Measuring angle: {curr_angle} degrees...')
radian_angle = math.radians(curr_angle) radian_angle = math.radians(curr_angle)
# Find the best accelerometer chip for the current angle if not specified # Find the best accelerometer chip for the current angle if not specified
@@ -86,6 +88,7 @@ def create_vibrations_profile(gcmd, gcode, printer, st_thread: ShakeTuneThread)
# 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):
curr_speed = MIN_SPEED + curr_speed_sample * speed_increment curr_speed = MIN_SPEED + curr_speed_sample * speed_increment
ConsoleOutput.print(f'Current speed: {curr_speed} mm/s')
# Reduce the segments length for the lower speed range (0-100mm/s). The minimum length is 1/3 of the SIZE and is gradually increased # Reduce the segments length for the lower speed range (0-100mm/s). The minimum length is 1/3 of the SIZE and is gradually increased
# to the nominal SIZE at 100mm/s. No further size changes are made above this speed. The goal is to ensure that the print head moves # to the nominal SIZE at 100mm/s. No further size changes are made above this speed. The goal is to ensure that the print head moves
@@ -126,12 +129,9 @@ def create_vibrations_profile(gcmd, gcode, printer, st_thread: ShakeTuneThread)
) )
toolhead.wait_moves() toolhead.wait_moves()
# Get the motors and TMC configurations from Klipper
motors_config_parser = MotorsConfigParser(printer, motors=['stepper_x', 'stepper_y'])
# Run post-processing # Run post-processing
ConsoleOutput.print('Machine vibrations profile generation...') ConsoleOutput.print('Machine vibrations profile generation...')
ConsoleOutput.print('This may take some time (5-8min)') ConsoleOutput.print('This may take some time (5-8min)')
creator = st_thread.get_graph_creator() creator = st_thread.get_graph_creator()
creator.configure(kinematics, accel, motors_config_parser) creator.configure(motors_config_parser.kinematics, accel, motors_config_parser)
st_thread.run() st_thread.run()

View File

@@ -109,7 +109,8 @@ def axesmap_calibration(lognames, accel=None):
axes_map = ','.join([f'{spike[0][0]}{spike[1]}' for spike in spikes_sorted]) axes_map = ','.join([f'{spike[0][0]}{spike[1]}' for spike in spikes_sorted])
# alignment_error, sensitivity_error = compute_errors(filtered_data, spikes_sorted, accel, NUM_POINTS) # alignment_error, sensitivity_error = compute_errors(filtered_data, spikes_sorted, accel, NUM_POINTS)
results = f'Detected axes_map:\n {axes_map}\n' results = f'Be aware that this macro is experimental and has been known to sometimes produce incorrect results. Use it with caution and always check the results!\n'
results += f'Detected axes_map:\n {axes_map}\n'
# TODO: work on this function that is currently not giving good results... # TODO: work on this function that is currently not giving good results...
# results += "Accelerometer angle deviation:\n" # results += "Accelerometer angle deviation:\n"

View File

@@ -1,6 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import abc import abc
import re
import shutil import shutil
import tarfile import tarfile
from datetime import datetime from datetime import datetime
@@ -189,6 +190,7 @@ class VibrationsGraphCreator(GraphCreator):
with tarfile.open(tar_path, 'w:gz') as tar: with tarfile.open(tar_path, 'w:gz') as tar:
for csv_file in lognames: for csv_file in lognames:
tar.add(csv_file, arcname=csv_file.name, recursive=False) tar.add(csv_file, arcname=csv_file.name, recursive=False)
csv_file.unlink()
def create_graph(self) -> None: def create_graph(self) -> None:
if not self._accel or not self._kinematics: if not self._accel or not self._kinematics:
@@ -197,7 +199,7 @@ class VibrationsGraphCreator(GraphCreator):
lognames = self._move_and_prepare_files( lognames = self._move_and_prepare_files(
glob_pattern='shaketune-vib_*.csv', glob_pattern='shaketune-vib_*.csv',
min_files_required=None, min_files_required=None,
custom_name_func=lambda f: f.name, custom_name_func=lambda f: re.search(r'shaketune-vib_(.*?)_\d{8}_\d{6}', f.name).group(1),
) )
fig = vibrations_profile( fig = vibrations_profile(
lognames=[str(path) for path in lognames], lognames=[str(path) for path in lognames],

View File

@@ -564,23 +564,23 @@ def plot_motor_config_txt(fig, motors, differences):
motor_details = [(motors[0], 'X motor'), (motors[1], 'Y motor')] motor_details = [(motors[0], 'X motor'), (motors[1], 'Y motor')]
distance = 0.12 distance = 0.12
if motors[0].get_property('autotune_enabled'): if motors[0].get_config('autotune_enabled'):
distance = 0.24 distance = 0.27
config_blocks = [ config_blocks = [
f"| {lbl}: {mot.get_property('motor').upper()} on {mot.get_property('tmc').upper()} @ {mot.get_property('voltage')}V {mot.get_property('run_current')}A" f"| {lbl}: {mot.get_config('motor').upper()} on {mot.get_config('tmc').upper()} @ {mot.get_config('voltage'):0.1f}V {mot.get_config('run_current'):0.2f}A - {mot.get_config('microsteps')}usteps"
for mot, lbl in motor_details for mot, lbl in motor_details
] ]
config_blocks.append('| TMC Autotune enabled') config_blocks.append('| TMC Autotune enabled')
else: else:
config_blocks = [ config_blocks = [
f"| {lbl}: {mot.get_property('tmc').upper()} @ {mot.get_property('run_current')}A" f"| {lbl}: {mot.get_config('tmc').upper()} @ {mot.get_config('run_current'):0.2f}A - {mot.get_config('microsteps')}usteps"
for mot, lbl in motor_details for mot, lbl in motor_details
] ]
config_blocks.append('| TMC Autotune not detected') config_blocks.append('| TMC Autotune not detected')
for idx, block in enumerate(config_blocks): for idx, block in enumerate(config_blocks):
fig.text( fig.text(
0.40, 0.990 - 0.015 * idx, block, ha='left', va='top', fontsize=10, color=KLIPPAIN_COLORS['dark_purple'] 0.41, 0.990 - 0.015 * idx, block, ha='left', va='top', fontsize=10, color=KLIPPAIN_COLORS['dark_purple']
) )
tmc_registers = motors[0].get_registers() tmc_registers = motors[0].get_registers()
@@ -589,7 +589,7 @@ def plot_motor_config_txt(fig, motors, differences):
settings_str = ' '.join(f'{k}={v}' for k, v in settings.items()) settings_str = ' '.join(f'{k}={v}' for k, v in settings.items())
tmc_block = f'| {register.upper()}: {settings_str}' tmc_block = f'| {register.upper()}: {settings_str}'
fig.text( fig.text(
0.40 + distance, 0.41 + distance,
0.990 - 0.015 * idx, 0.990 - 0.015 * idx,
tmc_block, tmc_block,
ha='left', ha='left',
@@ -601,7 +601,7 @@ def plot_motor_config_txt(fig, motors, differences):
if differences is not None: if differences is not None:
differences_text = f'| Y motor diff: {differences}' differences_text = f'| Y motor diff: {differences}'
fig.text( fig.text(
0.40 + distance, 0.41 + distance,
0.990 - 0.015 * (idx + 1), 0.990 - 0.015 * (idx + 1),
differences_text, differences_text,
ha='left', ha='left',

View File

@@ -18,8 +18,9 @@ from .shaketune_thread import ShakeTuneThread
class ShakeTune: class ShakeTune:
def __init__(self, config) -> None: def __init__(self, config) -> None:
self._pconfig = config
self._printer = config.get_printer() self._printer = config.get_printer()
self._gcode = self._printer.lookup_object('gcode') gcode = self._printer.lookup_object('gcode')
res_tester = self._printer.lookup_object('resonance_tester') res_tester = self._printer.lookup_object('resonance_tester')
if res_tester is None: if res_tester is None:
@@ -34,29 +35,29 @@ class ShakeTune:
dpi = config.getint('dpi', default=150, minval=100, maxval=500) dpi = config.getint('dpi', default=150, minval=100, maxval=500)
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(self._gcode.respond_info) ConsoleOutput.register_output_callback(gcode.respond_info)
self._gcode.register_command( gcode.register_command(
'EXCITATE_AXIS_AT_FREQ', 'EXCITATE_AXIS_AT_FREQ',
self.cmd_EXCITATE_AXIS_AT_FREQ, self.cmd_EXCITATE_AXIS_AT_FREQ,
desc=self.cmd_EXCITATE_AXIS_AT_FREQ_help, desc=self.cmd_EXCITATE_AXIS_AT_FREQ_help,
) )
self._gcode.register_command( gcode.register_command(
'AXES_MAP_CALIBRATION', 'AXES_MAP_CALIBRATION',
self.cmd_AXES_MAP_CALIBRATION, self.cmd_AXES_MAP_CALIBRATION,
desc=self.cmd_AXES_MAP_CALIBRATION_help, desc=self.cmd_AXES_MAP_CALIBRATION_help,
) )
self._gcode.register_command( gcode.register_command(
'COMPARE_BELTS_RESPONSES', 'COMPARE_BELTS_RESPONSES',
self.cmd_COMPARE_BELTS_RESPONSES, self.cmd_COMPARE_BELTS_RESPONSES,
desc=self.cmd_COMPARE_BELTS_RESPONSES_help, desc=self.cmd_COMPARE_BELTS_RESPONSES_help,
) )
self._gcode.register_command( gcode.register_command(
'AXES_SHAPER_CALIBRATION', 'AXES_SHAPER_CALIBRATION',
self.cmd_AXES_SHAPER_CALIBRATION, self.cmd_AXES_SHAPER_CALIBRATION,
desc=self.cmd_AXES_SHAPER_CALIBRATION_help, desc=self.cmd_AXES_SHAPER_CALIBRATION_help,
) )
self._gcode.register_command( gcode.register_command(
'CREATE_VIBRATIONS_PROFILE', 'CREATE_VIBRATIONS_PROFILE',
self.cmd_CREATE_VIBRATIONS_PROFILE, self.cmd_CREATE_VIBRATIONS_PROFILE,
desc=self.cmd_CREATE_VIBRATIONS_PROFILE_help, desc=self.cmd_CREATE_VIBRATIONS_PROFILE_help,
@@ -68,7 +69,7 @@ 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()}')
excitate_axis_at_freq(gcmd, self._gcode, self._printer) excitate_axis_at_freq(gcmd, self._pconfig)
cmd_AXES_MAP_CALIBRATION_help = 'Perform a set of movements to measure the orientation of the accelerometer and help you set the best axes_map configuration for your printer' cmd_AXES_MAP_CALIBRATION_help = 'Perform a set of movements to measure the orientation of the accelerometer and help you set the best axes_map configuration for your printer'
@@ -76,7 +77,7 @@ class ShakeTune:
ConsoleOutput.print(f'Shake&Tune version: {ShakeTuneConfig.get_git_version()}') ConsoleOutput.print(f'Shake&Tune version: {ShakeTuneConfig.get_git_version()}')
axes_map_finder = AxesMapFinder(self._config) axes_map_finder = AxesMapFinder(self._config)
st_thread = ShakeTuneThread(self._config, axes_map_finder, self._printer.get_reactor(), self.timeout) st_thread = ShakeTuneThread(self._config, axes_map_finder, self._printer.get_reactor(), self.timeout)
axes_map_calibration(gcmd, self._gcode, self._printer, st_thread) axes_map_calibration(gcmd, self._pconfig, st_thread)
cmd_COMPARE_BELTS_RESPONSES_help = 'Perform a custom half-axis test to analyze and compare the frequency profiles of individual belts on CoreXY printers' cmd_COMPARE_BELTS_RESPONSES_help = 'Perform a custom half-axis test to analyze and compare the frequency profiles of individual belts on CoreXY printers'
@@ -84,7 +85,7 @@ class ShakeTune:
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_thread = ShakeTuneThread(self._config, belt_graph_creator, self._printer.get_reactor(), self.timeout) st_thread = ShakeTuneThread(self._config, belt_graph_creator, self._printer.get_reactor(), self.timeout)
compare_belts_responses(gcmd, self._gcode, self._printer, st_thread) compare_belts_responses(gcmd, self._pconfig, st_thread)
cmd_AXES_SHAPER_CALIBRATION_help = ( cmd_AXES_SHAPER_CALIBRATION_help = (
'Perform standard axis input shaper tests on one or both XY axes to select the best input shaper filter' 'Perform standard axis input shaper tests on one or both XY axes to select the best input shaper filter'
@@ -94,7 +95,7 @@ class ShakeTune:
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_thread = ShakeTuneThread(self._config, shaper_graph_creator, self._printer.get_reactor(), self.timeout) st_thread = ShakeTuneThread(self._config, shaper_graph_creator, self._printer.get_reactor(), self.timeout)
axes_shaper_calibration(gcmd, self._gcode, self._printer, st_thread) axes_shaper_calibration(gcmd, self._pconfig, st_thread)
cmd_CREATE_VIBRATIONS_PROFILE_help = 'Perform a set of movements to measure the orientation of the accelerometer and help you set the best axes_map configuration for your printer' cmd_CREATE_VIBRATIONS_PROFILE_help = 'Perform a set of movements to measure the orientation of the accelerometer and help you set the best axes_map configuration for your printer'
@@ -102,4 +103,4 @@ class ShakeTune:
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_thread = ShakeTuneThread(self._config, vibration_profile_creator, self._printer.get_reactor(), self.timeout) st_thread = ShakeTuneThread(self._config, vibration_profile_creator, self._printer.get_reactor(), self.timeout)
create_vibrations_profile(gcmd, self._gcode, self._printer, st_thread) create_vibrations_profile(gcmd, self._pconfig, st_thread)