Subversion Repositories Tronxy-X3A-Marlin

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 ron 1
/**
2
 * Marlin 3D Printer Firmware
3
 * Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
4
 *
5
 * Based on Sprinter and grbl.
6
 * Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
7
 *
8
 * This program is free software: you can redistribute it and/or modify
9
 * it under the terms of the GNU General Public License as published by
10
 * the Free Software Foundation, either version 3 of the License, or
11
 * (at your option) any later version.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU General Public License
19
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
 *
21
 */
22
#include "MarlinConfig.h"
23
 
24
#if ENABLED(AUTO_BED_LEVELING_UBL)
25
 
26
  #include "Marlin.h"
27
  #include "ubl.h"
28
  #include "planner.h"
29
  #include "stepper.h"
30
  #include <avr/io.h>
31
  #include <math.h>
32
 
33
  #if AVR_AT90USB1286_FAMILY  // Teensyduino & Printrboard IDE extensions have compile errors without this
34
    inline void set_current_from_destination() { COPY(current_position, destination); }
35
  #else
36
    extern void set_current_from_destination();
37
  #endif
38
 
39
  #if !UBL_SEGMENTED
40
 
41
    void unified_bed_leveling::line_to_destination_cartesian(const float &feed_rate, const uint8_t extruder) {
42
      /**
43
       * Much of the nozzle movement will be within the same cell. So we will do as little computation
44
       * as possible to determine if this is the case. If this move is within the same cell, we will
45
       * just do the required Z-Height correction, call the Planner's buffer_line() routine, and leave
46
       */
47
      #if ENABLED(SKEW_CORRECTION)
48
        // For skew correction just adjust the destination point and we're done
49
        float start[XYZE] = { current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_CART] },
50
              end[XYZE] = { destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_CART] };
51
        planner.skew(start[X_AXIS], start[Y_AXIS], start[Z_AXIS]);
52
        planner.skew(end[X_AXIS], end[Y_AXIS], end[Z_AXIS]);
53
      #else
54
        const float (&start)[XYZE] = current_position,
55
                      (&end)[XYZE] = destination;
56
      #endif
57
 
58
      const int cell_start_xi = get_cell_index_x(start[X_AXIS]),
59
                cell_start_yi = get_cell_index_y(start[Y_AXIS]),
60
                cell_dest_xi  = get_cell_index_x(end[X_AXIS]),
61
                cell_dest_yi  = get_cell_index_y(end[Y_AXIS]);
62
 
63
      if (g26_debug_flag) {
64
        SERIAL_ECHOPAIR(" ubl.line_to_destination_cartesian(xe=", destination[X_AXIS]);
65
        SERIAL_ECHOPAIR(", ye=", destination[Y_AXIS]);
66
        SERIAL_ECHOPAIR(", ze=", destination[Z_AXIS]);
67
        SERIAL_ECHOPAIR(", ee=", destination[E_CART]);
68
        SERIAL_CHAR(')');
69
        SERIAL_EOL();
70
        debug_current_and_destination(PSTR("Start of ubl.line_to_destination_cartesian()"));
71
      }
72
 
73
      // A move within the same cell needs no splitting
