Skip to content

Commit 990fc00

Browse files
marciotW4tel-BiDi
authored andcommitted
Refactor Hilbert curve. Enhance Touch UI Bed Level Screen. (MarlinFirmware#21453)
1 parent 2d1ad9a commit 990fc00

13 files changed

Lines changed: 341 additions & 144 deletions

File tree

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*********************
2+
* hilbert_curve.cpp *
3+
*********************/
4+
5+
/****************************************************************************
6+
* Written By Marcio Teixeira 2021 - SynDaver Labs, Inc. *
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+
* To view a copy of the GNU General Public License, go to the following *
19+
* location: <https://www.gnu.org/licenses/>. *
20+
****************************************************************************/
21+
22+
#include "../../inc/MarlinConfig.h"
23+
24+
#if ENABLED(UBL_HILBERT_CURVE)
25+
26+
#include "bedlevel.h"
27+
#include "hilbert_curve.h"
28+
29+
constexpr int8_t to_fix(int8_t v) { return v * 2; }
30+
constexpr int8_t to_int(int8_t v) { return v / 2; }
31+
constexpr uint8_t log2(uint8_t n) { return (n > 1) ? 1 + log2(n >> 1) : 0; }
32+
constexpr uint8_t order(uint8_t n) { return uint8_t(log2(n - 1)) + 1; }
33+
constexpr uint8_t ord = order(_MAX(GRID_MAX_POINTS_X, GRID_MAX_POINTS_Y));
34+
constexpr uint8_t dim = _BV(ord);
35+
36+
static inline bool eval_candidate(int8_t x, int8_t y, hilbert_curve::callback_ptr func, void *data) {
37+
// The print bed likely has fewer points than the full Hilbert
38+
// curve, so cull unecessary points
39+
return x < GRID_MAX_POINTS_X && y < GRID_MAX_POINTS_Y ? func(x, y, data) : false;
40+
}
41+
42+
bool hilbert_curve::hilbert(int8_t x, int8_t y, int8_t xi, int8_t xj, int8_t yi, int8_t yj, uint8_t n, hilbert_curve::callback_ptr func, void *data) {
43+
/**
44+
* Hilbert space-filling curve implementation
45+
*
46+
* x and y : coordinates of the bottom left corner
47+
* xi and xj : i and j components of the unit x vector of the frame
48+
* yi and yj : i and j components of the unit y vector of the frame
49+
*
50+
* From: http://www.fundza.com/algorithmic/space_filling/hilbert/basics/index.html
51+
*/
52+
if (n)
53+
return hilbert(x, y, yi/2, yj/2, xi/2, xj/2, n-1, func, data) ||
54+
hilbert(x+xi/2, y+xj/2, xi/2, xj/2, yi/2, yj/2, n-1, func, data) ||
55+
hilbert(x+xi/2+yi/2, y+xj/2+yj/2, xi/2, xj/2, yi/2, yj/2, n-1, func, data) ||
56+
hilbert(x+xi/2+yi, y+xj/2+yj, -yi/2, -yj/2, -xi/2, -xj/2, n-1, func, data);
57+
else
58+
return eval_candidate(to_int(x+(xi+yi)/2), to_int(y+(xj+yj)/2), func, data);
59+
}
60+
61+
/**
62+
* Calls func(x, y, data) for all points in the Hilbert curve.
63+
* If that function returns true, the search is terminated.
64+
*/
65+
bool hilbert_curve::search(hilbert_curve::callback_ptr func, void *data) {
66+
return hilbert(to_fix(0), to_fix(0),to_fix(dim), to_fix(0), to_fix(0), to_fix(dim), ord, func, data);
67+
}
68+
69+
/* Helper function for starting the search at a particular point */
70+
71+
typedef struct {
72+
uint8_t x, y;
73+
bool found_1st;
74+
hilbert_curve::callback_ptr func;
75+
void *data;
76+
} search_from_t;
77+
78+
static bool search_from_helper(uint8_t x, uint8_t y, void *data) {
79+
search_from_t *d = (search_from_t *) data;
80+
if (d->x == x && d->y == y)
81+
d->found_1st = true;
82+
return d->found_1st ? d->func(x, y, d->data) : false;
83+
}
84+
85+
/**
86+
* Same as search, except start at a specific grid intersection point.
87+
*/
88+
bool hilbert_curve::search_from(uint8_t x, uint8_t y, hilbert_curve::callback_ptr func, void *data) {
89+
search_from_t d;
90+
d.x = x;
91+
d.y = y;
92+
d.found_1st = false;
93+
d.func = func;
94+
d.data = data;
95+
// Call twice to allow search to wrap back to the beginning and picked up points prior to the start.
96+
return search(search_from_helper, &d) || search(search_from_helper, &d);
97+
}
98+
99+
/**
100+
* Like search_from, but takes a bed position and starts from the nearest
101+
* point on the Hilbert curve.
102+
*/
103+
bool hilbert_curve::search_from_closest(const xy_pos_t &pos, hilbert_curve::callback_ptr func, void *data) {
104+
// Find closest grid intersection
105+
uint8_t grid_x = LROUND(float(pos.x - MESH_MIN_X) / MESH_X_DIST);
106+
uint8_t grid_y = LROUND(float(pos.y - MESH_MIN_Y) / MESH_Y_DIST);
107+
LIMIT(grid_x, 0, GRID_MAX_POINTS_X);
108+
LIMIT(grid_y, 0, GRID_MAX_POINTS_Y);
109+
return search_from(grid_x, grid_y, func, data);
110+
}
111+
112+
#endif // UBL_HILBERT_CURVE
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*******************
2+
* hilbert_curve.h *
3+
*******************/
4+
5+
/****************************************************************************
6+
* Written By Marcio Teixeira 2021 - SynDaver Labs, Inc. *
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+
* To view a copy of the GNU General Public License, go to the following *
19+
* location: <https://www.gnu.org/licenses/>. *
20+
****************************************************************************/
21+
22+
#pragma once
23+
24+
class hilbert_curve {
25+
public:
26+
typedef bool (*callback_ptr)(uint8_t x, uint8_t y, void *data);
27+
static bool search(callback_ptr func, void *data);
28+
static bool search_from(uint8_t x, uint8_t y, callback_ptr func, void *data);
29+
static bool search_from_closest(const xy_pos_t &pos, callback_ptr func, void *data);
30+
private:
31+
static bool hilbert(int8_t x, int8_t y, int8_t xi, int8_t xj, int8_t yi, int8_t yj, uint8_t n, callback_ptr func, void *data);
32+
};

Marlin/src/feature/bedlevel/ubl/ubl.h

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,6 @@ class unified_bed_leveling {
101101
static void display_map(const int) _O0;
102102
static mesh_index_pair find_closest_mesh_point_of_type(const MeshPointType, const xy_pos_t&, const bool=false, MeshFlags *done_flags=nullptr) _O0;
103103
static mesh_index_pair find_furthest_invalid_mesh_point() _O0;
104-
#if ENABLED(UBL_HILBERT_CURVE)
105-
static void check_if_missing(mesh_index_pair &pt, int x, int y);
106-
static void hilbert(mesh_index_pair &pt, int8_t x, int8_t y, int8_t xi, int8_t xj, int8_t yi, int8_t yj, uint8_t n);
107-
static mesh_index_pair find_next_mesh_point();
108-
#endif
109104
static void reset();
110105
static void invalidate();
111106
static void set_all_mesh_points_to_value(const float value);

Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp

Lines changed: 77 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@
4949
#include "../../../lcd/extui/ui_api.h"
5050
#endif
5151

52+
#if ENABLED(UBL_HILBERT_CURVE)
53+
#include "../hilbert_curve.h"
54+
#endif
55+
5256
#include <math.h>
5357

5458
#define UBL_G29_P31
@@ -747,11 +751,9 @@ void unified_bed_leveling::shift_mesh_height() {
747751
}
748752
#endif
749753

750-
best = do_furthest ? find_furthest_invalid_mesh_point()
751-
: TERN(UBL_HILBERT_CURVE,
752-
find_next_mesh_point(),
753-
find_closest_mesh_point_of_type(INVALID, nearby, true)
754-
);
754+
best = do_furthest
755+
? find_furthest_invalid_mesh_point()
756+
: find_closest_mesh_point_of_type(INVALID, nearby, true);
755757

756758
if (best.pos.x >= 0) { // mesh point found and is reachable by probe
757759
TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(best.pos, ExtUI::PROBE_START));
@@ -1269,97 +1271,93 @@ mesh_index_pair unified_bed_leveling::find_furthest_invalid_mesh_point() {
12691271
return farthest;
12701272
}
12711273

1272-
mesh_index_pair unified_bed_leveling::find_closest_mesh_point_of_type(const MeshPointType type, const xy_pos_t &pos, const bool probe_relative/*=false*/, MeshFlags *done_flags/*=nullptr*/) {
1273-
mesh_index_pair closest;
1274-
closest.invalidate();
1275-
closest.distance = -99999.9f;
1276-
1277-
// Get the reference position, either nozzle or probe
1278-
const xy_pos_t ref = probe_relative ? pos + probe.offset_xy : pos;
1279-
1280-
float best_so_far = 99999.99f;
1274+
#if ENABLED(UBL_HILBERT_CURVE)
12811275

1282-
GRID_LOOP(i, j) {
1283-
if ( (type == (isnan(z_values[i][j]) ? INVALID : REAL))
1284-
|| (type == SET_IN_BITMAP && !done_flags->marked(i, j))
1276+
typedef struct {
1277+
MeshPointType type;
1278+
MeshFlags *done_flags;
1279+
bool probe_relative;
1280+
mesh_index_pair closest;
1281+
} find_closest_t;
1282+
1283+
static bool test_func(uint8_t i, uint8_t j, void *data) {
1284+
find_closest_t *d = (find_closest_t*)data;
1285+
if ( (d->type == (isnan(ubl.z_values[i][j]) ? INVALID : REAL))
1286+
|| (d->type == SET_IN_BITMAP && !d->done_flags->marked(i, j))
12851287
) {
12861288
// Found a Mesh Point of the specified type!
1287-
const xy_pos_t mpos = { mesh_index_to_xpos(i), mesh_index_to_ypos(j) };
1289+
const xy_pos_t mpos = { ubl.mesh_index_to_xpos(i), ubl.mesh_index_to_ypos(j) };
12881290

12891291
// If using the probe as the reference there are some unreachable locations.
12901292
// Also for round beds, there are grid points outside the bed the nozzle can't reach.
12911293
// Prune them from the list and ignore them till the next Phase (manual nozzle probing).
12921294

1293-
if (!(probe_relative ? probe.can_reach(mpos) : position_is_reachable(mpos)))
1294-
continue;
1295+
if (!(d->probe_relative ? probe.can_reach(mpos) : position_is_reachable(mpos)))
1296+
return false;
1297+
d->closest.pos.set(i, j);
1298+
return true;
1299+
}
1300+
return false;
1301+
}
12951302

1296-
// Reachable. Check if it's the best_so_far location to the nozzle.
1303+
#endif
12971304

1298-
const xy_pos_t diff = current_position - mpos;
1299-
const float distance = (ref - mpos).magnitude() + diff.magnitude() * 0.1f;
1305+
mesh_index_pair unified_bed_leveling::find_closest_mesh_point_of_type(const MeshPointType type, const xy_pos_t &pos, const bool probe_relative/*=false*/, MeshFlags *done_flags/*=nullptr*/) {
13001306

1301-
// factor in the distance from the current location for the normal case
1302-
// so the nozzle isn't running all over the bed.
1303-
if (distance < best_so_far) {
1304-
best_so_far = distance; // Found a closer location with the desired value type.
1305-
closest.pos.set(i, j);
1306-
closest.distance = best_so_far;
1307-
}
1308-
}
1309-
} // GRID_LOOP
1307+
#if ENABLED(UBL_HILBERT_CURVE)
13101308

1311-
return closest;
1312-
}
1309+
find_closest_t d;
1310+
d.type = type;
1311+
d.done_flags = done_flags;
1312+
d.probe_relative = probe_relative;
1313+
d.closest.invalidate();
1314+
hilbert_curve::search_from_closest(pos, test_func, &d);
1315+
return d.closest;
13131316

1314-
#if ENABLED(UBL_HILBERT_CURVE)
1317+
#else
13151318

1316-
constexpr int8_t to_fix(int8_t v) { return v << 1; }
1317-
constexpr int8_t to_int(int8_t v) { return v >> 1; }
1318-
constexpr uint8_t log2(uint8_t n) { return (n > 1) ? 1 + log2(n >> 1) : 0; }
1319-
constexpr uint8_t order(uint8_t n) { return uint8_t(log2(n - 1)) + 1; }
1320-
1321-
void unified_bed_leveling::hilbert(mesh_index_pair &pt, int8_t x, int8_t y, int8_t xi, int8_t xj, int8_t yi, int8_t yj, uint8_t n) {
1322-
/* Hilbert space filling curve implementation
1323-
*
1324-
* x and y are the coordinates of the bottom left corner
1325-
* xi & xj are the i & j components of the unit x vector of the frame
1326-
* similarly yi and yj
1327-
*
1328-
* From: http://www.fundza.com/algorithmic/space_filling/hilbert/basics/index.html
1329-
*/
1330-
if (n <= 0)
1331-
check_if_missing(pt, to_int(x+(xi+yi)/2),to_int(y+(xj+yj)/2));
1332-
else {
1333-
hilbert(pt, x, y, yi/2, yj/2, xi/2, xj/2, n-1);
1334-
hilbert(pt, x+xi/2, y+xj/2, xi/2, xj/2, yi/2, yj/2, n-1);
1335-
hilbert(pt, x+xi/2+yi/2, y+xj/2+yj/2, xi/2, xj/2, yi/2, yj/2, n-1);
1336-
hilbert(pt, x+xi/2+yi, y+xj/2+yj, -yi/2, -yj/2, -xi/2, -xj/2, n-1);
1337-
}
1338-
}
1319+
mesh_index_pair closest;
1320+
closest.invalidate();
1321+
closest.distance = -99999.9f;
1322+
1323+
// Get the reference position, either nozzle or probe
1324+
const xy_pos_t ref = probe_relative ? pos + probe.offset_xy : pos;
13391325

1340-
void unified_bed_leveling::check_if_missing(mesh_index_pair &pt, int x, int y) {
1341-
if ( pt.distance < 0
1342-
&& x < GRID_MAX_POINTS_X
1343-
&& y < GRID_MAX_POINTS_Y
1344-
&& isnan(z_values[x][y])
1345-
&& probe.can_reach(mesh_index_to_xpos(x), mesh_index_to_ypos(y))
1326+
float best_so_far = 99999.99f;
1327+
1328+
GRID_LOOP(i, j) {
1329+
if ( (type == (isnan(z_values[i][j]) ? INVALID : REAL))
1330+
|| (type == SET_IN_BITMAP && !done_flags->marked(i, j))
13461331
) {
1347-
pt.pos.set(x, y);
1348-
pt.distance = 1;
1332+
// Found a Mesh Point of the specified type!
1333+
const xy_pos_t mpos = { mesh_index_to_xpos(i), mesh_index_to_ypos(j) };
1334+
1335+
// If using the probe as the reference there are some unreachable locations.
1336+
// Also for round beds, there are grid points outside the bed the nozzle can't reach.
1337+
// Prune them from the list and ignore them till the next Phase (manual nozzle probing).
1338+
1339+
if (!(probe_relative ? probe.can_reach(mpos) : position_is_reachable(mpos)))
1340+
continue;
1341+
1342+
// Reachable. Check if it's the best_so_far location to the nozzle.
1343+
1344+
const xy_pos_t diff = current_position - mpos;
1345+
const float distance = (ref - mpos).magnitude() + diff.magnitude() * 0.1f;
1346+
1347+
// factor in the distance from the current location for the normal case
1348+
// so the nozzle isn't running all over the bed.
1349+
if (distance < best_so_far) {
1350+
best_so_far = distance; // Found a closer location with the desired value type.
1351+
closest.pos.set(i, j);
1352+
closest.distance = best_so_far;
1353+
}
13491354
}
1350-
}
1351-
1352-
mesh_index_pair unified_bed_leveling::find_next_mesh_point() {
1353-
mesh_index_pair pt;
1354-
pt.invalidate();
1355-
pt.distance = -99999.9f;
1356-
constexpr uint8_t ord = order(_MAX(GRID_MAX_POINTS_X, GRID_MAX_POINTS_Y));
1357-
constexpr uint8_t dim = _BV(ord);
1358-
hilbert(pt, to_fix(0), to_fix(0), to_fix(dim), to_fix(0), to_fix(0), to_fix(dim), ord);
1359-
return pt;
1360-
}
1361-
1362-
#endif // UBL_HILBERT_CURVE
1355+
} // GRID_LOOP
1356+
1357+
return closest;
1358+
1359+
#endif
1360+
}
13631361

13641362
/**
13651363
* 'Smart Fill': Scan from the outward edges of the mesh towards the center.

0 commit comments

Comments
 (0)