Merge branch 'cart-grid' of git://github.com/lordofhyphens/Smoothieware into cart...
authorJim Morris <morris@wolfman.com>
Fri, 10 Feb 2017 06:48:36 +0000 (22:48 -0800)
committerJim Morris <morris@wolfman.com>
Fri, 10 Feb 2017 06:48:36 +0000 (22:48 -0800)
src/modules/tools/zprobe/CartGridStrategy.cpp [new file with mode: 0644]
src/modules/tools/zprobe/CartGridStrategy.h [new file with mode: 0644]
src/modules/tools/zprobe/ZProbe.cpp

diff --git a/src/modules/tools/zprobe/CartGridStrategy.cpp b/src/modules/tools/zprobe/CartGridStrategy.cpp
new file mode 100644 (file)
index 0000000..73b27b6
--- /dev/null
@@ -0,0 +1,547 @@
+/*
+ This code is derived from (and mostly copied from) Johann Rocholls code at https://github.com/jcrocholl/Marlin/blob/deltabot/Marlin/Marlin_main.cpp
+ license is the same as his code.
+
+    Summary
+    -------
+    Probes grid_size points in X and Y (total probes grid_size * grid_size) and stores the relative offsets from the 0,0 Z height
+    When enabled every move will calculate the Z offset based on interpolating the height offset within the grids nearest 4 points.
+
+    Configuration
+    -------------
+    The strategy must be enabled in the config as well as zprobe.
+
+      leveling-strategy.delta-grid.enable         true
+
+    The radius of the bed must be specified with...
+
+      leveling-strategy.delta-grid.radius        50
+
+      this needs to be at least as big as the maximum printing radius as moves outside of this will not be compensated for correctly
+
+    The size of the grid can be set with...
+
+      leveling-strategy.delta-grid.size        7
+
+      this is the X and Y size of the grid, it must be an odd number, the default is 7 which is 49 probe points
+
+   Optionally probe offsets from the nozzle or tool head can be defined with...
+
+      leveling-strategy.delta-grid.probe_offsets  0,0,0  # probe offsetrs x,y,z
+
+      they may also be set with M565 X0 Y0 Z0
+
+    If the saved grid is to be loaded on boot then this must be set in the config...
+
+      leveling-strategy.delta-grid.save        true
+
+      Then when M500 is issued it will save M375 which will cause the grid to be loaded on boot. The default is to not autoload the grid on boot
+
+    Optionally an initial_height can be set that tell the intial probe where to stop the fast decent before it probes, this should be around 5-10mm above the bed
+      leveling-strategy.delta-grid.initial_height  10
+
+
+    Usage
+    -----
+    G29 test probes in a spiral pattern within the radius producing a map of offsets, this can be imported into a graphing program to visualize the bed heights
+        optional parameters {{In}} sets the number of points to the value n, {{Jn}} sets the radius for this probe.
+
+    G31 probes the grid and turns the compensation on, this will remain in effect until reset or M561/M370
+        optional parameters {{Jn}} sets the radius for this probe, which gets saved with M375
+
+    M370 clears the grid and turns off compensation
+    M374 Save grid to /sd/delta.grid
+    M374.1 delete /sd/delta.grid
+    M375 Load the grid from /sd/delta.grid and enable compensation
+    M375.1 display the current grid
+    M561 clears the grid and turns off compensation
+    M565 defines the probe offsets from the nozzle or tool head
+
+
+    M500 saves the probe points
+    M503 displays the current settings
+*/
+
+#include "CartGridStrategy.h"
+
+#include "Kernel.h"
+#include "Config.h"
+#include "Robot.h"
+#include "StreamOutputPool.h"
+#include "Gcode.h"
+#include "checksumm.h"
+#include "ConfigValue.h"
+#include "PublicDataRequest.h"
+#include "PublicData.h"
+#include "Conveyor.h"
+#include "ZProbe.h"
+#include "nuts_bolts.h"
+#include "utils.h"
+#include "platform_memory.h"
+
+#include <string>
+#include <algorithm>
+#include <cstdlib>
+#include <cmath>
+#include <fastmath.h>
+
+#define grid_size_checksum           CHECKSUM("size")
+#define tolerance_checksum           CHECKSUM("tolerance")
+#define save_checksum                CHECKSUM("save")
+#define probe_offsets_checksum       CHECKSUM("probe_offsets")
+#define initial_height_checksum      CHECKSUM("initial_height")
+#define x_size_checksum              CHECKSUM("x")
+#define y_size_checksum              CHECKSUM("y")
+#define do_home_checksum             CHECKSUM("do_home")
+
+#define GRIDFILE "/sd/cartesian.grid"
+
+CartGridStrategy::CartGridStrategy(ZProbe *zprobe) : LevelingStrategy(zprobe)
+{
+    grid = nullptr;
+}
+
+CartGridStrategy::~CartGridStrategy()
+{
+    if(grid != nullptr) AHB0.dealloc(grid);
+}
+
+bool CartGridStrategy::handleConfig()
+{
+    grid_size = THEKERNEL->config->value(leveling_strategy_checksum, cart_grid_leveling_strategy_checksum, grid_size_checksum)->by_default(7)->as_number();
+    tolerance = THEKERNEL->config->value(leveling_strategy_checksum, cart_grid_leveling_strategy_checksum, tolerance_checksum)->by_default(0.03F)->as_number();
+    save = THEKERNEL->config->value(leveling_strategy_checksum, cart_grid_leveling_strategy_checksum, save_checksum)->by_default(false)->as_bool();
+    do_home = THEKERNEL->config->value(leveling_strategy_checksum, cart_grid_leveling_strategy_checksum, do_home_checksum)->by_default(true)->as_bool();
+
+    x_size = THEKERNEL->config->value(leveling_strategy_checksum, cart_grid_leveling_strategy_checksum, x_size_checksum)->by_default(0.0F)->as_number();
+    y_size = THEKERNEL->config->value(leveling_strategy_checksum, cart_grid_leveling_strategy_checksum, y_size_checksum)->by_default(0.0F)->as_number();
+    if (x_size == 0.0F || y_size == 0.0F) {
+        THEKERNEL->streams->printf("Error: Invalid config, x_size and y_size must be defined\n");
+        return false;
+    }
+
+    // the initial height above the bed we stop the intial move down after home to find the bed
+    // this should be a height that is enough that the probe will not hit the bed and is an offset from max_z (can be set to 0 if max_z takes into account the probe offset)
+    this->initial_height = THEKERNEL->config->value(leveling_strategy_checksum, cart_grid_leveling_strategy_checksum, initial_height_checksum)->by_default(10)->as_number();
+
+    // Probe offsets xxx,yyy,zzz
+    {
+        std::string po = THEKERNEL->config->value(leveling_strategy_checksum, cart_grid_leveling_strategy_checksum, probe_offsets_checksum)->by_default("0,0,0")->as_string();
+        std::vector<float> v = parse_number_list(po.c_str());
+        if(v.size() >= 3) {
+            this->probe_offsets = std::make_tuple(v[0], v[1], v[2]);
+        }
+    }
+
+    // allocate in AHB0
+    grid = (float *)AHB0.alloc(grid_size * grid_size * sizeof(float));
+
+    reset_bed_level();
+
+    return true;
+}
+
+void CartGridStrategy::save_grid(StreamOutput *stream)
+{
+    if(isnan(grid[0])) {
+        stream->printf("error:No grid to save\n");
+        return;
+    }
+
+    FILE *fp = fopen(GRIDFILE, "w");
+    if(fp == NULL) {
+        stream->printf("error:Failed to open grid file %s\n", GRIDFILE);
+        return;
+    }
+
+    if(fwrite(&grid_size, sizeof(uint8_t), 1, fp) != 1) {
+        stream->printf("error:Failed to write grid size\n");
+        fclose(fp);
+        return;
+    }
+
+    if(fwrite(&x_size, sizeof(float), 1, fp) != 1)  {
+        stream->printf("error:Failed to write x_size\n");
+        fclose(fp);
+        return;
+    }
+
+    if(fwrite(&y_size, sizeof(float), 1, fp) != 1)  {
+        stream->printf("error:Failed to write y_size\n");
+        fclose(fp);
+        return;
+    }
+
+    for (int y = 0; y < grid_size; y++) {
+        for (int x = 0; x < grid_size; x++) {
+            if(fwrite(&grid[x + (grid_size * y)], sizeof(float), 1, fp) != 1) {
+                stream->printf("error:Failed to write grid\n");
+                fclose(fp);
+                return;
+            }
+        }
+    }
+    stream->printf("grid saved to %s\n", GRIDFILE);
+    fclose(fp);
+}
+
+bool CartGridStrategy::load_grid(StreamOutput *stream)
+{
+    FILE *fp = fopen(GRIDFILE, "r");
+    if(fp == NULL) {
+        stream->printf("error:Failed to open grid %s\n", GRIDFILE);
+        return false;
+    }
+
+    uint8_t size;
+    float x, y;
+
+    if(fread(&size, sizeof(uint8_t), 1, fp) != 1) {
+        stream->printf("error:Failed to read grid size\n");
+        fclose(fp);
+        return false;
+    }
+
+    if(size != grid_size) {
+        stream->printf("error:grid size is different read %d - config %d\n", size, grid_size);
+        fclose(fp);
+        return false;
+    }
+
+    if(fread(&x, sizeof(float), 1, fp) != 1) {
+        stream->printf("error:Failed to read grid x size\n");
+        fclose(fp);
+        return false;
+    }
+
+    if(fread(&y, sizeof(float), 1, fp) != 1) {
+        stream->printf("error:Failed to read grid y size\n");
+        fclose(fp);
+        return false;
+    }
+
+    if(x != x_size || y != y_size) {
+        stream->printf("error:bed dimensions changed read (%f, %f) - config (%f,%f)\n", x, y, x_size, y_size);
+        fclose(fp);
+        return false;
+    }
+
+    for (int y = 0; y < grid_size; y++) {
+        for (int x = 0; x < grid_size; x++) {
+            if(fread(&grid[x + (grid_size * y)], sizeof(float), 1, fp) != 1) {
+                stream->printf("error:Failed to read grid\n");
+                fclose(fp);
+                return false;
+            }
+        }
+    }
+    stream->printf("grid loaded, grid: (%f, %f), size: %d\n", x_size, y_size, grid_size);
+    fclose(fp);
+    return true;
+}
+
+bool CartGridStrategy::probe_grid(int n, float x_size, float y_size, StreamOutput *stream)
+{
+    if(n < 5) {
+        stream->printf("Need at least a 5x5 grid to probe\n");
+        return true;
+    }
+
+    float initial_z = findBed();
+    if(isnan(initial_z)) return false;
+
+    for (int c = 0; c < n; ++c) {
+        float y = y_size * c;
+        for (int r = 0; r < n; ++r) {
+            float x = x_size * r;
+            // Avoid probing the corners (outside the round or hexagon print surface) on a delta printer.
+            float z = 0.0F;
+            if (x >= 0 && x <= x_size && y >= 0 && y <= y_size) {
+                float mm;
+                if(!zprobe->doProbeAt(mm, x, y)) return false;
+                z = zprobe->getProbeHeight() - mm;
+            }
+            stream->printf("%8.4f ", z);
+        }
+        stream->printf("\n");
+    }
+    return true;
+}
+
+bool CartGridStrategy::handleGcode(Gcode *gcode)
+{
+    if(gcode->has_g) {
+        if (gcode->g == 29) { // do a probe to test flatness
+            // first wait for an empty queue i.e. no moves left
+            THEKERNEL->conveyor->wait_for_idle();
+
+            int n = gcode->has_letter('I') ? gcode->get_value('I') : 0;
+            float x = x_size, y = y_size;
+            if(gcode->has_letter('X')) x = gcode->get_value('X'); // override default probe radius
+            if(gcode->has_letter('Y')) y = gcode->get_value('Y'); // override default probe radius
+            if(n == 0) n = 7;
+            probe_grid(n, x, y, gcode->stream);
+            
+            return true;
+
+        } else if( gcode->g == 31 ) { // do a grid probe
+            // first wait for an empty queue i.e. no moves left
+            THEKERNEL->conveyor->wait_for_idle();
+
+            if(!doProbe(gcode)) {
+                gcode->stream->printf("Probe failed to complete, check the initial probe height and/or initial_height settings\n");
+            } else {
+                gcode->stream->printf("Probe completed\n");
+            }
+            return true;
+        }
+
+    } else if(gcode->has_m) {
+        if(gcode->m == 370 || gcode->m == 561) { // M370: Clear bed, M561: Set Identity Transform
+            // delete the compensationTransform in robot
+            setAdjustFunction(false);
+            reset_bed_level();
+            gcode->stream->printf("grid cleared and disabled\n");
+            return true;
+
+        } else if(gcode->m == 374) { // M374: Save grid, M374.1: delete saved grid
+            if(gcode->subcode == 1) {
+                remove(GRIDFILE);
+                gcode->stream->printf("%s deleted\n", GRIDFILE);
+            } else {
+                save_grid(gcode->stream);
+            }
+
+            return true;
+
+        } else if(gcode->m == 375) { // M375: load grid, M375.1 display grid
+            if(gcode->subcode == 1) {
+                print_bed_level(gcode->stream);
+            } else {
+                if(load_grid(gcode->stream)) setAdjustFunction(true);
+            }
+            return true;
+
+        } else if(gcode->m == 565) { // M565: Set Z probe offsets
+            float x = 0, y = 0, z = 0;
+            if(gcode->has_letter('X')) x = gcode->get_value('X');
+            if(gcode->has_letter('Y')) y = gcode->get_value('Y');
+            if(gcode->has_letter('Z')) z = gcode->get_value('Z');
+            probe_offsets = std::make_tuple(x, y, z);
+            return true;
+
+        } else if(gcode->m == 500 || gcode->m == 503) { // M500 save, M503 display
+            float x, y, z;
+            std::tie(x, y, z) = probe_offsets;
+            gcode->stream->printf(";Probe offsets:\nM565 X%1.5f Y%1.5f Z%1.5f\n", x, y, z);
+            if(save) {
+                if(!isnan(grid[0])) gcode->stream->printf(";Load saved grid\nM375\n");
+                else if(gcode->m == 503) gcode->stream->printf(";WARNING No grid to save\n");
+            }
+            return true;
+        }
+    }
+
+    return false;
+}
+
+// These are convenience defines to keep the code as close to the original as possible it also saves memory and flash
+// set the rectangle in which to probe
+#define LEFT_PROBE_BED_POSITION (0)
+#define RIGHT_PROBE_BED_POSITION (x_size)
+#define BACK_PROBE_BED_POSITION (y_size)
+#define FRONT_PROBE_BED_POSITION (0)
+
+// probe at the points of a lattice grid
+#define AUTO_BED_LEVELING_GRID_X ((RIGHT_PROBE_BED_POSITION - LEFT_PROBE_BED_POSITION) / (grid_size - 1))
+#define AUTO_BED_LEVELING_GRID_Y ((BACK_PROBE_BED_POSITION - FRONT_PROBE_BED_POSITION) / (grid_size - 1))
+
+#define X_PROBE_OFFSET_FROM_EXTRUDER std::get<0>(probe_offsets)
+#define Y_PROBE_OFFSET_FROM_EXTRUDER std::get<1>(probe_offsets)
+#define Z_PROBE_OFFSET_FROM_EXTRUDER std::get<2>(probe_offsets)
+
+void CartGridStrategy::setAdjustFunction(bool on)
+{
+    if(on) {
+        // set the compensationTransform in robot
+        using std::placeholders::_1;
+        using std::placeholders::_2;
+        THEROBOT->compensationTransform = std::bind(&CartGridStrategy::doCompensation, this, _1, _2); // [this](float *target, bool inverse) { doCompensation(target, inverse); };
+    } else {
+        // clear it
+        THEROBOT->compensationTransform = nullptr;
+    }
+}
+
+float CartGridStrategy::findBed()
+{
+    if (do_home) zprobe->home();
+    // move to an initial position fast so as to not take all day, we move down max_z - initial_height, which is set in config, default 10mm
+    float deltaz = initial_height;
+    zprobe->coordinated_move(NAN, NAN, deltaz, zprobe->getFastFeedrate());
+    zprobe->coordinated_move(0, 0, NAN, zprobe->getFastFeedrate()); // move to 0,0
+
+    // find bed at 0,0 run at slow rate so as to not hit bed hard
+    float mm;
+    if(!zprobe->run_probe_return(mm, zprobe->getSlowFeedrate())) return NAN;
+
+    float dz = zprobe->getProbeHeight() - mm;
+    zprobe->coordinated_move(NAN, NAN, dz, zprobe->getFastFeedrate(), true); // relative move
+
+    return mm + deltaz - zprobe->getProbeHeight(); // distance to move from home to 5mm above bed
+}
+
+bool CartGridStrategy::doProbe(Gcode *gc)
+{
+    gc->stream->printf("Cartesian Grid Probe...\n");
+    setAdjustFunction(false);
+    reset_bed_level();
+
+    if(gc->has_letter('X')) x_size = gc->get_value('X'); // override default probe X bed, will get saved
+    if(gc->has_letter('Y')) x_size = gc->get_value('Y'); // override default probe Y bed, will get saved
+
+    // find bed, and leave probe probe height above bed
+    float initial_z = findBed();
+    if(isnan(initial_z)) {
+        gc->stream->printf("Finding bed failed, check the maxz and initial height settings\n");
+        return false;
+    }
+
+    gc->stream->printf("Probe start ht is %f mm, rectangular bed x %fmm, y %fmm, grid size is %dx%d\n", initial_z, x_size, y_size, grid_size, grid_size);
+
+    // do first probe for 0,0
+    float mm;
+    if(!zprobe->doProbeAt(mm, -X_PROBE_OFFSET_FROM_EXTRUDER, -Y_PROBE_OFFSET_FROM_EXTRUDER)) return false;
+    float z_reference = zprobe->getProbeHeight() - mm; // this should be zero
+    gc->stream->printf("probe at 0,0 is %f mm\n", z_reference);
+
+    // probe all the points in the grid within the given radius
+    for (int yCount = 0; yCount < grid_size; yCount++) {
+        float yProbe = FRONT_PROBE_BED_POSITION + AUTO_BED_LEVELING_GRID_Y * yCount;
+        int xStart, xStop, xInc;
+        if (yCount % 2) {
+            xStart = 0;
+            xStop = grid_size;
+            xInc = 1;
+        } else {
+            xStart = grid_size - 1;
+            xStop = -1;
+            xInc = -1;
+        }
+
+        for (int xCount = xStart; xCount != xStop; xCount += xInc) {
+            float xProbe = LEFT_PROBE_BED_POSITION + AUTO_BED_LEVELING_GRID_X * xCount;
+
+            if(!zprobe->doProbeAt(mm, xProbe - X_PROBE_OFFSET_FROM_EXTRUDER, yProbe - Y_PROBE_OFFSET_FROM_EXTRUDER)) return false;
+            float measured_z = zprobe->getProbeHeight() - mm - z_reference; // this is the delta z from bed at 0,0
+            gc->stream->printf("DEBUG: X%1.4f, Y%1.4f, Z%1.4f\n", xProbe, yProbe, measured_z);
+            grid[xCount + (grid_size * yCount)] = measured_z;
+        }
+    }
+
+    extrapolate_unprobed_bed_level();
+    print_bed_level(gc->stream);
+
+    setAdjustFunction(true);
+
+    return true;
+}
+
+void CartGridStrategy::extrapolate_one_point(int x, int y, int xdir, int ydir)
+{
+    if (!isnan(grid[x + (grid_size * y)])) {
+        return;  // Don't overwrite good values.
+    }
+    float a = 2 * grid[(x + xdir) + (y * grid_size)] - grid[(x + xdir * 2) + (y * grid_size)]; // Left to right.
+    float b = 2 * grid[x + ((y + ydir) * grid_size)] - grid[x + ((y + ydir * 2) * grid_size)]; // Front to back.
+    float c = 2 * grid[(x + xdir) + ((y + ydir) * grid_size)] - grid[(x + xdir * 2) + ((y + ydir * 2) * grid_size)]; // Diagonal.
+    float median = c;  // Median is robust (ignores outliers).
+    if (a < b) {
+        if (b < c) median = b;
+        if (c < a) median = a;
+    } else {  // b <= a
+        if (c < b) median = b;
+        if (a < c) median = a;
+    }
+    grid[x + (grid_size * y)] = median;
+}
+
+// Fill in the unprobed points (corners of circular print surface)
+// using linear extrapolation, away from the center.
+void CartGridStrategy::extrapolate_unprobed_bed_level()
+{
+    int half = (grid_size - 1) / 2;
+    for (int y = 0; y <= half; y++) {
+        for (int x = 0; x <= half; x++) {
+            if (x + y < 3) continue;
+            extrapolate_one_point(half - x, half - y, x > 1 ? +1 : 0, y > 1 ? +1 : 0);
+            extrapolate_one_point(half + x, half - y, x > 1 ? -1 : 0, y > 1 ? +1 : 0);
+            extrapolate_one_point(half - x, half + y, x > 1 ? +1 : 0, y > 1 ? -1 : 0);
+            extrapolate_one_point(half + x, half + y, x > 1 ? -1 : 0, y > 1 ? -1 : 0);
+        }
+    }
+}
+
+void CartGridStrategy::doCompensation(float *target, bool inverse)
+{
+    // Adjust print surface height by linear interpolation over the bed_level array.
+    int half = (grid_size - 1) / 2;
+    float grid_x = std::max(0.001F - half, std::min(half - 0.001F, target[X_AXIS] / AUTO_BED_LEVELING_GRID_X));
+    float grid_y = std::max(0.001F - half, std::min(half - 0.001F, target[Y_AXIS] / AUTO_BED_LEVELING_GRID_Y));
+    int floor_x = floorf(grid_x);
+    int floor_y = floorf(grid_y);
+    float ratio_x = grid_x - floor_x;
+    float ratio_y = grid_y - floor_y;
+    float z1 = grid[(floor_x + half) + ((floor_y + half) * grid_size)];
+    float z2 = grid[(floor_x + half) + ((floor_y + half + 1) * grid_size)];
+    float z3 = grid[(floor_x + half + 1) + ((floor_y + half) * grid_size)];
+    float z4 = grid[(floor_x + half + 1) + ((floor_y + half + 1) * grid_size)];
+    float left = (1 - ratio_y) * z1 + ratio_y * z2;
+    float right = (1 - ratio_y) * z3 + ratio_y * z4;
+    float offset = (1 - ratio_x) * left + ratio_x * right;
+
+    if(inverse)
+        target[Z_AXIS] -= offset;
+    else
+        target[Z_AXIS] += offset;
+
+
+    /*
+        THEKERNEL->streams->printf("//DEBUG: TARGET: %f, %f, %f\n", target[0], target[1], target[2]);
+        THEKERNEL->streams->printf("//DEBUG: grid_x= %f\n", grid_x);
+        THEKERNEL->streams->printf("//DEBUG: grid_y= %f\n", grid_y);
+        THEKERNEL->streams->printf("//DEBUG: floor_x= %d\n", floor_x);
+        THEKERNEL->streams->printf("//DEBUG: floor_y= %d\n", floor_y);
+        THEKERNEL->streams->printf("//DEBUG: ratio_x= %f\n", ratio_x);
+        THEKERNEL->streams->printf("//DEBUG: ratio_y= %f\n", ratio_y);
+        THEKERNEL->streams->printf("//DEBUG: z1= %f\n", z1);
+        THEKERNEL->streams->printf("//DEBUG: z2= %f\n", z2);
+        THEKERNEL->streams->printf("//DEBUG: z3= %f\n", z3);
+        THEKERNEL->streams->printf("//DEBUG: z4= %f\n", z4);
+        THEKERNEL->streams->printf("//DEBUG: left= %f\n", left);
+        THEKERNEL->streams->printf("//DEBUG: right= %f\n", right);
+        THEKERNEL->streams->printf("//DEBUG: offset= %f\n", offset);
+    */
+}
+
+
+// Print calibration results for plotting or manual frame adjustment.
+void CartGridStrategy::print_bed_level(StreamOutput *stream)
+{
+    for (int y = 0; y < grid_size; y++) {
+        for (int x = 0; x < grid_size; x++) {
+            stream->printf("%7.4f ", grid[x + (grid_size * y)]);
+        }
+        stream->printf("\n");
+    }
+}
+
+// Reset calibration results to zero.
+void CartGridStrategy::reset_bed_level()
+{
+    for (int y = 0; y < grid_size; y++) {
+        for (int x = 0; x < grid_size; x++) {
+            grid[x + (grid_size * y)] = NAN;
+        }
+    }
+}
diff --git a/src/modules/tools/zprobe/CartGridStrategy.h b/src/modules/tools/zprobe/CartGridStrategy.h
new file mode 100644 (file)
index 0000000..22a085b
--- /dev/null
@@ -0,0 +1,47 @@
+#pragma once
+
+#include "LevelingStrategy.h"
+
+#include <string.h>
+#include <tuple>
+
+#define cart_grid_leveling_strategy_checksum CHECKSUM("cart-grid")
+
+class StreamOutput;
+class Gcode;
+
+class CartGridStrategy : public LevelingStrategy
+{
+public:
+    CartGridStrategy(ZProbe *zprobe);
+    ~CartGridStrategy();
+    bool handleGcode(Gcode* gcode);
+    bool handleConfig();
+
+private:
+
+    void extrapolate_one_point(int x, int y, int xdir, int ydir);
+    void extrapolate_unprobed_bed_level();
+    bool doProbe(Gcode *gc);
+    float findBed();
+    void setAdjustFunction(bool on);
+    void print_bed_level(StreamOutput *stream);
+    void doCompensation(float *target, bool inverse);
+    void reset_bed_level();
+    void save_grid(StreamOutput *stream);
+    bool load_grid(StreamOutput *stream);
+    bool probe_grid(int n, float x_size, float y_size, StreamOutput *stream);
+
+    float initial_height;
+    float tolerance;
+
+    float *grid;
+    std::tuple<float, float, float> probe_offsets;
+    uint8_t grid_size;
+    float x_size,y_size;
+
+    struct {
+        bool save:1;
+        bool do_home:1;
+    };
+};
index 4d743f5..3ad50fc 100644 (file)
@@ -31,6 +31,7 @@
 #include "DeltaCalibrationStrategy.h"
 #include "ThreePointStrategy.h"
 #include "DeltaGridStrategy.h"
+#include "CartGridStrategy.h"
 
 #define enable_checksum          CHECKSUM("enable")
 #define probe_pin_checksum       CHECKSUM("probe_pin")
@@ -105,6 +106,11 @@ void ZProbe::config_load()
                     ls= new DeltaGridStrategy(this);
                     found= true;
                     break;
+
+                case cart_grid_leveling_strategy_checksum:
+                    ls= new CartGridStrategy(this);
+                    found= true;
+                    break;
             }
             if(found) {
                 if(ls->handleConfig()) {