74
      if (cell_start_xi == cell_dest_xi && cell_start_yi == cell_dest_yi) {
75
 
76
        // For a move off the bed, use a constant Z raise
77
        if (!WITHIN(cell_dest_xi, 0, GRID_MAX_POINTS_X - 1) || !WITHIN(cell_dest_yi, 0, GRID_MAX_POINTS_Y - 1)) {
78
 
79
          // Note: There is no Z Correction in this case. We are off the grid and don't know what
80
          // a reasonable correction would be.  If the user has specified a UBL_Z_RAISE_WHEN_OFF_MESH
81
          // value, that will be used instead of a calculated (Bi-Linear interpolation) correction.
82
 
83
          const float z_raise = 0.0
84
            #ifdef UBL_Z_RAISE_WHEN_OFF_MESH
85
              + UBL_Z_RAISE_WHEN_OFF_MESH
86
            #endif
87
          ;
88
          planner.buffer_segment(end[X_AXIS], end[Y_AXIS], end[Z_AXIS] + z_raise, end[E_CART], feed_rate, extruder);
89
          set_current_from_destination();
90
 
91
          if (g26_debug_flag)
92
            debug_current_and_destination(PSTR("out of bounds in ubl.line_to_destination_cartesian()"));
93
 
94
          return;
95
        }
96
 
97
        FINAL_MOVE:
98
 
99
        // The distance is always MESH_X_DIST so multiply by the constant reciprocal.
100
        const float xratio = (end[X_AXIS] - mesh_index_to_xpos(cell_dest_xi)) * (1.0f / (MESH_X_DIST));
101
 
102
        float z1 = z_values[cell_dest_xi    ][cell_dest_yi    ] + xratio *
103
                  (z_values[cell_dest_xi + 1][cell_dest_yi    ] - z_values[cell_dest_xi][cell_dest_yi    ]),
104
              z2 = z_values[cell_dest_xi    ][cell_dest_yi + 1] + xratio *
105
                  (z_values[cell_dest_xi + 1][cell_dest_yi + 1] - z_values[cell_dest_xi][cell_dest_yi + 1]);
106
 
107
        if (cell_dest_xi >= GRID_MAX_POINTS_X - 1) z1 = z2 = 0.0;
108
 
109
        // X cell-fraction done. Interpolate the two Z offsets with the Y fraction for the final Z offset.
110
        const float yratio = (end[Y_AXIS] - mesh_index_to_ypos(cell_dest_yi)) * (1.0f / (MESH_Y_DIST)),
111
                    z0 = cell_dest_yi < GRID_MAX_POINTS_Y - 1 ? (z1 + (z2 - z1) * yratio) * planner.fade_scaling_factor_for_z(end[Z_AXIS]) : 0.0;
112
 
113
        // Undefined parts of the Mesh in z_values[][] are NAN.
114
        // Replace NAN corrections with 0.0 to prevent NAN propagation.
115
        planner.buffer_segment(end[X_AXIS], end[Y_AXIS], end[Z_AXIS] + (isnan(z0) ? 0.0 : z0), end[E_CART], feed_rate, extruder);
116
 
117
        if (g26_debug_flag)
118
          debug_current_and_destination(PSTR("FINAL_MOVE in ubl.line_to_destination_cartesian()"));
119
 
120
        set_current_from_destination();
121
        return;
122
      }
123
 
124
      /**
125
       * Past this point the move is known to cross one or more mesh lines. Check for the most common
126
       * case - crossing only one X or Y line - after details are worked out to reduce computation.
127
       */
128
 
129
      const float dx = end[X_AXIS] - start[X_AXIS],
130
                  dy = end[Y_AXIS] - start[Y_AXIS];
131
 
132
      const int left_flag = dx < 0.0 ? 1 : 0,
133
                down_flag = dy < 0.0 ? 1 : 0;
134
 
135
      const float adx = left_flag ? -dx : dx,
136
                  ady = down_flag ? -dy : dy;
137
 
138
      const int dxi = cell_start_xi == cell_dest_xi ? 0 : left_flag ? -1 : 1,
139
                dyi = cell_start_yi == cell_dest_yi ? 0 : down_flag ? -1 : 1;
140
 
141
      /**
142
       * Compute the extruder scaling factor for each partial move, checking for
143
       * zero-length moves that would result in an infinite scaling factor.
144
       * A float divide is required for this, but then it just multiplies.
145
       * Also select a scaling factor based on the larger of the X and Y
146
       * components. The larger of the two is used to preserve precision.
147
       */
148
 
149
      const bool use_x_dist = adx > ady;
150
 
151
      float on_axis_distance = use_x_dist ? dx : dy,
152
            e_position = end[E_CART] - start[E_CART],
153
            z_position = end[Z_AXIS] - start[Z_AXIS];
154
 
155
      const float e_normalized_dist = e_position / on_axis_distance,
156
                  z_normalized_dist = z_position / on_axis_distance;
157
 
158
      int current_xi = cell_start_xi,
159
          current_yi = cell_start_yi;
160
 
161
      const float m = dy / dx,
162
                  c = start[Y_AXIS] - m * start[X_AXIS];
163
 
164
      const bool inf_normalized_flag = (isinf(e_normalized_dist) != 0),
165
                 inf_m_flag = (isinf(m) != 0);
166
 
167
      /**
168
       * Handle vertical lines that stay within one column.
169
       * These need not be perfectly vertical.
170
       */
