Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1e9269ba3 | ||
|
|
8e304a71ca | ||
|
|
5d54db0ca0 | ||
|
|
d52680738f | ||
|
|
f95c55230b | ||
|
|
0f7fa66af4 | ||
|
|
da10593ca7 | ||
|
|
060a800cc3 | ||
|
|
7c76be5077 | ||
|
|
a4c2ead732 | ||
|
|
6e884528c0 | ||
|
|
17ccddfa0f | ||
|
|
83f517758a | ||
|
|
c156459420 |
@@ -2,9 +2,10 @@
|
|||||||
###### STANDARD INPUT_SHAPER CALIBRATIONS ######
|
###### STANDARD INPUT_SHAPER CALIBRATIONS ######
|
||||||
################################################
|
################################################
|
||||||
# Written by Frix_x#0161 #
|
# Written by Frix_x#0161 #
|
||||||
# @version: 1.4
|
# @version: 1.5
|
||||||
|
|
||||||
# CHANGELOG:
|
# CHANGELOG:
|
||||||
|
# v1.5: modified EXCITATE_AXIS_AT_FREQ to allow A or B belt testing
|
||||||
# v1.4: added possibility to only run one axis at a time for the axes shaper calibration
|
# v1.4: added possibility to only run one axis at a time for the axes shaper calibration
|
||||||
# v1.3: added possibility to override the default parameters
|
# v1.3: added possibility to override the default parameters
|
||||||
# v1.2: added EXCITATE_AXIS_AT_FREQ to hold a specific excitating frequency on an axis and diagnose mechanical problems
|
# v1.2: added EXCITATE_AXIS_AT_FREQ to hold a specific excitating frequency on an axis and diagnose mechanical problems
|
||||||
@@ -26,7 +27,7 @@
|
|||||||
|
|
||||||
|
|
||||||
[gcode_macro AXES_SHAPER_CALIBRATION]
|
[gcode_macro AXES_SHAPER_CALIBRATION]
|
||||||
description: Run standard input shaper test for all axes
|
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 verbose = params.VERBOSE|default(true) %}
|
||||||
{% set min_freq = params.FREQ_START|default(5)|float %}
|
{% set min_freq = params.FREQ_START|default(5)|float %}
|
||||||
@@ -51,7 +52,8 @@ gcode:
|
|||||||
M400
|
M400
|
||||||
|
|
||||||
{% if verbose %}
|
{% if verbose %}
|
||||||
RESPOND MSG="X axis shaper graphs generation..."
|
RESPOND MSG="X axis frequency profile generation..."
|
||||||
|
RESPOND MSG="This may take some time (1-3min)"
|
||||||
{% endif %}
|
{% endif %}
|
||||||
RUN_SHELL_COMMAND CMD=plot_graph PARAMS=SHAPER
|
RUN_SHELL_COMMAND CMD=plot_graph PARAMS=SHAPER
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -61,14 +63,15 @@ gcode:
|
|||||||
M400
|
M400
|
||||||
|
|
||||||
{% if verbose %}
|
{% if verbose %}
|
||||||
RESPOND MSG="Y axis shaper graphs generation..."
|
RESPOND MSG="Y axis frequency profile generation..."
|
||||||
|
RESPOND MSG="This may take some time (1-3min)"
|
||||||
{% endif %}
|
{% endif %}
|
||||||
RUN_SHELL_COMMAND CMD=plot_graph PARAMS=SHAPER
|
RUN_SHELL_COMMAND CMD=plot_graph PARAMS=SHAPER
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
[gcode_macro BELTS_SHAPER_CALIBRATION]
|
[gcode_macro BELTS_SHAPER_CALIBRATION]
|
||||||
description: Run custom demi-axe test to analyze 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 verbose = params.VERBOSE|default(true) %}
|
||||||
{% set min_freq = params.FREQ_START|default(5)|float %}
|
{% set min_freq = params.FREQ_START|default(5)|float %}
|
||||||
@@ -77,21 +80,33 @@ gcode:
|
|||||||
|
|
||||||
TEST_RESONANCES AXIS=1,1 OUTPUT=raw_data NAME=b FREQ_START={min_freq} FREQ_END={max_freq} HZ_PER_SEC={hz_per_sec}
|
TEST_RESONANCES AXIS=1,1 OUTPUT=raw_data NAME=b FREQ_START={min_freq} FREQ_END={max_freq} HZ_PER_SEC={hz_per_sec}
|
||||||
M400
|
M400
|
||||||
|
|
||||||
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 %}
|
{% if verbose %}
|
||||||
RESPOND MSG="Belts graphs generation..."
|
RESPOND MSG="Belts comparative frequency profile generation..."
|
||||||
|
RESPOND MSG="This may take some time (3-5min)"
|
||||||
{% endif %}
|
{% endif %}
|
||||||
RUN_SHELL_COMMAND CMD=plot_graph PARAMS=BELTS
|
RUN_SHELL_COMMAND CMD=plot_graph PARAMS=BELTS
|
||||||
|
|
||||||
|
|
||||||
[gcode_macro EXCITATE_AXIS_AT_FREQ]
|
[gcode_macro EXCITATE_AXIS_AT_FREQ]
|
||||||
description: Maintain a specified input shaper excitating frequency for some time to diagnose vibrations
|
description: Maintain a specified input shaper excitation frequency for a period of time to diagnose and locate a source of vibration
|
||||||
gcode:
|
gcode:
|
||||||
{% set FREQUENCY = params.FREQUENCY|default(25)|int %}
|
{% set frequency = params.FREQUENCY|default(25)|int %}
|
||||||
{% set TIME = params.TIME|default(10)|int %}
|
{% set time = params.TIME|default(10)|int %}
|
||||||
{% set AXIS = params.AXIS|default("x")|string|lower %}
|
{% set axis = params.AXIS|default("x")|string|lower %}
|
||||||
|
|
||||||
TEST_RESONANCES OUTPUT=raw_data AXIS={AXIS} FREQ_START={FREQUENCY-1} FREQ_END={FREQUENCY+1} HZ_PER_SEC={1/(TIME/3)}
|
{% if axis not in ["x", "y", "a", "b"] %}
|
||||||
|
{ action_raise_error("AXIS selection invalid. Should be either x, y, a or b!") }
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if axis == "a" %}
|
||||||
|
{% set axis = "1,-1" %}
|
||||||
|
{% elif axis == "b" %}
|
||||||
|
{% set axis = "1,1" %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
TEST_RESONANCES OUTPUT=raw_data AXIS={axis} FREQ_START={frequency-1} FREQ_END={frequency+1} HZ_PER_SEC={1/(time/3)}
|
||||||
M400
|
M400
|
||||||
|
|||||||
@@ -2,40 +2,17 @@
|
|||||||
###### VIBRATIONS AND SPEED OPTIMIZATIONS ######
|
###### VIBRATIONS AND SPEED OPTIMIZATIONS ######
|
||||||
################################################
|
################################################
|
||||||
# Written by Frix_x#0161 #
|
# Written by Frix_x#0161 #
|
||||||
# @version: 2.1
|
# @version: 2.2
|
||||||
|
|
||||||
# CHANGELOG:
|
# CHANGELOG:
|
||||||
|
# v2.2: allow custom accel values and set sane defaults (3k) to avoid using very high accels that produce bad results
|
||||||
# v2.1: allow decimal entries for speed and increment and added the E axis as an option to be neasured
|
# v2.1: allow decimal entries for speed and increment and added the E axis as an option to be neasured
|
||||||
# v2.0: added the possibility to measure mutliple axis
|
# v2.0: added the possibility to measure mutliple axis
|
||||||
# v1.0: first speed and vibrations optimization macro
|
# v1.0: first speed and vibrations optimization macro
|
||||||
|
|
||||||
|
|
||||||
### What is it ? ###
|
|
||||||
# This macro helps you to identify the speed settings that exacerbate the vibrations of the machine (ie. where the frame resonate badly).
|
|
||||||
# It also helps to find the clean speed ranges where the machine is silent.
|
|
||||||
# I had some strong vibrations at very specific speeds on my machine (52mm/s for example) and I wanted to find all these problematic speeds
|
|
||||||
# to avoid them in my slicer profile and finally get the silent machine I was dreaming!
|
|
||||||
|
|
||||||
# It works by moving the toolhead at different speed settings while recording the vibrations using the ADXL chip. Then the macro call a custom script
|
|
||||||
# to compute and find the best speed settings. The results can be found in your config folder using Fluidd/Mainsail file manager.
|
|
||||||
|
|
||||||
# The goal is to make it easy to set, share and use it.
|
|
||||||
|
|
||||||
# This macro is parametric and most of the values can be adjusted with their respective input parameters.
|
|
||||||
# It can be called without any parameters - in which case the default values would be used - or with any combination of parameters as desired.
|
|
||||||
|
|
||||||
# Usage:
|
|
||||||
# 1. DO YOUR INPUT SHAPER CALIBRATION FIRST !!! This macro should not be used before as it would be useless and the results invalid.
|
|
||||||
# 2. Call the VIBRATIONS_CALIBRATION macro with the speed range you want to measure (default 20 to 200mm/s with 2mm/s increment).
|
|
||||||
# Be carefull about the Z_HEIGHT variable that default to 20mm -> if your ADXL is under the nozzle, increase it to avoid a crash of the ADXL on the bed of the machine.
|
|
||||||
# 3. Wait for it to finish all the measurement and compute the graph. Then look at it in the results folder.
|
|
||||||
|
|
||||||
|
|
||||||
[gcode_macro VIBRATIONS_CALIBRATION]
|
[gcode_macro VIBRATIONS_CALIBRATION]
|
||||||
gcode:
|
gcode:
|
||||||
#
|
|
||||||
# PARAMETERS
|
|
||||||
#
|
|
||||||
{% 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
|
||||||
@@ -45,16 +22,18 @@ gcode:
|
|||||||
{% 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
|
||||||
{% set speed_increment = params.SPEED_INCREMENT|default(2)|float * 60 %} # feedrate increment between each move
|
{% set speed_increment = params.SPEED_INCREMENT|default(2)|float * 60 %} # feedrate increment between each move
|
||||||
{% set feedrate_travel = params.TRAVEL_SPEED|default(200)|int * 60 %} # travel feedrate between moves
|
{% set feedrate_travel = params.TRAVEL_SPEED|default(200)|int * 60 %} # travel feedrate between moves
|
||||||
|
{% set accel = params.ACCEL|default(3000)|int %} # accel value used to move on the pattern
|
||||||
{% set accel_chip = params.ACCEL_CHIP|default("adxl345") %} # ADXL chip name in the config
|
{% set accel_chip = params.ACCEL_CHIP|default("adxl345") %} # ADXL chip name in the config
|
||||||
|
|
||||||
#
|
|
||||||
# COMPUTED VALUES
|
|
||||||
#
|
|
||||||
{% set mid_x = printer.toolhead.axis_maximum.x|float / 2 %}
|
{% set mid_x = printer.toolhead.axis_maximum.x|float / 2 %}
|
||||||
{% set mid_y = printer.toolhead.axis_maximum.y|float / 2 %}
|
{% set mid_y = printer.toolhead.axis_maximum.y|float / 2 %}
|
||||||
{% set nb_samples = ((max_speed - min_speed) / speed_increment + 1) | int %}
|
{% set nb_samples = ((max_speed - min_speed) / speed_increment + 1) | int %}
|
||||||
|
|
||||||
|
{% 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 %}
|
||||||
|
|
||||||
{% set direction_factor = {
|
{% set direction_factor = {
|
||||||
'XY' : {
|
'XY' : {
|
||||||
'start' : {'x': -0.5, 'y': -0.5 },
|
'start' : {'x': -0.5, 'y': -0.5 },
|
||||||
@@ -158,6 +137,9 @@ gcode:
|
|||||||
M83
|
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_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}
|
||||||
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}
|
||||||
@@ -188,4 +170,7 @@ gcode:
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
RUN_SHELL_COMMAND CMD=plot_graph PARAMS="VIBRATIONS {direction}"
|
RUN_SHELL_COMMAND CMD=plot_graph PARAMS="VIBRATIONS {direction}"
|
||||||
|
|
||||||
|
# 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_VIBRATIONS_CALIBRATION
|
RESTORE_GCODE_STATE NAME=STATE_VIBRATIONS_CALIBRATION
|
||||||
|
|||||||
@@ -4,9 +4,10 @@
|
|||||||
######## CoreXY BELTS CALIBRATION SCRIPT ########
|
######## CoreXY BELTS CALIBRATION SCRIPT ########
|
||||||
#################################################
|
#################################################
|
||||||
# Written by Frix_x#0161 #
|
# Written by Frix_x#0161 #
|
||||||
# @version: 2.0
|
# @version: 2.1
|
||||||
|
|
||||||
# CHANGELOG:
|
# CHANGELOG:
|
||||||
|
# v2.1: replaced the TwoSlopNorm by a custom made norm to allow the script to work on older versions of matplotlib
|
||||||
# v2.0: updated the script to align it to the new K-Shake&Tune module
|
# v2.0: updated the script to align it to the new K-Shake&Tune module
|
||||||
# v1.0: first version of this tool for enhanced vizualisation of belt graphs
|
# v1.0: first version of this tool for enhanced vizualisation of belt graphs
|
||||||
|
|
||||||
@@ -473,9 +474,13 @@ def plot_difference_spectrogram(ax, data1, data2, signal1, signal2, similarity_f
|
|||||||
ax.set_title(f"Differential Spectrogram", fontsize=14, color=KLIPPAIN_COLORS['dark_orange'], weight='bold')
|
ax.set_title(f"Differential Spectrogram", fontsize=14, color=KLIPPAIN_COLORS['dark_orange'], weight='bold')
|
||||||
ax.plot([], [], ' ', label=f'{textual_mhi} (experimental)')
|
ax.plot([], [], ' ', label=f'{textual_mhi} (experimental)')
|
||||||
|
|
||||||
# Draw the differential spectrogram with a specific norm to get light grey zero values and red for max values (vmin to vcenter is not used)
|
# Draw the differential spectrogram with a specific custom norm to get white or light orange zero values and red for max values
|
||||||
norm = matplotlib.colors.TwoSlopeNorm(vcenter=np.min(combined_data), vmax=np.max(combined_data))
|
colors = ['white', 'bisque', 'red', 'black']
|
||||||
ax.pcolormesh(bins, t, combined_data.T, cmap='RdBu_r', norm=norm, shading='gouraud')
|
n_bins = [0, 0.12, 0.9, 1] # These values where found experimentaly to get a good higlhlighting of the differences only
|
||||||
|
cm = matplotlib.colors.LinearSegmentedColormap.from_list('WhiteToRed', list(zip(n_bins, colors)))
|
||||||
|
norm = matplotlib.colors.Normalize(vmin=np.min(combined_data), vmax=np.max(combined_data))
|
||||||
|
ax.pcolormesh(bins, t, combined_data.T, cmap=cm, norm=norm, shading='gouraud')
|
||||||
|
|
||||||
ax.set_xlabel('Frequency (hz)')
|
ax.set_xlabel('Frequency (hz)')
|
||||||
ax.set_xlim([0., max_freq])
|
ax.set_xlim([0., max_freq])
|
||||||
ax.set_ylabel('Time (s)')
|
ax.set_ylabel('Time (s)')
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ def detect_peaks(psd, freqs, window_size=5, vicinity=3):
|
|||||||
# Graphing
|
# Graphing
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
def plot_freq_response_with_damping(ax, calibration_data, shapers, selected_shaper, fr, zeta, max_freq):
|
def plot_freq_response_with_damping(ax, calibration_data, shapers, performance_shaper, fr, zeta, max_freq):
|
||||||
freqs = calibration_data.freq_bins
|
freqs = calibration_data.freq_bins
|
||||||
psd = calibration_data.psd_sum[freqs <= max_freq]
|
psd = calibration_data.psd_sum[freqs <= max_freq]
|
||||||
px = calibration_data.psd_x[freqs <= max_freq]
|
px = calibration_data.psd_x[freqs <= max_freq]
|
||||||
@@ -194,32 +194,50 @@ def plot_freq_response_with_damping(ax, calibration_data, shapers, selected_shap
|
|||||||
ax2 = ax.twinx()
|
ax2 = ax.twinx()
|
||||||
ax2.yaxis.set_visible(False)
|
ax2.yaxis.set_visible(False)
|
||||||
|
|
||||||
best_shaper_vals = None
|
lowvib_shaper_vibrs = float('inf')
|
||||||
lowest_vibration = float('inf')
|
lowvib_shaper = None
|
||||||
lowest_vibration_shaper = None
|
lowvib_shaper_freq = None
|
||||||
lowest_vibration_shaper_freq = None
|
lowvib_shaper_accel = 0
|
||||||
lowest_vibration_shaper_accel = 0
|
|
||||||
|
|
||||||
# Draw the shappers curves and add their specific parameters in the legend
|
# Draw the shappers curves and add their specific parameters in the legend
|
||||||
# This adds also a way to find the best shaper with 0% of vibrations (to be printed in the legend later)
|
# This adds also a way to find the best shaper with a low level of vibrations (with a resonable level of smoothing)
|
||||||
for shaper in shapers:
|
for shaper in shapers:
|
||||||
shaper_max_accel = round(shaper.max_accel / 100.) * 100.
|
shaper_max_accel = round(shaper.max_accel / 100.) * 100.
|
||||||
label = "%s (%.1f Hz, vibr=%.1f%%, sm~=%.2f, accel<=%.f)" % (
|
label = "%s (%.1f Hz, vibr=%.1f%%, sm~=%.2f, accel<=%.f)" % (
|
||||||
shaper.name.upper(), shaper.freq,
|
shaper.name.upper(), shaper.freq,
|
||||||
shaper.vibrs * 100., shaper.smoothing,
|
shaper.vibrs * 100., shaper.smoothing,
|
||||||
shaper_max_accel)
|
shaper_max_accel)
|
||||||
linestyle = 'dotted'
|
ax2.plot(freqs, shaper.vals, label=label, linestyle='dotted')
|
||||||
if shaper.name == selected_shaper:
|
|
||||||
linestyle = 'dashdot'
|
# Get the performance shaper
|
||||||
selected_shaper_freq = shaper.freq
|
if shaper.name == performance_shaper:
|
||||||
best_shaper_vals = shaper.vals
|
performance_shaper_freq = shaper.freq
|
||||||
if (shaper.vibrs * 100 < lowest_vibration or (shaper.vibrs * 100 == lowest_vibration and shaper_max_accel > lowest_vibration_shaper_accel)) and shaper.smoothing < MAX_SMOOTHING:
|
performance_shaper_vibr = shaper.vibrs * 100.
|
||||||
lowest_vibration = shaper.vibrs * 100
|
performance_shaper_vals = shaper.vals
|
||||||
lowest_vibration_shaper_accel = shaper_max_accel
|
|
||||||
lowest_vibration_shaper = shaper.name
|
# Get the low vibration shaper
|
||||||
lowest_vibration_shaper_freq = shaper.freq
|
if (shaper.vibrs * 100 < lowvib_shaper_vibrs or (shaper.vibrs * 100 == lowvib_shaper_vibrs and shaper_max_accel > lowvib_shaper_accel)) and shaper.smoothing < MAX_SMOOTHING:
|
||||||
ax2.plot(freqs, shaper.vals, label=label, linestyle=linestyle)
|
lowvib_shaper_accel = shaper_max_accel
|
||||||
ax.plot(freqs, psd * best_shaper_vals, label='With %s applied' % (selected_shaper.upper()), color='cyan')
|
lowvib_shaper = shaper.name
|
||||||
|
lowvib_shaper_freq = shaper.freq
|
||||||
|
lowvib_shaper_vibrs = shaper.vibrs * 100
|
||||||
|
lowvib_shaper_vals = shaper.vals
|
||||||
|
|
||||||
|
# User recommendations are added to the legend: one is Klipper's original suggestion that is usually good for performances
|
||||||
|
# and the other one is the custom "low vibration" recommendation that looks for a suitable shaper that doesn't have excessive
|
||||||
|
# smoothing and that have a lower vibration level. If both recommendation are the same shaper, or if no suitable "low
|
||||||
|
# vibration" shaper is found, then only a single line as the "best shaper" recommendation is added to the legend
|
||||||
|
if lowvib_shaper != None and lowvib_shaper != performance_shaper and lowvib_shaper_vibrs <= performance_shaper_vibr:
|
||||||
|
ax2.plot([], [], ' ', label="Recommended performance shaper: %s @ %.1f Hz" % (performance_shaper.upper(), performance_shaper_freq))
|
||||||
|
ax.plot(freqs, psd * performance_shaper_vals, label='With %s applied' % (performance_shaper.upper()), color='cyan')
|
||||||
|
ax2.plot([], [], ' ', label="Recommended low vibrations shaper: %s @ %.1f Hz" % (lowvib_shaper.upper(), lowvib_shaper_freq))
|
||||||
|
ax.plot(freqs, psd * lowvib_shaper_vals, label='With %s applied' % (lowvib_shaper.upper()), color='lime')
|
||||||
|
else:
|
||||||
|
ax2.plot([], [], ' ', label="Recommended best shaper: %s @ %.1f Hz" % (performance_shaper.upper(), performance_shaper_freq))
|
||||||
|
ax.plot(freqs, psd * performance_shaper_vals, label='With %s applied' % (performance_shaper.upper()), color='cyan')
|
||||||
|
|
||||||
|
# And the estimated damping ratio is finally added at the end of the legend
|
||||||
|
ax2.plot([], [], ' ', label="Estimated damping ratio (ζ): %.3f" % (zeta))
|
||||||
|
|
||||||
# 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")
|
||||||
@@ -243,18 +261,6 @@ def plot_freq_response_with_damping(ax, calibration_data, shapers, selected_shap
|
|||||||
ax.fill_between(freqs, 0, peaks_warning_threshold, color='green', alpha=0.15, label='Relax Region')
|
ax.fill_between(freqs, 0, peaks_warning_threshold, color='green', alpha=0.15, label='Relax Region')
|
||||||
ax.fill_between(freqs, peaks_warning_threshold, peaks_effect_threshold, color='orange', alpha=0.2, label='Warning Region')
|
ax.fill_between(freqs, peaks_warning_threshold, peaks_effect_threshold, color='orange', alpha=0.2, label='Warning Region')
|
||||||
|
|
||||||
# User recommendations are added to the legend: one is Klipper's original suggestion that is usually good for performances
|
|
||||||
# and the other one is the custom "low vibration" recommendation that looks for a suitable shaper that doesn't have excessive
|
|
||||||
# smoothing (<0.1) and that have a lower vibration level. If both recommendation are the same shaper, or if no suitable "low
|
|
||||||
# vibration" shaper is found, then only a single line as the "best shaper" recommendation is added to the legend
|
|
||||||
if lowest_vibration_shaper != selected_shaper and lowest_vibration_shaper != None:
|
|
||||||
ax2.plot([], [], ' ', label="Recommended performance shaper: %s @ %.1f Hz" % (selected_shaper.upper(), selected_shaper_freq))
|
|
||||||
ax2.plot([], [], ' ', label="Recommended low vibrations shaper: %s @ %.1f Hz" % (lowest_vibration_shaper.upper(), lowest_vibration_shaper_freq))
|
|
||||||
else:
|
|
||||||
ax2.plot([], [], ' ', label="Recommended best shaper: %s @ %.1f Hz" % (selected_shaper.upper(), selected_shaper_freq))
|
|
||||||
|
|
||||||
# And the estimated damping ratio is finally added at the end of the legend
|
|
||||||
ax2.plot([], [], ' ', label="Estimated damping ratio (ζ): %.3f" % (zeta))
|
|
||||||
|
|
||||||
# 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("Axis Frequency Profile (ω0=%.1fHz, ζ=%.3f)" % (fr, zeta), fontsize=14, color=KLIPPAIN_COLORS['dark_orange'], weight='bold')
|
ax.set_title("Axis Frequency Profile (ω0=%.1fHz, ζ=%.3f)" % (fr, zeta), fontsize=14, color=KLIPPAIN_COLORS['dark_orange'], weight='bold')
|
||||||
@@ -326,7 +332,7 @@ def shaper_calibration(lognames, klipperdir="~/klipper", max_smoothing=None, max
|
|||||||
datas = [parse_log(fn) for fn in lognames]
|
datas = [parse_log(fn) for fn in lognames]
|
||||||
|
|
||||||
# Calibrate shaper and generate outputs
|
# Calibrate shaper and generate outputs
|
||||||
selected_shaper, shapers, calibration_data, fr, zeta = calibrate_shaper_with_damping(datas, max_smoothing)
|
performance_shaper, shapers, calibration_data, fr, zeta = calibrate_shaper_with_damping(datas, max_smoothing)
|
||||||
|
|
||||||
fig = matplotlib.pyplot.figure()
|
fig = matplotlib.pyplot.figure()
|
||||||
gs = matplotlib.gridspec.GridSpec(2, 1, height_ratios=[4, 3])
|
gs = matplotlib.gridspec.GridSpec(2, 1, height_ratios=[4, 3])
|
||||||
@@ -346,7 +352,7 @@ def shaper_calibration(lognames, klipperdir="~/klipper", max_smoothing=None, max
|
|||||||
fig.text(0.12, 0.957, title_line2, ha='left', va='top', fontsize=16, color=KLIPPAIN_COLORS['dark_purple'])
|
fig.text(0.12, 0.957, title_line2, ha='left', va='top', fontsize=16, color=KLIPPAIN_COLORS['dark_purple'])
|
||||||
|
|
||||||
# Plot the graphs
|
# Plot the graphs
|
||||||
peaks = plot_freq_response_with_damping(ax1, calibration_data, shapers, selected_shaper, fr, zeta, max_freq)
|
peaks = plot_freq_response_with_damping(ax1, calibration_data, shapers, performance_shaper, fr, zeta, max_freq)
|
||||||
plot_spectrogram(ax2, datas[0], peaks, max_freq)
|
plot_spectrogram(ax2, datas[0], peaks, max_freq)
|
||||||
|
|
||||||
fig.set_size_inches(8.3, 11.6)
|
fig.set_size_inches(8.3, 11.6)
|
||||||
|
|||||||
@@ -3,9 +3,10 @@
|
|||||||
###### INPUT SHAPER KLIPPAIN WORKFLOW ######
|
###### INPUT SHAPER KLIPPAIN WORKFLOW ######
|
||||||
############################################
|
############################################
|
||||||
# Written by Frix_x#0161 #
|
# Written by Frix_x#0161 #
|
||||||
# @version: 2.0
|
# @version: 2.1
|
||||||
|
|
||||||
# CHANGELOG:
|
# CHANGELOG:
|
||||||
|
# v2.1: added more filesystem sync and file handler checks to avoid using corrupted CSV files by going to fast
|
||||||
# v2.0: new version of this as a Python script (to replace the old bash script) and implement the newer and improved shaper plotting scripts
|
# v2.0: new version of this as a Python script (to replace the old bash script) and implement the newer and improved shaper plotting scripts
|
||||||
# v1.7: updated the handling of shaper files to account for the new analysis scripts as we are now using raw data directly
|
# v1.7: updated the handling of shaper files to account for the new analysis scripts as we are now using raw data directly
|
||||||
# v1.6: - updated the handling of shaper graph files to be able to optionnaly account for added positions in the filenames and remove them
|
# v1.6: - updated the handling of shaper graph files to be able to optionnaly account for added positions in the filenames and remove them
|
||||||
@@ -25,7 +26,6 @@
|
|||||||
# VIBRATIONS - To generate vibration diagram after calling the custom (Frix_x#0161) VIBRATIONS_CALIBRATION macro
|
# VIBRATIONS - To generate vibration diagram after calling the custom (Frix_x#0161) VIBRATIONS_CALIBRATION macro
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
import glob
|
import glob
|
||||||
@@ -55,6 +55,10 @@ def is_file_open(filepath):
|
|||||||
if os.path.samefile(fd, filepath):
|
if os.path.samefile(fd, filepath):
|
||||||
return True
|
return True
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
|
# Klipper has already released the CSV file
|
||||||
|
pass
|
||||||
|
except PermissionError:
|
||||||
|
# Unable to check for this particular process due to permissions
|
||||||
pass
|
pass
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -75,16 +79,21 @@ def get_belts_graph():
|
|||||||
for filename in sorted_files[:2]:
|
for filename in sorted_files[:2]:
|
||||||
# Wait for the file handler to be released by Klipper
|
# Wait for the file handler to be released by Klipper
|
||||||
while is_file_open(filename):
|
while is_file_open(filename):
|
||||||
time.sleep(3)
|
time.sleep(2)
|
||||||
|
|
||||||
# Extract the tested belt from the filename and rename/move the CSV file to the result folder
|
# Extract the tested belt from the filename and rename/move the CSV file to the result folder
|
||||||
belt = os.path.basename(filename).split('_')[3].split('.')[0].upper()
|
belt = os.path.basename(filename).split('_')[3].split('.')[0].upper()
|
||||||
new_file = os.path.join(RESULTS_FOLDER, RESULTS_SUBFOLDERS[0], f'belt_{current_date}_{belt}.csv')
|
new_file = os.path.join(RESULTS_FOLDER, RESULTS_SUBFOLDERS[0], f'belt_{current_date}_{belt}.csv')
|
||||||
shutil.move(filename, new_file)
|
shutil.move(filename, new_file)
|
||||||
|
os.sync() # Sync filesystem to avoid problems
|
||||||
|
|
||||||
# Save the file path for later
|
# Save the file path for later
|
||||||
lognames.append(new_file)
|
lognames.append(new_file)
|
||||||
|
|
||||||
|
# Wait for the file handler to be released by the move command
|
||||||
|
while is_file_open(new_file):
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
# Generate the belts graph and its name
|
# Generate the belts graph and its name
|
||||||
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')
|
||||||
@@ -105,12 +114,17 @@ def get_shaper_graph():
|
|||||||
|
|
||||||
# Wait for the file handler to be released by Klipper
|
# Wait for the file handler to be released by Klipper
|
||||||
while is_file_open(filename):
|
while is_file_open(filename):
|
||||||
time.sleep(3)
|
time.sleep(2)
|
||||||
|
|
||||||
# Extract the tested axis from the filename and rename/move the CSV file to the result folder
|
# Extract the tested axis from the filename and rename/move the CSV file to the result folder
|
||||||
axis = os.path.basename(filename).split('_')[3].split('.')[0].upper()
|
axis = os.path.basename(filename).split('_')[3].split('.')[0].upper()
|
||||||
new_file = os.path.join(RESULTS_FOLDER, RESULTS_SUBFOLDERS[1], f'resonances_{current_date}_{axis}.csv')
|
new_file = os.path.join(RESULTS_FOLDER, RESULTS_SUBFOLDERS[1], f'resonances_{current_date}_{axis}.csv')
|
||||||
shutil.move(filename, new_file)
|
shutil.move(filename, new_file)
|
||||||
|
os.sync() # Sync filesystem to avoid problems
|
||||||
|
|
||||||
|
# Wait for the file handler to be released by the move command
|
||||||
|
while is_file_open(new_file):
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
# Generate the shaper graph and its name
|
# Generate the shaper graph and its name
|
||||||
fig = shaper_calibration([new_file], KLIPPER_FOLDER)
|
fig = shaper_calibration([new_file], KLIPPER_FOLDER)
|
||||||
@@ -134,7 +148,7 @@ def get_vibrations_graph(axis_name):
|
|||||||
for filename in globbed_files:
|
for filename in globbed_files:
|
||||||
# Wait for the file handler to be released by Klipper
|
# Wait for the file handler to be released by Klipper
|
||||||
while is_file_open(filename):
|
while is_file_open(filename):
|
||||||
time.sleep(3)
|
time.sleep(2)
|
||||||
|
|
||||||
# Cleanup of the filename and moving it in the result folder
|
# Cleanup of the filename and moving it in the result folder
|
||||||
cleanfilename = os.path.basename(filename).replace('adxl345', f'vibr_{current_date}')
|
cleanfilename = os.path.basename(filename).replace('adxl345', f'vibr_{current_date}')
|
||||||
@@ -146,6 +160,7 @@ def get_vibrations_graph(axis_name):
|
|||||||
|
|
||||||
# Sync filesystem to avoid problems as there is a lot of file copied
|
# Sync filesystem to avoid problems as there is a lot of file copied
|
||||||
os.sync()
|
os.sync()
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
# Generate the vibration graph and its name
|
# Generate the vibration graph and its name
|
||||||
fig = vibrations_calibration(lognames, KLIPPER_FOLDER, axis_name)
|
fig = vibrations_calibration(lognames, KLIPPER_FOLDER, axis_name)
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ It operates in two steps:
|
|||||||
2. Relocates the graphs and associated CSV files to your Klipper config folder for easy access via Mainsail/Fluidd to eliminate the need for SSH.
|
2. Relocates the graphs and associated CSV files to your Klipper config folder for easy access via Mainsail/Fluidd to eliminate the need for SSH.
|
||||||
3. Manages the folder by retaining only the most recent results (default setting of keeping the latest three sets).
|
3. Manages the folder by retaining only the most recent results (default setting of keeping the latest three sets).
|
||||||
|
|
||||||
The [detailed documentation is here](./docs/README.md).
|
Check out the **[detailed documentation of the Shake&Tune module here](./docs/README.md)**. You can also look at the documentation for each type of graph by directly clicking on them below to better understand your results and tune your machine!
|
||||||
|
|
||||||
| Belts graphs | Axis graphs | Vibrations measurement |
|
| [Belts graph](./docs/macros/belts_tuning.md) | [Axis input shaper graphs](./docs/macros/axis_tuning.md) | [Vibrations graph](./docs/macros/vibrations_tuning.md) |
|
||||||
|:----------------:|:------------:|:---------------------:|
|
|:----------------:|:------------:|:---------------------:|
|
||||||
|  |  |  |
|
| [<img src="./docs/images/belts_example.png">](./docs/macros/belts_tuning.md) | [<img src="./docs/images/axis_example.png">](./docs/macros/axis_tuning.md) | [<img src="./docs/images/vibrations_example.png">](./docs/macros/vibrations_tuning.md) |
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
# Klippain Shake&Tune module documentation
|
# Klippain Shake&Tune module documentation
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
### Detailed documentation
|
### Detailed documentation
|
||||||
|
|
||||||
1. [Input Shaping and tuning generalities](./is_tuning_generalities.md)
|
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.
|
||||||
1. [Belt graphs](./macros/belts_tuning.md)
|
Then look at the documentation for each type of graph by clicking on them below to better understand your results and tune your machine!
|
||||||
1. [Axis Input Shaper graphs](./macros/axis_tuning.md)
|
|
||||||
1. [Klippain vibrations graphs](./macros/vibrations_tuning.md)
|
| [Belts graph](./macros/belts_tuning.md) | [Axis input shaper graphs](./macros/axis_tuning.md) | [Vibrations graph](./macros/vibrations_tuning.md) |
|
||||||
|
|:----------------:|:------------:|:---------------------:|
|
||||||
|
| [<img src="./images/belts_example.png">](./macros/belts_tuning.md) | [<img src="./images/axis_example.png">](./macros/axis_tuning.md) | [<img src="./images/vibrations_example.png">](./macros/vibrations_tuning.md) |
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### Complementary ressources
|
### Complementary ressources
|
||||||
|
|
||||||
|
|||||||
BIN
docs/banner_long.png
Normal file
BIN
docs/banner_long.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 740 KiB |
@@ -17,6 +17,7 @@ Call the `VIBRATIONS_CALIBRATION` macro with the direction and speed range you w
|
|||||||
|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|
|
|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|
|
||||||
|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|
|
||||||
|SPEED_INCREMENT|2|speed increments of the toolhead in mm/s between every movements|
|
|SPEED_INCREMENT|2|speed increments of the toolhead in mm/s between every movements|
|
||||||
|
|||||||
Reference in New Issue
Block a user