Skip to content

Commit

Permalink
Auto-commit by Git Backup
Browse files Browse the repository at this point in the history
  • Loading branch information
dfbpurcell committed Jul 31, 2024
1 parent 253efe5 commit 14ab9bc
Show file tree
Hide file tree
Showing 8 changed files with 1,346 additions and 1 deletion.
110 changes: 110 additions & 0 deletions GuppyScreen/guppy_cmd.cfg
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
1 change: 1 addition & 0 deletions GuppyScreen/guppy_update.cfg
186 changes: 186 additions & 0 deletions GuppyScreen/scripts/calibrate_shaper.py
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))
print


if __name__ == '__main__':
main()
Loading

0 comments on commit 14ab9bc

Please sign in to comment.