171
      if (dxi == 0) {             // Vertical line?
172
        current_yi += down_flag;  // Line going down? Just go to the bottom.
173
        while (current_yi != cell_dest_yi + down_flag) {
174
          current_yi += dyi;
175
          const float next_mesh_line_y = mesh_index_to_ypos(current_yi);
176
 
177
          /**
178
           * Skip the calculations for an infinite slope.
179
           * For others the next X is the same so this can continue.
180
           * Calculate X at the next Y mesh line.
181
           */
182
          const float rx = inf_m_flag ? start[X_AXIS] : (next_mesh_line_y - c) / m;
183
 
184
          float z0 = z_correction_for_x_on_horizontal_mesh_line(rx, current_xi, current_yi)
185
                     * planner.fade_scaling_factor_for_z(end[Z_AXIS]);
186
 
187
          // Undefined parts of the Mesh in z_values[][] are NAN.
188
          // Replace NAN corrections with 0.0 to prevent NAN propagation.
189
          if (isnan(z0)) z0 = 0.0;
190
 
191
          const float ry = mesh_index_to_ypos(current_yi);
192
 
193
          /**
194
           * Without this check, it's possible to generate a zero length move, as in the case where
195
           * the line is heading down, starting exactly on a mesh line boundary. Since this is rare
196
           * it might be fine to remove this check and let planner.buffer_segment() filter it out.
197
           */
198
          if (ry != start[Y_AXIS]) {
199
            if (!inf_normalized_flag) {
200
              on_axis_distance = use_x_dist ? rx - start[X_AXIS] : ry - start[Y_AXIS];
201
              e_position = start[E_CART] + on_axis_distance * e_normalized_dist;
202
              z_position = start[Z_AXIS] + on_axis_distance * z_normalized_dist;
203
            }
204
            else {
205
              e_position = end[E_CART];
206
              z_position = end[Z_AXIS];
207
            }
208
 
209
            planner.buffer_segment(rx, ry, z_position + z0, e_position, feed_rate, extruder);
210
          } //else printf("FIRST MOVE PRUNED  ");
211
        }
212
 
213
        if (g26_debug_flag)
214
          debug_current_and_destination(PSTR("vertical move done in ubl.line_to_destination_cartesian()"));
215
 
216
        // At the final destination? Usually not, but when on a Y Mesh Line it's completed.
217
        if (current_position[X_AXIS] != end[X_AXIS] || current_position[Y_AXIS] != end[Y_AXIS])
218
          goto FINAL_MOVE;
219
 
220
        set_current_from_destination();
221
        return;
222
      }
223
 
224
      /**
225
       * Handle horizontal lines that stay within one row.
226
       * These need not be perfectly horizontal.
227
       */
228
      if (dyi == 0) {             // Horizontal line?
229
        current_xi += left_flag;  // Heading left? Just go to the left edge of the cell for the first move.
230
        while (current_xi != cell_dest_xi + left_flag) {
231
          current_xi += dxi;
232
          const float next_mesh_line_x = mesh_index_to_xpos(current_xi),
233
                      ry = m * next_mesh_line_x + c;   // Calculate Y at the next X mesh line
234
 
235
          float z0 = z_correction_for_y_on_vertical_mesh_line(ry, current_xi, current_yi)
236
                     * planner.fade_scaling_factor_for_z(end[Z_AXIS]);
237
 
238
          // Undefined parts of the Mesh in z_values[][] are NAN.
239
          // Replace NAN corrections with 0.0 to prevent NAN propagation.
240
          if (isnan(z0)) z0 = 0.0;
241
 
242
          const float rx = mesh_index_to_xpos(current_xi);
243
 
244
          /**
245
           * Without this check, it's possible to generate a zero length move, as in the case where
246
           * the line is heading left, starting exactly on a mesh line boundary. Since this is rare
247
           * it might be fine to remove this check and let planner.buffer_segment() filter it out.
248
           */
249
          if (rx != start[X_AXIS]) {
250
            if (!inf_normalized_flag) {
251
              on_axis_distance = use_x_dist ? rx - start[X_AXIS] : ry - start[Y_AXIS];
252
              e_position = start[E_CART] + on_axis_distance * e_normalized_dist;  // is based on X or Y because this is a horizontal move
253
              z_position = start[Z_AXIS] + on_axis_distance * z_normalized_dist;
254
            }
255
            else {
256
              e_position = end[E_CART];
257
              z_position = end[Z_AXIS];
258
            }
259
 
260
            if (!planner.buffer_segment(rx, ry, z_position + z0, e_position, feed_rate, extruder))
261
              break;
262
          } //else printf("FIRST MOVE PRUNED  ");
263
        }
264
 
265
        if (g26_debug_flag)
266
          debug_current_and_destination(PSTR("horizontal move done in ubl.line_to_destination_cartesian()"));
267
 
268
        if (current_position[X_AXIS] != end[X_AXIS] || current_position[Y_AXIS] != end[Y_AXIS])
269
          goto FINAL_MOVE;
270
 
271
        set_current_from_destination();
272
        return;
273
      }
