diff --git a/Marlin/Conditionals_post.h b/Marlin/Conditionals_post.h index e16c74ef2..401b83687 100644 --- a/Marlin/Conditionals_post.h +++ b/Marlin/Conditionals_post.h @@ -890,7 +890,7 @@ /** * Set granular options based on the specific type of leveling */ - #define UBL_DELTA (ENABLED(AUTO_BED_LEVELING_UBL) && (ENABLED(DELTA) || ENABLED(UBL_GRANULAR_SEGMENTATION_FOR_CARTESIAN))) + #define UBL_DELTA (ENABLED(AUTO_BED_LEVELING_UBL) && (ENABLED(DELTA) || ENABLED(SEGMENT_LEVELED_MOVES))) #define ABL_PLANAR (ENABLED(AUTO_BED_LEVELING_LINEAR) || ENABLED(AUTO_BED_LEVELING_3POINT)) #define ABL_GRID (ENABLED(AUTO_BED_LEVELING_LINEAR) || ENABLED(AUTO_BED_LEVELING_BILINEAR)) #define OLDSCHOOL_ABL (ABL_PLANAR || ABL_GRID) @@ -905,6 +905,10 @@ #define PROBE_BED_HEIGHT abs(BACK_PROBE_BED_POSITION - (FRONT_PROBE_BED_POSITION)) #endif + #if ENABLED(SEGMENT_LEVELED_MOVES) && !defined(LEVELED_SEGMENT_LENGTH) + #define LEVELED_SEGMENT_LENGTH 5 + #endif + /** * Bed Probing rectangular bounds * These can be further constrained in code for Delta and SCARA diff --git a/Marlin/Configuration.h b/Marlin/Configuration.h index 2d904c750..2f42574eb 100644 --- a/Marlin/Configuration.h +++ b/Marlin/Configuration.h @@ -891,6 +891,11 @@ // The height can be set with M420 Z #define ENABLE_LEVELING_FADE_HEIGHT + // For Cartesian machines, instead of dividing moves on mesh boundaries, + // split up moves into short segments like a Delta. + #define SEGMENT_LEVELED_MOVES + #define LEVELED_SEGMENT_LENGTH 5.0 // (mm) Length of all segments (except the last one) + /** * Enable the G26 Mesh Validation Pattern tool. */ diff --git a/Marlin/Marlin_main.cpp b/Marlin/Marlin_main.cpp index 7b0c49dc6..96ab28493 100644 --- a/Marlin/Marlin_main.cpp +++ b/Marlin/Marlin_main.cpp @@ -12531,7 +12531,81 @@ void set_current_from_steppers_for_axis(const AxisEnum axis) { current_position[axis] = cartes[axis]; } -#if ENABLED(MESH_BED_LEVELING) +#if IS_CARTESIAN +#if ENABLED(SEGMENT_LEVELED_MOVES) + + /** + * Prepare a segmented move on a CARTESIAN setup. + * + * This calls planner.buffer_line several times, adding + * small incremental moves. This allows the planner to + * apply more detailed bed leveling to the full move. + */ + inline void segmented_line_to_destination(const float fr_mm_s, const float segment_size=LEVELED_SEGMENT_LENGTH) { + + const float xdiff = destination[X_AXIS] - current_position[X_AXIS], + ydiff = destination[Y_AXIS] - current_position[Y_AXIS]; + + // If the move is only in Z/E don't split up the move + if (!xdiff && !ydiff) { + planner.buffer_line_kinematic(destination, fr_mm_s, active_extruder); + return; + } + + // Remaining cartesian distances + const float zdiff = destination[Z_AXIS] - current_position[Z_AXIS], + ediff = destination[E_AXIS] - current_position[E_AXIS]; + + // Get the linear distance in XYZ + // If the move is very short, check the E move distance + // No E move either? Game over. + float cartesian_mm = SQRT(sq(xdiff) + sq(ydiff) + sq(zdiff)); + if (UNEAR_ZERO(cartesian_mm)) cartesian_mm = FABS(ediff); + if (UNEAR_ZERO(cartesian_mm)) return; + + // The length divided by the segment size + // At least one segment is required + uint16_t segments = cartesian_mm / segment_size; + NOLESS(segments, 1); + + // The approximate length of each segment + const float inv_segments = 1.0 / float(segments), + segment_distance[XYZE] = { + xdiff * inv_segments, + ydiff * inv_segments, + zdiff * inv_segments, + ediff * inv_segments + }; + + // SERIAL_ECHOPAIR("mm=", cartesian_mm); + // SERIAL_ECHOLNPAIR(" segments=", segments); + + // Drop one segment so the last move is to the exact target. + // If there's only 1 segment, loops will be skipped entirely. + --segments; + + // Get the raw current position as starting point + float raw[XYZE]; + COPY(raw, current_position); + + // Calculate and execute the segments + for (uint16_t s = segments + 1; --s;) { + static millis_t next_idle_ms = millis() + 200UL; + thermalManager.manage_heater(); // This returns immediately if not really needed. + if (ELAPSED(millis(), next_idle_ms)) { + next_idle_ms = millis() + 200UL; + idle(); + } + LOOP_XYZE(i) raw[i] += segment_distance[i]; + planner.buffer_line_kinematic(raw, fr_mm_s, active_extruder); + } + + // Since segment_distance is only approximate, + // the final move must be to the exact destination. + planner.buffer_line_kinematic(destination, fr_mm_s, active_extruder); + } + +#elif ENABLED(MESH_BED_LEVELING) /** * Prepare a mesh-leveled linear move in a Cartesian setup, @@ -12592,7 +12666,7 @@ void set_current_from_steppers_for_axis(const AxisEnum axis) { mesh_line_to_destination(fr_mm_s, x_splits, y_splits); } -#elif ENABLED(AUTO_BED_LEVELING_BILINEAR) && !IS_KINEMATIC +#elif ENABLED(AUTO_BED_LEVELING_BILINEAR) #define CELL_INDEX(A,V) ((V - bilinear_start[A##_AXIS]) * ABL_BG_FACTOR(A##_AXIS)) @@ -12656,6 +12730,7 @@ void set_current_from_steppers_for_axis(const AxisEnum axis) { } #endif // AUTO_BED_LEVELING_BILINEAR +#endif // IS_CARTESIAN #if !UBL_DELTA #if IS_KINEMATIC @@ -12674,8 +12749,11 @@ void set_current_from_steppers_for_axis(const AxisEnum axis) { // Get the top feedrate of the move in the XY plane const float _feedrate_mm_s = MMS_SCALED(feedrate_mm_s); + const float xdiff = rtarget[X_AXIS] - current_position[X_AXIS], + ydiff = rtarget[Y_AXIS] - current_position[Y_AXIS]; + // If the move is only in Z/E don't split up the move - if (rtarget[X_AXIS] == current_position[X_AXIS] && rtarget[Y_AXIS] == current_position[Y_AXIS]) { + if (!xdiff && !ydiff) { planner.buffer_line_kinematic(rtarget, _feedrate_mm_s, active_extruder); return false; } @@ -12683,21 +12761,15 @@ void set_current_from_steppers_for_axis(const AxisEnum axis) { // Fail if attempting move outside printable radius if (!position_is_reachable(rtarget[X_AXIS], rtarget[Y_AXIS])) return true; - // Get the cartesian distances moved in XYZE - const float difference[XYZE] = { - rtarget[X_AXIS] - current_position[X_AXIS], - rtarget[Y_AXIS] - current_position[Y_AXIS], - rtarget[Z_AXIS] - current_position[Z_AXIS], - rtarget[E_AXIS] - current_position[E_AXIS] - }; + // Remaining cartesian distances + const float zdiff = rtarget[Z_AXIS] - current_position[Z_AXIS], + ediff = rtarget[E_AXIS] - current_position[E_AXIS]; // Get the linear distance in XYZ - float cartesian_mm = SQRT(sq(difference[X_AXIS]) + sq(difference[Y_AXIS]) + sq(difference[Z_AXIS])); - // If the move is very short, check the E move distance - if (UNEAR_ZERO(cartesian_mm)) cartesian_mm = FABS(difference[E_AXIS]); - // No E move either? Game over. + float cartesian_mm = SQRT(sq(xdiff) + sq(ydiff) + sq(zdiff)); + if (UNEAR_ZERO(cartesian_mm)) cartesian_mm = FABS(ediff); if (UNEAR_ZERO(cartesian_mm)) return true; // Minimum number of seconds to move the given distance @@ -12718,10 +12790,10 @@ void set_current_from_steppers_for_axis(const AxisEnum axis) { // The approximate length of each segment const float inv_segments = 1.0 / float(segments), segment_distance[XYZE] = { - difference[X_AXIS] * inv_segments, - difference[Y_AXIS] * inv_segments, - difference[Z_AXIS] * inv_segments, - difference[E_AXIS] * inv_segments + xdiff * inv_segments, + ydiff * inv_segments, + zdiff * inv_segments, + ediff * inv_segments }; // SERIAL_ECHOPAIR("mm=", cartesian_mm); @@ -12806,10 +12878,13 @@ void set_current_from_steppers_for_axis(const AxisEnum axis) { */ inline bool prepare_move_to_destination_cartesian() { #if HAS_MESH - if (planner.leveling_active) { + if (planner.leveling_active && planner.leveling_active_at_z(destination[Z_AXIS])) { #if ENABLED(AUTO_BED_LEVELING_UBL) ubl.line_to_destination_cartesian(MMS_SCALED(feedrate_mm_s), active_extruder); // UBL's motion routine needs to know about return true; // all moves, including Z-only moves. + #elif ENABLED(SEGMENT_LEVELED_MOVES) + segmented_line_to_destination(MMS_SCALED(feedrate_mm_s)); + return false; #else /** * For MBL and ABL-BILINEAR only segment moves when X or Y are involved. diff --git a/Marlin/SanityCheck.h b/Marlin/SanityCheck.h index 955505ce1..af6032bc3 100644 --- a/Marlin/SanityCheck.h +++ b/Marlin/SanityCheck.h @@ -220,6 +220,8 @@ #error "ENABLE_MESH_EDIT_GFX_OVERLAY is now MESH_EDIT_GFX_OVERLAY. Please update your configuration." #elif defined(BABYSTEP_ZPROBE_GFX_REVERSE) #error "BABYSTEP_ZPROBE_GFX_REVERSE is now set by OVERLAY_GFX_REVERSE. Please update your configurations." +#elif defined(UBL_GRANULAR_SEGMENTATION_FOR_CARTESIAN) + #error "UBL_GRANULAR_SEGMENTATION_FOR_CARTESIAN is now SEGMENT_LEVELED_MOVES. Please update your configuration." #endif /**