refactored is_workflow.py to use OOP

This commit is contained in:
Félix Boisselier
2024-04-14 17:29:14 +02:00
parent c01704437e
commit 43ac2911a2
5 changed files with 354 additions and 326 deletions

View File

@@ -24,7 +24,6 @@ from common_func import (
compute_curve_similarity_factor, compute_curve_similarity_factor,
compute_spectrogram, compute_spectrogram,
detect_peaks, detect_peaks,
get_git_version,
parse_log, parse_log,
setup_klipper_import, setup_klipper_import,
) )
@@ -453,7 +452,7 @@ def compute_signal_data(data, max_freq):
###################################################################### ######################################################################
def belts_calibration(lognames, klipperdir='~/klipper', max_freq=200.0): def belts_calibration(lognames, klipperdir='~/klipper', max_freq=200.0, st_version=None):
set_locale() set_locale()
global shaper_calibrate global shaper_calibrate
shaper_calibrate = setup_klipper_import(klipperdir) shaper_calibrate = setup_klipper_import(klipperdir)
@@ -530,7 +529,6 @@ def belts_calibration(lognames, klipperdir='~/klipper', max_freq=200.0):
ax_logo.axis('off') ax_logo.axis('off')
# Adding Shake&Tune version in the top right corner # Adding Shake&Tune version in the top right corner
st_version = get_git_version()
if st_version is not None: if st_version is not None:
fig.text(0.995, 0.985, st_version, ha='right', va='bottom', fontsize=8, color=KLIPPAIN_COLORS['purple']) fig.text(0.995, 0.985, st_version, ha='right', va='bottom', fontsize=8, color=KLIPPAIN_COLORS['purple'])

View File