274
 
275
      /**
276
       *
277
       * Handle the generic case of a line crossing both X and Y Mesh lines.
278
       *
279
       */
280
 
281
      int xi_cnt = cell_start_xi - cell_dest_xi,
282
          yi_cnt = cell_start_yi - cell_dest_yi;
283
 
284
      if (xi_cnt < 0) xi_cnt = -xi_cnt;
285
      if (yi_cnt < 0) yi_cnt = -yi_cnt;
286
 
287
      current_xi += left_flag;
288
      current_yi += down_flag;
289
 
290
      while (xi_cnt || yi_cnt) {
291
 
292
        const float next_mesh_line_x = mesh_index_to_xpos(current_xi + dxi),
293
                    next_mesh_line_y = mesh_index_to_ypos(current_yi + dyi),
294
                    ry = m * next_mesh_line_x + c,   // Calculate Y at the next X mesh line
295
                    rx = (next_mesh_line_y - c) / m; // Calculate X at the next Y mesh line
296
                                                     // (No need to worry about m being zero.
297
                                                     //  If that was the case, it was already detected
298
                                                     //  as a vertical line move above.)
299
 
300
        if (left_flag == (rx > next_mesh_line_x)) { // Check if we hit the Y line first
301
          // Yes!  Crossing a Y Mesh Line next
302
          float z0 = z_correction_for_x_on_horizontal_mesh_line(rx, current_xi - left_flag, current_yi + dyi)
303
                     * planner.fade_scaling_factor_for_z(end[Z_AXIS]);
304
 
305
          // Undefined parts of the Mesh in z_values[][] are NAN.
306
          // Replace NAN corrections with 0.0 to prevent NAN propagation.
307
          if (isnan(z0)) z0 = 0.0;
308
 
309
          if (!inf_normalized_flag) {
310
            on_axis_distance = use_x_dist ? rx - start[X_AXIS] : next_mesh_line_y - start[Y_AXIS];
311
            e_position = start[E_CART] + on_axis_distance * e_normalized_dist;
312
            z_position = start[Z_AXIS] + on_axis_distance * z_normalized_dist;
313
          }
314
          else {
315
            e_position = end[E_CART];
316
            z_position = end[Z_AXIS];
317
          }
318
          if (!planner.buffer_segment(rx, next_mesh_line_y, z_position + z0, e_position, feed_rate, extruder))
319
            break;
320
          current_yi += dyi;
321
          yi_cnt--;
322
        }
323
        else {
324
          // Yes!  Crossing a X Mesh Line next
325
          float z0 = z_correction_for_y_on_vertical_mesh_line(ry, current_xi + dxi, current_yi - down_flag)
326
                     * planner.fade_scaling_factor_for_z(end[Z_AXIS]);
327
 
328
          // Undefined parts of the Mesh in z_values[][] are NAN.
329
          // Replace NAN corrections with 0.0 to prevent NAN propagation.
330
          if (isnan(z0)) z0 = 0.0;
331
 
332
          if (!inf_normalized_flag) {
333
            on_axis_distance = use_x_dist ? next_mesh_line_x - start[X_AXIS] : ry - start[Y_AXIS];
334
            e_position = start[E_CART] + on_axis_distance * e_normalized_dist;
335
            z_position = start[Z_AXIS] + on_axis_distance * z_normalized_dist;
336
          }
337
          else {
338
            e_position = end[E_CART];
339
            z_position = end[Z_AXIS];
340
          }
341
 
342
          if (!planner.buffer_segment(next_mesh_line_x, ry, z_position + z0, e_position, feed_rate, extruder))
343
            break;
344
          current_xi += dxi;
345
          xi_cnt--;
346
        }
347
 
348
        if (xi_cnt < 0 || yi_cnt < 0) break; // Too far! Exit the loop and go to FINAL_MOVE
349
      }
350
 
351
      if (g26_debug_flag)
352
        debug_current_and_destination(PSTR("generic move done in ubl.line_to_destination_cartesian()"));
