diff --git a/klippy/extras/monitored_move.py b/klippy/extras/monitored_move.py new file mode 100644 index 000000000..8de369136 --- /dev/null +++ b/klippy/extras/monitored_move.py @@ -0,0 +1,85 @@ +# Módulo para movimientos G1 con monitoreo de endstops +# +# Copyright (C) 2024 MicroLay +# +# This file may be distributed under the terms of the GNU GPLv3 license. + +import logging + +class MonitoredMove: + def __init__(self, config): + self.printer = config.get_printer() + self.gcode = self.printer.lookup_object('gcode') + self.printer.register_event_handler("klippy:connect", self._handle_connect) + self.toolhead = None + self.phoming = None + self.gcode.register_command('MONITORED_G1', self.cmd_MONITORED_G1, desc=self.cmd_MONITORED_G1_help) + logging.info("MonitoredMove: Initialized") + + def _handle_connect(self): + self.toolhead = self.printer.lookup_object('toolhead') + self.phoming = self.printer.lookup_object('homing') + + def _get_endstop_by_stepper(self, stepper_name, gcmd): + """Obtiene el endstop de un stepper buscándolo a través del rail de su eje.""" + kin = self.toolhead.get_kinematics() + target_rail = None + for rail in kin.rails: + if rail.get_name() == stepper_name: + target_rail = rail + break + + if target_rail is None: + rail_names = [r.get_name() for r in kin.rails] + raise gcmd.error(f"No se pudo encontrar el rail para el stepper '{stepper_name}'. Rails disponibles: {rail_names}") + + endstops = target_rail.get_endstops() + if not endstops: + raise gcmd.error(f"El rail del stepper '{stepper_name}' no tiene endstops configurados.") + + return endstops[0][0] + + cmd_MONITORED_G1_help = "Realiza un movimiento G1 monitoreando el endstop de un stepper" + def cmd_MONITORED_G1(self, gcmd): + if not all([self.toolhead, self.phoming]): + raise gcmd.error("MONITORED_G1: Sistema no listo.") + + stepper_name = gcmd.get('STEPPER') + if not stepper_name: + raise gcmd.error("El parámetro 'STEPPER' es requerido (ej: STEPPER=stepper_y).") + + x = gcmd.get_float('X', gcmd.get_command_parameters().get('X', None)) + y = gcmd.get_float('Y', gcmd.get_command_parameters().get('Y', None)) + z = gcmd.get_float('Z', gcmd.get_command_parameters().get('Z', None)) + f = gcmd.get_float('F', 60.0, above=0.) + + current_pos = self.toolhead.get_position() + target_pos = list(current_pos) + if x is not None: target_pos[0] = x + if y is not None: target_pos[1] = y + if z is not None: target_pos[2] = z + + endstop = self._get_endstop_by_stepper(stepper_name, gcmd) + + gcmd.respond_info(f"🎯 Movimiento monitoreado hacia Z={target_pos[2]:.3f}. Monitoreando '{stepper_name}'.") + + try: + # Corrección: Pasar el endstop directamente a probing_move + trigger_pos = self.phoming.probing_move(endstop, target_pos, f/60.0) + + gcmd.respond_info(f"🎯 Endstop activado en Z={trigger_pos[2]:.3f}") + self.gcode.run_script_from_command(f"G92 Z{trigger_pos[2]}") + gcmd.respond_info("✅ Movimiento detenido. Posición actualizada.") + + except self.printer.command_error as e: + if "No trigger" in str(e): + gcmd.respond_info("✅ Movimiento completado sin activación de endstop.") + self.gcode.run_script_from_command(f"G92 Z{target_pos[2]}") + gcmd.respond_info("✅ Posición actualizada al objetivo.") + elif "triggered prior to movement" in str(e): + raise gcmd.error("El endstop ya estaba activado antes de empezar el movimiento.") + else: + raise + +def load_config(config): + return MonitoredMove(config) \ No newline at end of file diff --git a/klippy/extras/zmax_homing.py b/klippy/extras/zmax_homing.py index fd12bdb3c..08cc5ae38 100644 --- a/klippy/extras/zmax_homing.py +++ b/klippy/extras/zmax_homing.py @@ -6,6 +6,18 @@ import logging +class YEndstopWrapper: + """Wrapper para el endstop de Y que es compatible con probing_move()""" + def __init__(self, y_endstop): + self.y_endstop = y_endstop + # Delegar métodos del endstop original + self.get_mcu = y_endstop.get_mcu + self.add_stepper = y_endstop.add_stepper + self.get_steppers = y_endstop.get_steppers + self.home_start = y_endstop.home_start + self.home_wait = y_endstop.home_wait + self.query_endstop = y_endstop.query_endstop + class ZMaxHomingAlt: def __init__(self, config): self.printer = config.get_printer() @@ -17,20 +29,16 @@ class ZMaxHomingAlt: # Variables self.toolhead = None self.y_endstop = None + self.y_endstop_wrapper = None self.z_stepper = None - self.endstop_triggered = False - self.trigger_position = None + self.phoming = None # Configuración - # Velocidades más bajas por defecto self.speed = config.getfloat('speed', 5.0, above=0.) self.second_speed = config.getfloat('second_speed', 2.0, above=0.) - self.retract_dist = config.getfloat('retract_dist', 2.0, minval=0.) + self.retract_dist = config.getfloat('retract_dist', 3.0, minval=0.) self.retract_speed = config.getfloat('retract_speed', 3.0, above=0.) - # Cambiar a False por defecto para que se quede en Zmax self.final_retract = config.getboolean('final_retract', False) - # Evitar que se apaguen los motores al completar - self.disable_motors = config.getboolean('disable_motors', False) # Registrar comandos self.gcode.register_command( @@ -38,37 +46,31 @@ class ZMaxHomingAlt: self.cmd_ZMAX_HOME, desc=self.cmd_ZMAX_HOME_help ) - self.gcode.register_command( - 'MEASURE_Z_LENGTH', - self.cmd_MEASURE_Z_LENGTH, - desc=self.cmd_MEASURE_Z_LENGTH_help - ) - # Mensaje de inicio - logging.info(f"ZMaxHoming: Initialized with speeds - homing:{self.speed} mm/s, second:{self.second_speed} mm/s, retract:{self.retract_speed} mm/s") - logging.info(f"ZMaxHoming: final_retract={self.final_retract}, disable_motors={self.disable_motors}") + logging.info(f"ZMaxHoming: Initialized with speeds - homing:{self.speed} mm/s, second:{self.second_speed} mm/s") def _handle_connect(self): - # Obtener el toolhead + # Obtener referencias necesarias self.toolhead = self.printer.lookup_object('toolhead') + self.phoming = self.printer.lookup_object('homing') - # Obtener el endstop de Y + # Obtener el endstop de Y usando la forma estándar kin = self.toolhead.get_kinematics() if hasattr(kin, 'rails'): - # Buscar el endstop de Y for rail in kin.rails: if rail.get_name() == "stepper_y": endstops = rail.get_endstops() if endstops: self.y_endstop = endstops[0][0] + # Crear wrapper compatible con probing_move() + self.y_endstop_wrapper = YEndstopWrapper(self.y_endstop) logging.info(f"ZMaxHoming: Using Y endstop for Z-max homing") break - # Buscar el stepper Z + # Obtener el stepper Z for rail in kin.rails: if rail.get_name() == "stepper_z": self.z_stepper = rail - # Obtener la posición máxima de Z z_max_cfg = self.z_stepper.get_range()[1] logging.info(f"ZMaxHoming: Z-max position from config: {z_max_cfg}mm") break @@ -78,754 +80,243 @@ class ZMaxHomingAlt: if self.z_stepper is None: raise self.printer.config_error("No se encontró el stepper Z") - def _monitor_endstop(self): - """Monitorea el endstop Y. Si se activa por primera vez, guarda la posición y actualiza el flag.""" - # Solo comprobar si aún no hemos detectado el trigger en esta secuencia - if not self.endstop_triggered: - if self.y_endstop.query_endstop(0): - self.endstop_triggered = True - # Capturar la posición inmediatamente al detectar el trigger - self.trigger_position = self.toolhead.get_position() - self.gcode.respond_info(f"!!! Endstop Y TRIGGERED at Z={self.trigger_position[2]:.3f} !!!") - # No forzamos la parada aquí, devolvemos True y el llamador decide - return True # Triggered NOW - # Si ya estaba triggered antes, o si query_endstop dio False, devolvemos el estado actual del flag - return self.endstop_triggered - - def _safe_move_z(self, pos, speed, increment): - """Realiza un movimiento seguro en Z con verificación de endstop (pre y post move)""" - curpos = self.toolhead.get_position() - target_z = pos[2] - z_max_cfg = self.z_stepper.get_range()[1] + def _continuous_probe_move(self, target_pos, speed): + """ + Realiza un movimiento continuo usando la función oficial probing_move() de Klipper. + Esta es la misma función que usan todos los módulos de sondeo oficiales. + """ + try: + trigger_pos = self.phoming.probing_move(self.y_endstop_wrapper, target_pos, speed) + # Mensaje en el momento exacto de detección + gcode = self.printer.lookup_object('gcode') + gcode.respond_info(f"🎯 Y-endstop ACTIVADO en Z={trigger_pos[2]:.3f}mm") + return trigger_pos + except self.printer.command_error as e: + if "No trigger" in str(e): + raise self.printer.command_error( + f"El endstop Y no se activó durante el movimiento hacia Z={target_pos[2]:.3f}. " + "Verifica que la plataforma pueda alcanzar el final de carrera.") + raise - # Comprobación inicial antes de cualquier movimiento - if self._monitor_endstop(): - self.gcode.respond_info(f"Endstop triggered before starting safe_move_z at Z={curpos[2]:.3f}") - return True - - while curpos[2] < target_z: - # Comprobar límites antes de calcular el siguiente paso - # Usar una pequeña tolerancia para evitar sobrepasar z_max_cfg - if curpos[2] >= z_max_cfg - 0.01: - self.gcode.respond_info(f"Approaching Z max limit ({z_max_cfg:.3f}) during safe move. Stopping at Z={curpos[2]:.3f}.") - break # Detenerse antes de exceder - - # Calcular siguiente posición, sin sobrepasar target_z ni z_max_cfg - next_z = min(curpos[2] + increment, target_z, z_max_cfg - 0.01) - # Evitar movimientos extremadamente pequeños si el incremento es muy bajo o ya estamos en el objetivo - if abs(next_z - curpos[2]) < 0.001: - self.gcode.respond_info(f"Safe move increment too small ({increment:.3f}mm) or target reached at Z={curpos[2]:.3f}. Stopping.") - break - - next_pos = list(curpos) - next_pos[2] = next_z - - # --- Comprobación Pre-movimiento --- (Dentro del bucle) - if self._monitor_endstop(): - # Endstop activado entre el último movimiento y esta comprobación - # La posición ya fue capturada por _monitor_endstop - self.gcode.respond_info(f"Endstop check (pre-move): TRIGGERED at Z={self.trigger_position[2]:.3f} (current Z={curpos[2]:.3f})") - return True - - try: - # Realizar el movimiento - # self.gcode.respond_info(f"Moving Z: {curpos[2]:.3f} -> {next_z:.3f} (incr: {increment:.3f}, target: {target_z:.3f})") # Debug - self.toolhead.move(next_pos, speed) - self.toolhead.wait_moves() # Asegurar que el movimiento se complete ANTES de la comprobación post-movimiento - - # --- Comprobación Post-movimiento --- (CRÍTICO) - if self._monitor_endstop(): - # Endstop activado durante o inmediatamente después del movimiento - # La posición ya fue capturada por _monitor_endstop - self.gcode.respond_info(f"Endstop check (post-move): TRIGGERED at Z={self.trigger_position[2]:.3f} (intended move Z={next_z:.3f})") - return True - - # Actualizar posición para la siguiente iteración si no hubo trigger - curpos = next_pos # Actualizar a la posición a la que nos acabamos de mover - - except Exception as e: - self.gcode.respond_info(f"Error during toolhead.move in _safe_move_z: {str(e)}") - # Comprobar endstop una última vez en caso de error durante el procesamiento del movimiento - return self._monitor_endstop() - - # Bucle terminado (alcanzó target_z, z_max_cfg, o incremento muy pequeño) - # Comprobación final por si se activó justo cuando la condición del bucle se volvió falsa - final_triggered_state = self._monitor_endstop() - if not final_triggered_state: - self.gcode.respond_info(f"safe_move_z finished loop. TargetZ: {target_z:.3f}, FinalZ: {curpos[2]:.3f}. Endstop triggered flag: {self.endstop_triggered}") - - return self.endstop_triggered # Devolver el estado final del flag - - def _find_z_max(self, gcmd, speed, second_speed, retract_dist, z_homed): - """Encuentra la posición Zmax. Retorna la posición del trigger si z_homed, o [None, None, z_max_cfg] si no.""" - toolhead = self.toolhead - z_max_cfg = self.z_stepper.get_range()[1] - z_min_cfg = self.z_stepper.get_range()[0] # Añadido para verificar límites - - self.endstop_triggered = False - self.trigger_position = None # Reset trigger position - toolhead.flush_step_generation() - toolhead.dwell(0.001) - - # Verificar si el endstop ya está activado al inicio - initial_endstop_state = self.y_endstop.query_endstop(0) - - if initial_endstop_state: - gcmd.respond_info("Endstop Y ya activado al inicio del procedimiento") - # Si el endstop ya está activado, registramos la posición actual - current_pos = toolhead.get_position() - self.endstop_triggered = True - self.trigger_position = list(current_pos) # Usar list() para crear una copia + def _measure_platform_travel(self, gcmd, start_pos, z_max_cfg): + """ + Mide el recorrido total de la plataforma desde Z-max hasta Z-min + position_endstop + """ + try: + gcmd.respond_info("=== INICIANDO MEDICIÓN DE RECORRIDO ===") - # Si necesitamos retracción y podemos retroceder sin salir del rango válido - safe_retract_dist = min(retract_dist, current_pos[2] - z_min_cfg) + # Obtener configuración del stepper Z + z_min_cfg = self.z_stepper.get_range()[0] - if safe_retract_dist > 0: - gcmd.respond_info(f"Retrayendo {safe_retract_dist}mm para desactivar el endstop") - retract_pos = list(current_pos) - retract_pos[2] -= safe_retract_dist - try: - toolhead.move(retract_pos, speed) - toolhead.wait_moves() - - # Verificar si el endstop se desactivó - if not self.y_endstop.query_endstop(0): - gcmd.respond_info("Endstop desactivado después de la retracción") - self.endstop_triggered = False - else: - gcmd.respond_info("ADVERTENCIA: Endstop sigue activado después de retracción") - except Exception as e: - gcmd.respond_info(f"Error durante retracción inicial: {str(e)}") - else: - gcmd.respond_info("No se puede retroceder más sin salir del rango válido") - - # Si el endstop sigue activado después de intentar retracción, no podemos continuar con normalidad - if initial_endstop_state and self.endstop_triggered: - gcmd.respond_info("El endstop sigue activado, asumiendo que ya estamos en posición Z-max") - if not z_homed: - # Para Z no homeado, retornamos la posición máxima de configuración con formato completo - current_pos = toolhead.get_position() - # Asegurar que current_pos tiene 4 elementos - if len(current_pos) < 4: - current_pos = list(current_pos) + [0.0] * (4 - len(current_pos)) - return [current_pos[0], current_pos[1], z_max_cfg, current_pos[3]] - else: - # Para Z homeado, retornamos la posición actual como trigger final - return self.trigger_position or toolhead.get_position() - - # Obtener la posición actual - current_pos = toolhead.get_position() - current_z = current_pos[2] - - # Asegurar que current_pos tiene 4 elementos - if len(current_pos) < 4: - current_pos = list(current_pos) + [0.0] * (4 - len(current_pos)) - gcmd.respond_info("Extendiendo posición actual para incluir el valor E") - - # Si estamos cerca de Zmax, proceder directamente a una aproximación directa - if z_homed and current_z > 70.0: # Si estamos a más de 70mm (cerca de Zmax que es ~79mm según los logs) - gcmd.respond_info(f"Posición actual Z={current_z:.3f} cercana a Zmax, intentando aproximación directa...") - # Aproximación directa a Z máximo + # Obtener position_endstop del stepper Z + z_endstop_pos = 0.0 # Valor por defecto try: - move_pos = list(current_pos) - move_pos[2] = z_max_cfg - toolhead.move(move_pos, speed/2) # Velocidad reducida para mayor precisión - toolhead.wait_moves() - - # Verificar si el endstop se activó - if self.y_endstop.query_endstop(0): - gcmd.respond_info("Endstop activado después de aproximación directa a Zmax") - self.endstop_triggered = True - self.trigger_position = toolhead.get_position() - - # Hacemos una retracción pequeña para liberar presión sobre el endstop - retract_pos = list(self.trigger_position) - retract_pos[2] -= min(1.0, retract_dist) # Retracción más pequeña (1mm o menos) - toolhead.move(retract_pos, speed) - toolhead.wait_moves() - - # Aproximación final lenta y precisa - gcmd.respond_info("Realizando aproximación final de precisión...") - move_pos = list(retract_pos) - move_pos[2] = z_max_cfg - toolhead.move(move_pos, second_speed/2) # Velocidad ultra-reducida - toolhead.wait_moves() - - # Verificar estado final - final_triggered = self.y_endstop.query_endstop(0) - gcmd.respond_info(f"Estado final del endstop: {'ACTIVADO' if final_triggered else 'NO ACTIVADO'}") - - if final_triggered: - self.endstop_triggered = True - self.trigger_position = toolhead.get_position() - return self.trigger_position - else: - # Si por alguna razón no está activado, usar posición máxima de configuración - gcmd.respond_info("Endstop no activado en aproximación final, usando posición máxima de configuración") - # Usar posición actual pero con Z máximo, asegurando los 4 elementos - pos = toolhead.get_position() - if len(pos) < 4: - pos = list(pos) + [0.0] * (4 - len(pos)) - self.trigger_position = [pos[0], pos[1], z_max_cfg, pos[3]] - return self.trigger_position - else: - # Si no se activó con la aproximación directa, intentamos una búsqueda normal - gcmd.respond_info("Endstop no activado con aproximación directa, intentando búsqueda normal") - # No retornamos, continuamos con la búsqueda normal + # Buscar en la configuración del stepper Z + kin = self.toolhead.get_kinematics() + if hasattr(kin, 'rails'): + for rail in kin.rails: + if rail.get_name() == "stepper_z": + endstops = rail.get_endstops() + if endstops: + # Obtener position_endstop del endstop Z + z_endstop = endstops[0][0] + if hasattr(z_endstop, 'position_endstop'): + z_endstop_pos = z_endstop.position_endstop + break except Exception as e: - gcmd.respond_info(f"Error durante aproximación directa: {str(e)}") - # Continuamos con búsqueda normal si falla la aproximación directa - - if z_homed: - # --- Homing Z cuando ya está homeado (lógica original adaptada) --- - gcmd.respond_info("Iniciando búsqueda rápida (absoluta)...") - curpos = toolhead.get_position() - # Asegurar que curpos tiene 4 elementos - if len(curpos) < 4: - curpos = list(curpos) + [0.0] * (4 - len(curpos)) + gcmd.respond_info(f"Advertencia: No se pudo obtener position_endstop: {e}") - movepos = list(curpos) - # Asegurarse de que la posición objetivo no sea igual a la actual si ya estamos cerca del máximo - if abs(curpos[2] - z_max_cfg) < 0.1: - movepos[2] = curpos[2] - 0.2 # Moverse ligeramente hacia abajo si ya estamos en el límite + gcmd.respond_info(f"Configuración Z: min={z_min_cfg:.3f}, max={z_max_cfg:.3f}, position_endstop={z_endstop_pos:.3f}") + + # Posición inicial (Z-max) + start_z = start_pos[2] + gcmd.respond_info(f"Posición inicial (Z-max): {start_z:.3f}mm") + + # Mover a Z-min + gcmd.respond_info("Moviendo a Z-min...") + min_pos = list(start_pos) + min_pos[2] = z_min_cfg + self.toolhead.move(min_pos, self.speed) + self.toolhead.wait_moves() + gcmd.respond_info(f"En Z-min: {z_min_cfg:.3f}mm") + + # Mover posición adicional según position_endstop + target_z = z_min_cfg - abs(z_endstop_pos) + gcmd.respond_info(f"Bajando {abs(z_endstop_pos):.3f}mm adicionales (position_endstop)...") + final_pos = list(min_pos) + final_pos[2] = target_z + self.toolhead.move(final_pos, self.speed) + self.toolhead.wait_moves() + + # Calcular recorrido total + total_travel = start_z - target_z + config_travel = z_max_cfg - (z_min_cfg - abs(z_endstop_pos)) + + gcmd.respond_info("=== RESULTADOS DE MEDICIÓN ===") + gcmd.respond_info(f"📏 Recorrido REAL medido: {total_travel:.3f}mm") + gcmd.respond_info(f"📐 Recorrido CONFIG teórico: {config_travel:.3f}mm") + gcmd.respond_info(f"📊 Diferencia: {abs(total_travel - config_travel):.3f}mm") + + if abs(total_travel - config_travel) > 0.5: + gcmd.respond_info("⚠️ ATENCIÓN: Diferencia > 0.5mm - Revisar montaje/configuración") else: - # Si Z ya está homeado, empezar desde una posición más baja para asegurar que - # el endstop se active durante el movimiento - target_z = max(z_max_cfg - 5.0, curpos[2]) - # Nunca bajar más de 5mm desde la posición actual - target_z = max(target_z, curpos[2] - 5.0) - # Nunca subir si ya estamos en una posición alta - target_z = min(target_z, z_max_cfg - 0.1) - movepos[2] = target_z - gcmd.respond_info(f"Posición actual Z: {curpos[2]:.3f}, posición objetivo Z: {target_z:.3f}") - - # Verificar si el endstop ya estaba activado antes de mover - if self.y_endstop.query_endstop(0): - gcmd.respond_info("Endstop Y ya activado antes de iniciar movimiento") - self.endstop_triggered = True - self.trigger_position = list(curpos) # Usar la posición actual - # No necesitamos hacer el movimiento inicial, continuamos con la retracción - else: - # Mover y verificar si se activa el endstop - if not self._safe_move_z(movepos, speed, 1.0): # Incrementos de 1mm - # Si safe_move_z retorna False pero el endstop SÍ se activó, significa que se activó inmediatamente. - if not self.endstop_triggered: - # Verificar una última vez por si acaso - if self.y_endstop.query_endstop(0): - gcmd.respond_info("Endstop se activó en verificación final después de no detectarse en búsqueda") - self.endstop_triggered = True - self.trigger_position = toolhead.get_position() - else: - # No se encontró el endstop, pero podemos estar en Zmax - # Intentar mover un poco más arriba directamente como último recurso - gcmd.respond_info("Intentando aproximación directa a Zmax como último recurso...") - final_pos = list(toolhead.get_position()) - # Asegurar que final_pos tiene 4 elementos - if len(final_pos) < 4: - final_pos = list(final_pos) + [0.0] * (4 - len(final_pos)) - - # Intentar subir directamente a Zmax - final_pos[2] = z_max_cfg - try: - toolhead.move(final_pos, speed/2) - toolhead.wait_moves() - # Verificar si ahora el endstop está activado - if self.y_endstop.query_endstop(0): - gcmd.respond_info("Endstop activado después de movimiento directo a Zmax") - self.endstop_triggered = True - self.trigger_position = toolhead.get_position() - else: - # Intento adicional: moverse un poco más allá de Zmax por si el final de carrera está ligeramente más alto - try: - gcmd.respond_info("Último intento: movimiento extendido más allá de Zmax...") - extended_pos = list(final_pos) - extended_pos[2] = z_max_cfg + 0.2 # 0.2mm más allá del máximo configurado - toolhead.move(extended_pos, speed/4) # Velocidad muy baja - toolhead.wait_moves() - # Verificación final - if self.y_endstop.query_endstop(0): - gcmd.respond_info("Endstop activado en movimiento extendido") - self.endstop_triggered = True - self.trigger_position = toolhead.get_position() - else: - raise gcmd.error("No se detectó el endstop después de intentar todas las opciones") - except Exception as e: - if "Move out of range" in str(e): - gcmd.respond_info("Límite de movimiento alcanzado en intento extendido. Asumiendo Zmax.") - self.endstop_triggered = True - # Crear trigger_position con 4 elementos, incluyendo E de la posición actual - self.trigger_position = [final_pos[0], final_pos[1], z_max_cfg, final_pos[3]] - else: - raise gcmd.error(f"No se detectó el endstop durante la búsqueda rápida: {str(e)}") - except Exception as e: - if "Move out of range" in str(e): - gcmd.respond_info("Límite de movimiento alcanzado. Asumiendo posición Zmax.") - self.endstop_triggered = True - # Crear trigger_position con 4 elementos, incluyendo E de la posición actual - self.trigger_position = [final_pos[0], final_pos[1], z_max_cfg, final_pos[3]] - else: - raise gcmd.error(f"No se detectó el endstop durante la búsqueda rápida: {str(e)}") - else: - gcmd.respond_info("Endstop detectado inmediatamente en búsqueda rápida.") - # Si no hay trigger_position guardado aún (pasó en el primer check), tomar la posición actual - if not self.trigger_position: - self.trigger_position = toolhead.get_position() - - - # Si _safe_move_z terminó porque se alcanzó el objetivo sin trigger (raro), verificar una vez más - if not self.endstop_triggered: - if self._monitor_endstop(): - gcmd.respond_info("Endstop detectado al final del movimiento rápido.") - else: - # Si realmente no se disparó, es un error - raise gcmd.error("Se alcanzó el objetivo de movimiento rápido sin detectar el endstop.") - - - # Asegurar que tenemos una posición de trigger - if not self.trigger_position: - # Si por alguna razón no se guardó, tomar la posición actual como referencia (menos preciso) - self.trigger_position = toolhead.get_position() - gcmd.respond_info("No se guardó la posición exacta del trigger rápido, usando la posición actual.") - - # Asegurar que trigger_position tiene 4 elementos - if len(self.trigger_position) < 4: - # Obtener el valor E actual - current_pos = toolhead.get_position() - e_value = 0.0 - if len(current_pos) >= 4: - e_value = current_pos[3] - # Extender trigger_position con el valor E - self.trigger_position = list(self.trigger_position) + [0.0] * (4 - len(self.trigger_position)) - self.trigger_position[3] = e_value - gcmd.respond_info("Extendiendo posición de trigger para incluir valor E") - - first_trigger_pos = list(self.trigger_position) # Guardar posición donde se activó el endstop - - # Retraer - gcmd.respond_info(f"Primer trigger detectado en Z={first_trigger_pos[2]:.3f}. Retrayendo {retract_dist}mm...") - retract_pos = list(first_trigger_pos) - retract_pos[2] -= retract_dist - # Asegurar que la retracción no vaya por debajo de position_min - if retract_pos[2] < z_min_cfg: - retract_pos[2] = z_min_cfg - gcmd.respond_info(f"Ajustando retracción a Z={retract_pos[2]:.3f} para no exceder Zmin") - toolhead.move(retract_pos, speed) # Retraer a velocidad normal - toolhead.wait_moves() - - # Búsqueda lenta - gcmd.respond_info("Iniciando aproximación fina (absoluta)...") - self.endstop_triggered = False # Resetear para la segunda búsqueda - self.trigger_position = None - toolhead.flush_step_generation() - toolhead.dwell(0.001) - - # Mover lentamente hacia z_max de nuevo desde la posición retraída - movepos = list(retract_pos) - # Asegurarse de que la posición objetivo no sea igual a la actual - if abs(retract_pos[2] - z_max_cfg) < 0.01: - movepos[2] = retract_pos[2] - 0.02 # Moverse ligeramente hacia abajo si ya estamos en el límite - else: - movepos[2] = z_max_cfg - 0.1 - - if not self._safe_move_z(movepos, second_speed, 0.02): # Incrementos de 0.02mm - # Si safe_move_z retorna False pero el endstop SÍ se activó - if not self.endstop_triggered: - # Si falla durante la aproximación lenta (raro, a menos que haya problema con el endstop), usar el primer trigger - gcmd.respond_info("No se detectó el endstop en la aproximación fina. Usando posición del primer trigger.") - self.trigger_position = first_trigger_pos - else: - gcmd.respond_info("Endstop detectado inmediatamente en aproximación fina.") - if not self.trigger_position: - self.trigger_position = toolhead.get_position() # Posición actual como fallback - - # Verificar de nuevo si se alcanzó el objetivo sin trigger - if not self.endstop_triggered: - if self._monitor_endstop(): - gcmd.respond_info("Endstop detectado al final de la aproximación fina.") - else: - # Si no se activó, usar la posición del primer trigger como fallback más seguro - gcmd.respond_info("Se alcanzó el objetivo de aproximación fina sin detectar el endstop. Usando posición del primer trigger.") - self.trigger_position = first_trigger_pos - - - if not self.trigger_position: - # Fallback final: usar el primer trigger si todo lo demás falla - gcmd.respond_info("No se registró la posición del trigger en la aproximación fina. Usando el primer trigger.") - self.trigger_position = first_trigger_pos - - # Verificar que trigger_position tiene 4 elementos antes de retornar - if len(self.trigger_position) < 4: - # Obtener valor E actual - current_pos = toolhead.get_position() - e_value = 0.0 - if len(current_pos) >= 4: - e_value = current_pos[3] - # Extender trigger_position - self.trigger_position = list(self.trigger_position) + [0.0] * (4 - len(self.trigger_position)) - self.trigger_position[3] = e_value - gcmd.respond_info("Extendiendo posición de trigger final para incluir valor E") - - gcmd.respond_info(f"Trigger final detectado en Z={self.trigger_position[2]:.3f}") - return self.trigger_position # Retornar la posición medida - - else: - # --- Homing Z cuando no está homeado (marcar Z como homeado temporalmente) --- - gcmd.respond_info("Eje Z no homeado. Marcando Z como homeado temporalmente para permitir movimiento...") - # Obtener posición actual (puede ser [0,0,0] o la última conocida antes del reinicio) - current_pos = toolhead.get_position() - # Asegurar que current_pos tiene 4 elementos - if len(current_pos) < 4: - current_pos = list(current_pos) + [0.0] * (4 - len(current_pos)) + gcmd.respond_info("✅ Medición dentro de tolerancia") - # Marcar solo Z como homeado en su posición actual - toolhead.set_position(current_pos, homing_axes=('z',)) - toolhead.wait_moves() # Asegurar que el estado se actualiza - gcmd.respond_info(f"Z temporalmente homeado en {current_pos[2]:.3f}. Iniciando búsqueda ZMAX...") + except Exception as e: + gcmd.respond_info(f"❌ Error durante medición: {e}") - # Si el endstop ya estaba activado y no pudimos desactivarlo, simplemente retornamos - if initial_endstop_state and self.endstop_triggered: - gcmd.respond_info("Ya en posición Z-max, omitiendo búsqueda") - # Retornar posición con formato completo (4 elementos) - return [current_pos[0], current_pos[1], z_max_cfg, current_pos[3]] - - # Ahora que Z está "homeado", podemos usar la lógica de movimiento absoluto - # Realizar búsqueda rápida - gcmd.respond_info("Iniciando búsqueda rápida (absoluta simulada)...") - movepos = list(current_pos) # Usar la posición actual "falsa" como punto de partida - movepos[2] = z_max_cfg - 0.1 # Objetivo justo debajo del máximo configurado - - # Usamos _safe_move_z, que ahora funcionará porque Z está marcado como homeado - if not self._safe_move_z(movepos, speed, 1.0): - if not self.endstop_triggered: - raise gcmd.error("No se detectó el endstop durante la búsqueda rápida (Z no homeado)") - else: - gcmd.respond_info("Endstop detectado inmediatamente en búsqueda rápida (Z no homeado).") - if not self.trigger_position: - self.trigger_position = toolhead.get_position() - - if not self.endstop_triggered: - if self._monitor_endstop(): - gcmd.respond_info("Endstop detectado al final del movimiento rápido (Z no homeado).") - else: - raise gcmd.error("Se alcanzó el objetivo de movimiento rápido sin detectar el endstop (Z no homeado).") - - if not self.trigger_position: - self.trigger_position = toolhead.get_position() - gcmd.respond_info("No se guardó la posición exacta del trigger rápido (Z no homeado), usando la posición actual.") - - # Asegurar que trigger_position tiene 4 elementos - if len(self.trigger_position) < 4: - self.trigger_position = list(self.trigger_position) + [0.0] * (4 - len(self.trigger_position)) - if len(current_pos) >= 4: - self.trigger_position[3] = current_pos[3] # Copiar E de la posición actual - gcmd.respond_info("Extendiendo posición de trigger para incluir valor E (Z no homeado)") - - first_trigger_pos = list(self.trigger_position) - - # Retraer (ahora se puede usar toolhead.move) - gcmd.respond_info(f"Primer trigger (Z no homeado) detectado en Z={first_trigger_pos[2]:.3f}. Retrayendo {retract_dist}mm...") - retract_pos = list(first_trigger_pos) - retract_pos[2] -= retract_dist - # Verificar límites para evitar movimientos fuera de rango - if retract_pos[2] < z_min_cfg: - retract_pos[2] = z_min_cfg - gcmd.respond_info(f"Ajustando retracción a Z={retract_pos[2]:.3f} para no exceder Zmin") - toolhead.move(retract_pos, speed) - toolhead.wait_moves() - - # Búsqueda lenta - gcmd.respond_info("Iniciando aproximación fina (absoluta simulada)...") - self.endstop_triggered = False # Resetear para la segunda búsqueda - self.trigger_position = None - toolhead.flush_step_generation() - toolhead.dwell(0.001) - - movepos = list(retract_pos) - movepos[2] = z_max_cfg - 0.1 # Objetivo de nuevo justo debajo del máximo - - if not self._safe_move_z(movepos, second_speed, 0.02): - if not self.endstop_triggered: - gcmd.respond_info("No se detectó el endstop en la aproximación fina (Z no homeado). Usando posición del primer trigger.") - self.trigger_position = first_trigger_pos - else: - gcmd.respond_info("Endstop detectado inmediatamente en aproximación fina (Z no homeado).") - if not self.trigger_position: - self.trigger_position = toolhead.get_position() - - if not self.endstop_triggered: - if self._monitor_endstop(): - gcmd.respond_info("Endstop detectado al final de la aproximación fina (Z no homeado).") - else: - gcmd.respond_info("Se alcanzó el objetivo de aproximación fina sin detectar el endstop (Z no homeado). Usando posición del primer trigger.") - self.trigger_position = first_trigger_pos - - if not self.trigger_position: - gcmd.respond_info("No se registró la posición del trigger en la aproximación fina (Z no homeado). Usando el primer trigger.") - self.trigger_position = first_trigger_pos - - # Asegurar que trigger_position tiene 4 elementos - if len(self.trigger_position) < 4: - self.trigger_position = list(self.trigger_position) + [0.0] * (4 - len(self.trigger_position)) - if len(current_pos) >= 4: - self.trigger_position[3] = current_pos[3] # Copiar E de la posición actual - gcmd.respond_info("Extendiendo posición de trigger final para incluir valor E (Z no homeado)") - - gcmd.respond_info(f"Trigger final (Z no homeado) detectado en Z={self.trigger_position[2]:.3f}") - # Retornar posición con formato completo (4 elementos) - return [current_pos[0], current_pos[1], z_max_cfg, current_pos[3]] - - cmd_ZMAX_HOME_help = "Realiza el homing del eje Z hacia Zmax usando el endstop de Y" + cmd_ZMAX_HOME_help = "Realiza el homing del eje Z hacia Zmax usando el endstop de Y. Usa MEASURE=1 para medir el recorrido total de la plataforma." def cmd_ZMAX_HOME(self, gcmd): - toolhead = self.toolhead - if self.y_endstop is None: - raise gcmd.error("ZMAX_HOME: Endstop de Y no inicializado") + if not all([self.toolhead, self.y_endstop_wrapper, self.z_stepper, self.phoming]): + raise gcmd.error("ZMAX_HOME: Sistema no inicializado correctamente") # Obtener parámetros speed = gcmd.get_float('SPEED', self.speed, above=0.) second_speed = gcmd.get_float('SECOND_SPEED', self.second_speed, above=0.) retract_dist = gcmd.get_float('RETRACT_DIST', self.retract_dist, minval=0.) - retract_speed = gcmd.get_float('RETRACT_SPEED', self.retract_speed, above=0.) - - # Usar la configuración del archivo printer.cfg - # Por defecto mantiene el valor en printer.cfg, sólo lo cambia si se especifica en el comando final_retract = gcmd.get_int('FINAL_RETRACT', -1) if final_retract == -1: - # Si no se especificó en el comando, usar el valor configurado final_retract = self.final_retract else: - # Si se especificó en el comando, convertir de entero a booleano - final_retract = final_retract != 0 - - # Mostrar estado para debug - gcmd.respond_info(f"Configuración: final_retract={final_retract} (valor en archivo={self.final_retract})") + final_retract = bool(final_retract) + measure_travel = gcmd.get_int('MEASURE', 0, minval=0, maxval=1) - # Verificar estado de homing - status = toolhead.get_status(self.printer.get_reactor().monotonic()) - z_homed = 'z' in status['homed_axes'] - x_homed = 'x' in status['homed_axes'] - y_homed = 'y' in status['homed_axes'] +# gcmd.respond_info(f"ZMAX_HOME: Iniciando movimiento continuo (velocidad: {speed} mm/s)") - gcmd.respond_info(f"ZMAX_HOME: Iniciando con velocidad:{speed} mm/s, velocidad fina:{second_speed} mm/s") - if not z_homed: - gcmd.respond_info("Eje Z no homeado. Realizando ZMAX_HOME relativo.") - else: - gcmd.respond_info("ZMAX_HOME: Iniciando búsqueda (eje Z ya homeado)...") + # Obtener configuración Z + z_max_cfg = self.z_stepper.get_range()[1] - # Guardar estado Gcode (posición relativa/absoluta) + # Verificar estado de Z + curtime = self.printer.get_reactor().monotonic() + z_homed = 'z' in self.toolhead.get_status(curtime)['homed_axes'] + + # Detectar si ya estamos en Z-max desde el inicio + if not z_homed and self.y_endstop.query_endstop(0): + pos = self.toolhead.get_position() + pos[2] = z_max_cfg # Establecer directamente en Z-max + self.toolhead.set_position(pos, homing_axes=('z',)) + z_homed = True # Marcar como homeado + elif not z_homed: + # Homear Z temporalmente en posición actual + pos = self.toolhead.get_position() + self.toolhead.set_position(pos, homing_axes=('z',)) + + # Configurar modo absoluto gcode_move = self.printer.lookup_object('gcode_move') gcode_state = gcode_move.get_status()['absolute_coordinates'] - - # Verificar estado inicial del endstop - initial_endstop_state = self.y_endstop.query_endstop(0) - gcmd.respond_info(f"Estado inicial del endstop Y: {'ACTIVADO' if initial_endstop_state else 'NO ACTIVADO'}") + self.gcode.run_script_from_command("G90") try: - # Encontrar Zmax - # Pasamos retract_dist a _find_z_max - final_pos_data = self._find_z_max(gcmd, speed, second_speed, retract_dist, z_homed) - - # Verificar que final_pos_data no sea None y tenga el formato esperado - if final_pos_data is None: - raise gcmd.error("Error: _find_z_max devolvió None") + # Variables para detección de pérdida de pasos + initial_z_pos = None + detected_z_pos = None + + # Obtener posición inicial + current_pos = self.toolhead.get_position() + initial_z_pos = current_pos[2] + + # Detectar si el endstop ya está activado al inicio + endstop_initially_triggered = self.y_endstop.query_endstop(0) + + # Solo considerar que está "ya activado" si estamos cerca de Z-max + if endstop_initially_triggered and current_pos[2] > (z_max_cfg - 10.0): + # Ya estamos en contacto con el endstop cerca de Z-max + gcmd.respond_info(f"🎯 Y-endstop YA ACTIVADO en Z={current_pos[2]:.3f}mm") + detected_z_pos = current_pos[2] - # Asegurar que final_pos_data sea una lista con al menos 3 elementos - if not isinstance(final_pos_data, (list, tuple)) or len(final_pos_data) < 3: - # Si no es una lista válida, usar la posición actual como fallback - gcmd.respond_info("Formato de posición inválido, usando posición actual") - final_pos_data = toolhead.get_position() - - # Obtener la posición Z máxima de la configuración - z_max_cfg = self.z_stepper.get_range()[1] + else: + # Endstop no activado - hacer búsqueda rápida primero + + # --- Búsqueda rápida continua --- + movepos = list(current_pos) + movepos[2] = z_max_cfg + + try: + first_trigger_pos = self._continuous_probe_move(movepos, speed) + detected_z_pos = first_trigger_pos[2] # Guardar posición REAL donde se detectó + except self.printer.command_error as e: + if "Probe triggered prior to movement" in str(e): + # El endstop se activó inmediatamente al iniciar el movimiento + gcmd.respond_info(f"🎯 Y-endstop YA ACTIVADO en Z={current_pos[2]:.3f}mm") + detected_z_pos = current_pos[2] + else: + raise - # Determinar la posición final Z - # Si Z no estaba homeado, la posición final es z_max_cfg. - # Si Z estaba homeado, usamos la posición medida del trigger devuelta por _find_z_max. - final_z = z_max_cfg if not z_homed or final_pos_data[2] is None else final_pos_data[2] + # --- Calcular pérdida de pasos ANTES de cambiar posición --- + if initial_z_pos is not None and detected_z_pos is not None: + expected_travel = z_max_cfg - initial_z_pos + actual_travel = detected_z_pos - initial_z_pos + step_loss = actual_travel - expected_travel + + if abs(step_loss) > 0.01: + if step_loss < 0: + gcmd.respond_info(f"⚠️ PÉRDIDA DE PASOS: {step_loss:.3f}mm (perdió pasos BAJANDO)") + else: + gcmd.respond_info(f"⚠️ PÉRDIDA DE PASOS: +{step_loss:.3f}mm (perdió pasos SUBIENDO)") + else: + gcmd.respond_info(f"✅ Sin pérdida de pasos (+{step_loss:.3f}mm)") - # Obtener posición actual completa (X, Y, Z, E) - current_pos = toolhead.get_position() # Llama a esto DESPUÉS de _find_z_max que puede cambiar el modo G90/G91 + # --- "Engañar" a Klipper estableciendo Z=Z-max para poder retraer --- + fake_pos = self.toolhead.get_position() + fake_pos[2] = z_max_cfg + self.toolhead.set_position(fake_pos) + + # --- Retracción desde Z-max --- + retract_pos = list(fake_pos) + retract_pos[2] = z_max_cfg - retract_dist + self.toolhead.move(retract_pos, self.retract_speed) + self.toolhead.wait_moves() - # Asegurar que current_pos tiene 4 elementos (X, Y, Z, E) - if len(current_pos) < 4: - gcmd.respond_info(f"Advertencia: la posición actual no tiene 4 elementos: {current_pos}") - # Extender la lista a 4 elementos añadiendo 0 para E si es necesario - current_pos = list(current_pos) + [0.0] * (4 - len(current_pos)) + # Verificar que el endstop se liberó + if self.y_endstop.query_endstop(0): + raise gcmd.error("Endstop Y sigue activado después de retracción. Homing fallido.") + + # --- Búsqueda fina continua --- + movepos = list(retract_pos) + movepos[2] = z_max_cfg - # Crear nueva posición manteniendo X, Y y E originales, solo cambiando Z - new_pos = list(current_pos) # Copia completa de la posición actual (incluye E) - - # Actualizar X e Y según el estado de homing - new_pos[0] = current_pos[0] if x_homed else 0.0 # Usar 0 si no está homeado X - new_pos[1] = current_pos[1] if y_homed else 0.0 # Usar 0 si no está homeado Y - new_pos[2] = final_z # Establecer Z a la posición final determinada + try: + final_trigger_pos = self._continuous_probe_move(movepos, second_speed) + except self.printer.command_error as e: + if "Probe triggered prior to movement" in str(e): + # El endstop se activó inmediatamente, usar posición actual + current_pos = self.toolhead.get_position() + gcmd.respond_info(f"🎯 Y-endstop ACTIVADO en Z={current_pos[2]:.3f}mm") + final_trigger_pos = current_pos + else: + raise + + # --- Establecer posición final en Z-max --- + final_pos = list(final_trigger_pos) + final_pos[2] = z_max_cfg if not z_homed: - # Si Z no estaba homeado, marcarlo como homeado ahora en z_max_cfg - toolhead.set_position(new_pos, homing_axes=('z',)) - gcmd.respond_info(f"ZMAX_HOME: Eje Z homeado en {final_z:.3f}mm") + self.toolhead.set_position(final_pos, homing_axes=('z',)) else: - # Si Z ya estaba homeado, simplemente establecer su nueva posición medida - toolhead.set_position(new_pos) - gcmd.respond_info(f"ZMAX_HOME: Posición Z establecida a {final_z:.3f}mm") + self.toolhead.set_position(final_pos) + + gcmd.respond_info(f"✅ Posición Z establecida en {final_pos[2]:.3f}mm") + + # Medición silenciosa de pérdida de pasos (sin reportar) - # Realizar retracción final para liberar presión SOLO si está habilitado + # --- Retracción final opcional --- if final_retract and retract_dist > 0: - try: - # Crear una copia nueva de new_pos para la retracción - retract_pos = list(new_pos) # Mantener la posición completa (4 elementos) - retract_pos[2] = final_z - retract_dist - - # Verificar límites de Z - z_min_cfg = self.z_stepper.get_range()[0] - if retract_pos[2] < z_min_cfg: - retract_pos[2] = z_min_cfg - gcmd.respond_info(f"Ajustando retracción final a Z={retract_pos[2]:.3f} para no exceder Zmin") - - gcmd.respond_info(f"ZMAX_HOME: Retracción final a Z={retract_pos[2]:.3f}mm") - toolhead.move(retract_pos, retract_speed) - toolhead.wait_moves() - except Exception as e: - # Si falla la retracción final, solo mostramos advertencia pero no abortamos - gcmd.respond_info(f"Error en retracción final: {str(e)}") - else: - # Informar que no se realiza retracción final (quedándose en Zmax) - gcmd.respond_info(f"ZMAX_HOME: Sin retracción final, quedando en posición Zmax ({final_z:.3f}mm)") + retract_pos = list(final_pos) + retract_pos[2] -= retract_dist + z_min_cfg = self.z_stepper.get_range()[0] + if retract_pos[2] < z_min_cfg: + retract_pos[2] = z_min_cfg + self.toolhead.move(retract_pos, self.retract_speed) + self.toolhead.wait_moves() + # --- Medición del recorrido total (opcional) --- + if measure_travel: + self._measure_platform_travel(gcmd, final_pos, z_max_cfg) + + finally: # Restaurar estado Gcode if not gcode_state: self.gcode.run_script_from_command("G91") - - # Mensaje final de éxito - gcmd.respond_info(f"ZMAX_HOME completado con éxito. Eje Z establecido a {final_z:.3f}mm") - - except Exception as e: - # Asegurarse de volver a modo absoluto en caso de error - try: - self.gcode.run_script_from_command("G90") - except Exception: - pass # Ignorar errores durante la limpieza - # Restaurar estado Gcode anterior si es posible - finally: - if not gcode_state: - try: - self.gcode.run_script_from_command("G91") - except Exception: - pass - # Si hay un error, desactivar motores solo si está configurado para hacerlo - if self.disable_motors: - stepper_enable = self.printer.lookup_object('stepper_enable') - stepper_enable.motor_off() - gcmd.respond_info("Motores desactivados debido a error.") - - raise gcmd.error(f"Error en ZMAX_HOME: {str(e)}") - - cmd_MEASURE_Z_LENGTH_help = "Mide la longitud real del eje Z entre Zmin y Zmax" - def cmd_MEASURE_Z_LENGTH(self, gcmd): - try: - # Verificar que el sistema está listo - if self.y_endstop is None: - raise gcmd.error("MEASURE_Z_LENGTH: Endstop de Y no inicializado") - - # Primero hacer home en Zmin usando comandos directos - gcmd.respond_info("Iniciando medición - Buscando Zmin...") - - # Obtener el objeto homing - toolhead = self.printer.lookup_object('toolhead') - homing = self.printer.lookup_object('homing') - - if homing is None: - raise gcmd.error("No se pudo acceder al módulo de homing") - - # Realizar homing en Z - try: - gcmd.respond_info("Ejecutando homing en Z (G28 Z)...") - self.gcode.run_script_from_command("G28 Z") - gcmd.respond_info("Homing Z completado") - except Exception as e: - raise gcmd.error(f"Error durante homing Z: {str(e)}") - - # Esperar a que se complete el movimiento - toolhead.wait_moves() - - # Obtener la posición después del home - z_min_pos = toolhead.get_position()[2] - gcmd.respond_info(f"Posición Zmin: {z_min_pos}mm") - - # Mover a una posición intermedia: 5mm por encima de Zmin - speed = gcmd.get_float('SPEED', 15, above=0.) - self.gcode.run_script_from_command("G91") - curpos = list(toolhead.get_position()) - movepos = list(curpos) - movepos[2] = z_min_pos + 5 - toolhead.move(movepos, speed) - toolhead.wait_moves() - - # Fase 1: Aproximación gruesa en tramos de 1mm - coarse_travel = 0.0 - max_coarse = 300 # límite de seguridad en mm - gcmd.respond_info("Iniciando aproximación gruesa: incrementos de 1mm...") - while coarse_travel < max_coarse: - if self.y_endstop.query_endstop(0): - gcmd.respond_info(f"Endstop Y activado en aproximación gruesa tras {coarse_travel:.2f}mm") - break - self.gcode.run_script_from_command(f"G1 Z1 F{speed*60}") - toolhead.wait_moves() - coarse_travel += 1.0 - else: - raise gcmd.error("No se activó el endstop Y durante la aproximación gruesa.") - - # Retroceder 2mm para iniciar la aproximación fina - gcmd.respond_info("Retrocediendo 2mm para aproximación fina...") - self.gcode.run_script_from_command(f"G1 Z-2 F{speed*60}") - toolhead.wait_moves() - - # Fase 2: Aproximación fina en tramos de 0.1mm - fine_speed = gcmd.get_float('SECOND_SPEED', 2, above=0.) - fine_travel = 0.0 - max_fine = 50 # límite de seguridad en mm - gcmd.respond_info("Iniciando aproximación fina: incrementos de 0.1mm...") - while fine_travel < max_fine: - if self.y_endstop.query_endstop(0): - gcmd.respond_info(f"Endstop Y activado en aproximación fina tras {fine_travel:.2f}mm") - break - self.gcode.run_script_from_command(f"G1 Z0.1 F{fine_speed*60}") - toolhead.wait_moves() - fine_travel += 0.1 - else: - raise gcmd.error("No se activó el endstop Y durante la aproximación fina.") - - # Fase 3: Aproximación ultra fina en tramos de 0.01mm - gcmd.respond_info("Iniciando fase ultra fina: retrocediendo 0.2mm para iniciar...") - self.gcode.run_script_from_command(f"G1 Z-0.2 F{fine_speed*60}") - toolhead.wait_moves() - - micro_travel = 0.0 - max_micro = 10 # límite de seguridad en mm para la fase ultra fina - gcmd.respond_info("Iniciando fase ultra fina: incrementos de 0.01mm...") - while micro_travel < max_micro: - if self.y_endstop.query_endstop(0): - gcmd.respond_info(f"Endstop Y activado en fase ultra fina tras {micro_travel:.2f}mm") - break - self.gcode.run_script_from_command(f"G1 Z0.01 F{fine_speed*60}") - toolhead.wait_moves() - micro_travel += 0.01 - else: - raise gcmd.error("No se activó el endstop Y durante la fase ultra fina.") - - self.gcode.run_script_from_command("G90") - - # Calcular la longitud total medida desde la posición intermedia - # Fórmula: (coarse_travel - 2.2mm) + fine_travel + micro_travel + 1mm - z_length = (coarse_travel - 2.2) + fine_travel + micro_travel + 1.0 - - gcmd.respond_info( - f"Medición completada:\n" - f" Posición Zmin: {z_min_pos:.3f}mm\n" - f" Longitud total del eje Z: {z_length:.3f}mm" - ) - - except Exception as e: - raise gcmd.error(f"Error en MEASURE_Z_LENGTH: {str(e)}") def load_config(config): return ZMaxHomingAlt(config) \ No newline at end of file