-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
253efe5
commit 14ab9bc
Showing
8 changed files
with
1,346 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
[gcode_shell_command guppy_input_shaper] | ||
command: /usr/data/printer_data/config/GuppyScreen/scripts/calibrate_shaper.py | ||
timeout: 600.0 | ||
verbose: True | ||
|
||
[gcode_shell_command guppy_belts_calibration] | ||
command: /usr/data/printer_data/config/GuppyScreen/scripts/graph_belts.py | ||
timeout: 600.0 | ||
verbose: True | ||
|
||
[calibrate_shaper_config] | ||
|
||
[guppy_module_loader] | ||
|
||
[respond] | ||
default_type: echo | ||
default_prefix: | ||
|
||
[gcode_macro GUPPY_SHAPERS] | ||
description: Shaper Tuning + Plot Generation | ||
gcode: | ||
{% set x_png = params.X_PNG|default("/usr/data/printer_data/config/resonances_x.png") %} | ||
{% set y_png = params.Y_PNG|default("/usr/data/printer_data/config/resonances_y.png") %} | ||
|
||
RESPOND TYPE=command MSG='Homing' | ||
G28 | ||
RESPOND TYPE=command MSG='Testing X Resonances' | ||
TEST_RESONANCES AXIS=X NAME=x | ||
M400 | ||
RESPOND TYPE=command MSG='Generating X Plots' | ||
RUN_SHELL_COMMAND CMD=guppy_input_shaper PARAMS="/tmp/resonances_x_x.csv -o {x_png}" | ||
RESPOND TYPE=command MSG='Testing X Resonances' | ||
TEST_RESONANCES AXIS=Y NAME=y | ||
M400 | ||
RESPOND TYPE=command MSG='Generating Y Plots' | ||
RUN_SHELL_COMMAND CMD=guppy_input_shaper PARAMS="/tmp/resonances_y_y.csv -o {y_png}" | ||
|
||
[gcode_macro GUPPY_BELTS_SHAPER_CALIBRATION] | ||
description: Perform a custom half-axis test to analyze and compare the frequency profiles of individual belts on CoreXY printers | ||
gcode: | ||
{% set min_freq = params.FREQ_START|default(5)|float %} | ||
{% set max_freq = params.FREQ_END|default(133.33)|float %} | ||
{% set hz_per_sec = params.HZ_PER_SEC|default(1)|float %} | ||
{% set png_out_path = params.PNG_OUT_PATH|default("/usr/data/printer_data/config/belts_calibration.png") %} | ||
{% set png_width = params.PNG_WIDTH|default(8)|float %} | ||
{% set png_height = params.PNG_HEIGHT|default(4.8)|float %} | ||
|
||
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 | ||
|
||
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 | ||
|
||
RESPOND MSG="Belts comparative frequency profile generation..." | ||
RESPOND MSG="This may take some time (3-5min)" | ||
RUN_SHELL_COMMAND CMD=guppy_belts_calibration PARAMS="-w {png_width} -l {png_height} -n -o {png_out_path} -k /usr/share/klipper /tmp/raw_data_axis=1.000,-1.000_a.csv /tmp/raw_data_axis=1.000,1.000_b.csv" | ||
|
||
################################################ | ||
###### STANDARD INPUT_SHAPER CALIBRATIONS ###### | ||
################################################ | ||
# Written by Frix_x#0161 # | ||
# https://github.com/Frix-x/klippain-shaketune | ||
|
||
[gcode_macro GUPPY_EXCITATE_AXIS_AT_FREQ] | ||
description: Maintain a specified excitation frequency for a period of time to diagnose and locate a source of vibration | ||
gcode: | ||
{% set frequency = params.FREQUENCY|default(25)|int %} | ||
{% set time = params.TIME|default(10)|int %} | ||
{% set axis = params.AXIS|default("x")|string|lower %} | ||
|
||
{% 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 | ||
|
||
[gcode_shell_command GUPPY_K1_SSH_RESTART] | ||
command: /etc/init.d/S50dropbear | ||
timeout: 600.0 | ||
verbose: True | ||
|
||
|
||
#### k1 load and unload ### | ||
[gcode_macro _GUPPY_LOAD_MATERIAL] | ||
gcode: | ||
{% set extruder_temp = params.EXTRUDER_TEMP|default(240)|int %} | ||
{% set extrude_len = params.EXTRUDE_LEN|default(35)|int %} | ||
LOAD_MATERIAL_CLOSE_FAN2 | ||
M109 S{extruder_temp} | ||
G91 | ||
G1 E{extrude_len} F180 | ||
LOAD_MATERIAL_RESTORE_FAN2 # k1 stuff | ||
|
||
[gcode_macro _GUPPY_QUIT_MATERIAL] | ||
gcode: | ||
{% set extruder_temp = params.EXTRUDER_TEMP|default(240)|int %} | ||
SAVE_GCODE_STATE NAME=myMoveState | ||
M109 S{extruder_temp} | ||
G91 | ||
G1 E20 F180 | ||
G1 E-30 F180 | ||
G1 E-50 F2000 | ||
RESTORE_GCODE_STATE NAME=myMoveState |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/usr/data/helper-script/files/guppy-screen/guppy_update.cfg |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
#!/usr/bin/env python3 | ||
###!/usr/data/rootfs/usr/bin/python3 | ||
# Shaper auto-calibration script | ||
# | ||
# Copyright (C) 2020 Dmitry Butyugin <[email protected]> | ||
# Copyright (C) 2020 Kevin O'Connor <[email protected]> | ||
# | ||
# This file may be distributed under the terms of the GNU GPLv3 license. | ||
from __future__ import print_function | ||
import importlib, optparse, os, sys, pathlib | ||
from textwrap import wrap | ||
import numpy as np, matplotlib | ||
import shaper_calibrate | ||
import json | ||
|
||
MAX_TITLE_LENGTH=65 | ||
|
||
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=',') | ||
# Parse power spectral density data | ||
data = np.loadtxt(logname, skiprows=1, comments='#', delimiter=',') | ||
calibration_data = shaper_calibrate.CalibrationData( | ||
freq_bins=data[:,0], psd_sum=data[:,4], | ||
psd_x=data[:,1], psd_y=data[:,2], psd_z=data[:,3]) | ||
calibration_data.set_numpy(np) | ||
# If input shapers are present in the CSV file, the frequency | ||
# response is already normalized to input frequencies | ||
if 'mzv' not in header: | ||
calibration_data.normalize_to_frequencies() | ||
return calibration_data | ||
|
||
###################################################################### | ||
# Shaper calibration | ||
###################################################################### | ||
|
||
# Find the best shaper parameters | ||
def calibrate_shaper(datas, csv_output, max_smoothing): | ||
helper = shaper_calibrate.ShaperCalibrate(printer=None) | ||
if isinstance(datas[0], shaper_calibrate.CalibrationData): | ||
calibration_data = datas[0] | ||
for data in datas[1:]: | ||
calibration_data.add_data(data) | ||
else: | ||
# Process accelerometer data | ||
calibration_data = helper.process_accelerometer_data(datas[0]) | ||
for data in datas[1:]: | ||
calibration_data.add_data(helper.process_accelerometer_data(data)) | ||
calibration_data.normalize_to_frequencies() | ||
shaper, all_shapers, resp = helper.find_best_shaper( | ||
calibration_data, max_smoothing, print) | ||
if csv_output is not None: | ||
helper.save_calibration_data( | ||
csv_output, calibration_data, all_shapers) | ||
return shaper.name, all_shapers, calibration_data, resp | ||
|
||
###################################################################### | ||
# Plot frequency response and suggested input shapers | ||
###################################################################### | ||
|
||
def plot_freq_response(lognames, calibration_data, shapers, | ||
selected_shaper, max_freq): | ||
freqs = calibration_data.freq_bins | ||
psd = calibration_data.psd_sum[freqs <= max_freq] | ||
px = calibration_data.psd_x[freqs <= max_freq] | ||
py = calibration_data.psd_y[freqs <= max_freq] | ||
pz = calibration_data.psd_z[freqs <= max_freq] | ||
freqs = freqs[freqs <= max_freq] | ||
|
||
fontP = matplotlib.font_manager.FontProperties() | ||
fontP.set_size('small') | ||
|
||
fig, ax = matplotlib.pyplot.subplots() | ||
ax.set_xlabel('Frequency, Hz') | ||
ax.set_xlim([0, max_freq]) | ||
ax.set_ylabel('Power spectral density') | ||
|
||
ax.plot(freqs, psd, label='X+Y+Z', color='purple') | ||
ax.plot(freqs, px, label='X', color='red') | ||
ax.plot(freqs, py, label='Y', color='green') | ||
ax.plot(freqs, pz, label='Z', color='blue') | ||
|
||
title = "Frequency response and shapers (%s)" % (', '.join(lognames)) | ||
ax.set_title("\n".join(wrap(title, MAX_TITLE_LENGTH))) | ||
ax.xaxis.set_minor_locator(matplotlib.ticker.MultipleLocator(5)) | ||
ax.yaxis.set_minor_locator(matplotlib.ticker.AutoMinorLocator()) | ||
ax.ticklabel_format(axis='y', style='scientific', scilimits=(0,0)) | ||
ax.grid(which='major', color='grey') | ||
ax.grid(which='minor', color='lightgrey') | ||
|
||
ax2 = ax.twinx() | ||
ax2.set_ylabel('Shaper vibration reduction (ratio)') | ||
best_shaper_vals = None | ||
for shaper in shapers: | ||
label = "%s (%.1f Hz, vibr=%.1f%%, sm~=%.2f, accel<=%.f)" % ( | ||
shaper.name.upper(), shaper.freq, | ||
shaper.vibrs * 100., shaper.smoothing, | ||
round(shaper.max_accel / 100.) * 100.) | ||
linestyle = 'dotted' | ||
if shaper.name == selected_shaper: | ||
linestyle = 'dashdot' | ||
best_shaper_vals = shaper.vals | ||
ax2.plot(freqs, shaper.vals, label=label, linestyle=linestyle) | ||
ax.plot(freqs, psd * best_shaper_vals, | ||
label='After\nshaper', color='cyan') | ||
# A hack to add a human-readable shaper recommendation to legend | ||
ax2.plot([], [], ' ', | ||
label="Recommended shaper: %s" % (selected_shaper.upper())) | ||
|
||
ax.legend(loc='upper left', prop=fontP) | ||
ax2.legend(loc='upper right', prop=fontP) | ||
|
||
fig.tight_layout() | ||
return fig | ||
|
||
###################################################################### | ||
# Startup | ||
###################################################################### | ||
|
||
def setup_matplotlib(output_to_file): | ||
global matplotlib | ||
if output_to_file: | ||
matplotlib.rcParams.update({'figure.autolayout': True}) | ||
matplotlib.use('Agg') | ||
import matplotlib.pyplot, matplotlib.dates, matplotlib.font_manager | ||
import matplotlib.ticker | ||
|
||
def main(): | ||
# Parse command-line arguments | ||
usage = "%prog [options] <logs>" | ||
opts = optparse.OptionParser(usage) | ||
opts.add_option("-o", "--output", type="string", dest="output", | ||
default=None, help="filename of output graph") | ||
opts.add_option("-c", "--csv", type="string", dest="csv", | ||
default=None, help="filename of output csv file") | ||
opts.add_option("-f", "--max_freq", type="float", default=200., | ||
help="maximum frequency to graph") | ||
opts.add_option("-s", "--max_smoothing", type="float", default=None, | ||
help="maximum shaper smoothing to allow") | ||
opts.add_option("-w", "--width", type="float", dest="width", | ||
default=8.3, help="width (inches) of the graph(s)") | ||
opts.add_option("-l", "--height", type="float", dest="height", | ||
default=11.6, help="height (inches) of the graph(s)") | ||
|
||
options, args = opts.parse_args() | ||
if len(args) < 1: | ||
opts.error("Incorrect number of arguments") | ||
if options.max_smoothing is not None and options.max_smoothing < 0.05: | ||
opts.error("Too small max_smoothing specified (must be at least 0.05)") | ||
|
||
# Parse data | ||
datas = [parse_log(fn) for fn in args] | ||
|
||
# Calibrate shaper and generate outputs | ||
selected_shaper, shapers, calibration_data, resp = calibrate_shaper( | ||
datas, options.csv, options.max_smoothing) | ||
|
||
resp['logfile'] = args[0] | ||
|
||
if not options.csv or options.output: | ||
# Draw graph | ||
setup_matplotlib(options.output is not None) | ||
|
||
fig = plot_freq_response(args, calibration_data, shapers, | ||
selected_shaper, options.max_freq) | ||
|
||
# Show graph | ||
if options.output is None: | ||
matplotlib.pyplot.show() | ||
else: | ||
pathlib.Path(options.output).unlink(missing_ok=True) | ||
fig.set_size_inches(options.width, options.height) | ||
fig.savefig(options.output) | ||
resp['png'] = options.output | ||
|
||
print(json.dumps(resp)) | ||
|
||
|
||
if __name__ == '__main__': | ||
main() |
Oops, something went wrong.