353
 
354
      if (current_position[X_AXIS] != end[X_AXIS] || current_position[Y_AXIS] != end[Y_AXIS])
355
        goto FINAL_MOVE;
356
 
357
      set_current_from_destination();
358
    }
359
 
360
  #else // UBL_SEGMENTED
361
 
362
    #if IS_SCARA // scale the feed rate from mm/s to degrees/s
363
      static float scara_feed_factor, scara_oldA, scara_oldB;
364
    #endif
365
 
366
    // We don't want additional apply_leveling() performed by regular buffer_line or buffer_line_kinematic,
367
    // so we call buffer_segment directly here.  Per-segmented leveling and kinematics performed first.
368
 
369
    inline void _O2 ubl_buffer_segment_raw(const float (&in_raw)[XYZE], const float &fr) {
370
 
371
      #if ENABLED(SKEW_CORRECTION)
372
        float raw[XYZE] = { in_raw[X_AXIS], in_raw[Y_AXIS], in_raw[Z_AXIS] };
373
        planner.skew(raw[X_AXIS], raw[Y_AXIS], raw[Z_AXIS]);
374
      #else
375
        const float (&raw)[XYZE] = in_raw;
376
      #endif
377
 
378
      #if ENABLED(DELTA)  // apply delta inverse_kinematics
379
 
380
        DELTA_IK(raw);
381
        planner.buffer_segment(delta[A_AXIS], delta[B_AXIS], delta[C_AXIS], in_raw[E_CART], fr, active_extruder);
382
 
383
      #elif ENABLED(HANGPRINTER)   // apply hangprinter inverse_kinematics
384
 
385
        HANGPRINTER_IK(raw);
386
        planner.buffer_segment(line_lengths[A_AXIS], line_lengths[B_AXIS], line_lengths[C_AXIS], line_lengths[D_AXIS], in_raw[E_CART], fr, active_extruder);
387
 
388
      #elif IS_SCARA  // apply scara inverse_kinematics (should be changed to save raw->logical->raw)
389
 
390
        inverse_kinematics(raw);  // this writes delta[ABC] from raw[XYZE]
391
                                  // should move the feedrate scaling to scara inverse_kinematics
392
 
393
        const float adiff = ABS(delta[A_AXIS] - scara_oldA),
394
                    bdiff = ABS(delta[B_AXIS] - scara_oldB);
395
        scara_oldA = delta[A_AXIS];
396
        scara_oldB = delta[B_AXIS];
397
        float s_feedrate = MAX(adiff, bdiff) * scara_feed_factor;
398
 
399
        planner.buffer_segment(delta[A_AXIS], delta[B_AXIS], delta[C_AXIS], in_raw[E_CART], s_feedrate, active_extruder);
400
 
401
      #else // CARTESIAN
402
 
403
        planner.buffer_segment(raw[X_AXIS], raw[Y_AXIS], raw[Z_AXIS], in_raw[E_CART], fr, active_extruder);
404
 
405
      #endif
406
    }
407
 
408
    #if IS_SCARA
409
      #define DELTA_SEGMENT_MIN_LENGTH 0.25 // SCARA minimum segment size is 0.25mm
410
    #elif ENABLED(DELTA)
411
      #define DELTA_SEGMENT_MIN_LENGTH 0.10 // mm (still subject to DELTA_SEGMENTS_PER_SECOND)
412
    #else // CARTESIAN
413
      #ifdef LEVELED_SEGMENT_LENGTH
414
        #define DELTA_SEGMENT_MIN_LENGTH LEVELED_SEGMENT_LENGTH
415
      #else
416
        #define DELTA_SEGMENT_MIN_LENGTH 1.00 // mm (similar to G2/G3 arc segmentation)
417
      #endif
418
    #endif
419
 
420
    /**
421
     * Prepare a segmented linear move for DELTA/SCARA/CARTESIAN with UBL and FADE semantics.
422
     * This calls planner.buffer_segment multiple times for small incremental moves.
423
     * Returns true if did NOT move, false if moved (requires current_position update).
424
     */
425
 
