diff --git a/autospeed/main.py b/autospeed/main.py index 0bfcdc2..176e5bc 100644 --- a/autospeed/main.py +++ b/autospeed/main.py @@ -39,6 +39,7 @@ def __init__(self, config): self.accel_min = config.getfloat('accel_min', default=1000.0, above=1.0) self.accel_max = config.getfloat('accel_max', default=100000.0, above=self.accel_min) self.accel_accu = config.getfloat('accel_accu', default=0.05, above=0.0, below=1.0) + self.scv = config.getfloat('scv', default=5, above=1.0, below=50) self.veloc_min = config.getfloat('velocity_min', default=50.0, above=1.0) self.veloc_max = config.getfloat('velocity_max', default=5000.0, above=self.veloc_min) @@ -58,7 +59,7 @@ def __init__(self, config): ): if os.path.exists(path): results_default = path - self.results_dir = config.get('results_dir',default=results_default) + self.results_dir = os.path.expanduser(config.get('results_dir',default=results_default)) self.toolhead = None self.printer.register_event_handler("klippy:connect", self.handle_connect) @@ -70,6 +71,9 @@ def __init__(self, config): ('AUTO_SPEED_ACCEL', self.cmd_AUTO_SPEED_ACCEL, self.cmd_AUTO_SPEED_ACCEL_help), ('AUTO_SPEED_VALIDATE', self.cmd_AUTO_SPEED_VALIDATE, self.cmd_AUTO_SPEED_VALIDATE_help), ('AUTO_SPEED_GRAPH', self.cmd_AUTO_SPEED_GRAPH, self.cmd_AUTO_SPEED_GRAPH_help), + ('X_ENDSTOP_ACCURACY', self.cmd_X_ENDSTOP_ACCURACY, self.cmd_AUTO_SPEED_GRAPH_help), + ('Y_ENDSTOP_ACCURACY', self.cmd_Y_ENDSTOP_ACCURACY, self.cmd_AUTO_SPEED_GRAPH_help), + ('Z_ENDSTOP_ACCURACY', self.cmd_Z_ENDSTOP_ACCURACY, self.cmd_AUTO_SPEED_GRAPH_help) ] command_descriptions = {name: desc for name, _, desc in measurement_commands} gcode = self.printer.lookup_object('gcode') @@ -117,6 +121,7 @@ def handle_connect(self): # Reduce speed/acceleration for positioning movement self.th_accel = self.toolhead.max_accel/2 self.th_veloc = self.toolhead.max_velocity/2 + self.th_scv = self.toolhead.square_corner_velocity # Find and define leveling method if self.printer.lookup_object("screw_tilt_adjust", None) is not None: @@ -140,8 +145,9 @@ def handle_home_rails_end(self, homing_state, rails): if name in ["stepper_x", "stepper_y", "stepper_z"]: config = self.printer.lookup_object('configfile').status_raw_config[name] microsteps = int(config["microsteps"]) - self.steppers[name[-1]] = [pos_min, pos_max, microsteps] - + homing_retract_dist = float (config["homing_retract_dist"]) + second_homing_speed = float(config["second_homing_speed"]) + self.steppers[name[-1]] = [pos_min, pos_max, microsteps, homing_retract_dist, second_homing_speed] if self.steppers.get("x", None) is not None: self.axis_limits["x"] = { @@ -221,6 +227,7 @@ def cmd_AUTO_SPEED_ACCEL(self, gcmd): accel_accu = gcmd.get_float('ACCEL_ACCU', self.accel_accu, above=0.0, below=1.0) veloc = gcmd.get_float('VELOCITY', 1.0, above=1.0) + scv = gcmd.get_float('SCV', self.scv, above=1.0) respond = "AUTO SPEED finding maximum acceleration on" for axis in axes: @@ -239,6 +246,7 @@ def cmd_AUTO_SPEED_ACCEL(self, gcmd): aw.min = accel_min aw.max = accel_max aw.veloc = veloc + aw.scv = scv self.init_axis(aw, axis) rw.vals[aw.axis] = self.binary_search(aw) rw.duration = perf_counter() - start @@ -275,6 +283,7 @@ def cmd_AUTO_SPEED_VELOCITY(self, gcmd): veloc_accu = gcmd.get_float('VELOCITY_ACCU', self.veloc_accu, above=0.0, below=1.0) accel = gcmd.get_float('ACCEL', 1.0, above=1.0) + scv = gcmd.get_float('SCV', self.scv, above=1.0) respond = "AUTO SPEED finding maximum velocity on" for axis in axes: @@ -293,6 +302,7 @@ def cmd_AUTO_SPEED_VELOCITY(self, gcmd): aw.min = veloc_min aw.max = veloc_max aw.accel = accel + aw.scv = scv self.init_axis(aw, axis) rw.vals[aw.axis] = self.binary_search(aw) rw.duration = perf_counter() - start @@ -326,12 +336,14 @@ def cmd_AUTO_SPEED_VALIDATE(self, gcmd): accel = gcmd.get_float('ACCEL', default=self.toolhead.max_accel, above=0.0) veloc = gcmd.get_float('VELOCITY', default=self.toolhead.max_velocity, above=0.0) - + scv = gcmd.get_float('SCV', default=self.toolhead.square_corner_velocity, above=1.0) + respond = f"AUTO SPEED validating over {iterations} iterations\n" respond += f"Acceleration: {accel:.0f}\n" - respond += f"Velocity: {veloc:.0f}" + respond += f"Velocity: {veloc:.0f}\n" + respond += f"SCV: {scv:.0f}" self.gcode.respond_info(respond) - self._set_velocity(veloc, accel) + self._set_velocity(veloc, accel, scv) valid, duration, missed_x, missed_y = self._validate(veloc, iterations, margin, small_margin, max_missed) respond = f"AUTO SPEED validated results after {duration:.2f}s\n" @@ -598,10 +610,10 @@ def binary_search(self, aw: AttemptWrapper): def _attempt(self, aw: AttemptWrapper): timeAttempt = perf_counter() - self._set_velocity(self.th_veloc, self.th_accel) + self._set_velocity(self.th_veloc, self.th_accel, self.th_scv) self._move([aw.move.pos["x"][0], aw.move.pos["y"][0], aw.move.pos["z"][0]], self.th_veloc) self.toolhead.wait_moves() - self._set_velocity(aw.veloc, aw.accel) + self._set_velocity(aw.veloc, aw.accel, aw.scv) timeMove = perf_counter() self._move([aw.move.pos["x"][1], aw.move.pos["y"][1], aw.move.pos["z"][1]], aw.veloc) @@ -715,7 +727,8 @@ def _move(self, coord, speed): def _home(self, x=True, y=True, z=True): prevAccel = self.toolhead.max_accel prevVeloc = self.toolhead.max_velocity - self._set_velocity(self.th_veloc, self.th_accel) + prevScv = self.toolhead.square_corner_velocity + self._set_velocity(self.th_veloc, self.th_accel, self.th_scv) command = ["G28"] if x: command[-1] += " X0" @@ -725,7 +738,7 @@ def _home(self, x=True, y=True, z=True): command[-1] += " Z0" self.gcode._process_commands(command, False) self.toolhead.wait_moves() - self._set_velocity(prevVeloc, prevAccel) + self._set_velocity(prevVeloc, prevAccel, prevScv) def _get_steps(self): kin = self.toolhead.get_kinematics() @@ -776,9 +789,168 @@ def _posttest(self, start_steps, max_missed, home: list): return valid, stop_steps, missed, dur - def _set_velocity(self, velocity: float, accel: float): + def _set_velocity(self, velocity: float, accel: float, scv: float): #self.gcode.respond_info(f"AUTO SPEED setting limits to VELOCITY={velocity} ACCEL={accel}") self.toolhead.max_velocity = velocity self.toolhead.max_accel = accel - self.toolhead.requested_accel_to_decel = accel/2 + + self.toolhead.requested_accel_to_decel = accel + self.toolhead.square_corner_velocity = scv self.toolhead._calc_junction_deviation() + + def cmd_X_ENDSTOP_ACCURACY(self, gcmd): + + if not len(self.steppers.keys()) == 3: + raise gcmd.error(f"Printer must be homed first! Found {len(self.steppers.keys())} homed axes.") + + # Number of samples for accuracy check + sample_count = gcmd.get_int("SAMPLES", 10, minval=1) + + # Retrieve homing parameters for the X axis from the previously stored values + second_homing_speed = self.steppers['x'][4] + homing_retract_dist = self.steppers['x'][3] + + # Toolhead object to control the movement + toolhead = self.printer.lookup_object('toolhead') + pos = toolhead.get_position() + + # Log the starting position for X + gcmd.respond_info("X_ENDSTOP_ACCURACY at X:%.3f (samples=%d)\n" % (pos[0], sample_count)) + gcmd.respond_info("Second Homing Speed: %.2f mm/s" % second_homing_speed) + gcmd.respond_info("Homing Retract Distance: %.2f mm" % homing_retract_dist) + + + # Create a dummy gcode command for a single sample + fo_params = dict(gcmd.get_command_parameters()) + fo_params['SAMPLES'] = '1' + gcode = self.printer.lookup_object('gcode') + fo_gcmd = gcode.create_gcode_command("", "", fo_params) + + # List to store the X positions hit during each sample + positions = [] + + # Move to the X endstop sample_count times and collect the X positions + for _ in range(sample_count): + self._home(True, False, False) + pos = toolhead.get_position() # Get the current X position after homing + positions.append(pos[0]) + toolhead.manual_move([pos[0] - homing_retract_dist, None, None], speed=second_homing_speed) # Move away from the endstop + + # Calculate the maximum, minimum, average, and standard deviation for X positions + max_value = max(positions) + min_value = min(positions) + avg_value = sum(positions) / len(positions) + range_value = max_value - min_value + + deviation_sum = sum([(x - avg_value) ** 2 for x in positions]) + sigma = (deviation_sum / len(positions)) ** 0.5 + + # Display results + gcmd.respond_info( + "X endstop accuracy results: maximum %.6f, minimum %.6f, range %.6f, " + "average %.6f, standard deviation %.6f" % (max_value, min_value, range_value, avg_value, sigma)) + + + def cmd_Y_ENDSTOP_ACCURACY(self, gcmd): + + if not len(self.steppers.keys()) == 3: + raise gcmd.error(f"Printer must be homed first! Found {len(self.steppers.keys())} homed axes.") + + # Number of samples for accuracy check + sample_count = gcmd.get_int("SAMPLES", 10, minval=1) + + # Retrieve homing parameters for the Y axis from the previously stored values + second_homing_speed = self.steppers['y'][4] + homing_retract_dist = self.steppers['y'][3] + + # Toolhead object to control the movement + toolhead = self.printer.lookup_object('toolhead') + pos = toolhead.get_position() + + # Log the starting position for Y + gcmd.respond_info("Y_ENDSTOP_ACCURACY at Y:%.3f (samples=%d)\n" % (pos[1], sample_count)) + gcmd.respond_info("Second Homing Speed: %.2f mm/s" % second_homing_speed) + gcmd.respond_info("Homing Retract Distance: %.2f mm" % homing_retract_dist) + + + # Create a dummy gcode command for a single sample + fo_params = dict(gcmd.get_command_parameters()) + fo_params['SAMPLES'] = '1' + gcode = self.printer.lookup_object('gcode') + fo_gcmd = gcode.create_gcode_command("", "", fo_params) + + # List to store the Y positions hit during each sample + positions = [] + + # Move to the Y endstop sample_count times and collect the Y positions + for _ in range(sample_count): + self._home(False, True, False) + pos = toolhead.get_position() # Get the current Y position after homing + positions.append(pos[1]) + toolhead.manual_move([None, pos[1] - homing_retract_dist, None], speed=second_homing_speed) # Move away from the endstop + + # Calculate the maximum, minimum, average, and standard deviation for Y positions + max_value = max(positions) + min_value = min(positions) + avg_value = sum(positions) / len(positions) + range_value = max_value - min_value + + deviation_sum = sum([(y - avg_value) ** 2 for y in positions]) + sigma = (deviation_sum / len(positions)) ** 0.5 + + # Display results + gcmd.respond_info( + "Y endstop accuracy results: maximum %.6f, minimum %.6f, range %.6f, " + "average %.6f, standard deviation %.6f" % (max_value, min_value, range_value, avg_value, sigma)) + + def cmd_Z_ENDSTOP_ACCURACY(self, gcmd): + + if not len(self.steppers.keys()) == 3: + raise gcmd.error(f"Printer must be homed first! Found {len(self.steppers.keys())} homed axes.") + + # Number of samples for accuracy check + sample_count = gcmd.get_int("SAMPLES", 10, minval=1) + + # Retrieve homing parameters for the Z axis from the previously stored values + second_homing_speed = self.steppers['z'][4] + homing_retract_dist = self.steppers['z'][3] + + # Toolhead object to control the movement + toolhead = self.printer.lookup_object('toolhead') + pos = toolhead.get_position() + + # Log the starting position for Z + gcmd.respond_info("Z_ENDSTOP_ACCURACY at Z:%.3f (samples=%d)\n" % (pos[2], sample_count)) + gcmd.respond_info("Second Homing Speed: %.2f mm/s" % second_homing_speed) + gcmd.respond_info("Homing Retract Distance: %.2f mm" % homing_retract_dist) + + + # Create a dummy gcode command for a single sample + fo_params = dict(gcmd.get_command_parameters()) + fo_params['SAMPLES'] = '1' + gcode = self.printer.lookup_object('gcode') + fo_gcmd = gcode.create_gcode_command("", "", fo_params) + + # List to store the Z positions hit during each sample + positions = [] + + # Move to the Z endstop sample_count times and collect the Z positions + for _ in range(sample_count): + self._home(False, False, True) + pos = toolhead.get_position() # Get the current Z position after homing + positions.append(pos[2]) + toolhead.manual_move([None, None, pos[2] + homing_retract_dist], speed=second_homing_speed) # Move away from the endstop + + # Calculate the maximum, minimum, average, and standard deviation for Z positions + max_value = max(positions) + min_value = min(positions) + avg_value = sum(positions) / len(positions) + range_value = max_value - min_value + + deviation_sum = sum([(z - avg_value) ** 2 for z in positions]) + sigma = (deviation_sum / len(positions)) ** 0.5 + + # Display results + gcmd.respond_info( + "Z endstop accuracy results: maximum %.6f, minimum %.6f, range %.6f, " + "average %.6f, standard deviation %.6f" % (max_value, min_value, range_value, avg_value, sigma)) diff --git a/install.sh b/install.sh index d2e3a15..fb18c92 100755 --- a/install.sh +++ b/install.sh @@ -26,6 +26,9 @@ else exit -1 fi +# Check for old python +~/klippy-env/bin/python -c 'import sys; assert sys.version_info[0] == 3, "Python 3 is required."' + # Link auto speed to klipper echo "Linking auto speed to Klipper..." ln -sf "${SRCDIR}/auto_speed.py" "${KLIPPER_PATH}/klippy/extras/auto_speed.py"