@@ -24,7 +24,6 @@ from common_func import (
compute_mechanical_parameters, compute_mechanical_parameters,
compute_spectrogram, compute_spectrogram,
detect_peaks, detect_peaks,
get_git_version,
parse_log, parse_log,
setup_klipper_import, setup_klipper_import,
) )
@@ -295,7 +294,7 @@ def plot_spectrogram(ax, t, bins, pdata, peaks, max_freq):
###################################################################### ######################################################################
def shaper_calibration(lognames, klipperdir='~/klipper', max_smoothing=None, scv=5.0, max_freq=200.0): def shaper_calibration(lognames, klipperdir='~/klipper', max_smoothing=None, scv=5.0, max_freq=200.0, st_version=None):
set_locale() set_locale()
global shaper_calibrate global shaper_calibrate
shaper_calibrate = setup_klipper_import(klipperdir) shaper_calibrate = setup_klipper_import(klipperdir)
@@ -387,7 +386,6 @@ def shaper_calibration(lognames, klipperdir='~/klipper', max_smoothing=None, scv
ax_logo.axis('off') ax_logo.axis('off')
# Adding Shake&Tune version in the top right corner # Adding Shake&Tune version in the top right corner
st_version = get_git_version()
if st_version is not None: if st_version is not None:
fig.text(0.995, 0.985, st_version, ha='right', va='bottom', fontsize=8, color=KLIPPAIN_COLORS['purple']) fig.text(0.995, 0.985, st_version, ha='right', va='bottom', fontsize=8, color=KLIPPAIN_COLORS['purple'])

View File

@@ -24,7 +24,6 @@ matplotlib.use('Agg')
from common_func import ( from common_func import (
compute_mechanical_parameters, compute_mechanical_parameters,
detect_peaks, detect_peaks,
get_git_version,
identify_low_energy_zones, identify_low_energy_zones,
parse_log, parse_log,
setup_klipper_import, setup_klipper_import,
@@ -533,7 +532,9 @@ def extract_angle_and_speed(logname):
return float(angle), float(speed) return float(angle), float(speed)
def vibrations_profile(lognames, klipperdir='~/klipper', kinematics='cartesian', accel=None, max_freq=1000.0): def vibrations_profile(
lognames, klipperdir='~/klipper', kinematics='cartesian', accel=None, max_freq=1000.0, st_version=None
):
set_locale() set_locale()
global shaper_calibrate global shaper_calibrate
shaper_calibrate = setup_klipper_import(klipperdir) shaper_calibrate = setup_klipper_import(klipperdir)
@@ -708,7 +709,6 @@ def vibrations_profile(lognames, klipperdir='~/klipper', kinematics='cartesian',
ax_logo.axis('off') ax_logo.axis('off')
# Adding Shake&Tune version in the top right corner # Adding Shake&Tune version in the top right corner
st_version = get_git_version()
if st_version is not None: if st_version is not None:
fig.text(0.995, 0.985, st_version, ha='right', va='bottom', fontsize=8, color=KLIPPAIN_COLORS['purple']) fig.text(0.995, 0.985, st_version, ha='right', va='bottom', fontsize=8, color=KLIPPAIN_COLORS['purple'])

View File

@@ -9,362 +9,390 @@
# Use the provided Shake&Tune macros instead! # Use the provided Shake&Tune macros instead!
import abc
import argparse
import glob import glob
import optparse
import os import os
import shutil import shutil
import sys import sys
import tarfile import tarfile
import time import time
from datetime import datetime from datetime import datetime
from pathlib import Path
from git import GitCommandError, Repo
from analyze_axesmap import axesmap_calibration from analyze_axesmap import axesmap_calibration
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_profile from graph_vibrations import vibrations_profile
from locale_utils import print_with_c_locale
RESULTS_FOLDER = os.path.expanduser('~/printer_data/config/K-ShakeTune_results')
KLIPPER_FOLDER = os.path.expanduser('~/klipper')
RESULTS_SUBFOLDERS = ['belts', 'inputshaper', 'vibrations']
def is_file_open(filepath): class Config:
KLIPPER_FOLDER = os.path.expanduser('~/klipper')
RESULTS_BASE_FOLDER = os.path.expanduser('~/printer_data/config/K-ShakeTune_results')
RESULTS_SUBFOLDERS = {'belts': 'belts', 'shaper': 'inputshaper', 'vibrations': 'vibrations'}
@staticmethod
def get_results_folder(type):
return os.path.join(Config.RESULTS_BASE_FOLDER, Config.RESULTS_SUBFOLDERS[type])
@staticmethod
def get_git_version():
try:
# Get the absolute path of the script, resolving any symlinks
# Then get 1 times to parent dir to be at the git root folder
script_path = Path(__file__).resolve()
repo_path = script_path.parents[1]
repo = Repo(repo_path)
try:
version = repo.git.describe('--tags')
except GitCommandError:
version = repo.head.commit.hexsha[:7] # If no tag is found, use the simplified commit SHA instead
return version
except Exception:
return None
@staticmethod
def parse_arguments():
parser = argparse.ArgumentParser(description='Shake&Tune graphs generation script')
parser.add_argument(
'-t',
'--type',
dest='type',
choices=['belts', 'shaper', 'vibrations', 'axesmap', 'clean'],
required=True,
help='Type of output graph to produce',
)
parser.add_argument(
'--accel',
type=int,
default=None,
dest='accel_used',
help='Accelerometion used for vibrations profile creation or axes map calibration',
)
parser.add_argument(
'--chip_name',
type=str,
default='adxl345',
dest='chip_name',
help='Accelerometer chip name used for vibrations profile creation or axes map calibration',
)
parser.add_argument(
'--max_smoothing',
type=float,
default=None,
dest='max_smoothing',
help='Maximum smoothing to allow for input shaper filter recommendations',
)
parser.add_argument(
'--scv',
'--square_corner_velocity',
type=float,
default=5.0,
dest='scv',
help='Square corner velocity used to compute max accel for input shapers filter recommendations',
)
parser.add_argument(
'-m',
'--kinematics',
dest='kinematics',
default='cartesian',
choices=['cartesian', 'corexy'],
help='Machine kinematics configuration used for the vibrations profile creation',
)
parser.add_argument(
'-c',
'--keep_csv',
action='store_true',
default=False,
dest='keep_csv',
help='Whether to keep the raw CSV files after processing in addition to the PNG graphs',
)
parser.add_argument(
'-n',
'--keep_results',
type=int,
default=3,
dest='keep_results',
help='Number of results to keep in the result folder after each run of the script',
)
parser.add_argument('--dpi', type=int, default=150, dest='dpi', help='DPI of the output PNG files')
parser.add_argument('-v', '--version', action='version', version='%(prog)s ' + Config.get_git_version())
return parser.parse_args()
class FileManager:
@staticmethod
def wait_file_ready(filepath):
file_busy = True
loop_count = 0
while file_busy:
for proc in os.listdir('/proc'): for proc in os.listdir('/proc'):
if proc.isdigit(): if proc.isdigit():
for fd in glob.glob(f'/proc/{proc}/fd/*'): for fd in glob.glob(f'/proc/{proc}/fd/*'):
try: try:
if os.path.samefile(fd, filepath): if os.path.samefile(fd, filepath):
return True
except FileNotFoundError:
# Klipper has already released the CSV file
pass pass
except PermissionError: except FileNotFoundError: # Klipper has already released the CSV file
# Unable to check for this particular process due to permissions file_busy = False
except PermissionError: # Unable to check for this particular process due to permissions
pass pass
return False if loop_count > 60:
# If Klipper is taking too long to release the file (60 * 1s = 1min), exit the script
print_with_c_locale(f'Error: Klipper is taking too long to release {filepath}!')
sys.exit(1)
else:
loop_count += 1
time.sleep(1)
return
@staticmethod
def ensure_folders_exist():
for subfolder in Config.RESULTS_SUBFOLDERS.values():
folder = os.path.join(Config.RESULTS_BASE_FOLDER, subfolder)
os.makedirs(folder, exist_ok=True)
@staticmethod
def clean_old_files(type, keep_results=3, extension='.png'):
folder = Config.get_results_folder(type)
files = [os.path.join(folder, f) for f in os.listdir(folder) if f.endswith(extension)]
files.sort(key=os.path.getmtime, reverse=True)
if 'belts' in folder:
if len(files) <= keep_results + 1:
return
else: # delete the older files
for old_file in files[keep_results + 1 :]:
file_date = '_'.join(os.path.splitext(os.path.basename(old_file))[0].split('_')[1:3])
for suffix in ['A', 'B']:
csv_file = os.path.join(folder, f'belt_{file_date}_{suffix}.csv')
if os.path.exists(csv_file):
os.remove(csv_file)
os.remove(old_file)
elif 'shaper' in folder:
if len(files) <= 2 * keep_results + 1:
return
else: # delete the older files
for old_file in files[2 * keep_results + 1 :]:
csv_file = os.path.join(folder, os.path.splitext(os.path.basename(old_file))[0] + '.csv')
if os.path.exists(csv_file):
os.remove(csv_file)
os.remove(old_file)
elif 'vibrations' in folder:
if len(files) <= keep_results + 1:
return
else: # delete the older files
for old_file in files[keep_results + 1 :]:
os.remove(old_file)
tar_file = os.path.join(folder, os.path.splitext(os.path.basename(old_file))[0] + '.tar.gz')
if os.path.exists(tar_file):
os.remove(tar_file)
def create_belts_graph(keep_csv): class GraphCreator(abc.ABC):
current_date = datetime.now().strftime('%Y%m%d_%H%M%S') def __init__(self, keep_csv, dpi):
lognames = [] self._keep_csv = keep_csv
self._dpi = dpi
self._graph_date = datetime.now().strftime('%Y%m%d_%H%M%S')
self._version = Config.get_git_version()
@abc.abstractmethod
def create_graph(self):
pass
class BeltsGraphCreator(GraphCreator):
def __init__(self, keep_csv=False, dpi=150):
super().__init__(keep_csv, dpi)
self._type = 'belts'
self._folder = Config.get_results_folder(self._type)
def create_graph(self):
globbed_files = glob.glob('/tmp/raw_data_axis*.csv') globbed_files = glob.glob('/tmp/raw_data_axis*.csv')
if not globbed_files: if not globbed_files:
print('No CSV files found in the /tmp folder to create the belt graphs!') raise FileNotFoundError('no CSV files found in the /tmp folder to create the belt comparison graphs!')
sys.exit(1)
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!') raise FileNotFoundError('two CSV files are needed to create the belt comparison graphs!')
sys.exit(1)
sorted_files = sorted(globbed_files, key=os.path.getmtime, reverse=True) lognames = []
for filename in sorted(globbed_files, key=os.path.getmtime, reverse=True)[: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): FileManager.wait_file_ready(filename)
time.sleep(2)
# Extract the tested belt from the filename and rename/move the CSV file to the result folder # Cleanup of the filename and moving it in 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(self._folder, f'{self._type}_{self._graph_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
lognames.append(new_file) lognames.append(new_file)
# Wait for the file handler to be released by the move command # Check if the file is ready to be read
while is_file_open(new_file): os.sync()
time.sleep(2) FileManager.wait_file_ready(new_file)
# Generate the belts graph and its name fig = belts_calibration(lognames, Config.KLIPPER_FOLDER, self._version)
fig = belts_calibration(lognames, KLIPPER_FOLDER) png_filename = os.path.join(self._folder, f'{self._type}_{self._graph_date}.png')
png_filename = os.path.join(RESULTS_FOLDER, RESULTS_SUBFOLDERS[0], f'belts_{current_date}.png') fig.savefig(png_filename, dpi=self._dpi)
fig.savefig(png_filename, dpi=150)
# Remove the CSV files if the user don't want to keep them # Remove the CSV files if the user don't want to keep them
if not keep_csv: if not self._keep_csv:
for csv in lognames: for csv in lognames:
if os.path.exists(csv): if os.path.exists(csv):
os.remove(csv) os.remove(csv)
return
class ShaperGraphCreator(GraphCreator):
def __init__(self, max_smoothing=None, scv=5.0, keep_csv=False, dpi=150):
super().__init__(keep_csv, dpi)
def create_shaper_graph(keep_csv, max_smoothing, scv): self._max_smoothing = max_smoothing
current_date = datetime.now().strftime('%Y%m%d_%H%M%S') self._scv = scv
# Get all the files and sort them based on last modified time to select the most recent one self._type = 'shaper'
self._folder = Config.get_results_folder(self._type)
def create_graph(self):
globbed_files = glob.glob('/tmp/raw_data*.csv') globbed_files = glob.glob('/tmp/raw_data*.csv')
if not globbed_files: if not globbed_files:
print('No CSV files found in the /tmp folder to create the input shaper graphs!') raise FileNotFoundError('no CSV files found in the /tmp folder to create the input shaper graphs!')
sys.exit(1)
sorted_files = sorted(globbed_files, key=os.path.getmtime, reverse=True) # Find the CSV files with the latest timestamp and wait for it to be released by Klipper
filename = sorted_files[0] filename = sorted(globbed_files, key=os.path.getmtime, reverse=True)[0]
FileManager.wait_file_ready(filename)
# Wait for the file handler to be released by Klipper # Cleanup of the filename and moving it in the result folder
while is_file_open(filename):
time.sleep(2)
# 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(self._folder, f'{self._type}_{self._graph_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 # Check if the file is ready to be read
while is_file_open(new_file): os.sync()
time.sleep(2) FileManager.wait_file_ready(new_file)
# Generate the shaper graph and its name fig = shaper_calibration(
fig = shaper_calibration([new_file], KLIPPER_FOLDER, max_smoothing=max_smoothing, scv=scv) [new_file],
png_filename = os.path.join(RESULTS_FOLDER, RESULTS_SUBFOLDERS[1], f'resonances_{current_date}_{axis}.png') Config.KLIPPER_FOLDER,
fig.savefig(png_filename, dpi=150) max_smoothing=self._max_smoothing,
scv=self._scv,
st_version=self._version,
)
png_filename = os.path.join(self._folder, f'{self._type}_{self._graph_date}_{axis}.png')
fig.savefig(png_filename, dpi=self._dpi)
# Remove the CSV file if the user don't want to keep it # Remove the CSV files if the user don't want to keep them
if not keep_csv: if not self._keep_csv:
if os.path.exists(new_file): if os.path.exists(new_file):
os.remove(new_file) os.remove(new_file)
return axis return axis
def create_vibrations_graph(accel, kinematics, chip_name, keep_csv): class VibrationsGraphCreator(GraphCreator):
current_date = datetime.now().strftime('%Y%m%d_%H%M%S') def __init__(self, kinematics, accel, chip_name, keep_csv=False, dpi=150):
lognames = [] super().__init__(keep_csv, dpi)
globbed_files = glob.glob(f'/tmp/{chip_name}-*.csv') self._kinematics = kinematics
self._accel = accel
self._chip_name = chip_name
self._type = 'vibrations'
self._folder = Config.get_results_folder(self._type)
def create_graph(self):
globbed_files = glob.glob(f'/tmp/{self._chip_name}-*.csv')
if not globbed_files: if not globbed_files:
print('No CSV files found in the /tmp folder to create the vibration graphs!') raise FileNotFoundError('no CSV files found in the /tmp folder to create the vibrations graphs!')
sys.exit(1)
if len(globbed_files) < 3: if len(globbed_files) < 3:
print('Not enough CSV files found in the /tmp folder. At least 3 files are required for the vibration graphs!') raise FileNotFoundError('at least 3 CSV files are needed to create the vibrations graphs!')
sys.exit(1)
lognames = []
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): FileManager.wait_file_ready(filename)
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(chip_name, f'vibr_{current_date}') cleanfilename = os.path.basename(filename).replace(self._chip_name, f'{self._type}_{self._graph_date}')
new_file = os.path.join(RESULTS_FOLDER, RESULTS_SUBFOLDERS[2], cleanfilename) new_file = os.path.join(self._folder, cleanfilename)
shutil.move(filename, new_file) shutil.move(filename, new_file)
# Save the file path for later
lognames.append(new_file) lognames.append(new_file)
# 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) time.sleep(5)
# Generate the vibration graph and its name fig = vibrations_profile(lognames, Config.KLIPPER_FOLDER, self._kinematics, self._accel, self._version)
fig = vibrations_profile(lognames, KLIPPER_FOLDER, kinematics, accel) png_filename = os.path.join(self._folder, f'{self._type}_{self._graph_date}.png')
png_filename = os.path.join(RESULTS_FOLDER, RESULTS_SUBFOLDERS[2], f'vibrations_{current_date}.png') fig.savefig(png_filename, dpi=self._dpi)
fig.savefig(png_filename, dpi=150)
# Archive all the csv files in a tarball in case the user want to keep them # Archive all the csv files in a tarball in case the user want to keep them
if keep_csv: if self._keep_csv:
with tarfile.open( with tarfile.open(os.path.join(self._folder, f'{self._type}_{self._graph_date}.tar.gz'), 'w:gz') as tar:
os.path.join(RESULTS_FOLDER, RESULTS_SUBFOLDERS[2], f'vibrations_{current_date}.tar.gz'), 'w:gz'
) as tar:
for csv_file in lognames: for csv_file in lognames:
tar.add(csv_file, arcname=os.path.basename(csv_file), recursive=False) tar.add(csv_file, arcname=os.path.basename(csv_file), recursive=False)
# Remove the remaining CSV files not needed anymore (tarball is safe if it was created) # Remove the remaining CSV files not needed anymore (tarball is safe if it was created)
for csv_file in lognames: for csv in lognames:
if os.path.exists(csv_file): if os.path.exists(csv):
os.remove(csv_file) os.remove(csv)
return
def find_axesmap(accel, chip_name): class AxesMapFinder:
current_date = datetime.now().strftime('%Y%m%d_%H%M%S') def __init__(self, accel, chip_name):
result_filename = os.path.join(RESULTS_FOLDER, f'axes_map_{current_date}.txt') self._accel = accel
lognames = [] self._chip_name = chip_name
globbed_files = glob.glob(f'/tmp/{chip_name}-*.csv') self._graph_date = datetime.now().strftime('%Y%m%d_%H%M%S')
self._type = 'axesmap'
self._folder = Config.RESULTS_BASE_FOLDER
def find_axesmap(self):
globbed_files = glob.glob(f'/tmp/{self._chip_name}-*.csv')
if not globbed_files: if not globbed_files:
print('No CSV files found in the /tmp folder to analyze and find the axes_map!') raise FileNotFoundError('no CSV files found in the /tmp folder to find the axes map!')
sys.exit(1)
sorted_files = sorted(globbed_files, key=os.path.getmtime, reverse=True) # Find the CSV files with the latest timestamp and wait for it to be released by Klipper
filename = sorted_files[0] logname = sorted(globbed_files, key=os.path.getmtime, reverse=True)[0]
FileManager.wait_file_ready(logname)
# 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)
results = axesmap_calibration([logname], self._accel)
result_filename = os.path.join(self._folder, f'{self._type}_{self._graph_date}.txt')
with open(result_filename, 'w') as f: with open(result_filename, 'w') as f:
f.write(results) f.write(results)
return
# Utility function to get old files based on their modification time
def get_old_files(folder, extension, limit):
files = [os.path.join(folder, f) for f in os.listdir(folder) if f.endswith(extension)]
files.sort(key=lambda x: os.path.getmtime(x), reverse=True)
return files[limit:]
def clean_files(keep_results):
# Define limits based on STORE_RESULTS
keep1 = keep_results + 1
keep2 = 2 * keep_results + 1
# Find old files in each directory
old_belts_files = get_old_files(os.path.join(RESULTS_FOLDER, RESULTS_SUBFOLDERS[0]), '.png', keep1)
old_inputshaper_files = get_old_files(os.path.join(RESULTS_FOLDER, RESULTS_SUBFOLDERS[1]), '.png', keep2)
old_speed_vibr_files = get_old_files(os.path.join(RESULTS_FOLDER, RESULTS_SUBFOLDERS[2]), '.png', keep1)
# Remove the old belt files
for old_file in old_belts_files:
file_date = '_'.join(os.path.splitext(os.path.basename(old_file))[0].split('_')[1:3])
for suffix in ['A', 'B']:
csv_file = os.path.join(RESULTS_FOLDER, RESULTS_SUBFOLDERS[0], f'belt_{file_date}_{suffix}.csv')
if os.path.exists(csv_file):
os.remove(csv_file)
os.remove(old_file)
# Remove the old shaper files
for old_file in old_inputshaper_files:
csv_file = os.path.join(
RESULTS_FOLDER, RESULTS_SUBFOLDERS[1], os.path.splitext(os.path.basename(old_file))[0] + '.csv'
)
if os.path.exists(csv_file):
os.remove(csv_file)
os.remove(old_file)
# Remove the old vibrations files
for old_file in old_speed_vibr_files:
os.remove(old_file)
tar_file = os.path.join(
RESULTS_FOLDER, RESULTS_SUBFOLDERS[2], os.path.splitext(os.path.basename(old_file))[0] + '.tar.gz'
)
if os.path.exists(tar_file):
os.remove(tar_file)
def main(): def main():
# Parse command-line arguments print_with_c_locale(f'Shake&Tune version: {Config.get_git_version()}')
usage = '%prog [options] <logs>'
opts = optparse.OptionParser(usage)
opts.add_option('-t', '--type', type='string', dest='type', default=None, help='type of output graph to produce')
opts.add_option(
'--accel',
type='int',
default=None,
dest='accel_used',
help='acceleration used during the vibration macro or axesmap macro',
)
opts.add_option(
'--axis_name', type='string', default=None, dest='axis_name', help='axis tested during the vibration macro'
)
opts.add_option(
'--chip_name',
type='string',
default='adxl345',
dest='chip_name',
help='accelerometer chip name in klipper used during the vibration macro or the axesmap macro',
)
opts.add_option(
'-n',
'--keep_results',
type='int',
default=3,
dest='keep_results',
help='number of results to keep in the result folder after each run of the script',
)
opts.add_option(
'-c',
'--keep_csv',
action='store_true',
default=False,
dest='keep_csv',
help='weither or not to keep the CSV files alongside the PNG graphs image results',
)
opts.add_option(
'--scv',
'--square_corner_velocity',
type='float',
dest='scv',
default=5.0,
help='square corner velocity used to compute max accel for axis shapers graphs',
)
opts.add_option(
'--max_smoothing', type='float', dest='max_smoothing', default=None, help='maximum shaper smoothing to allow'
)
opts.add_option(
'-m',
'--kinematics',
type='string',
dest='kinematics',
default='cartesian',
help='machine kinematics configuration used for the vibrations graphs',
)
options, _ = opts.parse_args()
if options.type is None: options = Config.parse_arguments()
opts.error('You must specify the type of output graph you want to produce (option -t)') FileManager.ensure_folders_exist()
elif options.type.lower() is None or options.type.lower() not in [
'belts', graph_creator = None
'shaper', if options.type == 'belts':
'vibrations', graph_creator = BeltsGraphCreator(options.keep_csv, options.dpi)
'axesmap', elif options.type == 'shaper':
'clean', graph_creator = ShaperGraphCreator(options.max_smoothing, options.scv, options.keep_csv, options.dpi)
]: elif options.type == 'vibrations':
opts.error( graph_creator = VibrationsGraphCreator(
"Type of output graph need to be in the list of 'belts', 'shaper', 'vibrations', 'axesmap' or 'clean'" options.kinematics, options.accel_used, options.chip_name, options.keep_csv, options.dpi
) )
else: elif options.type == 'axesmap':
graph_mode = options.type graph_creator = AxesMapFinder(options.accel_used, options.chip_name)
elif options.type == 'clean':
print_with_c_locale(f'Cleaning output folder to keep only the last {options.keep_results} results...')
FileManager.clean_old_files(options.type, options.keep_results)
return
if graph_mode.lower() == 'vibrations' and options.kinematics not in ['cartesian', 'corexy']: if graph_creator:
opts.error('Only Cartesian and CoreXY kinematics are supported by this tool at the moment!') graph_creator.create_graph()
print_with_c_locale(f'{options.type} graphs created successfully!')
# Check if results folders are there or create them before doing anything else if options.keep_csv is False and options.type != 'clean':
for result_subfolder in RESULTS_SUBFOLDERS: print_with_c_locale('Deleting raw CSV files... If you want to keep them, use the --keep_csv option!')
folder = os.path.join(RESULTS_FOLDER, result_subfolder)
if not os.path.exists(folder):
os.makedirs(folder)
if graph_mode.lower() == 'belts':
create_belts_graph(keep_csv=options.keep_csv)
print(f'Belt graph created. You will find the results in {RESULTS_FOLDER}/{RESULTS_SUBFOLDERS[0]}')
elif graph_mode.lower() == 'shaper':
axis = create_shaper_graph(keep_csv=options.keep_csv, max_smoothing=options.max_smoothing, scv=options.scv)
print(
f'{axis} input shaper graph created. You will find the results in {RESULTS_FOLDER}/{RESULTS_SUBFOLDERS[1]}'
)
elif graph_mode.lower() == 'vibrations':
create_vibrations_graph(
accel=options.accel_used,
kinematics=options.kinematics,
chip_name=options.chip_name,
keep_csv=options.keep_csv,
)
print(f'Vibrations graph created. You will find the results in {RESULTS_FOLDER}/{RESULTS_SUBFOLDERS[2]}')
elif graph_mode.lower() == 'axesmap':
print(
'WARNING: AXES_MAP_CALIBRATION is currently very experimental and may produce incorrect results... Please validate the output!'
)
find_axesmap(accel=options.accel_used, chip_name=options.chip_name)
elif graph_mode.lower() == 'clean':
print(f'Cleaning output folder to keep only the last {options.keep_results} results...')
clean_files(keep_results=options.keep_results)
if options.keep_csv is False and graph_mode.lower() != 'clean':
print('Deleting raw CSV files... If you want to keep them, use the --keep_csv option!')
if __name__ == '__main__': if __name__ == '__main__':

View File

@@ -6,6 +6,7 @@
import locale import locale
# Set the best locale for time and date formating (generation of the titles) # Set the best locale for time and date formating (generation of the titles)
def set_locale(): def set_locale():
try: try:
@@ -15,16 +16,19 @@ def set_locale():
except locale.Error: except locale.Error:
locale.setlocale(locale.LC_TIME, 'C') locale.setlocale(locale.LC_TIME, 'C')
# Print function to avoid problem in Klipper console (that doesn't support special characters) due to locale settings # Print function to avoid problem in Klipper console (that doesn't support special characters) due to locale settings
def print_with_c_locale(*args, **kwargs): def print_with_c_locale(*args, **kwargs):
try: try:
original_locale = locale.getlocale() original_locale = locale.getlocale()
locale.setlocale(locale.LC_ALL, 'C') locale.setlocale(locale.LC_ALL, 'C')
except locale.Error as e: except locale.Error as e:
print("Warning: Failed to set a basic locale. Special characters may not display correctly in Klipper console:", e) print(
'Warning: Failed to set a basic locale. Special characters may not display correctly in Klipper console:', e
)
finally: finally:
print(*args, **kwargs) # Proceed with printing regardless of locale setting success print(*args, **kwargs) # Proceed with printing regardless of locale setting success
try: try:
locale.setlocale(locale.LC_ALL, original_locale) locale.setlocale(locale.LC_ALL, original_locale)
except locale.Error as e: except locale.Error as e:
print("Warning: Failed to restore the original locale setting:", e) print('Warning: Failed to restore the original locale setting:', e)