426
    bool _O2 unified_bed_leveling::prepare_segmented_line_to(const float (&rtarget)[XYZE], const float &feedrate) {
427
 
428
      if (!position_is_reachable(rtarget[X_AXIS], rtarget[Y_AXIS]))  // fail if moving outside reachable boundary
429
        return true; // did not move, so current_position still accurate
430
 
431
      const float total[XYZE] = {
432
        rtarget[X_AXIS] - current_position[X_AXIS],
433
        rtarget[Y_AXIS] - current_position[Y_AXIS],
434
        rtarget[Z_AXIS] - current_position[Z_AXIS],
435
        rtarget[E_CART] - current_position[E_CART]
436
      };
437
 
438
      const float cartesian_xy_mm = HYPOT(total[X_AXIS], total[Y_AXIS]);  // total horizontal xy distance
439
 
440
      #if IS_KINEMATIC
441
        const float seconds = cartesian_xy_mm / feedrate;                                  // seconds to move xy distance at requested rate
442
        uint16_t segments = lroundf(delta_segments_per_second * seconds),                  // preferred number of segments for distance @ feedrate
443
                 seglimit = lroundf(cartesian_xy_mm * (1.0f / (DELTA_SEGMENT_MIN_LENGTH))); // number of segments at minimum segment length
444
        NOMORE(segments, seglimit);                                                        // limit to minimum segment length (fewer segments)
445
      #else
446
        uint16_t segments = lroundf(cartesian_xy_mm * (1.0f / (DELTA_SEGMENT_MIN_LENGTH))); // cartesian fixed segment length
447
      #endif
448
 
449
      NOLESS(segments, 1U);                        // must have at least one segment
450
      const float inv_segments = 1.0f / segments;  // divide once, multiply thereafter
451
 
452
      #if IS_SCARA // scale the feed rate from mm/s to degrees/s
453
        scara_feed_factor = cartesian_xy_mm * inv_segments * feedrate;
454
        scara_oldA = planner.get_axis_position_degrees(A_AXIS);
455
        scara_oldB = planner.get_axis_position_degrees(B_AXIS);
456
      #endif
457
 
458
      const float diff[XYZE] = {
459
        total[X_AXIS] * inv_segments,
460
        total[Y_AXIS] * inv_segments,
461
        total[Z_AXIS] * inv_segments,
462
        total[E_CART] * inv_segments
463
      };
464
 
465
      // Note that E segment distance could vary slightly as z mesh height
466
      // changes for each segment, but small enough to ignore.
467
 
468
      float raw[XYZE] = {
469
        current_position[X_AXIS],
470
        current_position[Y_AXIS],
471
        current_position[Z_AXIS],
472
        current_position[E_CART]
473
      };
474
 
475
      // Only compute leveling per segment if ubl active and target below z_fade_height.
476
      if (!planner.leveling_active || !planner.leveling_active_at_z(rtarget[Z_AXIS])) {   // no mesh leveling
477
        while (--segments) {
478
          LOOP_XYZE(i) raw[i] += diff[i];
479
          ubl_buffer_segment_raw(raw, feedrate);
480
        }
481
        ubl_buffer_segment_raw(rtarget, feedrate);
482
        return false; // moved but did not set_current_from_destination();
483
      }
484
 
485
      // Otherwise perform per-segment leveling
486
 
487
      #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
488
        const float fade_scaling_factor = planner.fade_scaling_factor_for_z(rtarget[Z_AXIS]);
489
      #endif
490
 
491
      // increment to first segment destination
492
      LOOP_XYZE(i) raw[i] += diff[i];
493
 
494
      for (;;) {  // for each mesh cell encountered during the move
495
 
496
        // Compute mesh cell invariants that remain constant for all segments within cell.
497
        // Note for cell index, if point is outside the mesh grid (in MESH_INSET perimeter)
498
        // the bilinear interpolation from the adjacent cell within the mesh will still work.
499
        // Inner loop will exit each time (because out of cell bounds) but will come back
500
        // in top of loop and again re-find same adjacent cell and use it, just less efficient
501
        // for mesh inset area.
502
 
503
        int8_t cell_xi = (raw[X_AXIS] - (MESH_MIN_X)) * (1.0f / (MESH_X_DIST)),
504
               cell_yi = (raw[Y_AXIS] - (MESH_MIN_Y)) * (1.0f / (MESH_Y_DIST));
505
 
506
        cell_xi = constrain(cell_xi, 0, (GRID_MAX_POINTS_X) - 1);
507
        cell_yi = constrain(cell_yi, 0, (GRID_MAX_POINTS_Y) - 1);
508
 
509
        const float x0 = mesh_index_to_xpos(cell_xi),   // 64 byte table lookup avoids mul+add
510
                    y0 = mesh_index_to_ypos(cell_yi);
511
 
512
        float z_x0y0 = z_values[cell_xi  ][cell_yi  ],  // z at lower left corner
513
              z_x1y0 = z_values[cell_xi+1][cell_yi  ],  // z at upper left corner
514
              z_x0y1 = z_values[cell_xi  ][cell_yi+1],  // z at lower right corner
515
              z_x1y1 = z_values[cell_xi+1][cell_yi+1];  // z at upper right corner
516
 
517
        if (isnan(z_x0y0)) z_x0y0 = 0;              // ideally activating planner.leveling_active (G29 A)
518
        if (isnan(z_x1y0)) z_x1y0 = 0;              //   should refuse if any invalid mesh points
519
        if (isnan(z_x0y1)) z_x0y1 = 0;              //   in order to avoid isnan tests per cell,
520
        if (isnan(z_x1y1)) z_x1y1 = 0;              //   thus guessing zero for undefined points
521
 
522
        float cx = raw[X_AXIS] - x0,   // cell-relative x and y
523
              cy = raw[Y_AXIS] - y0;
524
 
525
        const float z_xmy0 = (z_x1y0 - z_x0y0) * (1.0f / (MESH_X_DIST)),   // z slope per x along y0 (lower left to lower right)
526
                    z_xmy1 = (z_x1y1 - z_x0y1) * (1.0f / (MESH_X_DIST));   // z slope per x along y1 (upper left to upper right)
527
 
528
              float z_cxy0 = z_x0y0 + z_xmy0 * cx;            // z height along y0 at cx (changes for each cx in cell)
529
 
530
        const float z_cxy1 = z_x0y1 + z_xmy1 * cx,            // z height along y1 at cx
531
                    z_cxyd = z_cxy1 - z_cxy0;                 // z height difference along cx from y0 to y1
532
 
533
              float z_cxym = z_cxyd * (1.0f / (MESH_Y_DIST));  // z slope per y along cx from y0 to y1 (changes for each cx in cell)
534
 
535
        //    float z_cxcy = z_cxy0 + z_cxym * cy;            // interpolated mesh z height along cx at cy (do inside the segment loop)
536
 
537
        // As subsequent segments step through this cell, the z_cxy0 intercept will change
538
        // and the z_cxym slope will change, both as a function of cx within the cell, and
539
        // each change by a constant for fixed segment lengths.
540
 
541
        const float z_sxy0 = z_xmy0 * diff[X_AXIS],                                     // per-segment adjustment to z_cxy0
542
                    z_sxym = (z_xmy1 - z_xmy0) * (1.0f / (MESH_Y_DIST)) * diff[X_AXIS];  // per-segment adjustment to z_cxym
543
 
544
        for (;;) {  // for all segments within this mesh cell
545
 
546
          if (--segments == 0)                      // if this is last segment, use rtarget for exact
547
            COPY(raw, rtarget);
548
 
549
          const float z_cxcy = (z_cxy0 + z_cxym * cy) // interpolated mesh z height along cx at cy
550
            #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
551
              * fade_scaling_factor                   // apply fade factor to interpolated mesh height
552
            #endif
553
          ;
554
 
555
          const float z = raw[Z_AXIS];
556
          raw[Z_AXIS] += z_cxcy;
557
          ubl_buffer_segment_raw(raw, feedrate);
558
          raw[Z_AXIS] = z;
559
 
560
          if (segments == 0)                        // done with last segment
561
            return false;                           // did not set_current_from_destination()
562
 
563
          LOOP_XYZE(i) raw[i] += diff[i];
564
 
565
          cx += diff[X_AXIS];
566
          cy += diff[Y_AXIS];
567
 
568
          if (!WITHIN(cx, 0, MESH_X_DIST) || !WITHIN(cy, 0, MESH_Y_DIST))    // done within this cell, break to next
569
            break;
570
 
571
          // Next segment still within same mesh cell, adjust the per-segment
572
          // slope and intercept to compute next z height.
573
 
574
          z_cxy0 += z_sxy0;   // adjust z_cxy0 by per-segment z_sxy0
575
          z_cxym += z_sxym;   // adjust z_cxym by per-segment z_sxym
576
 
577
        } // segment loop
578
      } // cell loop
579
 
580
      return false; // caller will update current_position
581
    }
582
 
583
  #endif // UBL_SEGMENTED
584
 
585
#endif // AUTO_BED_LEVELING_UBL