added axes_map computation
This commit is contained in:
60
K-ShakeTune/K-SnT_axes_map.cfg
Normal file
60
K-ShakeTune/K-SnT_axes_map.cfg
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
############################################################
|
||||||
|
###### AXE_MAP DETECTION AND ACCELEROMETER VALIDATION ######
|
||||||
|
############################################################
|
||||||
|
# Written by Frix_x#0161 #
|
||||||
|
|
||||||
|
[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
|
||||||
|
|
||||||
|
{% set mid_x = printer.toolhead.axis_maximum.x|float / 2 %}
|
||||||
|
{% set mid_y = printer.toolhead.axis_maximum.y|float / 2 %}
|
||||||
|
|
||||||
|
{% set accel = [accel, printer.configfile.settings.printer.max_accel]|min %}
|
||||||
|
{% set old_accel = printer.toolhead.max_accel %}
|
||||||
|
{% set old_accel_to_decel = printer.toolhead.max_accel_to_decel %}
|
||||||
|
{% set old_sqv = printer.toolhead.square_corner_velocity %}
|
||||||
|
|
||||||
|
|
||||||
|
{% if not 'xyz' in printer.toolhead.homed_axes %}
|
||||||
|
{ action_raise_error("Must Home printer first!") }
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{action_respond_info("")}
|
||||||
|
{action_respond_info("Starting accelerometer axe_map calibration")}
|
||||||
|
{action_respond_info("This operation can not be interrupted by normal means. Hit the \"emergency stop\" button to stop it if needed")}
|
||||||
|
{action_respond_info("")}
|
||||||
|
|
||||||
|
SAVE_GCODE_STATE NAME=STATE_AXESMAP_CALIBRATION
|
||||||
|
|
||||||
|
G90
|
||||||
|
|
||||||
|
# Set the wanted acceleration values (not too high to avoid oscillation, not too low to be able to reach constant speed on each segments)
|
||||||
|
SET_VELOCITY_LIMIT ACCEL={accel} ACCEL_TO_DECEL={accel} SQUARE_CORNER_VELOCITY={[(accel / 1000), 5.0]|max}
|
||||||
|
|
||||||
|
# Going to the start position
|
||||||
|
G1 Z{z_height} F{feedrate_travel / 8}
|
||||||
|
G1 X{mid_x - 15} Y{mid_y - 15} F{feedrate_travel}
|
||||||
|
G4 P500
|
||||||
|
|
||||||
|
ACCELEROMETER_MEASURE CHIP={accel_chip}
|
||||||
|
G4 P1000 # This first waiting time is to record the background accelerometer noise before moving
|
||||||
|
G1 X{mid_x + 15} F{speed}
|
||||||
|
G4 P1000
|
||||||
|
G1 Y{mid_y + 15} F{speed}
|
||||||
|
G4 P1000
|
||||||
|
G1 Z{z_height + 15} F{speed}
|
||||||
|
G4 P1000
|
||||||
|
ACCELEROMETER_MEASURE CHIP={accel_chip} NAME=axemap
|
||||||
|
|
||||||
|
RESPOND MSG="Analysis of the movements..."
|
||||||
|
RUN_SHELL_COMMAND CMD=shaketune PARAMS="AXESMAP {accel}"
|
||||||
|
|
||||||
|
# Restore the previous acceleration values
|
||||||
|
SET_VELOCITY_LIMIT ACCEL={old_accel} ACCEL_TO_DECEL={old_accel_to_decel} SQUARE_CORNER_VELOCITY={old_sqv}
|
||||||
|
|
||||||
|
RESTORE_GCODE_STATE NAME=STATE_AXESMAP_CALIBRATION
|
||||||
@@ -6,7 +6,6 @@
|
|||||||
[gcode_macro AXES_SHAPER_CALIBRATION]
|
[gcode_macro AXES_SHAPER_CALIBRATION]
|
||||||
description: Perform standard axis input shaper tests on one or both XY axes to select the best input shaper filter
|
description: Perform standard axis input shaper tests on one or both XY axes to select the best input shaper filter
|
||||||
gcode:
|
gcode:
|
||||||
{% set verbose = params.VERBOSE|default(true) %}
|
|
||||||
{% set min_freq = params.FREQ_START|default(5)|float %}
|
{% set min_freq = params.FREQ_START|default(5)|float %}
|
||||||
{% set max_freq = params.FREQ_END|default(133.3)|float %}
|
{% set max_freq = params.FREQ_END|default(133.3)|float %}
|
||||||
{% set hz_per_sec = params.HZ_PER_SEC|default(1)|float %}
|
{% set hz_per_sec = params.HZ_PER_SEC|default(1)|float %}
|
||||||
@@ -28,10 +27,8 @@ gcode:
|
|||||||
TEST_RESONANCES AXIS=X OUTPUT=raw_data NAME=x FREQ_START={min_freq} FREQ_END={max_freq} HZ_PER_SEC={hz_per_sec}
|
TEST_RESONANCES AXIS=X OUTPUT=raw_data NAME=x FREQ_START={min_freq} FREQ_END={max_freq} HZ_PER_SEC={hz_per_sec}
|
||||||
M400
|
M400
|
||||||
|
|
||||||
{% if verbose %}
|
|
||||||
RESPOND MSG="X axis frequency profile generation..."
|
RESPOND MSG="X axis frequency profile generation..."
|
||||||
RESPOND MSG="This may take some time (1-3min)"
|
RESPOND MSG="This may take some time (1-3min)"
|
||||||
{% endif %}
|
|
||||||
RUN_SHELL_COMMAND CMD=shaketune PARAMS=SHAPER
|
RUN_SHELL_COMMAND CMD=shaketune PARAMS=SHAPER
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
@@ -39,9 +36,7 @@ gcode:
|
|||||||
TEST_RESONANCES AXIS=Y OUTPUT=raw_data NAME=y FREQ_START={min_freq} FREQ_END={max_freq} HZ_PER_SEC={hz_per_sec}
|
TEST_RESONANCES AXIS=Y OUTPUT=raw_data NAME=y FREQ_START={min_freq} FREQ_END={max_freq} HZ_PER_SEC={hz_per_sec}
|
||||||
M400
|
M400
|
||||||
|
|
||||||
{% if verbose %}
|
|
||||||
RESPOND MSG="Y axis frequency profile generation..."
|
RESPOND MSG="Y axis frequency profile generation..."
|
||||||
RESPOND MSG="This may take some time (1-3min)"
|
RESPOND MSG="This may take some time (1-3min)"
|
||||||
{% endif %}
|
|
||||||
RUN_SHELL_COMMAND CMD=shaketune PARAMS=SHAPER
|
RUN_SHELL_COMMAND CMD=shaketune PARAMS=SHAPER
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
[gcode_macro BELTS_SHAPER_CALIBRATION]
|
[gcode_macro BELTS_SHAPER_CALIBRATION]
|
||||||
description: Perform a custom half-axis test to analyze and compare the frequency profiles of individual belts on CoreXY printers
|
description: Perform a custom half-axis test to analyze and compare the frequency profiles of individual belts on CoreXY printers
|
||||||
gcode:
|
gcode:
|
||||||
{% set verbose = params.VERBOSE|default(true) %}
|
|
||||||
{% set min_freq = params.FREQ_START|default(5)|float %}
|
{% set min_freq = params.FREQ_START|default(5)|float %}
|
||||||
{% set max_freq = params.FREQ_END|default(133.33)|float %}
|
{% set max_freq = params.FREQ_END|default(133.33)|float %}
|
||||||
{% set hz_per_sec = params.HZ_PER_SEC|default(1)|float %}
|
{% set hz_per_sec = params.HZ_PER_SEC|default(1)|float %}
|
||||||
@@ -17,8 +16,6 @@ gcode:
|
|||||||
TEST_RESONANCES AXIS=1,-1 OUTPUT=raw_data NAME=a FREQ_START={min_freq} FREQ_END={max_freq} HZ_PER_SEC={hz_per_sec}
|
TEST_RESONANCES AXIS=1,-1 OUTPUT=raw_data NAME=a FREQ_START={min_freq} FREQ_END={max_freq} HZ_PER_SEC={hz_per_sec}
|
||||||
M400
|
M400
|
||||||
|
|
||||||
{% if verbose %}
|
|
||||||
RESPOND MSG="Belts comparative frequency profile generation..."
|
RESPOND MSG="Belts comparative frequency profile generation..."
|
||||||
RESPOND MSG="This may take some time (3-5min)"
|
RESPOND MSG="This may take some time (3-5min)"
|
||||||
{% endif %}
|
|
||||||
RUN_SHELL_COMMAND CMD=shaketune PARAMS=BELTS
|
RUN_SHELL_COMMAND CMD=shaketune PARAMS=BELTS
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ gcode:
|
|||||||
{% set size = params.SIZE|default(60)|int %} # size of the area where the movements are done
|
{% set size = params.SIZE|default(60)|int %} # size of the area where the movements are done
|
||||||
{% set direction = params.DIRECTION|default('XY') %} # can be set to either XY, AB, ABXY, A, B, X, Y, Z
|
{% set direction = params.DIRECTION|default('XY') %} # can be set to either XY, AB, ABXY, A, B, X, Y, Z
|
||||||
{% set z_height = params.Z_HEIGHT|default(20)|int %} # z height to put the toolhead before starting the movements
|
{% set z_height = params.Z_HEIGHT|default(20)|int %} # z height to put the toolhead before starting the movements
|
||||||
{% set verbose = params.VERBOSE|default(true) %} # Wether to log the current speed in the console
|
|
||||||
|
|
||||||
{% set min_speed = params.MIN_SPEED|default(20)|float * 60 %} # minimum feedrate for the movements
|
{% set min_speed = params.MIN_SPEED|default(20)|float * 60 %} # minimum feedrate for the movements
|
||||||
{% set max_speed = params.MAX_SPEED|default(200)|float * 60 %} # maximum feedrate for the movements
|
{% set max_speed = params.MAX_SPEED|default(200)|float * 60 %} # maximum feedrate for the movements
|
||||||
@@ -100,9 +99,7 @@ gcode:
|
|||||||
}
|
}
|
||||||
%}
|
%}
|
||||||
|
|
||||||
#
|
|
||||||
# STARTING...
|
|
||||||
#
|
|
||||||
{% if not 'xyz' in printer.toolhead.homed_axes %}
|
{% if not 'xyz' in printer.toolhead.homed_axes %}
|
||||||
{ action_raise_error("Must Home printer first!") }
|
{ action_raise_error("Must Home printer first!") }
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -126,22 +123,19 @@ gcode:
|
|||||||
|
|
||||||
SAVE_GCODE_STATE NAME=STATE_VIBRATIONS_CALIBRATION
|
SAVE_GCODE_STATE NAME=STATE_VIBRATIONS_CALIBRATION
|
||||||
|
|
||||||
M83
|
|
||||||
G90
|
G90
|
||||||
|
|
||||||
# Set the wanted acceleration values (not too high to avoid oscillation, not too low to be able to reach constant speed on each segments)
|
# Set the wanted acceleration values (not too high to avoid oscillation, not too low to be able to reach constant speed on each segments)
|
||||||
SET_VELOCITY_LIMIT ACCEL={accel} ACCEL_TO_DECEL={accel} SQUARE_CORNER_VELOCITY={[(accel / 1000), 5.0]|max}
|
SET_VELOCITY_LIMIT ACCEL={accel} ACCEL_TO_DECEL={accel} SQUARE_CORNER_VELOCITY={[(accel / 1000), 5.0]|max}
|
||||||
|
|
||||||
# Going to the start position
|
# Going to the start position
|
||||||
G1 Z{z_height}
|
G1 Z{z_height} F{feedrate_travel / 10}
|
||||||
G1 X{mid_x + (size * direction_factor[direction].start.x) } Y{mid_y + (size * direction_factor[direction].start.y)} F{feedrate_travel}
|
G1 X{mid_x + (size * direction_factor[direction].start.x) } Y{mid_y + (size * direction_factor[direction].start.y)} F{feedrate_travel}
|
||||||
|
|
||||||
# vibration pattern for each frequency
|
# vibration pattern for each frequency
|
||||||
{% for curr_sample in range(0, nb_samples) %}
|
{% for curr_sample in range(0, nb_samples) %}
|
||||||
{% set curr_speed = min_speed + curr_sample * speed_increment %}
|
{% set curr_speed = min_speed + curr_sample * speed_increment %}
|
||||||
{% if verbose %}
|
|
||||||
RESPOND MSG="{"Current speed: %.2f mm/s" % (curr_speed / 60)|float}"
|
RESPOND MSG="{"Current speed: %.2f mm/s" % (curr_speed / 60)|float}"
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
ACCELEROMETER_MEASURE CHIP={accel_chip}
|
ACCELEROMETER_MEASURE CHIP={accel_chip}
|
||||||
{% if direction == 'E' %}
|
{% if direction == 'E' %}
|
||||||
@@ -152,14 +146,13 @@ gcode:
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
ACCELEROMETER_MEASURE CHIP={accel_chip} NAME=sp{("%.2f" % (curr_speed / 60)|float)|replace('.','_')}n1
|
ACCELEROMETER_MEASURE CHIP={accel_chip} NAME=sp{("%.2f" % (curr_speed / 60)|float)|replace('.','_')}n1
|
||||||
|
|
||||||
G4 P300
|
G4 P300
|
||||||
|
|
||||||
M400
|
M400
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{% if verbose %}
|
RESPOND MSG="Machine and motors vibration graph generation..."
|
||||||
RESPOND MSG="Graphs generation... Please wait a minute or two and look in the configured folder."
|
RESPOND MSG="This may take some time (3-5min)"
|
||||||
{% endif %}
|
|
||||||
RUN_SHELL_COMMAND CMD=shaketune PARAMS="VIBRATIONS {direction}"
|
RUN_SHELL_COMMAND CMD=shaketune PARAMS="VIBRATIONS {direction}"
|
||||||
|
|
||||||
# Restore the previous acceleration values
|
# Restore the previous acceleration values
|
||||||
|
|||||||
171
K-ShakeTune/scripts/analyze_axesmap.py
Executable file
171
K-ShakeTune/scripts/analyze_axesmap.py
Executable file
@@ -0,0 +1,171 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
######################################
|
||||||
|
###### AXE_MAP DETECTION SCRIPT ######
|
||||||
|
######################################
|
||||||
|
# Written by Frix_x#0161 #
|
||||||
|
|
||||||
|
# Be sure to make this script executable using SSH: type 'chmod +x ./analyze_axesmap.py' when in the folder !
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
################ !!! DO NOT EDIT BELOW THIS LINE !!! ################
|
||||||
|
#####################################################################
|
||||||
|
|
||||||
|
import optparse
|
||||||
|
import numpy as np
|
||||||
|
import locale
|
||||||
|
from scipy.signal import butter, filtfilt
|
||||||
|
|
||||||
|
|
||||||
|
NUM_POINTS = 500
|
||||||
|
|
||||||
|
|
||||||
|
# Set the best locale for time and date formating (generation of the titles)
|
||||||
|
try:
|
||||||
|
locale.setlocale(locale.LC_TIME, locale.getdefaultlocale())
|
||||||
|
except locale.Error:
|
||||||
|
locale.setlocale(locale.LC_TIME, 'C')
|
||||||
|
|
||||||
|
# Override the built-in print function to avoid problem in Klipper due to locale settings
|
||||||
|
original_print = print
|
||||||
|
def print_with_c_locale(*args, **kwargs):
|
||||||
|
original_locale = locale.setlocale(locale.LC_ALL, None)
|
||||||
|
locale.setlocale(locale.LC_ALL, 'C')
|
||||||
|
original_print(*args, **kwargs)
|
||||||
|
locale.setlocale(locale.LC_ALL, original_locale)
|
||||||
|
print = print_with_c_locale
|
||||||
|
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
# Computation
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
def accel_signal_filter(data, cutoff=2, fs=100, order=5):
|
||||||
|
nyq = 0.5 * fs
|
||||||
|
normal_cutoff = cutoff / nyq
|
||||||
|
b, a = butter(order, normal_cutoff, btype='low', analog=False)
|
||||||
|
filtered_data = filtfilt(b, a, data)
|
||||||
|
filtered_data -= np.mean(filtered_data)
|
||||||
|
return filtered_data
|
||||||
|
|
||||||
|
def find_first_spike(data):
|
||||||
|
min_index, max_index = np.argmin(data), np.argmax(data)
|
||||||
|
return ('-', min_index) if min_index < max_index else ('', max_index)
|
||||||
|
|
||||||
|
def get_movement_vector(data, start_idx, end_idx):
|
||||||
|
if start_idx < end_idx:
|
||||||
|
vector = []
|
||||||
|
for i in range(3):
|
||||||
|
vector.append(np.mean(data[i][start_idx:end_idx], axis=0))
|
||||||
|
return vector
|
||||||
|
else:
|
||||||
|
return np.zeros(3)
|
||||||
|
|
||||||
|
def angle_between(v1, v2):
|
||||||
|
v1_u = v1 / np.linalg.norm(v1)
|
||||||
|
v2_u = v2 / np.linalg.norm(v2)
|
||||||
|
return np.arccos(np.clip(np.dot(v1_u, v2_u), -1.0, 1.0))
|
||||||
|
|
||||||
|
def compute_errors(filtered_data, spikes_sorted, accel_value, num_points):
|
||||||
|
# Get the movement start points in the correct order from the sorted bag of spikes
|
||||||
|
movement_starts = [spike[0][1] for spike in spikes_sorted]
|
||||||
|
|
||||||
|
# Theoretical unit vectors for X, Y, Z printer axes
|
||||||
|
printer_axes = {
|
||||||
|
'x': np.array([1, 0, 0]),
|
||||||
|
'y': np.array([0, 1, 0]),
|
||||||
|
'z': np.array([0, 0, 1])
|
||||||
|
}
|
||||||
|
|
||||||
|
alignment_errors = {}
|
||||||
|
sensitivity_errors = {}
|
||||||
|
for i, axis in enumerate(['x', 'y', 'z']):
|
||||||
|
movement_start = movement_starts[i]
|
||||||
|
movement_end = movement_start + num_points
|
||||||
|
movement_vector = get_movement_vector(filtered_data, movement_start, movement_end)
|
||||||
|
alignment_errors[axis] = angle_between(movement_vector, printer_axes[axis])
|
||||||
|
|
||||||
|
measured_accel_magnitude = np.linalg.norm(movement_vector)
|
||||||
|
if accel_value != 0:
|
||||||
|
sensitivity_errors[axis] = abs(measured_accel_magnitude - accel_value) / accel_value * 100
|
||||||
|
else:
|
||||||
|
sensitivity_errors[axis] = None
|
||||||
|
|
||||||
|
return alignment_errors, sensitivity_errors
|
||||||
|
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
# Startup and main routines
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
def parse_log(logname):
|
||||||
|
with open(logname) as f:
|
||||||
|
for header in f:
|
||||||
|
if not header.startswith('#'):
|
||||||
|
break
|
||||||
|
if not header.startswith('freq,psd_x,psd_y,psd_z,psd_xyz'):
|
||||||
|
# Raw accelerometer data
|
||||||
|
return np.loadtxt(logname, comments='#', delimiter=',')
|
||||||
|
# Power spectral density data or shaper calibration data
|
||||||
|
raise ValueError("File %s does not contain raw accelerometer data and therefore "
|
||||||
|
"is not supported by this script. Please use the official Klipper "
|
||||||
|
"calibrate_shaper.py script to process it instead." % (logname,))
|
||||||
|
|
||||||
|
|
||||||
|
def axesmap_calibration(lognames, accel=None):
|
||||||
|
# Parse the raw data and get them ready for analysis
|
||||||
|
raw_datas = [parse_log(filename) for filename in lognames]
|
||||||
|
if len(raw_datas) > 1:
|
||||||
|
raise ValueError("Analysis of multiple CSV files at once is not possible with this script")
|
||||||
|
|
||||||
|
filtered_data = [accel_signal_filter(raw_datas[0][:, i+1]) for i in range(3)]
|
||||||
|
spikes = [find_first_spike(filtered_data[i]) for i in range(3)]
|
||||||
|
spikes_sorted = sorted([(spikes[0], 'x'), (spikes[1], 'y'), (spikes[2], 'z')], key=lambda x: x[0][1])
|
||||||
|
|
||||||
|
# Using the previous variables to get the axes_map and errors
|
||||||
|
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)
|
||||||
|
|
||||||
|
results = f"Detected axes_map:\n {axes_map}\n"
|
||||||
|
|
||||||
|
# TODO: work on this function that is currently not giving good results...
|
||||||
|
# results += "Accelerometer angle deviation:\n"
|
||||||
|
# for axis, angle in alignment_error.items():
|
||||||
|
# angle_degrees = np.degrees(angle) # Convert radians to degrees
|
||||||
|
# results += f" {axis.upper()} axis: {angle_degrees:.2f} degrees\n"
|
||||||
|
|
||||||
|
# results += "Accelerometer sensitivity error:\n"
|
||||||
|
# for axis, error in sensitivity_error.items():
|
||||||
|
# results += f" {axis.upper()} axis: {error:.2f}%\n"
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Parse command-line arguments
|
||||||
|
usage = "%prog [options] <raw logs>"
|
||||||
|
opts = optparse.OptionParser(usage)
|
||||||
|
opts.add_option("-o", "--output", type="string", dest="output",
|
||||||
|
default=None, help="filename of output graph")
|
||||||
|
opts.add_option("-a", "--accel", type="string", dest="accel",
|
||||||
|
default=None, help="acceleration value used to do the movements")
|
||||||
|
options, args = opts.parse_args()
|
||||||
|
if len(args) < 1:
|
||||||
|
opts.error("No CSV file(s) to analyse")
|
||||||
|
if options.accel is None:
|
||||||
|
opts.error("You must specify the acceleration value used when generating the CSV file (option -a)")
|
||||||
|
try:
|
||||||
|
accel_value = float(options.accel)
|
||||||
|
except ValueError:
|
||||||
|
opts.error("Invalid acceleration value. It should be a numeric value.")
|
||||||
|
|
||||||
|
results = axesmap_calibration(args, accel_value)
|
||||||
|
print(results)
|
||||||
|
|
||||||
|
if options.output is not None:
|
||||||
|
with open(options.output, 'w') as f:
|
||||||
|
f.write(results)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -317,7 +317,7 @@ def parse_log(logname):
|
|||||||
return np.loadtxt(logname, comments='#', delimiter=',')
|
return np.loadtxt(logname, comments='#', delimiter=',')
|
||||||
# Power spectral density data or shaper calibration data
|
# Power spectral density data or shaper calibration data
|
||||||
raise ValueError("File %s does not contain raw accelerometer data and therefore "
|
raise ValueError("File %s does not contain raw accelerometer data and therefore "
|
||||||
"is not supported by graph_vibrations.py script. Please use "
|
"is not supported by this script. Please use the official Klipper"
|
||||||
"calibrate_shaper.py script to process it instead." % (logname,))
|
"calibrate_shaper.py script to process it instead." % (logname,))
|
||||||
|
|
||||||
|
|
||||||
@@ -326,7 +326,7 @@ def extract_speed(logname):
|
|||||||
speed = re.search('sp(.+?)n', os.path.basename(logname)).group(1).replace('_','.')
|
speed = re.search('sp(.+?)n', os.path.basename(logname)).group(1).replace('_','.')
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise ValueError("File %s does not contain speed in its name and therefore "
|
raise ValueError("File %s does not contain speed in its name and therefore "
|
||||||
"is not supported by graph_vibrations.py script." % (logname,))
|
"is not supported by this script." % (logname,))
|
||||||
return float(speed)
|
return float(speed)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ STORE_RESULTS = 3
|
|||||||
from graph_belts import belts_calibration
|
from graph_belts import belts_calibration
|
||||||
from graph_shaper import shaper_calibration
|
from graph_shaper import shaper_calibration
|
||||||
from graph_vibrations import vibrations_calibration
|
from graph_vibrations import vibrations_calibration
|
||||||
|
from analyze_axesmap import axesmap_calibration
|
||||||
|
|
||||||
RESULTS_SUBFOLDERS = ['belts', 'inputshaper', 'vibrations']
|
RESULTS_SUBFOLDERS = ['belts', 'inputshaper', 'vibrations']
|
||||||
|
|
||||||
@@ -50,7 +51,7 @@ def is_file_open(filepath):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def get_belts_graph():
|
def create_belts_graph():
|
||||||
current_date = datetime.now().strftime('%Y%m%d_%H%M%S')
|
current_date = datetime.now().strftime('%Y%m%d_%H%M%S')
|
||||||
lognames = []
|
lognames = []
|
||||||
|
|
||||||
@@ -61,6 +62,7 @@ def get_belts_graph():
|
|||||||
if len(globbed_files) < 2:
|
if len(globbed_files) < 2:
|
||||||
print("Not enough CSV files found in the /tmp folder. Two files are required for the belt graphs!")
|
print("Not enough CSV files found in the /tmp folder. Two files are required for the belt graphs!")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
sorted_files = sorted(globbed_files, key=os.path.getmtime, reverse=True)
|
sorted_files = sorted(globbed_files, key=os.path.getmtime, reverse=True)
|
||||||
|
|
||||||
for filename in sorted_files[:2]:
|
for filename in sorted_files[:2]:
|
||||||
@@ -85,10 +87,11 @@ def get_belts_graph():
|
|||||||
fig = belts_calibration(lognames, KLIPPER_FOLDER)
|
fig = belts_calibration(lognames, KLIPPER_FOLDER)
|
||||||
png_filename = os.path.join(RESULTS_FOLDER, RESULTS_SUBFOLDERS[0], f'belts_{current_date}.png')
|
png_filename = os.path.join(RESULTS_FOLDER, RESULTS_SUBFOLDERS[0], f'belts_{current_date}.png')
|
||||||
|
|
||||||
return fig, png_filename
|
fig.savefig(png_filename)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
def get_shaper_graph():
|
def create_shaper_graph():
|
||||||
current_date = datetime.now().strftime('%Y%m%d_%H%M%S')
|
current_date = datetime.now().strftime('%Y%m%d_%H%M%S')
|
||||||
|
|
||||||
# Get all the files and sort them based on last modified time to select the most recent one
|
# Get all the files and sort them based on last modified time to select the most recent one
|
||||||
@@ -96,6 +99,7 @@ def get_shaper_graph():
|
|||||||
if not globbed_files:
|
if not globbed_files:
|
||||||
print("No CSV files found in the /tmp folder to create the input shaper graphs!")
|
print("No CSV files found in the /tmp folder to create the input shaper graphs!")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
sorted_files = sorted(globbed_files, key=os.path.getmtime, reverse=True)
|
sorted_files = sorted(globbed_files, key=os.path.getmtime, reverse=True)
|
||||||
filename = sorted_files[0]
|
filename = sorted_files[0]
|
||||||
|
|
||||||
@@ -117,10 +121,11 @@ def get_shaper_graph():
|
|||||||
fig = shaper_calibration([new_file], KLIPPER_FOLDER)
|
fig = shaper_calibration([new_file], KLIPPER_FOLDER)
|
||||||
png_filename = os.path.join(RESULTS_FOLDER, RESULTS_SUBFOLDERS[1], f'resonances_{current_date}_{axis}.png')
|
png_filename = os.path.join(RESULTS_FOLDER, RESULTS_SUBFOLDERS[1], f'resonances_{current_date}_{axis}.png')
|
||||||
|
|
||||||
return fig, png_filename
|
fig.savefig(png_filename)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
def get_vibrations_graph(axis_name):
|
def create_vibrations_graph(axis_name):
|
||||||
current_date = datetime.now().strftime('%Y%m%d_%H%M%S')
|
current_date = datetime.now().strftime('%Y%m%d_%H%M%S')
|
||||||
lognames = []
|
lognames = []
|
||||||
|
|
||||||
@@ -159,7 +164,35 @@ def get_vibrations_graph(axis_name):
|
|||||||
tar.add(csv_file, recursive=False)
|
tar.add(csv_file, recursive=False)
|
||||||
os.remove(csv_file)
|
os.remove(csv_file)
|
||||||
|
|
||||||
return fig, png_filename
|
fig.savefig(png_filename)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def find_axesmap(accel):
|
||||||
|
current_date = datetime.now().strftime('%Y%m%d_%H%M%S')
|
||||||
|
result_filename = os.path.join(RESULTS_FOLDER, f'axes_map_{current_date}.txt')
|
||||||
|
lognames = []
|
||||||
|
|
||||||
|
globbed_files = glob.glob('/tmp/adxl345-*.csv')
|
||||||
|
if not globbed_files:
|
||||||
|
print("No CSV files found in the /tmp folder to analyze and find the axes_map!")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
sorted_files = sorted(globbed_files, key=os.path.getmtime, reverse=True)
|
||||||
|
filename = sorted_files[0]
|
||||||
|
|
||||||
|
# Wait for the file handler to be released by Klipper
|
||||||
|
while is_file_open(filename):
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
# Analyze the CSV to find the axes_map parameter
|
||||||
|
lognames.append(filename)
|
||||||
|
results = axesmap_calibration(lognames, accel)
|
||||||
|
|
||||||
|
with open(result_filename, 'w') as f:
|
||||||
|
f.write(results)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
# Utility function to get old files based on their modification time
|
# Utility function to get old files based on their modification time
|
||||||
@@ -210,20 +243,21 @@ def main():
|
|||||||
os.makedirs(folder)
|
os.makedirs(folder)
|
||||||
|
|
||||||
if len(sys.argv) < 2:
|
if len(sys.argv) < 2:
|
||||||
print("Usage: is_workflow.py [SHAPER|BELTS|VIBRATIONS]")
|
print("Usage: is_workflow.py [BELTS|SHAPER|VIBRATIONS|AXESMAP]")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if sys.argv[1].lower() == 'belts':
|
if sys.argv[1].lower() == 'belts':
|
||||||
fig, png_filename = get_belts_graph()
|
create_belts_graph()
|
||||||
elif sys.argv[1].lower() == 'shaper':
|
elif sys.argv[1].lower() == 'shaper':
|
||||||
fig, png_filename = get_shaper_graph()
|
create_shaper_graph()
|
||||||
elif sys.argv[1].lower() == 'vibrations':
|
elif sys.argv[1].lower() == 'vibrations':
|
||||||
fig, png_filename = get_vibrations_graph(axis_name=sys.argv[2])
|
create_vibrations_graph(axis_name=sys.argv[2])
|
||||||
|
elif sys.argv[1].lower() == 'axesmap':
|
||||||
|
find_axesmap(accel=sys.argv[2])
|
||||||
else:
|
else:
|
||||||
print("Usage: is_workflow.py [SHAPER|BELTS|VIBRATIONS]")
|
print("Usage: is_workflow.py [BELTS|SHAPER|VIBRATIONS|AXESMAP]")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
fig.savefig(png_filename)
|
|
||||||
|
|
||||||
clean_files()
|
clean_files()
|
||||||
print(f"Graphs created. You will find the results in {RESULTS_FOLDER}")
|
print(f"Graphs created. You will find the results in {RESULTS_FOLDER}")
|
||||||
|
|||||||
@@ -2,3 +2,5 @@
|
|||||||
command: ~/printer_data/config/K-ShakeTune/scripts/shaketune.sh
|
command: ~/printer_data/config/K-ShakeTune/scripts/shaketune.sh
|
||||||
timeout: 600.0
|
timeout: 600.0
|
||||||
verbose: True
|
verbose: True
|
||||||
|
|
||||||
|
[respond]
|
||||||
|
|||||||
@@ -4,8 +4,9 @@
|
|||||||
|
|
||||||
### Detailed documentation
|
### Detailed documentation
|
||||||
|
|
||||||
First, check out **[input shaping and tuning generalities](./is_tuning_generalities.md)** to understand how it all works and what to look for when taking these measurements.
|
Before running any tests, you should first run the `AXES_MAP_CALIBRATION` macro to detect the and set Klipper's accelerometer axes_map parameter and validate that your accelerometer is working correctly and properly mounted. Then, check out **[input shaping and tuning generalities](./is_tuning_generalities.md)** to understand how it all works and what to look for when taking these measurements.
|
||||||
Then look at the documentation for each type of graph by clicking on them below to better understand your results and tune your machine!
|
|
||||||
|
Finally look at the documentation for each type of graph by clicking on them below tu run the tests and better understand your results to tune your machine!
|
||||||
|
|
||||||
| [Belts graph](./macros/belts_tuning.md) | [Axis input shaper graphs](./macros/axis_tuning.md) | [Vibrations graph](./macros/vibrations_tuning.md) |
|
| [Belts graph](./macros/belts_tuning.md) | [Axis input shaper graphs](./macros/axis_tuning.md) | [Vibrations graph](./macros/vibrations_tuning.md) |
|
||||||
|:----------------:|:------------:|:---------------------:|
|
|:----------------:|:------------:|:---------------------:|
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ Then, call the `AXES_SHAPER_CALIBRATION` macro and look for the graphs in the re
|
|||||||
|
|
||||||
| parameters | default value | description |
|
| parameters | default value | description |
|
||||||
|-----------:|---------------|-------------|
|
|-----------:|---------------|-------------|
|
||||||
|VERBOSE|1|Wether to log things in the console|
|
|
||||||
|FREQ_START|5|Starting excitation frequency|
|
|FREQ_START|5|Starting excitation frequency|
|
||||||
|FREQ_END|133|Maximum excitation frequency|
|
|FREQ_END|133|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|
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ Then, call the `BELTS_SHAPER_CALIBRATION` macro and look for the graphs in the r
|
|||||||
|
|
||||||
| parameters | default value | description |
|
| parameters | default value | description |
|
||||||
|-----------:|---------------|-------------|
|
|-----------:|---------------|-------------|
|
||||||
|VERBOSE|1|Wether to log things in the console|
|
|
||||||
|FREQ_START|5|Starting excitation frequency|
|
|FREQ_START|5|Starting excitation frequency|
|
||||||
|FREQ_END|133|Maximum excitation frequency|
|
|FREQ_END|133|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|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ Call the `VIBRATIONS_CALIBRATION` macro with the direction and speed range you w
|
|||||||
|SIZE|60|size in mm of the area where the movements are done|
|
|SIZE|60|size in mm of the area where the movements are done|
|
||||||
|DIRECTION|"XY"|direction vector where you want to do the measurements. Can be set to either "XY", "AB", "ABXY", "A", "B", "X", "Y", "Z", "E"|
|
|DIRECTION|"XY"|direction vector where you want to do the measurements. Can be set to either "XY", "AB", "ABXY", "A", "B", "X", "Y", "Z", "E"|
|
||||||
|Z_HEIGHT|20|z height to put the toolhead before starting the movements. Be careful, if your ADXL is under the nozzle, increase it to avoid a crash of the ADXL on the bed of the machine|
|
|Z_HEIGHT|20|z height to put the toolhead before starting the movements. Be careful, if your ADXL is under the nozzle, increase it to avoid a crash of the ADXL on the bed of the machine|
|
||||||
|VERBOSE|1|Wether to log the current speed in the console|
|
|
||||||
|ACCEL|3000 (or max printer accel)|accel in mm/s^2 used for all the moves. Try to keep it relatively low to avoid bad oscillations that affect the measurements, but but high enough to reach constant speed for >~70% of the segments|
|
|ACCEL|3000 (or max printer accel)|accel in mm/s^2 used for all the moves. Try to keep it relatively low to avoid bad oscillations that affect the measurements, but but high enough to reach constant speed for >~70% of the segments|
|
||||||
|MIN_SPEED|20|minimum speed of the toolhead in mm/s for the movements|
|
|MIN_SPEED|20|minimum speed of the toolhead in mm/s for the movements|
|
||||||
|MAX_SPEED|200|maximum speed of the toolhead in mm/s for the movements|
|
|MAX_SPEED|200|maximum speed of the toolhead in mm/s for the movements|
|
||||||
|
|||||||
@@ -8,4 +8,5 @@ packaging==23.2
|
|||||||
Pillow==10.1.0
|
Pillow==10.1.0
|
||||||
pyparsing==3.1.1
|
pyparsing==3.1.1
|
||||||
python-dateutil==2.8.2
|
python-dateutil==2.8.2
|
||||||
|
scipy==1.11.4
|
||||||
six==1.16.0
|
six==1.16.0
|
||||||
|
|||||||
Reference in New Issue
Block a user