My Marlin configs for Fabrikator Mini and CTC i3 Pro B
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

bed_mesh_screen.cpp 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. /***********************
  2. * bed_mesh_screen.cpp *
  3. ***********************/
  4. /****************************************************************************
  5. * Written By Marcio Teixeira 2020 *
  6. * *
  7. * This program is free software: you can redistribute it and/or modify *
  8. * it under the terms of the GNU General Public License as published by *
  9. * the Free Software Foundation, either version 3 of the License, or *
  10. * (at your option) any later version. *
  11. * *
  12. * This program is distributed in the hope that it will be useful, *
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of *
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
  15. * GNU General Public License for more details. *
  16. * *
  17. * To view a copy of the GNU General Public License, go to the following *
  18. * location: <https://www.gnu.org/licenses/>. *
  19. ****************************************************************************/
  20. #include "../config.h"
  21. #include "screens.h"
  22. #include "screen_data.h"
  23. #ifdef FTDI_BED_MESH_SCREEN
  24. using namespace FTDI;
  25. using namespace Theme;
  26. using namespace ExtUI;
  27. constexpr static BedMeshScreenData &mydata = screen_data.BedMeshScreen;
  28. constexpr static float gaugeThickness = 0.25;
  29. #if ENABLED(TOUCH_UI_PORTRAIT)
  30. #define GRID_COLS 3
  31. #define GRID_ROWS 10
  32. #define MESH_POS BTN_POS(1, 2), BTN_SIZE(3,5)
  33. #define MESSAGE_POS BTN_POS(1, 7), BTN_SIZE(3,1)
  34. #define Z_LABEL_POS BTN_POS(1, 8), BTN_SIZE(1,1)
  35. #define Z_VALUE_POS BTN_POS(2, 8), BTN_SIZE(2,1)
  36. #define OKAY_POS BTN_POS(1,10), BTN_SIZE(3,1)
  37. #else
  38. #define GRID_COLS 5
  39. #define GRID_ROWS 5
  40. #define MESH_POS BTN_POS(1,1), BTN_SIZE(3,5)
  41. #define MESSAGE_POS BTN_POS(4,1), BTN_SIZE(2,1)
  42. #define Z_LABEL_POS BTN_POS(4,2), BTN_SIZE(2,1)
  43. #define Z_VALUE_POS BTN_POS(4,3), BTN_SIZE(2,1)
  44. #define OKAY_POS BTN_POS(4,5), BTN_SIZE(2,1)
  45. #endif
  46. void BedMeshScreen::drawMesh(int16_t x, int16_t y, int16_t w, int16_t h, ExtUI::bed_mesh_t data, uint8_t opts, float autoscale_max) {
  47. constexpr uint8_t rows = GRID_MAX_POINTS_Y;
  48. constexpr uint8_t cols = GRID_MAX_POINTS_X;
  49. #define VALUE(X,Y) (data ? data[X][Y] : 0)
  50. #define ISVAL(X,Y) (data ? !isnan(VALUE(X,Y)) : true)
  51. #define HEIGHT(X,Y) (ISVAL(X,Y) ? (VALUE(X,Y) - val_min) * scale_z : 0)
  52. // Compute the mean, min and max for the points
  53. float val_mean = 0;
  54. float val_max = -INFINITY;
  55. float val_min = INFINITY;
  56. uint8_t val_cnt = 0;
  57. if (data && (opts & USE_AUTOSCALE)) {
  58. for (uint8_t y = 0; y < rows; y++) {
  59. for (uint8_t x = 0; x < cols; x++) {
  60. if (ISVAL(x,y)) {
  61. const float val = VALUE(x,y);
  62. val_mean += val;
  63. val_max = max(val_max, val);
  64. val_min = min(val_min, val);
  65. val_cnt++;
  66. }
  67. }
  68. }
  69. }
  70. if (val_cnt)
  71. val_mean /= val_cnt;
  72. else {
  73. val_mean = 0;
  74. val_min = 0;
  75. val_max = 0;
  76. }
  77. const float scale_z = ((val_max == val_min) ? 1 : 1/(val_max - val_min)) * autoscale_max;
  78. /**
  79. * The 3D points go through a 3D graphics pipeline to determine the final 2D point on the screen.
  80. * This is written out as a stack of macros that each apply an affine transformation to the point.
  81. * At compile time, the compiler should be able to reduce these expressions.
  82. *
  83. * The last transformation in the chain (TRANSFORM_5) is initially set to a no-op so we can measure
  84. * the dimensions of the grid, but is later replaced with a scaling transform that scales the grid
  85. * to fit.
  86. */
  87. #define TRANSFORM_5(X,Y,Z) (X), (Y) // No transform
  88. #define TRANSFORM_4(X,Y,Z) TRANSFORM_5((X)/(Z),(Y)/-(Z), 0) // Perspective
  89. #define TRANSFORM_3(X,Y,Z) TRANSFORM_4((X), (Z), (Y)) // Swap Z and Y
  90. #define TRANSFORM_2(X,Y,Z) TRANSFORM_3((X), (Y) + 2.5, (Z) - 1) // Translate
  91. #define TRANSFORM(X,Y,Z) TRANSFORM_2(float(X)/(cols-1) - 0.5, float(Y)/(rows-1) - 0.5, (Z)) // Normalize
  92. // Compute the bounding box for the grid prior to scaling. Do this at compile-time by
  93. // transforming the four corner points via the transformation equations and finding
  94. // the min and max for each axis.
  95. constexpr float bounds[][3] = {{TRANSFORM(0 , 0 , 0)},
  96. {TRANSFORM(cols-1, 0 , 0)},
  97. {TRANSFORM(0 , rows-1, 0)},
  98. {TRANSFORM(cols-1, rows-1, 0)}};
  99. #define APPLY(FUNC, AXIS) FUNC(FUNC(bounds[0][AXIS], bounds[1][AXIS]), FUNC(bounds[2][AXIS], bounds[3][AXIS]))
  100. constexpr float grid_x = APPLY(min,0);
  101. constexpr float grid_y = APPLY(min,1);
  102. constexpr float grid_w = APPLY(max,0) - grid_x;
  103. constexpr float grid_h = APPLY(max,1) - grid_y;
  104. constexpr float grid_cx = grid_x + grid_w/2;
  105. constexpr float grid_cy = grid_y + grid_h/2;
  106. // Figure out scale and offset such that the grid fits within the rectangle given by (x,y,w,h)
  107. const float scale_x = float(w)/grid_w;
  108. const float scale_y = float(h)/grid_h;
  109. const float center_x = x + w/2;
  110. const float center_y = y + h/2;
  111. // Now replace the last transformation in the chain with a scaling operation.
  112. #undef TRANSFORM_5
  113. #define TRANSFORM_6(X,Y,Z) (X)*16, (Y)*16 // Scale to 1/16 pixel units
  114. #define TRANSFORM_5(X,Y,Z) TRANSFORM_6( center_x + ((X) - grid_cx) * scale_x, \
  115. center_y + ((Y) - grid_cy) * scale_y, 0) // Scale to bounds
  116. // Draw the grid
  117. const uint16_t basePointSize = min(w,h) / max(cols,rows);
  118. CommandProcessor cmd;
  119. cmd.cmd(SAVE_CONTEXT())
  120. .cmd(TAG_MASK(false))
  121. .cmd(SAVE_CONTEXT());
  122. for (uint8_t y = 0; y < rows; y++) {
  123. for (uint8_t x = 0; x < cols; x++) {
  124. if (ISVAL(x,y)) {
  125. const bool hasLeftSegment = x < cols - 1 && ISVAL(x+1,y);
  126. const bool hasRightSegment = y < rows - 1 && ISVAL(x,y+1);
  127. if (hasLeftSegment || hasRightSegment) {
  128. cmd.cmd(BEGIN(LINE_STRIP));
  129. if (hasLeftSegment) cmd.cmd(VERTEX2F(TRANSFORM(x + 1, y , HEIGHT(x + 1, y ))));
  130. cmd.cmd( VERTEX2F(TRANSFORM(x , y , HEIGHT(x , y ))));
  131. if (hasRightSegment) cmd.cmd(VERTEX2F(TRANSFORM(x , y + 1, HEIGHT(x , y + 1))));
  132. }
  133. }
  134. }
  135. if (opts & USE_POINTS) {
  136. const float sq_min = sq(val_min - val_mean);
  137. const float sq_max = sq(val_max - val_mean);
  138. cmd.cmd(POINT_SIZE(basePointSize * 2));
  139. cmd.cmd(BEGIN(POINTS));
  140. for (uint8_t x = 0; x < cols; x++) {
  141. if (ISVAL(x,y)) {
  142. if (opts & USE_COLORS) {
  143. const float val_dev = VALUE(x, y) - val_mean;
  144. const uint8_t neg_byte = sq(val_dev) / (val_dev < 0 ? sq_min : sq_max) * 0xFF;
  145. const uint8_t pos_byte = 255 - neg_byte;
  146. cmd.cmd(COLOR_RGB(pos_byte, pos_byte, 0xFF));
  147. }
  148. cmd.cmd(VERTEX2F(TRANSFORM(x, y, HEIGHT(x, y))));
  149. }
  150. }
  151. if (opts & USE_COLORS) {
  152. cmd.cmd(RESTORE_CONTEXT())
  153. .cmd(SAVE_CONTEXT());
  154. }
  155. }
  156. }
  157. cmd.cmd(RESTORE_CONTEXT())
  158. .cmd(TAG_MASK(true));
  159. if (opts & USE_TAGS) {
  160. cmd.cmd(COLOR_MASK(false, false, false, false))
  161. .cmd(POINT_SIZE(basePointSize * 10))
  162. .cmd(BEGIN(POINTS));
  163. for (uint8_t y = 0; y < rows; y++) {
  164. for (uint8_t x = 0; x < cols; x++) {
  165. const uint8_t tag = pointToTag(x, y);
  166. cmd.tag(tag).cmd(VERTEX2F(TRANSFORM(x, y, HEIGHT(x, y))));
  167. }
  168. }
  169. cmd.cmd(COLOR_MASK(true, true, true, true));
  170. }
  171. if (opts & USE_HIGHLIGHT) {
  172. const uint8_t tag = mydata.highlightedTag;
  173. xy_uint8_t pt;
  174. if (tagToPoint(tag, pt)) {
  175. cmd.cmd(COLOR_A(128))
  176. .cmd(POINT_SIZE(basePointSize * 6))
  177. .cmd(BEGIN(POINTS))
  178. .tag(tag).cmd(VERTEX2F(TRANSFORM(pt.x, pt.y, HEIGHT(pt.x, pt.y))));
  179. }
  180. }
  181. cmd.cmd(END());
  182. cmd.cmd(RESTORE_CONTEXT());
  183. }
  184. uint8_t BedMeshScreen::pointToTag(uint8_t x, uint8_t y) {
  185. return y * (GRID_MAX_POINTS_X) + x + 10;
  186. }
  187. bool BedMeshScreen::tagToPoint(uint8_t tag, xy_uint8_t &pt) {
  188. if (tag < 10) return false;
  189. pt.x = (tag - 10) % (GRID_MAX_POINTS_X);
  190. pt.y = (tag - 10) / (GRID_MAX_POINTS_X);
  191. return true;
  192. }
  193. void BedMeshScreen::onEntry() {
  194. mydata.allowEditing = true;
  195. mydata.highlightedTag = 0;
  196. mydata.zAdjustment = 0;
  197. mydata.count = GRID_MAX_POINTS;
  198. mydata.message = mydata.MSG_NONE;
  199. BaseScreen::onEntry();
  200. }
  201. float BedMeshScreen::getHighlightedValue(bool nanAsZero) {
  202. xy_uint8_t pt;
  203. if (tagToPoint(mydata.highlightedTag, pt)) {
  204. const float val = ExtUI::getMeshPoint(pt);
  205. return (isnan(val) && nanAsZero) ? 0 : val;
  206. }
  207. return NAN;
  208. }
  209. void BedMeshScreen::setHighlightedValue(float value) {
  210. xy_uint8_t pt;
  211. if (tagToPoint(mydata.highlightedTag, pt))
  212. ExtUI::setMeshPoint(pt, value);
  213. }
  214. void BedMeshScreen::moveToHighlightedValue() {
  215. xy_uint8_t pt;
  216. if (tagToPoint(mydata.highlightedTag, pt))
  217. ExtUI::moveToMeshPoint(pt, gaugeThickness + mydata.zAdjustment);
  218. }
  219. void BedMeshScreen::adjustHighlightedValue(float increment) {
  220. mydata.zAdjustment += increment;
  221. moveToHighlightedValue();
  222. }
  223. void BedMeshScreen::saveAdjustedHighlightedValue() {
  224. if (mydata.zAdjustment) {
  225. BedMeshScreen::setHighlightedValue(BedMeshScreen::getHighlightedValue(true) + mydata.zAdjustment);
  226. mydata.zAdjustment = 0;
  227. }
  228. }
  229. void BedMeshScreen::changeHighlightedValue(uint8_t tag) {
  230. if (mydata.allowEditing) saveAdjustedHighlightedValue();
  231. mydata.highlightedTag = tag;
  232. if (mydata.allowEditing) moveToHighlightedValue();
  233. }
  234. void BedMeshScreen::drawHighlightedPointValue() {
  235. CommandProcessor cmd;
  236. cmd.font(Theme::font_medium)
  237. .colors(normal_btn)
  238. .text(Z_LABEL_POS, GET_TEXT_F(MSG_MESH_EDIT_Z))
  239. .font(font_small);
  240. if (mydata.allowEditing)
  241. draw_adjuster(cmd, Z_VALUE_POS, 2, getHighlightedValue(true) + mydata.zAdjustment, GET_TEXT_F(MSG_UNITS_MM), 4, 3);
  242. else
  243. draw_adjuster_value(cmd, Z_VALUE_POS, getHighlightedValue(true) + mydata.zAdjustment, GET_TEXT_F(MSG_UNITS_MM), 4, 3);
  244. cmd.colors(action_btn)
  245. .tag(1).button(OKAY_POS, GET_TEXT_F(MSG_BUTTON_OKAY))
  246. .tag(0);
  247. switch (mydata.message) {
  248. case mydata.MSG_MESH_COMPLETE: cmd.text(MESSAGE_POS, GET_TEXT_F(MSG_BED_MAPPING_DONE)); break;
  249. case mydata.MSG_MESH_INCOMPLETE: cmd.text(MESSAGE_POS, GET_TEXT_F(MSG_BED_MAPPING_INCOMPLETE)); break;
  250. default: break;
  251. }
  252. }
  253. void BedMeshScreen::onRedraw(draw_mode_t what) {
  254. #define _INSET_POS(x,y,w,h) x + min(w,h)/10, y + min(w,h)/10, w - min(w,h)/5, h - min(w,h)/5
  255. #define INSET_POS(pos) _INSET_POS(pos)
  256. if (what & BACKGROUND) {
  257. CommandProcessor cmd;
  258. cmd.cmd(CLEAR_COLOR_RGB(bg_color))
  259. .cmd(CLEAR(true,true,true));
  260. // Draw the shadow and tags
  261. cmd.cmd(COLOR_RGB(Theme::bed_mesh_shadow_rgb));
  262. BedMeshScreen::drawMesh(INSET_POS(MESH_POS), nullptr, USE_POINTS | USE_TAGS);
  263. cmd.cmd(COLOR_RGB(bg_text_enabled));
  264. }
  265. if (what & FOREGROUND) {
  266. constexpr float autoscale_max_amplitude = 0.03;
  267. const bool gotAllPoints = mydata.count >= GRID_MAX_POINTS;
  268. if (gotAllPoints) {
  269. drawHighlightedPointValue();
  270. }
  271. CommandProcessor cmd;
  272. cmd.cmd(COLOR_RGB(Theme::bed_mesh_lines_rgb));
  273. const float levelingProgress = sq(float(mydata.count) / GRID_MAX_POINTS);
  274. BedMeshScreen::drawMesh(INSET_POS(MESH_POS), ExtUI::getMeshArray(),
  275. USE_POINTS | USE_HIGHLIGHT | USE_AUTOSCALE | (gotAllPoints ? USE_COLORS : 0),
  276. autoscale_max_amplitude * levelingProgress
  277. );
  278. }
  279. }
  280. bool BedMeshScreen::onTouchEnd(uint8_t tag) {
  281. constexpr float increment = 0.01;
  282. switch (tag) {
  283. case 1:
  284. saveAdjustedHighlightedValue();
  285. injectCommands_P(PSTR("G29 S1"));
  286. GOTO_PREVIOUS();
  287. return true;
  288. case 2: adjustHighlightedValue(-increment); break;
  289. case 3: adjustHighlightedValue( increment); break;
  290. default:
  291. if (tag >= 10)
  292. changeHighlightedValue(tag);
  293. else
  294. return false;
  295. }
  296. return true;
  297. }
  298. void BedMeshScreen::onMeshUpdate(const int8_t, const int8_t, const float) {
  299. if (AT_SCREEN(BedMeshScreen))
  300. onRefresh();
  301. }
  302. void BedMeshScreen::onMeshUpdate(const int8_t x, const int8_t y, const ExtUI::probe_state_t state) {
  303. switch (state) {
  304. case ExtUI::MESH_START:
  305. mydata.allowEditing = false;
  306. mydata.count = 0;
  307. mydata.message = mydata.MSG_NONE;
  308. break;
  309. case ExtUI::MESH_FINISH:
  310. if (mydata.count == GRID_MAX_POINTS && ExtUI::getMeshValid())
  311. mydata.message = mydata.MSG_MESH_COMPLETE;
  312. else
  313. mydata.message = mydata.MSG_MESH_INCOMPLETE;
  314. mydata.count = GRID_MAX_POINTS;
  315. break;
  316. case ExtUI::PROBE_START:
  317. mydata.highlightedTag = pointToTag(x, y);
  318. break;
  319. case ExtUI::PROBE_FINISH:
  320. mydata.count++;
  321. break;
  322. }
  323. BedMeshScreen::onMeshUpdate(x, y, 0);
  324. }
  325. void BedMeshScreen::startMeshProbe() {
  326. GOTO_SCREEN(BedMeshScreen);
  327. mydata.allowEditing = false;
  328. mydata.count = 0;
  329. injectCommands_P(PSTR(BED_LEVELING_COMMANDS));
  330. }
  331. void BedMeshScreen::showMesh() {
  332. GOTO_SCREEN(BedMeshScreen);
  333. mydata.allowEditing = false;
  334. }
  335. void BedMeshScreen::showMeshEditor() {
  336. SpinnerDialogBox::enqueueAndWait_P(ExtUI::isMachineHomed() ? F("M420 S1") : F("G28\nM420 S1"));
  337. // After the spinner, go to this screen.
  338. current_screen.forget();
  339. PUSH_SCREEN(BedMeshScreen);
  340. }
  341. #endif // FTDI_BED_MESH_SCREEN