diff --git a/klippy/extras/belay.py b/klippy/extras/belay.py index aefa0a4ea34b..1c91a3ce0e0b 100644 --- a/klippy/extras/belay.py +++ b/klippy/extras/belay.py @@ -151,9 +151,7 @@ def handle_enable(self): if not condition(): return self.enabled = True - self.reactor.update_timer( - self.update_direction_timer, self.reactor.NOW - ) + self.reactor.update_timer(self.update_direction_timer, self.reactor.NOW) self.update_multiplier() def handle_disable(self): @@ -170,9 +168,7 @@ def sensor_callback(self, eventtime, state): self.last_state = state if self.enabled: virtual_sdcard = self.printer.lookup_object("virtual_sdcard") - file_pos = virtual_sdcard.get_status(eventtime).get( - "file_position" - ) + file_pos = virtual_sdcard.get_status(eventtime).get("file_position") if file_pos > 1: self.timeout = self.reactor.monotonic() + self.stuck_timeout if self.debug_level >= 1: @@ -233,7 +229,8 @@ def update_verify_stuck(self, eventtime) -> float: if state.lower() == "printing": if self.debug_level >= 1: self.gcode.respond_info( - f"Belay stuck with timeout {self.timeout} timer: {self.reactor.monotonic()}" + f"Belay stuck with timeout {self.timeout} "\ + f"timer: {self.reactor.monotonic()}" ) if self.timeout > self.reactor.monotonic(): diff --git a/klippy/extras/bo_idex_xy_offset_calibration.py b/klippy/extras/bo_idex_xy_offset_calibration.py deleted file mode 100644 index 6cbcb0ff6133..000000000000 --- a/klippy/extras/bo_idex_xy_offset_calibration.py +++ /dev/null @@ -1,379 +0,0 @@ -from __future__ import annotations - - -class XYOffsetCalibrationTool: - """Tool that helps the calibration of the XY offsets for Idex printers.""" - - def __init__(self, config): - self.printer = config.get_printer() - self.reactor = self.printer.get_reactor() - - # self.name = config.get_name().split()[-1] - - # * Register event handlers - self.printer.register_event_handler("klippy:connect", self.handle_connect) - self.printer.register_event_handler("klippy:ready", self.handle_ready) - - self.gcode = self.printer.lookup_object("gcode") - self.enable = False - self.last_state = False - - # * Control variables - self.prime: bool = False - self.line_number: int = 0 - self.line_spacing: float = 0.0 - self.line_height: float = 0.0 - self.line_top: float = 0.0 - self.initial_x_h: float = 0.0 - self.initial_y_h: float = 0.0 - self.initial_x_h_2: float = 0.0 - self.initial_y_h_2: float = 0.0 - self.initial_x_v: float = 0.0 - self.initial_y_v: float = 0.0 - self.initial_x_v_2: float = 0.0 - self.initial_y_v_2: float = 0.0 - self.extrude_per_mm: float = 0.0 - - self.horizontal_line_spacing: float = 0.0 - self.horizontal_line_height: float = 0.0 - self.horizontal_line_top: float = 0.0 - - self.vertical_line_spacing: float = 0.0 - self.vertical_line_height: float = 0.0 - self.vertical_line_top: float = 0.0 - - self.tool_2_x_offset: float = 0.0 - self.tool_2_y_offset: float = 0.0 - - self.filament_temperature: int = 190 - # * Register gcode commands - self.gcode.register_command( - "XYCALIBRATION", - self.cmd_XYCALIBRATION, - desc=self.cmd_XYCALIBRATION_help, - ) - - # self.gcode.register_mux_command( - # "XYNOZZLEPRIME", - # "XYOffsetCalibrationTool", - # self.name, - # self.cmd_XYNOZZLEPRIME, - # self.cmd_XYNOZZLEPRIME_help, - # ) - - def handle_connect(self): - """Event handler method for when klippy connects""" - self.toolhead = self.printer.lookup_object("toolhead") - # self.configfile = self.printer.lookup_object("configfile") - # self.manual_probe = self.printer.lookup_object("manual_probe") - # self.stepper_enable = self.printer.lookup_object("stepper_enable") - # self.mcu = self.printer.lookup_object("mcu") - # printer.register_event_handler("toolhead:set_position", - # self.reset_last_position) - # printer.register_event_handler("toolhead:manual_move", - # self.reset_last_position) - # printer.register_event_handler("gcode:command_error", - # self.reset_last_position) - # printer.register_event_handler("extruder:activate_extruder", - # self._handle_activate_extruder) - # printer.register_event_handler("homing:home_rails_end", - # self._handle_home_rails_end) - # printer.register_event_handler("gcode:request_restart", self._handle_request_restart) - - # self.printer.send_event("stepper_enable:motor_off", print_time) - - # gcmd.get_int("ENABLE", 1) - - # cur_time = self.printer.get_reactor().monotonic() - # kin_status = toolhead.get_kinematics().get_status(curtime) - - # toolhead.set_position(pos, homing_axes=[2]) - # toolhead.manual_move([None, None, self.z_hop], self.z_hop_speed) - # toolhead.get_kinematics().note_z_homed() - # need_x, need_y, need_z = [gcmd.get(axis, None) is not None for axis in "XYZ"] - # g28_cmd = self.gcode.create_gcode_command("G28", "G28", new_params) - # self.prev_G28(g28_cmd) - - # toolhead.get_position() - - def handle_ready(self): - """Event handler method for when klippy is enabled""" - self.toolhead = self.printer.lookup_object("toolhead") - self.kin = self.printer.lookup_object("toolhead").get_kinematics() - - - - # self.enable = True - # self.reactor.update_timer( - # self.update_direction_timer, self.reactor.Never - # ) - # self.enable = False - - cmd_XYCALIBRATION_help = "Handles the calibration of xy axes on Idex printers." - def cmd_XYCALIBRATION(self, gcmd): - # * First home axes - self.printer.send_event( - "stepper_enable:motor_on", self.printer.get_reactor().monotonic() - ) - gcmd.respond_info("XY calibration tool for Idex printers loaded.") - gcmd.respond_info(f"{self.toolhead.get_position()}") - - gcmd.respond_info(f"{self.get_homed_axes()}") - if self.get_homed_axes() is None or self.get_homed_axes() != "xy": - gcmd.respond_info("Must home axes before running the XY offset calibration for Idex printers.") - # return # I cannot continue if there is no homing - - - - _dual_carriage_module = self.kin.dc_module - - if _dual_carriage_module is None: - gcmd.respond_info("No dual carriage config section defined. Cannot run tool") - return - - - # Set extruder 0 - _dual_carriage_module.activate_dc_mode( - index= 0, - mode= "PRIMARY" #This is the default - ) - - _cur_extruder = self.toolhead.get_extruder() - _cur_extruder_name = _cur_extruder.get_name() - - - _available_printer_objects = self.printer.objects - gcmd.respond_info(f"Available Objects : {_available_printer_objects}") - gcmd.respond_info(f"Current extruder name : {_cur_extruder_name,}") - # Check that the kin type is cartesian - - # * Maybe home axes - # _homexy = "G28" - # # * Force usage of toolhead 0 T0 - # * Get extruder names, and activate the main toolhead - # gcmd.run_script_from_command("G28") - - # # * Horizontal Print in tool 0 - _horizontal_print_tool_0 = [ - "G90", - "M83", - f"G1 X{self.initial_x_h_2} Y{self.initial_y_h - 2} E{-0.1} F{12000}", # Brim - "G1 Z0.2 E0.5 F2000", # Brim - f"G1 X{self.initial_x_h} E{(self.initial_x_h_2 - self.initial_x_h) * self.extrude_per_mm} F3000", # U turn of the brim - f"G1 Y{self.initial_y_h} E{2 * self.extrude_per_mm}", # Start of the calibration - "G91", - ] - - # # self.print_horizontal_line - # # Raise the nozze G1 Z2 E-0.1 - - # # * Vertical Print in tool 0 - # _horizontal_print_tool_0 = [ - # "G90", - # "M83 ", - # f"G1 X{self.initial_x_v - 2} Y{self.initial_y_v} E{-0.1} F{12000}", # Brim - # "G1 Z0.2 E0.5 F2000", # Brim - # f"G1 X{self.initial_y_v} E{(self.initial_y_h_2 - self.initial_x_h) * self.extrude_per_mm} F3000", # U turn of the brim - # f"G1 Y{self.initial_y_h} E{2 * self.extrude_per_mm}", # Start of the calibration - # "G91", - # ] - - # # self.print_vertical_line - - # # Park the nozzle - # # G91 - # # park_nozzle - - # # Change to tool 1 - # # prime - # # * Horizontal print in tool 1 - - # _horizontal_print_tool_1 = [ - # "G90", - # "M83", - # f"G1 X{self.initial_x_h} Y{self.initial_y_h_2 - 2 + 2} E{-0.1} F{12000}", # Brim - # "G1 Z0.2 E0.5 F2000", # Lower nozzle for brim - # f"G1 X{self.initial_x_h_2} E{(self.initial_x_h_2 - self.initial_x_h) * self.extrude_per_mm} F3000", # U turn of the brim - # f"G1 Y{self.initial_y_h_2} E{2 * self.extrude_per_mm}", # Start of the calibration - # "G91", - # ] - # # self.print_horizontal_line - - # # Raise nozzle G1 Z2 E-0.1 - - # # * Vertical Print in tool 1 - # _horizontal_print_tool_0 = [ - # "G90", - # "M83 ", - # f"G1 X{self.initial_x_v_2 + 2} Y{self.initial_y_v} E{-0.1} F{12000}", # Brim - # "G1 Z0.2 E0.5 F2000", # Brim - # f"G1 X{self.initial_y_v_2} E{(self.initial_y_h_2 - self.initial_x_h) * self.extrude_per_mm} F3000", # U turn of the brim - # f"G1 Y{self.initial_x_v_2} E{2 * self.extrude_per_mm}", # Start of the calibration - # "G91", - # ] - # # self.print_vertical_line - - # # * Park the nozzle - # # G90 - # # G1 Y150 # So the user can see the result and select the line - - # # Change to tool 0 - # # T0 - - # # Permit the user to choose the line - # pass - - # cmd_OFFSETCALIBRATION_helpe = ( - # "calls a ui to select the ranges or something i don't know" - # ) - - # def cmd_OFFSETCALIBRATION(self, gcmd): - # pass - - # cmd_XYNOZZLEPRIME_help = "Primes the nozzle that it's going to be used" - - # def cmd_XYNOZZLEPRIME(self, gcmd): - # commands = [ - # f"M109 S{self.filament_temperature}", - # "G92 E0", # Specify that Extruder is at position 0 - # "M83", # Relative coordinates - # "G1 E50 F300", # Prime the nozzle - # "G92 E0", # Specify that Extruder is at position 0 - # ] - # for command_line in commands: - # gcmd.run_script_as_command(command_line) - - def get_status(self, eventtime): - return {"last_state": self.last_state, "enabled": self.enable} - - ################################################################################ - # Helper function that are useful for performing the XY calibration - ################################################################################ - - - def print_horizontal_line(self, gcmd, callback): - gcode_lines = [ - f"G1 X{self.horizontal_line_spacing} E{ abs(self.horizontal_line_spacing * self.extrude_per_mm)}", - f"G1 Y{self.horizontal_line_height} E{abs(self.horizontal_line_height * self.extrude_per_mm)}", - f"G1 X{self.horizontal_line_top} E{ abs(self.horizontal_line_top * self.extrude_per_mm)}", - f"G1 Y{int(self.horizontal_line_height) * -1} E{abs(self.horizontal_line_height * self.extrude_per_mm)}", - ] - for line in range(self.line_number + 1): - gcmd.run_command( - next(iter(gcode_lines)) - ) # Something like this to run all the lines - - def print_vertical_lines(self, gcmd, callback): - gcode_lines = [ - f"G1 Y{self.vertical_line_spacing} E{abs(self.vertical_line_spacing * self.extrude_per_mm)}", - f"G1 X{self.vertical_line_height} E{abs(self.vertical_line_height * self.extrude_per_mm)}", - f"G1 Y{self.vertical_line_top} E{abs(self.vertical_line_top * self.extrude_per_mm)}", - f"G1 X{int(self.vertical_line_height * -1)} E{abs(self.vertical_line_height * self.extrude_per_mm)}", - ] - for line in range(self.line_number + 1): - gcmd.run_command( - next(iter(gcode_lines)) - ) # Something like this to run all the lines - - def update_offsets(self): - pass - - def get_homed_axes(self) -> str | None: - cur_time = self.reactor.monotonic() - _kin_status = self.kin.get_status(cur_time) - - if not isinstance(_kin_status, dict): - return None - - if "homed_axes" in _kin_status.keys(): - return _kin_status["homed_axes"] - - return None - - # gcmd respond_info #Displays stuff on the console - # gcode run_script #Don't know what this one does Same as run_script_from_command but with mutex - # gcode run_script_from_command #Runs a command from a string - # toolhead move #Moves the toolhead - # toohead wait_moves() #Waits for the toolhead to finish its movements - - # When i have the tool head i can use the move command move(newpos, speed) - # There is also on toolhead a method called check_busy - # also a get_kinematics - # - - -def load_config(config): - return XYOffsetCalibrationTool(config) - - -# def load_config_prefix(config): -# return XYOffsetCalibrationTool(config) - - - -""" -From the kinmatics i can run - -get_steppers() -calc_position(self, stepper_positions) -set_position(self, newpos, homign_axes) -note_z_not_homed(self) -home(homign_state) -_motor_off(slef, pritne_time) -_check_positions(slef, move) -check_move(self, move) -get_status - - -variables: -self.rails -max_velocity, max_accel -max_z_velocity -max_z_accel -limits -ranges -axes_min -axes_max - - -From the main mcu class - - -canbus_uuid -setup_pin -create_oid(self) -get_printer -get_name -register_response(self, cb, msg, oid=None) -get_query_slot(self, oid) -seconds_to_clock(self, time) -get_max_stepper_error(self) -alloc_command_queue(self) - -lookup_command(self, msgformat, cp=None) -lookup_query_command(sef, msgformat, respformat, oid=None, cp=None, is_async=None) -estimate_print_time -get_contatns() -get_enumerations() -get_constants_float() -register_stepqueue(self, stepqueue) -request_move_queue_slot(self) -register_flush_callback(self, callback) -flush_move(self, printe_time, clear_history_time) -check_active() -is_fileoutput(self) -is_shutdown() -stats() - - -can also go get the extruder object so i can run some specific methods - - - - - - - -No final quando tiver-mos os valores, colocamos um gcode offset na segunda cabeça com o valor obtido -cada vez que cada cabeca e activada colocamos um offse, na cabela um e 0,0 e a segunda cabeca e o valor obtido -""" \ No newline at end of file diff --git a/klippy/extras/bucket.py b/klippy/extras/bucket.py index c6f6dd724bc6..4f152cea97f7 100644 --- a/klippy/extras/bucket.py +++ b/klippy/extras/bucket.py @@ -1,4 +1,3 @@ -import logging import typing @@ -29,7 +28,8 @@ def __init__(self, config): self.gcode.register_command( "MOVE_TO_BUCKET", self.cmd_MOVE_TO_BUCKET, - "Gcode for moving the toolhead to the bucket position. Takes into account if there is a custom bed boundary", + "Gcode for moving the toolhead to the bucket position." + "Takes into account if there is a custom bed boundary", ) def handle_ready(self): @@ -61,7 +61,6 @@ def move_to_bucket(self, split: typing.Optional["bool"] = False): "status", "" ) == "custom": self.custom_bed_bound_object.restore_default_boundary() - if not split: self.toolhead.manual_move( @@ -88,7 +87,8 @@ def move_to_bucket(self, split: typing.Optional["bool"] = False): self.custom_bed_bound_object.set_custom_boundary() except Exception as e: raise BucketMoveError( - f"Exception occurred when moving toolhead to bucket position: {e}" + "Exception occurred when moving " + f"toolhead to bucket position: {e}" ) def cmd_MOVE_TO_BUCKET(self, gcmd): @@ -97,7 +97,8 @@ def cmd_MOVE_TO_BUCKET(self, gcmd): class BucketMoveError(Exception): - "Raised when there is an exception moving the toolhead to the bucket" + """Raised when there is an exception + moving the toolhead to the bucket""" def __init__(self, message, errors=None): super(BucketMoveError, self).__init__(message) diff --git a/klippy/extras/cutter_sensor.py b/klippy/extras/cutter_sensor.py index a36f368e093f..070ffb5e7a1f 100644 --- a/klippy/extras/cutter_sensor.py +++ b/klippy/extras/cutter_sensor.py @@ -12,8 +12,6 @@ # This file may be distributed under the terms of the GNU GPLv3 license. import logging -import typing -from functools import partial class CutterSensorError(Exception): @@ -31,22 +29,20 @@ def __init__(self, config): self.printer = config.get_printer() self.reactor = self.printer.get_reactor() self.gcode = self.printer.lookup_object("gcode") - self.toolhead = None - self.printer.register_event_handler("klippy:connect", self._handle_connect) self.printer.register_event_handler("klippy:ready", self._handle_ready) - self.min_event_systime = self.reactor.NEVER self.filament_present = False self.sensor_enabled = True - self.is_cutting: bool = False + self.is_cutting = False self.prev_pos = None self._extrude_timeout = 30 self._timeout = 0 - + self.custom_boundary_object = None + self.load_filament_object = None + self.unload_filament_object = None self.pause_delay = config.getfloat("pause_delay", 0.5, above=0.0) self.event_delay = config.getfloat("event_delay", 3.0, above=0.0) self.runout_pause = config.getboolean("pause_on_runout", False) - self.extrude_length_mm = config.getfloat( "extrude_length_mm", 5.0, minval=1.0, maxval=100.0 ) @@ -63,9 +59,11 @@ def __init__(self, config): "travel_speed", 100.0, above=0.0, minval=30.0, maxval=600.0 ) self.cut_speed = config.getfloat( - "cut_speed", 100.0, above=50.0, minval=50.0, maxval=300.0 + "cut_speed", 100.0, above=0.0, minval=2.0, maxval=300.0 + ) + self.cutter_position = config.getfloatlist( + "cutter_position_xy", count=2 ) - self.cutter_position = config.getfloatlist("cutter_position_xy", count=2) self.pre_cutter_position = config.getfloatlist( "pre_cutter_position_xy", count=2 ) @@ -77,24 +75,28 @@ def __init__(self, config): gcode_macro = self.printer.load_object(config, "gcode_macro") if config.get("runout_gcode", None) is not None: - self.runout_gcode = gcode_macro.load_template(config, "runout_gcode", "") + self.runout_gcode = gcode_macro.load_template( + config, "runout_gcode", "" + ) if config.get("insert_gcode", None) is not None: - self.insert_gcode = gcode_macro.load_template(config, "insert_gcode") + self.insert_gcode = gcode_macro.load_template( + config, "insert_gcode" + ) if self.bucked_position_xy: - self.bucked_position_x, self.bucked_position_y = self.bucked_position_xy + self.bucked_position_x, self.bucked_position_y = ( + self.bucked_position_xy + ) self.cutter_position_x, self.cutter_position_y = self.cutter_position self.pre_cutter_position_x, self.pre_cutter_position_y = ( self.pre_cutter_position ) - cutter_sensor_pin = config.get("cutter_sensor_pin") buttons = self.printer.load_object(config, "buttons") buttons.register_debounce_button( cutter_sensor_pin, self._cutter_sensor_callback, config ) - self.unextrude_to_sensor_timer = self.reactor.register_timer( self.unextrude, self.reactor.NEVER ) @@ -116,9 +118,6 @@ def __init__(self, config): self.cmd_SET_FILAMENT_SENSOR_helper, ) - def _handle_connect(self) -> None: - self.toolhead = self.printer.lookup_object("toolhead") - def _handle_ready(self) -> None: self.min_event_systime = self.reactor.monotonic() + 2.0 self.custom_boundary_object = self.printer.lookup_object( @@ -152,19 +151,19 @@ def cmd_CUT(self, gcmd) -> None: Call CUT gcode command to perform the filament cutting """ - return_last_position = gcmd.get("MOVE_TO_LAST_POS", default=False, parser=bool) - turn_off_heaters = gcmd.get("TURN_OFF_HEATER", default=False, parser=bool) + return_last_position = gcmd.get( + "MOVE_TO_LAST_POS", default=False, parser=bool + ) + turn_off_heaters = gcmd.get( + "TURN_OFF_HEATER", default=False, parser=bool + ) temperature = gcmd.get( "TEMPERATURE", default=220.0, parser=float, minval=200, maxval=250 ) - - self.reactor.register_callback( - partial( - self.cut, - return_last_pos=return_last_position, - off_heaters=turn_off_heaters, - temp=temperature, - ) + self.cut( + return_last_pos=return_last_position, + off_heaters=turn_off_heaters, + temp=temperature, ) def cut( @@ -175,37 +174,37 @@ def cut( temp: int = 220, ) -> None: """Perform cut""" - if not self.toolhead: - return - + toolhead = self.printer.lookup_object("toolhead") if self.is_cutting: self.gcode.respond_info( f"[CUTTER {self.name} sensor] Already cutting filament" ) return - self.is_cutting = True self.home_needed() - self.toolhead.wait_moves() eventtime = self.reactor.monotonic() - kin_status = self.toolhead.get_kinematics().get_status(eventtime) - + kin_status = toolhead.get_kinematics().get_status(eventtime) if "xyz" not in kin_status["homed_axes"]: self.gcode.respond_info( - f"[CUTTER {self.name} sensor] Printer needs to be homed for filament cutting.", + f"[CUTTER {self.name} sensor] Printer " + "needs to be homed for filament cutting.", log=True, ) return - self.prev_pos = self.toolhead.get_position() - self.gcode.run_script_from_command("G90\nM400") + self.prev_pos = toolhead.get_position() + self.gcode.run_script_from_command("G91\nM400") self.gcode.run_script_from_command("M83\nM400") self.gcode.run_script_from_command("G92 E0.0\nM400") - self.gcode.respond_info(f"[CUTTER {self.name} sensor ] Heating extruder.") + self.gcode.respond_info( + f"[CUTTER {self.name} sensor ] Heating extruder." + ) self.heat_extruder(temp, wait=True) if self.bucked_position_xy: self.move_to_bucket() self.move_extruder_mm(distance=10, speed=self.extrude_speed) - self.move_extruder_mm(distance=self.retract_length_mm, speed=self.extrude_speed) + self.move_extruder_mm( + distance=self.retract_length_mm, speed=self.extrude_speed + ) if self.custom_boundary_object: self.custom_boundary_object.restore_default_boundary() self.move_to_cutter_pos() @@ -218,12 +217,14 @@ def cut( self.move_extruder_mm( distance=self.extrude_length_mm + 10, speed=self.extrude_speed ) - self.toolhead.wait_moves() - self.reactor.update_timer(self.unextrude_to_sensor_timer, self.reactor.NOW) + toolhead.wait_moves() + self.reactor.update_timer( + self.unextrude_to_sensor_timer, self.reactor.NOW + ) if self.prev_pos and return_last_pos: self.move_back() - self.toolhead.wait_moves() + toolhead.wait_moves() if self.custom_boundary_object: self.custom_boundary_object.set_custom_boundary() if off_heaters: @@ -234,60 +235,55 @@ def unextrude(self, eventtime): """Unextrude""" if not self.is_cutting: return self.reactor.NEVER - try: - self.move_extruder_mm(distance=-10, speed=self.extrude_speed, wait=False) - logging.info("[CUTTER] unextruding") - return float(eventtime + float(10 / self.extrude_speed)) - except Exception as e: - raise CutterSensorError( - f"[CUTTER {self.name} sensor] Error Unextruding: {e}" - ) + self.move_extruder_mm( + distance=-10, speed=self.extrude_speed, wait=False + ) + return eventtime + float(10 / self.extrude_speed) def move_extruder_mm(self, distance=10.0, speed=10, wait=True) -> None: """Move the extruder. Args: dist (float in mm): The distance in a certain amount. """ - if not self.toolhead: - return + toolhead = self.printer.lookup_object("toolhead") try: + toolhead = self.printer.lookup_object("toolhead") eventtime = self.reactor.monotonic() gcode_move = self.printer.lookup_object("gcode_move") - prev_pos = self.toolhead.get_position() + prev_pos = toolhead.get_position() v = distance * gcode_move.get_status(eventtime)["extrude_factor"] new_distance = v + prev_pos[3] - self.toolhead.move( + toolhead.move( [prev_pos[0], prev_pos[1], prev_pos[2], new_distance], speed ) if wait: - self.toolhead.wait_moves() + toolhead.wait_moves() except Exception as e: - raise CutterSensorError( - f"[CUTTER {self.name} sensor] Unable to move extruder error: {e}." + logging.info( + f"[CUTTER {self.name} sensor]" + f"Unable to move extruder error: {e}." ) def home_needed(self) -> None: - if not self.toolhead: + toolhead = self.printer.lookup_object("toolhead") + if not toolhead: raise CutterSensorError( "Toolhead object is missing, called on home_needed." ) try: eventtime = self.reactor.monotonic() - - kin = self.toolhead.get_kinematics() + kin = toolhead.get_kinematics() _homed_axes = kin.get_status(eventtime)["homed_axes"] - if "xyz" in _homed_axes.lower(): self.gcode.respond_info("Printer already homed.") return else: self.gcode.respond_info("Homing.") self.gcode.run_script_from_command("G28\nM400") - except Exception as e: raise CutterSensorError(f"Unable to home: {e}") - def heat_extruder(self, temp, wait: typing.Optional["bool"] = True) -> None: + def heat_extruder(self, temp, wait: bool = True) -> None: """Heats the extruder and wait. Method returns when temperature is [temp - 5 ; temp + 5]. @@ -295,17 +291,17 @@ def heat_extruder(self, temp, wait: typing.Optional["bool"] = True) -> None: temp (float): Target temperature in Celsius. wait (bool, optional): - Weather to wait or not for the temperature to reach the interval . Defaults to True + Weather to wait or not for the + temperature to reach the interval. + Defaults to True """ try: - if self.toolhead is None: - return + toolhead = self.printer.lookup_object("toolhead") eventtime = self.reactor.monotonic() - extruder = self.toolhead.get_extruder() + extruder = toolhead.get_extruder() pheaters = self.printer.lookup_object("heaters") pheaters.set_temperature(extruder.get_heater(), temp, False) extruder_heater = extruder.get_heater() - while not (self.printer.is_shutdown() and wait): self.gcode.respond_info("Waiting for temperature to stabilize.") heater_temp, target = extruder_heater.get_temp(eventtime) @@ -318,176 +314,128 @@ def heat_extruder(self, temp, wait: typing.Optional["bool"] = True) -> None: def cut_move(self) -> None: """Performs the cut movement""" try: - if not self.toolhead: - return - self.toolhead.manual_move( + toolhead = self.printer.lookup_object("toolhead") + toolhead.manual_move( [self.cutter_position_x, self.cutter_position_y], self.travel_speed, ) - self.toolhead.manual_move( + toolhead.manual_move( [self.pre_cutter_position_x, self.pre_cutter_position_y], self.cut_speed, ) - self.toolhead.wait_moves() + toolhead.wait_moves() except Exception as e: - raise CutterSensorError(f"Error performing performing cut move: {e}.") + raise CutterSensorError( + f"Error performing performing cut move: {e}." + ) def move_to_cutter_pos(self) -> None: """Moves the toolhead to the pre cutting position""" try: - if not self.toolhead: - return + toolhead = self.printer.lookup_object("toolhead") curtime = self.reactor.monotonic() - kin_status = self.toolhead.get_kinematics().get_status(curtime) + kin_status = toolhead.get_kinematics().get_status(curtime) if "xyz" not in kin_status["homed_axes"]: return - self.toolhead.manual_move( + toolhead.manual_move( [self.pre_cutter_position_x, self.pre_cutter_position_y], self.travel_speed, ) - self.toolhead.wait_moves() + toolhead.wait_moves() except Exception as e: raise CutterSensorError(f"Error moving to cutter position: {e}.") def move_home(self) -> None: try: - if not self.toolhead: - return + toolhead = self.printer.lookup_object("toolhead") """Moves to the homing position""" gcode_move = self.printer.lookup_object("gcode_move") homing_origin = gcode_move.get_status()["homing_origin"] - self.toolhead.manual_move(homing_origin, self.travel_speed) + toolhead.manual_move(homing_origin, self.travel_speed) except Exception as e: raise CutterSensorError(f"Error moving to home position: {e}.") def move_to_bucket(self, split=False) -> None: """Moves to the bucket position""" try: - if not self.toolhead: - return + toolhead = self.printer.lookup_object("toolhead") if self.custom_boundary_object: - self.gcode.respond_info("Restoring original printer Boundaries.") + self.gcode.respond_info( + "Restoring original printer Boundaries." + ) self.custom_boundary_object.restore_default_boundary() - if not split: - self.toolhead.manual_move( + toolhead.manual_move( [self.bucked_position_x, self.bucked_position_y], self.travel_speed, ) else: - self.toolhead.manual_move([self.bucked_position_x], self.travel_speed) - self.toolhead.wait_moves() - self.toolhead.manual_move([self.bucked_position_y], self.travel_speed) - - self.toolhead.wait_moves() + toolhead.manual_move( + [self.bucked_position_x], self.travel_speed + ) + toolhead.wait_moves() + toolhead.manual_move( + [self.bucked_position_y], self.travel_speed + ) + toolhead.wait_moves() except Exception as e: raise CutterSensorError(f"Error moving to bucket position: {e}.") def move_back(self): - """Moves back to the original position where the CUT gcode command was called""" + """Moves back to the original position + where the CUT gcode command was called""" try: - if not self.prev_pos or not self.toolhead: + toolhead = self.printer.lookup_object("toolhead") + if not self.prev_pos: return - - self.toolhead.manual_move( + toolhead.manual_move( [self.prev_pos[0], self.prev_pos[1], self.prev_pos[2]], self.travel_speed, ) - self.toolhead.wait_moves() + toolhead.wait_moves() except Exception as e: - raise CutterSensorError(f"Error moving to the original position: {e}.") + raise CutterSensorError( + f"Error moving to the original position: {e}" + ) def _cutter_sensor_callback(self, eventtime, state): """Callback for the sensor state change""" if self.filament_present == state: return self.filament_present = state - - logging.info(f"[CUTTER {self.name} Sensor] state changed to {str(state)}") - + logging.info( + f"[CUTTER {self.name} Sensor] state changed to {str(state)}" + ) eventtime = self.reactor.monotonic() if ( eventtime < self.min_event_systime ): # Can also add self.enabled, if not enabled does nothing return - + self.min_event_systime = self.reactor.NEVER idle_timeout = self.printer.lookup_object("idle_timeout") is_printing = ( idle_timeout.get_status(eventtime)["state"] == "Printing" ) # state can be either ["Ready", "Printing", "Idle"] - - if ( - self.load_filament_object - and self.load_filament_object.get_status(eventtime).get("state", False) - ) or ( - self.unload_filament_object - and self.unload_filament_object.get_status(eventtime).get("state", False) - ): - if state: - self.printer.send_event("cutter_sensor:filament_present") - self.gcode.respond_info( - f"[CUTTER {self.name}] detected filament while loading filament" - ) - logging.info( - f"[CUTTER {self.name}] detected filament while loading filament" - ) - else: - if self.is_cutting: - self.reactor.update_timer( - self.unextrude_to_sensor_timer, self.reactor.NEVER - ) - self.is_cutting = False - self.gcode.run_script_from_command("G90\nM400") - self.gcode.run_script_from_command("M83\nM400") - self.gcode.run_script_from_command("G92 E0.0\nM400") - self.gcode.respond_info(f"[CUTTER {self.name} sensor] Cut done.") - self.printer.send_event("cutter_sensor:no_filament") - self.min_event_systime = self.reactor.monotonic() + 2.0 - - elif state: - if not is_printing and self.insert_gcode is not None: - self.printer.send_event("cutter_sensor:filament_present") - # self.min_event_systime = self.reactor.NEVER - self.min_event_systime = self.reactor.monotonic() + 2.0 - logging.info( - f"[CUTTER {self.name} sensor] Filament Detected | Time: {eventtime}" + if state: + self.printer.send_event("cutter_sensor:filament_present") + logging.info("Cutter sensor reports filament present") + self.gcode.respond_info("cutter sensor reports filament present") + else: + self.gcode.respond_info("Cutter sensor reports no filament") + if self.is_cutting: + self.reactor.update_timer( + self.unextrude_to_sensor_timer, self.reactor.NEVER ) - # self.reactor.register_callback(self._insert_event_handler) - - elif not is_printing and self.runout_gcode is not None: - # runout detected - - # self.min_event_systime = self.reactor.NEVER - self.min_event_systime = self.reactor.monotonic() + 2.0 - # if self.is_cutting: - # self.reactor.update_timer( - # self.unextrude_to_sensor_timer, self.reactor.NEVER - # ) - # self.is_cutting = False - self.printer.send_event("cutter_sensor:no_filament") - logging.info( - f"[CUTTER {self.name} sensor] Filament Not Detected | Time: {eventtime}" - ) - self.gcode.run_script_from_command("G90\nM400") - self.gcode.run_script_from_command("M83\nM400") - self.gcode.run_script_from_command("G92 E0.0\nM400") - elif ( - is_printing and self.runout_gcode is not None - ): # Runout detected while printing - # self.min_event_systime = self.reactor.NEVER - self.min_event_systime = self.reactor.monotonic() + 2.0 - + self.is_cutting = False + self.gcode.run_script_from_command("G90") + self.gcode.run_script_from_command("M83") + self.gcode.run_script_from_command("G92 E0.0") + logging.info("Cut done") + self.gcode.respond_info("Cut Done!") self.printer.send_event("cutter_sensor:no_filament") - logging.info( - f"[CUTTER {self.name} sensor] Filament Not Detected | Time: {eventtime}" - ) - # self.reactor.register_callback(self._runout_event_handler) - self.gcode.run_script_from_command("G90\nM400") - self.gcode.run_script_from_command("M83\nM400") - self.gcode.run_script_from_command("G92 E0.0\nM400") - return + self.min_event_systime = self.reactor.monotonic() + 2.0 def _insert_event_handler(self, eventtime): self._exec_gcode("", self.insert_gcode) @@ -502,12 +450,14 @@ def _runout_event_handler(self, eventtime): self._exec_gcode(pause_prefix, self.runout_gcode) def _exec_gcode(self, prefix, template): - """Internal Executes a gcode just like what's in the klipper filament_switch_sensor.py""" + """Internal Executes a gcode just like + what's in the klipper filament_switch_sensor.py""" try: self.gcode.run_script(prefix + template.render() + "\nM400") except Exception: logging.exception("Script running error") + self.min_event_systime = self.reactor.monotonic() + self.event_delay def get_status(self, eventtime): diff --git a/klippy/extras/load_filament.py b/klippy/extras/load_filament.py index 31846e756a49..34cc24885d38 100644 --- a/klippy/extras/load_filament.py +++ b/klippy/extras/load_filament.py @@ -3,15 +3,9 @@ from functools import partial -class LoadFilamentError(Exception): - """Raised when there is an error loading filament""" - - def __init__(self, message, errors): - super(LoadFilamentError, self).__init__(message) - self.errors = errors - - class LoadFilament: + steps = {"None", "heating", "pulling", "purging"} + def __init__(self, config): self.printer = config.get_printer() self.reactor = self.printer.get_reactor() @@ -25,25 +19,19 @@ def __init__(self, config): self.filament_switch_sensor_name = ( self.filament_switch_sensor_object ) = None - self.filament_flow_sensor_name = self.filament_flow_sensor_object = ( - None - ) - self.load_started: bool = False - self.current_purge_index: int = 0 + self.filament_flow_sensor_name = self.filament_flow_sensor_object = None + self.load_started= False + self.current_purge_index = 0 self.travel_speed = None - self._old_extrude_distance: float | None = None - self.extrude_count: int = 0 - - self.printer.register_event_handler( - "klippy:connect", self.handle_connect - ) + self._old_extrude_distance = None + self.extrude_count = 0 self.printer.register_event_handler("klippy:ready", self.handle_ready) - + self.min_event_systime = self.reactor.NEVER self.idex = config.getboolean("idex", False) self.has_custom_boundary = config.getboolean( "has_custom_boundary", False ) - + self.step = "None" if config.get("filament_flow_sensor_name", None): self.filament_flow_sensor_name = config.get( "filament_flow_sensor_name" @@ -89,7 +77,7 @@ def __init__(self, config): "purge_interval", default=3.0, minval=0.5, maxval=10.0 ) self.extrude_timeout = config.getint( - "extrude_max_count", default=None, minval=2, maxval=1000 + "dist_timeout", default=None, minval=2, maxval=1000 ) # * Callback Timers @@ -112,7 +100,8 @@ def __init__(self, config): "TOOLHEAD", self.name, self.cmd_LOAD_FILAMENT, - "GCODE MACRO to load filament, takes into account if there is a belay and or a filament cutter with a sensor.", + "GCODE MACRO to load filament, takes into account" + "if there is a belay and or a filament cutter with a sensor.", ) self.gcode.register_mux_command( "PURGE_STOP", @@ -122,9 +111,6 @@ def __init__(self, config): "Helper gcode command that stop filament purging", ) - def handle_connect(self): - self.toolhead = self.printer.lookup_object("toolhead") - def handle_ready(self): self.min_event_systime = self.reactor.monotonic() + 2.0 if self.bucket: @@ -143,14 +129,9 @@ def handle_ready(self): self.handle_cutter_filament_present, ) logging.info( - "[LOAD FILAMENT EXTRA] Cutter Sensor recognized, cutter sensor state event handlers set" + "[LOAD FILAMENT EXTRA] Cutter Sensor recognized," + "cutter sensor state event handlers set" ) - # if self.printer.lookup_object("unload_filament", None) is not None: - # self.printer.register_event_handler( - # "unload_filament", self.handle_filament_unload - # ) - # logging.info("[LOAD FILAMENT EXTRA] Unload Extra recognized") - if self.idex: self.idex_object = self.printer.lookup_object("dual_carriage") logging.info("[LOAD FILAMENT EXTRA] Idex machine recognized") @@ -160,7 +141,8 @@ def handle_ready(self): default=None, ) logging.info( - f"[LOAD FILAMENT EXTRA] Filament flow sensor {self.filament_flow_sensor_name} recognized" + "[LOAD FILAMENT EXTRA] Filament flow sensor " + f"{self.filament_flow_sensor_name} recognized" ) if self.filament_switch_sensor_name: self.filament_switch_sensor_object = self.printer.lookup_object( @@ -168,7 +150,8 @@ def handle_ready(self): default=None, ) logging.info( - f"[LOAD FILAMENT EXTRA] Filament switch sensor {self.filament_switch_sensor_name} recognized" + f"[LOAD FILAMENT EXTRA] Filament switch sensor" + f" {self.filament_switch_sensor_name} recognized" ) if self.has_custom_boundary: self.custom_boundary_object = self.printer.lookup_object( @@ -179,29 +162,29 @@ def handle_ready(self): ) def handle_cutter_filament_present(self, eventtime=None) -> None: - if not self.load_started or not self.cutter_object: + if not self.load_started: + return + if not self.cutter_object: return - self.gcode.respond_info("Filament reached toolhead") - logging.info( - "[LOAD FILAMENT EXTRA] 'filament_present' event received from cutter sensor" - ) self.reactor.update_timer( self.extrude_to_sensor_timer, self.reactor.NEVER ) self.reactor.update_timer(self.extrude_purge_timer, self.reactor.NOW) - return def verify_switch_sensor_state(self, eventtime): if not self.load_started or not self.filament_switch_sensor_object: return self.reactor.NEVER - if self.filament_switch_sensor_object.get_status(eventtime)[ "filament_detected" ]: + self.gcode.respond("Filament reached sensor") self.reactor.update_timer( self.extrude_to_sensor_timer, self.reactor.NEVER ) + self.reactor.update_timer( + self.verify_switch_sensor_timer, self.reactor.NEVER + ) self.move_extruder_mm( self.extruder_to_nozzle_dist, speed=30, wait=True ) @@ -209,32 +192,34 @@ def verify_switch_sensor_state(self, eventtime): self.extrude_purge_timer, self.reactor.NOW ) return self.reactor.NEVER - else: - return eventtime + 1.50 + return eventtime + 0.80 def verify_flow_sensor_state(self, eventtime): - if self.load_started and self.filament_flow_sensor_object: - if self.filament_flow_sensor_object.runout_helper.get_status( - eventtime - )["filament_detected"]: - self.reactor.update_timer( - self.extrude_to_sensor_timer, self.reactor.NEVER - ) - self.move_extruder_mm( - self.extruder_to_nozzle_dist, speed=30, wait=True - ) - self.reactor.update_timer( - self.extrude_purge_timer, self.reactor.NOW - ) - return self.reactor.NEVER - return eventtime + 0.775 + if not self.load_started or not self.filament_flow_sensor_object: + return self.reactor.NEVER + if self.filament_flow_sensor_object.runout_helper.get_status(eventtime)[ + "filament_detected" + ]: + self.reactor.update_timer( + self.extrude_to_sensor_timer, self.reactor.NEVER + ) + self.move_extruder_mm( + self.extruder_to_nozzle_dist, speed=30, wait=True + ) + self.reactor.update_timer( + self.extrude_purge_timer, self.reactor.NOW + ) + return self.reactor.NEVER + return eventtime + 0.8 def load(self, eventtime): if not self.load_started: return self.reactor.NEVER - if self.extrude_timeout: if self.extrude_count >= self.extrude_timeout: + self.reactor.update_timer( + self.extrude_to_sensor_timer, self.reactor.NEVER + ) self.move_extruder_mm( self.extruder_to_nozzle_dist, speed=30, wait=True ) # Extrude to the nozzle @@ -243,22 +228,26 @@ def load(self, eventtime): ) return self.reactor.NEVER self.extrude_count += 1 - self.move_extruder_mm(distance=10, speed=self.load_speed, wait=False) + self.step = "pulling" return eventtime + float(10 / self.load_speed) def purge_extrude(self, eventtime): + self.step = "purging" if not self.load_started: + self.gcode.respond_info( + "Filament loading has not started on purging loading" + ) return self.reactor.NEVER - if self.current_purge_index >= self.purge_max_retries: - self.gcode.respond_info("[LOAD FILAMENT] End.") + self.reactor.update_timer( + self.extrude_purge_timer, self.reactor.NEVER + ) + self.gcode.respond_info("Filament Loaded") completion = self.reactor.register_callback(self._purge_end) completion.wait() return self.reactor.NEVER - self.gcode.respond_info( - f"[LOAD FILAMENT] Purging....{self.current_purge_index + 1}" - ) + self.gcode.respond_info(f"Purging....{self.current_purge_index + 1}") self.move_extruder_mm( distance=self.purge_distance, speed=self.purge_speed ) @@ -267,38 +256,31 @@ def purge_extrude(self, eventtime): def _purge_end(self, eventtime): self.reactor.update_timer(self.extrude_purge_timer, self.reactor.NEVER) - + toolhead = self.printer.lookup_object("toolhead") if self.park is not None: - self.toolhead.manual_move( + toolhead.manual_move( [self.park[0], self.park[1]], self.travel_speed ) - self.toolhead.wait_moves() - - if ( - self.has_custom_boundary - and self.custom_boundary_object is not None - ): + toolhead.wait_moves() + if self.has_custom_boundary and self.custom_boundary_object is not None: if self.custom_boundary_object.get_status()["status"] == "default": self.custom_boundary_object.set_custom_boundary() - self._old_extrude_distance = None self.current_purge_index = 0 self.load_started = False - self.printer.send_event("load_filament:end") - self.gcode.run_script_from_command("G91\nM400") - self.gcode.run_script_from_command("M83\nM400") + self.gcode.run_script_from_command("G91") + self.gcode.run_script_from_command("M83") # relative self.gcode.run_script_from_command( - "G92 E0.0\nM400" + "G92 E0.0" ) # Restore extruder position - self.gcode.run_script_from_command("M82\nM400") - self.toolhead.wait_moves() - self.gcode.respond_info("Restoring gcode positions.") self.restore_state() - self.toolhead.wait_moves() + self.gcode.respond_info("Restoring gcode positions.") + toolhead.wait_moves() self.heat_and_wait(0, wait=False) - if self.idex: - self.gcode.run_script_from_command("T0 PARK\nM400") + self.gcode.run_script_from_command("T0 PARK") + self.step = "None" + self.printer.send_event("load_filament:end") def move_extruder_mm(self, distance=10.0, speed=30.0, wait=True) -> None: """Move the extruder @@ -307,38 +289,38 @@ def move_extruder_mm(self, distance=10.0, speed=30.0, wait=True) -> None: distance (float): The distance in mm to move the extruder. """ try: + toolhead = self.printer.lookup_object("toolhead") eventtime = self.reactor.monotonic() gcode_move = self.printer.lookup_object("gcode_move") - prev_pos = self.toolhead.get_position() + prev_pos = toolhead.get_position() v = distance * gcode_move.get_status(eventtime)["extrude_factor"] new_distance = v + prev_pos[3] - self.toolhead.manual_move( + toolhead.manual_move( [prev_pos[0], prev_pos[1], prev_pos[2], new_distance], speed ) if wait: - self.toolhead.wait_moves() + toolhead.wait_moves() return except Exception: logging.error("Unexpected error while trying to move extruder.") - return def move_home(self): """Move the toolhead to the home position (To the park position)""" - if self.toolhead is None: + toolhead = self.printer.lookup_object("toolhead", None) + if toolhead is None: return - self.toolhead.manual_move( - [self.park[0], self.park[1]], self.travel_speed - ) - self.toolhead.wait_moves() + toolhead.manual_move([self.park[0], self.park[1]], self.travel_speed) + toolhead.wait_moves() def home_needed(self): """Perform home if needed""" - if self.toolhead is None: + toolhead = self.printer.lookup_object("toolhead", None) + if toolhead is None: return try: eventtime = self.reactor.monotonic() - kin = self.toolhead.get_kinematics() + kin = toolhead.get_kinematics() _homed_axes = kin.get_status(eventtime)["homed_axes"] if "xyz" in _homed_axes.lower(): return @@ -358,13 +340,17 @@ def heat_and_wait(self, temp, wait: typing.Optional["bool"] = False): temp (float): Target temperature in Celsius. wait (bool, optional): - Weather to wait or not for the temperature to reach the interval . Defaults to True + Weather to wait or not for the + temperature to reach the interval. + Defaults to True """ eventtime = self.reactor.monotonic() - extruder = self.toolhead.get_extruder() + toolhead = self.printer.lookup_object("toolhead") + extruder = toolhead.get_extruder() pheaters = self.printer.lookup_object("heaters") extruder_heater = extruder.get_heater() pheaters.set_temperature(extruder.get_heater(), temp, False) + self.step = "heating" while not self.printer.is_shutdown() and wait: heater_temp, _ = extruder_heater.get_temp(eventtime) if heater_temp >= (temp - 5) and heater_temp <= (temp + 5): @@ -376,43 +362,42 @@ def disable_sensors(self): return if self.filament_flow_sensor_object is not None: self.filament_flow_sensor_object.runout_helper.sensor_enabled = 0 - if self.filament_switch_sensor_object is not None: self.filament_switch_sensor_object.runout_helper.sensor_enabled = 0 - return def enable_sensors(self): if self.filament_flow_sensor_object is not None: self.filament_flow_sensor_object.runout_helper.sensor_enabled = 1 - if self.filament_switch_sensor_object is not None: self.filament_switch_sensor_object.runout_helper.sensor_enabled = 1 def save_state(self): - """Save gcode state and dual carriage state if the system is in IDEX configuration""" + """Save gcode state and dual carriage state if the system + is in IDEX configuration""" if self.idex: self.gcode.run_script_from_command( - f"SAVE_DUAL_CARRIAGE_STATE NAME=load_carriage_state_{self.name}\nM400" + f"SAVE_DUAL_CARRIAGE_STATE NAME=load_carriage_state_{self.name}" ) - self.gcode.run_script_from_command( - "SAVE_GCODE_STATE NAME=_LOAD_STATE\nM400" - ) + self.gcode.run_script_from_command("SAVE_GCODE_STATE NAME=_LOAD_STATE") return True def restore_state(self): - """Restore gcode state and dual carriage state if the system is in IDEX configuration""" + """Restore gcode state and dual carriage state if the system + is in IDEX configuration""" self.gcode.run_script_from_command( - "RESTORE_GCODE_STATE NAME=_LOAD_STATE MOVE=0\nM400" + "RESTORE_GCODE_STATE NAME=_LOAD_STATE MOVE=0" ) if self.idex: self.gcode.run_script_from_command( - f"RESTORE_DUAL_CARRIAGE_STATE NAME=load_carriage_state_{self.name}\nM400" + "RESTORE_DUAL_CARRIAGE_STATE " \ + f"NAME=load_carriage_state_{self.name}" ) return True - cmd_PURGE_STOP_HELPER = """Helper gcode command to stop filament purging when in the Load filament routine""" + cmd_PURGE_STOP_HELPER = """Helper gcode command to stop filament purging"\ + "when in the Load filament routine""" def cmd_PURGE_STOP(self, gcmd): if self.load_started: @@ -427,41 +412,38 @@ def cmd_LOAD_FILAMENT(self, gcmd): "TEMPERATURE", 220.0, parser=float, minval=210, maxval=250 ) try: + pstat = self.printer.lookup_object("print_stats") + pstat_state = pstat.get_status(self.reactor.monotonic())["state"] + is_printing = pstat_state == "printing" + if is_printing: + self.printer.command_error("Cannot load while printing") + return if self.load_started: - self.gcode.respond_info("Already loading filament") + self.gcode.respond_info("Filament already loading") return - self.home_needed() self.save_state() self.disable_sensors() - self.load_started = True self.printer.send_event("load_filament:start") - self.gcode.respond_info("[LOAD FILAMENT] Start") - + self.gcode.respond_info("Filament Loading start") # * Select the head -> Meant for Idex systems if self.idex: if gcmd.get("TOOLHEAD") == "Load_T0": self.gcode.run_script_from_command("T0 LOAD") else: self.gcode.run_script_from_command("T1 LOAD") - - self.gcode.run_script_from_command("G91\nM400") - self.gcode.run_script_from_command("M83\nM400") - - self.extrude_count = 0 - + self.gcode.run_script_from_command("G91") + self.gcode.run_script_from_command("M83") + self.gcode.run_script_from_command("G92 E0.0") + if self.extrude_timeout: + self.extrude_count = 0 if self.custom_boundary_object is not None: self.custom_boundary_object.restore_default_boundary() - self.heat_and_wait(temp, wait=False) - if self.bucket_object is not None: self.bucket_object.move_to_bucket() - self.heat_and_wait(temp, wait=True) - self.toolhead.wait_moves() - # * Force the motion sensor to "No Filament" state if self.filament_flow_sensor_object is not None: self.reactor.register_callback( @@ -471,27 +453,27 @@ def cmd_LOAD_FILAMENT(self, gcmd): ) ) self.move_extruder_mm(distance=30, speed=40, wait=True) - # * Actually start loading the machine self.reactor.update_timer( self.extrude_to_sensor_timer, self.reactor.NOW ) - if self.filament_flow_sensor_object is not None: self.reactor.update_timer( self.verify_flow_sensor_timer, self.reactor.NOW ) - if self.filament_switch_sensor_object is not None: self.reactor.update_timer( self.verify_switch_sensor_timer, self.reactor.NOW ) - - except LoadFilamentError as e: + except Exception as e: logging.error(f"[LOADING FILAMENT ERROR]: {e}") def get_status(self, eventtime): - return {"state": bool(self.load_started)} + return { + "state": bool(self.load_started), + "step": self.step, + "avail_steps": list(self.steps), + } def load_config_prefix(config): diff --git a/klippy/extras/repetability.py b/klippy/extras/repetability.py deleted file mode 100644 index b1c560d42296..000000000000 --- a/klippy/extras/repetability.py +++ /dev/null @@ -1,176 +0,0 @@ -import pandas as pd -import logging -import os -import stepper - -class Repetability: - DATAFRAME_SETUP = [ - "timestamp", - "iteration", - "motion_report_live_position", - "motion_report_velocity", - "gcode_move_absolute_coordinates", - "gcode_move_homing_origin", - "gcode_move_position", - "toolhead_position", - "kin_homed_axes", - "kin_axis_minimum", - "kin_axis_maximum", - "limits", - "kin_pos" - ] - def __init__(self, config): - - self.printer = config.get_printer() - self.reactor = self.printer.get_reactor() - self.toolhead = None - self.gcode = self.printer.lookup_object("gcode") - - self.start_position= config.getfloatlist("start_position",None, count=2) - self.end_position= config.getfloatlist("end_position",None, count=2) - self.bed_boundary = config.getboolean("bed_boundary", False) - - self.movement = config.getboolean("movement", False) - self.z_raise = config.getfloat("z_raise") - self.travel_speed = config.getfloat("travel_speed", minval=50.0, maxval=500.0) - self.iterations = config.getint("iterations", minval=1, maxval=1000) - self.save_path = config.get("save_path") - - self.gcode.register_command( - "TEST_ENDSTOP", - self.cmd_TEST_ENDSTOP, - "Tests an z enstop" - ) - - self.data_save: pd.DataFrame = pd.DataFrame(columns=self.DATAFRAME_SETUP) - - self.printer.register_event_handler("klippy:connect", self.handle_connect) - - - def cmd_TEST_ENDSTOP(self, gcmd): - if self.toolhead is None: - return - if self.bed_boundary: - if self.printer.lookup_object("bed_custom_bound") is not None: - self.bed_boundary_object = self.printer.lookup_object("bed_custom_bound") - - for iteration in range(0, self.iterations): - if self.bed_boundary: - self.bed_boundary_object.restore_default_boundary() - - gcmd.respond_info(f"[TEST REPEATABILITY] Iteration: {iteration}") - - # self.move(x=self.start_position[0],y= self.start_position[1]) - - self.gcode.run_script_from_command("G28 \nM400") - - self.save_info(iteration) - - - self.reactor.register_callback(self.save_to_file) - - def handle_connect(self): - self.toolhead = self.printer.lookup_object("toolhead") - - - - def move(self, x, y): - if self.toolhead is None: - return - self.toolhead.manual_move([x, y], self.travel_speed) - - def get_kinematics_pos(self): - self.toolhead.flush_step_generation() - kin = self.toolhead.flush_step_generation() - kin_spos = {s.get_name(): s.get_commanded_position() for s in kin.get_steppers()} - kin_pos = kin.calc_position(kin_spos) - return kin_pos - - - def save_info(self, iteration): - eventtime = self.reactor.monotonic() - _gcode_move_status = self.get_gcode_move_status(eventtime) - _toolhead_status = self.get_toolhead_status(eventtime) - _extruder_status = self.get_extruder_status(eventtime) - _motion_report_status = self.get_motion_report_status(eventtime) - _kin = self.toolhead.get_kinematics() - _kin_status = _kin.get_status(eventtime) - - _row_data = [ - self.reactor.monotonic(), - iteration, - _motion_report_status["live_position"], - _motion_report_status["live_velocity"], - _gcode_move_status["absolute_coordinates"], - _gcode_move_status["homing_origin"], - _gcode_move_status["position"], - _toolhead_status["position"], - _kin_status["homed_axes"], - _kin_status["axis_minimum"], - _kin_status["axis_maximum"], - _kin.limits, - self.get_kinematics_pos() - ] - - _new_row_entry: pd.DataFrame = pd.DataFrame( - [_row_data], columns=self.data_save.columns - ) - - # * Update the csv file. - self.data_save = pd.concat( - [ - self.data_save, - _new_row_entry, - ], - ignore_index=True, - ) - - return - - def save_to_file(self, eventtime): - """Saves the print data dataframe into a csv file - - Returns: - bool: True if saved, False if an exception occurred - """ - try: - self.data_save.to_csv( - os.path.join(self.save_path, "repetability_test.csv"), - index=False, - ) - return True - except Exception as e: - logging.error( - f"Exception occurred when saving print data to csv file. MSG= {e}" - ) - # raise EdnaException("Error saving dataframe to directory") - return False - - - -################################################################################ - def get_gcode_move_status(self, eventtime): - gcode_move = self.printer.lookup_object("gcode_move") - return gcode_move.get_status(eventtime) - - def get_toolhead_status(self, eventtime): - toolhead = self.printer.lookup_object("toolhead") - return toolhead.get_status(eventtime) - - def get_extruder_status(self, eventtime): - toolhead = self.printer.lookup_object("toolhead") - extruder = toolhead.get_extruder() - return extruder.get_status(eventtime) - - def get_motion_report_status(self, eventtime): - motion_report = self.printer.lookup_object("motion_report") - return motion_report.get_status(eventtime) - - def get_kin_status(self, eventtime): - toolhead = self.printer.lookup_object("toolhead") - kin = toolhead.get_kinematics() - return kin.get_status(eventtime) - - -def load_config(config): - return Repetability(config) \ No newline at end of file diff --git a/klippy/extras/toolhead_fan_duct_sensor.py b/klippy/extras/toolhead_fan_duct_sensor.py deleted file mode 100644 index a92d465675a8..000000000000 --- a/klippy/extras/toolhead_fan_duct_sensor.py +++ /dev/null @@ -1,29 +0,0 @@ -import logging -import typing - - -class ToolheadFanDuctSensor: - def __init__(self, config): - self.printer = config.get_printer() - self.name = config.get_name().split()[-1] - self.reactor = self.printer.get_reactor() - self.gcode = self.printer.lookup_object("gcode") - - # * Register fan duct sensor - duct_sensor_pin = config.get("sensor_pin") - buttons = self.printer.load_object(config, "buttons") - buttons.register_buttons([duct_sensor_pin], self.fan_duct_sensor_handler) - - def fan_duct_sensor_handler(self, eventtime, state): - if not state: - return - - if state: - # * Stop the printer - self.printer.invoke_shutdown( - msg="Toolhead Fan duct is currently open, close the fan duct and restart" - ) - - -def load_config_prefix(config): - return ToolheadFanDuctSensor(config) diff --git a/klippy/extras/unload_filament.py b/klippy/extras/unload_filament.py index 22211da6952a..ee170983b3dd 100644 --- a/klippy/extras/unload_filament.py +++ b/klippy/extras/unload_filament.py @@ -6,12 +6,14 @@ class UnloadFilamentError(Exception): """Raised when there is an error unloading filament""" - def __init__(self, message, errors: typing.Optional[list] = None): + def __init__(self, message, errors: list[str] | None = None): super(UnloadFilamentError, self).__init__(message) self.errors = errors class UnloadFilament: + steps = {"None", "heating", "pulling", "cutting"} + def __init__(self, config): self.printer = config.get_printer() self.reactor = self.printer.get_reactor() @@ -20,18 +22,15 @@ def __init__(self, config): self.min_event_systime = None self.bucket_object = None self.cutter_object = None - self.filament_flow_sensor_object = self.filament_flow_sensor_name = ( - None - ) + self.filament_flow_sensor_object = self.filament_flow_sensor_name = None self.filament_switch_sensor_object = ( self.filament_switch_sensor_name ) = None + self.idex_object = None self.unload_started = False - self.unextrude_count: int = 0 + self.unextrude_count = 0 self.travel_speed = None - self.printer.register_event_handler( - "klippy:connect", self.handle_connect - ) + self.step = "None" self.printer.register_event_handler("klippy:ready", self.handle_ready) self.printer.register_event_handler( "unload_filament:end", @@ -74,23 +73,18 @@ def __init__(self, config): self.gcode.register_command( "UNLOAD_FILAMENT", self.cmd_UNLOAD_FILAMENT, - "GCODE Macro to unload filament, takes into account if there is a belay and or a filament cutter with a sensor", + "GCODE Macro to unload filament, takes into account if" + "there is a belay and or a filament cutter with a sensor", ) - def handle_connect(self): - self.toolhead = self.printer.lookup_object("toolhead") - def handle_ready(self): self.min_event_systime = self.reactor.monotonic() + 2.0 - if self.has_custom_boundary: self.custom_boundary_object = self.printer.lookup_object( "bed_custom_bound" ) - if self.bucket: self.bucket_object = self.printer.lookup_object("bucket") - if self.cutter_name: if ( self.printer.lookup_object( @@ -101,21 +95,17 @@ def handle_ready(self): self.cutter_object = self.printer.lookup_object( f"cutter_sensor {self.cutter_name}", None ) - self.printer.register_event_handler( "cutter_sensor:no_filament", self.handle_cutter_fnp, ) - if self.idex: self.idex_object = self.printer.lookup_object("dual_carriage") - if self.filament_flow_sensor_name: self.filament_flow_sensor_object = self.printer.lookup_object( f"filament_motion_sensor {self.filament_flow_sensor_name}", None, ) - if self.filament_switch_sensor_name: self.filament_switch_sensor_object = self.printer.lookup_object( f"filament_switch_sensor {self.filament_switch_sensor_name}", @@ -128,13 +118,18 @@ def handle_cutter_fnp(self) -> None: return self.gcode.respond_info("Pulling filament out of the printer wait....") logging.info( - "Cutter reported not having filament, pulling filament out of the printer" + "Cutter reported not having filament," + " pulling filament out of the printer" + ) + self.reactor.update_timer( + self.verify_switch_sensor_timer, float(self.reactor.NOW + 10.0) ) self.reactor.update_timer(self.unextrude_timer, self.reactor.NOW) def verify_switch_sensor_state(self, eventtime): """Verifies if the filament is actually unloaded or not - The switch sensor here is assumed to be at the end of the filament pathway. + The switch sensor here is assumed to be at + the end of the filament pathway. """ if not self.unload_started or not self.filament_switch_sensor_object: return self.reactor.NEVER @@ -144,42 +139,37 @@ def verify_switch_sensor_state(self, eventtime): ): self.reactor.update_timer(self.unextrude_timer, self.reactor.NEVER) completion = self.reactor.register_callback(self.unload_end) - return completion.wait() + completion.wait() + return self.reactor.NEVER return eventtime + 1.50 def verify_flow_sensor_state(self, eventtime): """TODO: - Verifies the presence of filament on the **flow sensor** and reacts to this.""" + Verifies the presence of filament on + the **flow sensor** and reacts to this.""" ... def unload_end(self, eventtime=None): - if not self.unload_started or not self.toolhead: + toolhead = self.printer.lookup_object("toolhead") + if not self.unload_started or not toolhead: return self.reactor.NEVER - self.gcode.run_script_from_command("G91") self.gcode.run_script_from_command("M83") self.gcode.run_script_from_command( "G92 E0.0" ) # Restore extruder position - self.gcode.run_script_from_command("M82\nM400") self.restore_state() - if self.custom_boundary_object: self.custom_boundary_object.set_custom_boundary() self.custom_boundary_object.move_to_park() - - - self.gcode.respond_info("Cooling down extruder") self.heat_extruder(0, wait=False) - if self.idex: self.gcode.respond_info("Parking toolhead 0") - self.gcode.run_script_from_command("T0 PARK\nM400") - - self.gcode.respond_info("[UNLOAD FILAMENT] Finished") + self.gcode.run_script_from_command("T0 PARK") self.unload_started = False + self.step = None self.printer.send_event("unload_filament:end") return self.reactor.NEVER @@ -193,11 +183,10 @@ def unextrude(self, eventtime): self.reactor.update_timer( self.verify_switch_sensor_timer, self.reactor.NEVER ) - completion = self.reactor.register_callback( - self.unload_end - ) + completion = self.reactor.register_callback(self.unload_end) return completion.wait() self.unextrude_count += 1 + self.step = "pulling" self.move_extruder_mm( distance=-10, speed=self.unload_speed, wait=False ) @@ -222,9 +211,7 @@ def disable_sensors(self): def enable_sensors(self): if self.filament_flow_sensor_object: - self.filament_flow_sensor_object.runout_helper.sensor_enabled = ( - True - ) + self.filament_flow_sensor_object.runout_helper.sensor_enabled = True if self.filament_switch_sensor_object: self.gcode.respond_info("filament switch sensor is now enabled") @@ -238,31 +225,29 @@ def move_extruder_mm(self, distance=10.0, speed=30.0, wait=True) -> None: Args: distance (float): The distance in mm to move the extruder. """ - if not self.toolhead: - return + toolhead = self.printer.lookup_object("toolhead") try: eventtime = self.reactor.monotonic() gcode_move = self.printer.lookup_object("gcode_move") - prev_pos = self.toolhead.get_position() + prev_pos = toolhead.get_position() gcode_move.absolute_coord = False # G91 v = distance * gcode_move.get_status(eventtime)["extrude_factor"] new_distance = v + prev_pos[3] - self.toolhead.manual_move( + toolhead.manual_move( [prev_pos[0], prev_pos[1], prev_pos[2], new_distance], speed ) if wait: - self.toolhead.wait_moves() + toolhead.wait_moves() except Exception as e: raise UnloadFilamentError( f"[UNLOAD FILAMENT] Error moving extruder {e}" ) def home_needed(self) -> None: - if not self.toolhead: - return try: + toolhead = self.printer.lookup_object("toolhead") eventtime = self.reactor.monotonic() - kin = self.toolhead.get_kinematics() + kin = toolhead.get_kinematics() _homed_axes = kin.get_status(eventtime)["homed_axes"] if "xyz" in _homed_axes.lower(): return @@ -279,15 +264,16 @@ def heat_extruder(self, temp, wait: bool = False) -> None: temp (float): Target temperature in Celsius. wait (bool, optional): - Whether to wait or not for the temperature to reach the interval . Defaults to True + Whether to wait or not for the + temperature to reach the interval . Defaults to True """ - # if not self.toolhead: - # return + toolhead = self.printer.lookup_object("toolhead") eventtime = self.reactor.monotonic() - extruder = self.toolhead.get_extruder() + extruder = toolhead.get_extruder() pheaters = self.printer.lookup_object("heaters") extruder_heater = extruder.get_heater() pheaters.set_temperature(extruder_heater, temp, False) + self.step = "heating" while not self.printer.is_shutdown() and wait: heater_temp, _ = extruder_heater.get_temp(eventtime) if heater_temp >= (temp - 5) and heater_temp <= (temp + 5): @@ -296,65 +282,69 @@ def heat_extruder(self, temp, wait: bool = False) -> None: return def save_state(self) -> None: - """Save gcode state and dual carriage state if the system is in IDEX configuration""" + """Save gcode state and dual carriage state + if the system is in IDEX configuration""" if self.idex: self.gcode.run_script_from_command( - "SAVE_DUAL_CARRIAGE_STATE NAME=unload_carriage_state\nM400" + "SAVE_DUAL_CARRIAGE_STATE NAME=unload_carriage_state" ) self.gcode.run_script_from_command( - "SAVE_GCODE_STATE NAME=_UNLOAD_STATE\nM400" + "SAVE_GCODE_STATE NAME=_UNLOAD_STATE" ) def restore_state(self) -> None: - """Restore gcode state and dual carriage state if the system is in IDEX configuration""" + """Restore gcode state and dual carriage state + if the system is in IDEX configuration""" self.gcode.run_script_from_command( - "RESTORE_GCODE_STATE NAME=_UNLOAD_STATE MOVE=1 MOVE_SPEED=100\nM400" + "RESTORE_GCODE_STATE NAME=_UNLOAD_STATE MOVE=1 MOVE_SPEED=100" ) if self.idex: self.gcode.run_script_from_command( - "RESTORE_DUAL_CARRIAGE_STATE NAME=unload_carriage_state MOVE=0\nM400" + "RESTORE_DUAL_CARRIAGE_STATE NAME=unload_carriage_state MOVE=0" ) def cmd_UNLOAD_FILAMENT(self, gcmd): temp = gcmd.get( "TEMPERATURE", 250.0, parser=float, minval=220.0, maxval=500.0 ) - if not self.toolhead: - return try: + pstat = self.printer.lookup_object("print_stats") + pstat_state = pstat.get_status(self.reactor.monotonic())["state"] + is_printing = pstat_state == "printing" + self.gcode.respond_info( + f"Current idle timeout {pstat_state} state {is_printing}" + ) + if is_printing: + self.gcode.respond_info("Cannot unload while printing") + return if self.unload_started: self.gcode.respond_info("Printer already unloading filament") return self.home_needed() self.save_state() - self.disable_sensors() # So not to pause the filament switch sensor when filament is taken out - + self.disable_sensors() + # So not to pause the filament switch + # sensor when filament is taken out if self.idex: if gcmd.get("TOOLHEAD") == "Load_T0": self.gcode.run_script_from_command("T0 UNLOAD") else: self.gcode.run_script_from_command("T1 UNLOAD") - + self.step = "None" self.unload_started = True self.printer.send_event("unload_filament:start") self.gcode.respond_info("[UNLOAD FILAMENT] Start") - - self.gcode.run_script_from_command("G91\nM400") - self.gcode.run_script_from_command("M83\nM400") - + self.gcode.run_script_from_command("G91") + self.gcode.run_script_from_command("M83") + self.gcode.run_script_from_command("G92 E0.0") if self.timeout: self.unextrude_count = 0 - self.heat_extruder(temp, wait=False) - if self.bucket_object: self.bucket_object.move_to_bucket() - self.heat_extruder(temp, wait=True) - - self.toolhead.wait_moves() - if self.cutter_object: + self.step = "cutting" self.reactor.register_callback( partial( self.cutter_object.cut, @@ -363,6 +353,7 @@ def cmd_UNLOAD_FILAMENT(self, gcmd): off_heaters=False, ) ) + return if not self.cutter_object and self.timeout: self.reactor.update_timer( self.unextrude_timer, self.reactor.NOW @@ -371,22 +362,28 @@ def cmd_UNLOAD_FILAMENT(self, gcmd): self.reactor.update_timer( self.verify_flow_sensor_timer, self.reactor.NOW ) - if self.filament_switch_sensor_object: self.gcode.respond_info( - "[UNLOAD] Starting filament switch sensor verification in 10 seconds" + "[UNLOAD] Starting filament switch sensor" + " verification in 10 seconds" ) self.reactor.update_timer( - self.verify_switch_sensor_timer, self.reactor.NOW + 5.0 + self.verify_switch_sensor_timer, + float(self.reactor.NOW + 15.0), ) except Exception as e: raise UnloadFilamentError( - f"[UNLOAD] Unexpected error while trying to unload filament: {e}" + f"[UNLOAD] Unexpected error while trying "\ + f"to unload filament: {e}" ) def get_status(self, eventtime): - return {"state": bool(self.unload_started)} + return { + "state": bool(self.unload_started), + "step": self.step, + "steps": list(self.steps), + } def load_config(config):