diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h index 0717a3ac5489..02ca9b4d1d41 100644 --- a/Marlin/Configuration_adv.h +++ b/Marlin/Configuration_adv.h @@ -2494,6 +2494,27 @@ */ //#define CNC_COORDINATE_SYSTEMS +/** + * Drilling canned cycles + * + * Enables G81, G82, G83 for CNC Drilling + * + */ +//#define DRILLING_CANNED_CYCLES + +/** + * 5x Conversion (developing) + * + * Enables M168 for 5 axis control + * + */ +//#define CNC_5X + #if ENABLED(CNC_5X) + #define A_5x // Enable A axis + //#define B_5x // Enable B axis + #define C_5x // Enable C axis + #endif + /** * Auto-report temperatures with M155 S */ @@ -2588,7 +2609,66 @@ * User-defined menu items that execute custom GCode */ //#define CUSTOM_USER_MENUS + #if ENABLED(CUSTOM_USER_MENUS) + + #if ENABLED(SPINDLE_FEATURE) + + #define CUSTOM_USER_MENU_TITLE "CNC Zeros" + #define USER_SCRIPT_DONE "Done" + #define USER_SCRIPT_AUDIBLE_FEEDBACK + //#define USER_SCRIPT_RETURN // Return to status screen after a script + + #define USER_DESC_1 "Set G53" + #define USER_GCODE_1 "G28 \nG53 G92 X0 Y0 Z300 \nM500 \nG54 " + + #define USER_DESC_2 "Set Zero G92 X Y" + #define USER_GCODE_2 "G92 X0 Y0 \nM500" + + #define USER_DESC_3 "Probe Z" + #define USER_GCODE_3 "G91 \nG38.2 F200 Z-50 \nG92 Z20.2 \nG91 \nG1 Z3 \nG91 \nG38.2 F30 Z-10 \nG92 Z20.2 \nG91 \nG0 Z5 \nG90 \nM500" + + #define USER_DESC_4 "Probe X+" + #define USER_GCODE_4 "G91 \nG38.2 F200 X50 Z-0.001 \nG92 X-20.2 \nG91 \nG1 X-3 \nG91 \nG38.2 F30 X10 Z-0.001 \nG92 X-20.2 \nG91 \nG0 X-5 \nG90 \nM500" + + #define USER_DESC_5 "Probe Y+" + #define USER_GCODE_5 "G91 \nG38.2 F200 Y50 Z-0.001 \nG92 Y-20.2 \nG91 \nG1 Y-3 \nG91 \nG38.2 F30 Y10 Z-0.001 \nG92 Y-20.2 \nG91 \nG0 Y-5 \nG90 \nM500" + + #define USER_DESC_6 "Probe X-" + #define USER_GCODE_6 "G91 \nG38.2 F200 X-50 Z-0.001 \nG92 X20.2 \nG91 \nG1 X3 \nG91 \nG38.2 F30 X-10 Z-0.001 \nG92 X20.2 \nG91 \nG0 X5 \nG90 \nM500" + + #define USER_DESC_7 "Probe Y-" + #define USER_GCODE_7 "G91 \nG38.2 F200 Y-50 Z-0.001 \nG92 Y20.2 \nG91 \nG1 Y3 \nG91 \nG38.2 F30 Y-10 Z-0.001 \nG92 Y20.2 \nG91 \nG0 Y5 \nG90 \nM500" + + #define USER_DESC_8 "Select G54" + #define USER_GCODE_8 "G54" + + #define USER_DESC_9 "Select G55" + #define USER_GCODE_9 "G55" + + #define USER_DESC_10 "Select G56" + #define USER_GCODE_10 "G56" + + #define USER_DESC_11 "Select G57" + #define USER_GCODE_11 "G57" + + #define USER_DESC_12 "Select G58" + #define USER_GCODE_12 "G58" + + #define USER_DESC_13 "Select G59" + #define USER_GCODE_13 "G59" + + #define USER_DESC_14 "Move X0 Y0" + #define USER_GCODE_14 "G00 X0 Y0" + + #define USER_DESC_15 "Move Z0" + #define USER_GCODE_15 "G00 Z0" + + #define USER_DESC_16 "Set Zero G92 Z" + #define USER_GCODE_16 "G92 Z0 \nM500" + + #else + //#define CUSTOM_USER_MENU_TITLE "Custom Commands" #define USER_SCRIPT_DONE "M117 User Script Done" #define USER_SCRIPT_AUDIBLE_FEEDBACK @@ -2610,6 +2690,8 @@ #define USER_GCODE_5 "G28\nM503" #endif +#endif + /** * Host Action Commands * diff --git a/Marlin/src/gcode/gcode.cpp b/Marlin/src/gcode/gcode.cpp index 92928cf8b335..5d0e96a1c1b0 100644 --- a/Marlin/src/gcode/gcode.cpp +++ b/Marlin/src/gcode/gcode.cpp @@ -322,6 +322,12 @@ void GcodeSuite::process_parsed_command(const bool no_ok/*=false*/) { #if ENABLED(GCODE_MOTION_MODES) case 80: G80(); break; // G80: Reset the current motion mode #endif + + #if ENABLED(DRILLING_CANNED_CYCLES) + case 81: G81(); break; + case 82: G82(); break; + case 83: G83(); break; + #endif case 90: set_relative_mode(false); break; // G90: Absolute Mode case 91: set_relative_mode(true); break; // G91: Relative Mode @@ -535,6 +541,10 @@ void GcodeSuite::process_parsed_command(const bool no_ok/*=false*/) { case 166: M166(); break; // M166: Set Gradient Mix #endif #endif + + #if ENABLED(CNC_5X) + case 168: M168(); break; + #endif #if DISABLED(NO_VOLUMETRICS) case 200: M200(); break; // M200: Set filament diameter, E to cubic units diff --git a/Marlin/src/gcode/gcode.h b/Marlin/src/gcode/gcode.h index 9f4675291fc8..798e0534adbe 100644 --- a/Marlin/src/gcode/gcode.h +++ b/Marlin/src/gcode/gcode.h @@ -68,6 +68,9 @@ * G38 - Probe in any direction using the Z_MIN_PROBE (Requires G38_PROBE_TARGET) * G42 - Coordinated move to a mesh point (Requires MESH_BED_LEVELING, AUTO_BED_LEVELING_BLINEAR, or AUTO_BED_LEVELING_UBL) * G80 - Cancel current motion mode (Requires GCODE_MOTION_MODES) + * G81 - Drilling Cycles - drilling (Requires DRILLING_CANNED_CYCLES) + * G82 - Drilling Cycles - spot drill (Requires DRILLING_CANNED_CYCLES) + * G83 - Drilling Cycles - pecking (Requires DRILLING_CANNED_CYCLES) * G90 - Use Absolute Coordinates * G91 - Use Relative Coordinates * G92 - Set current position to coordinates given @@ -157,6 +160,7 @@ * M164 - Commit the mix and save to a virtual tool (current, or as specified by 'S'). (Requires MIXING_EXTRUDER) * M165 - Set the mix for the mixing extruder (and current virtual tool) with parameters ABCDHI. (Requires MIXING_EXTRUDER and DIRECT_MIXING_IN_G1) * M166 - Set the Gradient Mix for the mixing extruder. (Requires GRADIENT_MIX) + * M168 - Use 5x Movement (developing) * M190 - S Wait for bed current temp to reach target temp. ** Wait only when heating! ** * R Wait for bed current temp to reach target temp. ** Wait for heating or cooling. ** * M200 - Set filament diameter, D, setting E axis units to cubic. (Use S0 to revert to linear units.) @@ -467,6 +471,12 @@ class GcodeSuite { #if ENABLED(GCODE_MOTION_MODES) static void G80(); #endif + + #if ENABLED(DRILLING_CANNED_CYCLES) + static void G81(); + static void G82(); + static void G83(); + #endif static void G92(); @@ -654,6 +664,10 @@ class GcodeSuite { static void M166(); #endif #endif + + #if ENABLED(CNC_5X) + static void M168(); + #endif static void M200(); static void M201(); diff --git a/Marlin/src/gcode/motion/G81_G83.cpp b/Marlin/src/gcode/motion/G81_G83.cpp new file mode 100644 index 000000000000..7c0555b0231b --- /dev/null +++ b/Marlin/src/gcode/motion/G81_G83.cpp @@ -0,0 +1,446 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (C) 2019 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * + * Based on Sprinter and grbl. + * Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + + + +#include "../../inc/MarlinConfig.h" + +#if ENABLED(DRILLING_CANNED_CYCLES) + +#include "../../gcode/gcode.h" +#include "../../feature/bedlevel/bedlevel.h" + +#include "../../Marlin.h" +#include "../../module/planner.h" +#include "../../module/stepper.h" +#include "../../module/motion.h" +#include "../../module/tool_change.h" +#include "../../module/temperature.h" +#include "../../lcd/ultralcd.h" + +bool fast_move;/*=false*/ + + +/** + * G81 Peck Drill Canned Cycle + * + * X Y - Hole position + * Z - Hole Depth - Relative to the Retraction plan R + * R - Position on Retraction plan + * Q - Max Depth for each Cut/Peck + * P - Dwell time on bottom of each Cut/Peck + * F - Cutting feedrate + * + */ + +// Private functions + + +float G81_Z, + G81_R, + G81_Q=0, + G81_F, + G81_N_D = 0; + +millis_t G81_P=0; + + +/** + * G81 + */ +void GcodeSuite::G81() +{ + + + #ifdef G0_FEEDRATE + float g0_feedrate_mm_s, saved_feedrate_mm_s; + + saved_feedrate_mm_s = feedrate_mm_s; + + #endif + + if (parser.seen('Z')) G81_Z = parser.value_float(); + + SERIAL_ECHO("Z"); + SERIAL_ECHOLN(G81_Z); + + if (parser.seen('R'))G81_R = parser.value_float(); + + SERIAL_ECHO("R"); + SERIAL_ECHOLN(G81_R); + + if (parser.seen('F')) { + + G81_F = parser.value_float(); + feedrate_mm_s = MMM_TO_MMS(G81_F); + } + + SERIAL_ECHO("F"); + SERIAL_ECHOLN(G81_F); + + g0_feedrate_mm_s = MMM_TO_MMS(G0_FEEDRATE); + + + float sav_pos_X = current_position[X_AXIS], + sav_pos_Y = current_position[Y_AXIS], + sav_pos_Z = current_position[Z_AXIS]; + + + float z_zero_pos = sav_pos_Z - G81_R; + + float Z_fin_pos = z_zero_pos + G81_Z; + + destination[X_AXIS] = sav_pos_X; + destination[Y_AXIS] = sav_pos_Y; + + + destination[Z_AXIS] = z_zero_pos + G81_R; + prepare_move_to_destination(); + planner.synchronize(); + + if(G81_Q == 0){ + G81_N_D = Z_fin_pos; + } + else{ + G81_N_D = z_zero_pos + G81_R - G81_Q; + } + + while (Z_fin_pos < G81_N_D){ + + destination[Z_AXIS] = G81_N_D; + prepare_move_to_destination(); + planner.synchronize(); + + dwell(G81_P); + + feedrate_mm_s = g0_feedrate_mm_s; + + destination[Z_AXIS] = z_zero_pos + G81_R; + prepare_move_to_destination(); + planner.synchronize(); + + feedrate_mm_s = MMM_TO_MMS(G81_F); + + G81_N_D = G81_N_D - G81_Q; + } + + destination[Z_AXIS] = z_zero_pos + G81_Z; + prepare_move_to_destination(); + planner.synchronize(); + + dwell(G81_P); + + feedrate_mm_s = g0_feedrate_mm_s; + + destination[Z_AXIS] = z_zero_pos + G81_R; + prepare_move_to_destination(); + planner.synchronize(); + + + #ifdef G0_FEEDRATE + // Restore the motion mode feedrate + if (fast_move) feedrate_mm_s = saved_feedrate_mm_s; + #endif + + + + + +} + + + +/** + * G82 Peck Drill Canned Cycle + * + * X Y - Hole position + * Z - Hole Depth - Relative to the Retraction plan R + * R - Position on Retraction plan + * Q - Max Depth for each Cut/Peck + * P - Dwell time on bottom of each Cut/Peck + * F - Cutting feedrate + * + */ + +// Private functions + + +float G82_Z, + G82_R, + G82_Q=0, + G82_F, + G82_N_D = 0; + +millis_t G82_P; + + +/** + * G82 + */ +void GcodeSuite::G82() +{ + + + #ifdef G0_FEEDRATE + float g0_feedrate_mm_s, saved_feedrate_mm_s; + + saved_feedrate_mm_s = feedrate_mm_s; + + #endif + + if (parser.seen('Z')) G82_Z = parser.value_float(); + + SERIAL_ECHO("Z"); + SERIAL_ECHOLN(G82_Z); + + if (parser.seen('R'))G82_R = parser.value_float(); + + SERIAL_ECHO("R"); + SERIAL_ECHOLN(G82_R); + + if (parser.seenval('P')) G82_P = parser.value_millis(); + + SERIAL_ECHO("P"); + SERIAL_ECHOLN(G82_P); + + if (parser.seen('F')) { + + G82_F = parser.value_float(); + feedrate_mm_s = MMM_TO_MMS(G82_F); + } + + SERIAL_ECHO("F"); + SERIAL_ECHOLN(G82_F); + + g0_feedrate_mm_s = MMM_TO_MMS(G0_FEEDRATE); + + + float sav_pos_X = current_position[X_AXIS], + sav_pos_Y = current_position[Y_AXIS], + sav_pos_Z = current_position[Z_AXIS]; + + + float z_zero_pos = sav_pos_Z - G82_R; + + float Z_fin_pos = z_zero_pos + G82_Z; + + destination[X_AXIS] = sav_pos_X; + destination[Y_AXIS] = sav_pos_Y; + + + destination[Z_AXIS] = z_zero_pos + G82_R; + prepare_move_to_destination(); + planner.synchronize(); + + if(G82_Q == 0){ + G82_N_D = Z_fin_pos; + } + else{ + G82_N_D = z_zero_pos + G82_R - G82_Q; + } + + while (Z_fin_pos < G82_N_D){ + + destination[Z_AXIS] = G82_N_D; + prepare_move_to_destination(); + planner.synchronize(); + + dwell(G82_P); + + feedrate_mm_s = g0_feedrate_mm_s; + + destination[Z_AXIS] = z_zero_pos + G82_R; + prepare_move_to_destination(); + planner.synchronize(); + + feedrate_mm_s = MMM_TO_MMS(G82_F); + + G82_N_D = G82_N_D - G82_Q; + } + + destination[Z_AXIS] = z_zero_pos + G82_Z; + prepare_move_to_destination(); + planner.synchronize(); + + dwell(G82_P); + + feedrate_mm_s = g0_feedrate_mm_s; + + destination[Z_AXIS] = z_zero_pos + G82_R; + prepare_move_to_destination(); + planner.synchronize(); + + + #ifdef G0_FEEDRATE + // Restore the motion mode feedrate + if (fast_move) feedrate_mm_s = saved_feedrate_mm_s; + #endif + + + + + +} + + + +/** + * G83 Peck Drill Canned Cycle + * + * X Y - Hole position + * Z - Hole Depth - Relative to the Retraction plan R + * R - Position on Retraction plan + * Q - Max Depth for each Cut/Peck + * P - Dwell time on bottom of each Cut/Peck + * F - Cutting feedrate + * + */ + +// External references + +// Private functions + + +float G83_Z, + G83_R, + G83_Q, + G83_F, + N_D = 0; + +millis_t G83_P; + + +/** + * G83 + */ +void GcodeSuite::G83() +{ + + + #ifdef G0_FEEDRATE + float g0_feedrate_mm_s, saved_feedrate_mm_s; + + saved_feedrate_mm_s = feedrate_mm_s; + + #endif + + if (parser.seen('Z')) G83_Z = parser.value_float(); + + SERIAL_ECHO("Z"); + SERIAL_ECHOLN(G83_Z); + + if (parser.seen('R'))G83_R = parser.value_float(); + + SERIAL_ECHO("R"); + SERIAL_ECHOLN(G83_R); + + if (parser.seen('Q')) G83_Q = parser.value_float(); + + SERIAL_ECHO("Q"); + SERIAL_ECHOLN(G83_Q); + + if (parser.seenval('P')) G83_P = parser.value_millis(); + + SERIAL_ECHO("P"); + SERIAL_ECHOLN(G83_P); + + if (parser.seen('F')) { + + G83_F = parser.value_float(); + feedrate_mm_s = MMM_TO_MMS(G83_F); + } + + SERIAL_ECHO("F"); + SERIAL_ECHOLN(G83_F); + + g0_feedrate_mm_s = MMM_TO_MMS(G0_FEEDRATE); + + + float sav_pos_X = current_position[X_AXIS], + sav_pos_Y = current_position[Y_AXIS], + sav_pos_Z = current_position[Z_AXIS]; + + + float z_zero_pos = sav_pos_Z - G83_R; + + float Z_fin_pos = z_zero_pos + G83_Z; + + destination[X_AXIS] = sav_pos_X; + destination[Y_AXIS] = sav_pos_Y; + + + destination[Z_AXIS] = z_zero_pos + G83_R; + prepare_move_to_destination(); + planner.synchronize(); + + if(G83_Q == 0){ + N_D = Z_fin_pos; + } + else{ + N_D = z_zero_pos + G83_R - G83_Q; + } + + while (Z_fin_pos < N_D){ + + destination[Z_AXIS] = N_D; + prepare_move_to_destination(); + planner.synchronize(); + + dwell(G83_P); + + feedrate_mm_s = g0_feedrate_mm_s; + + destination[Z_AXIS] = z_zero_pos + G83_R; + prepare_move_to_destination(); + planner.synchronize(); + + feedrate_mm_s = MMM_TO_MMS(G83_F); + + N_D = N_D - G83_Q; + } + + destination[Z_AXIS] = z_zero_pos + G83_Z; + prepare_move_to_destination(); + planner.synchronize(); + + dwell(G83_P); + + feedrate_mm_s = g0_feedrate_mm_s; + + destination[Z_AXIS] = z_zero_pos + G83_R; + prepare_move_to_destination(); + planner.synchronize(); + + + #ifdef G0_FEEDRATE + // Restore the motion mode feedrate + if (fast_move) feedrate_mm_s = saved_feedrate_mm_s; + #endif + + + + + +} + +#endif + diff --git a/Marlin/src/gcode/motion/M168.cpp b/Marlin/src/gcode/motion/M168.cpp new file mode 100644 index 000000000000..4720523c69ab --- /dev/null +++ b/Marlin/src/gcode/motion/M168.cpp @@ -0,0 +1,162 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (C) 2019 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * + * Based on Sprinter and grbl. + * Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "../../inc/MarlinConfig.h" + +#if ENABLED(CNC_5X) + +#include "../../gcode/gcode.h" +#include "../../feature/bedlevel/bedlevel.h" + +#include "../../Marlin.h" +#include "../../module/planner.h" +#include "../../module/stepper.h" +#include "../../module/motion.h" +#include "../../module/tool_change.h" +#include "../../module/temperature.h" +#include "../../lcd/ultralcd.h" +#include "../../lcd/menu/menu.h" + +extern bool fast_move;/*=false*/ + + + + +/** + * M168 - 5 Axis Simple Convertion + * + * X Y Z - position of cartesians axis + * E A B - position of rotary axis + * + * + */ + +// External references + +// Private functions + + +float X_5, + Y_5, + Z_5, + IX_5, + IY_5, + IZ_5, + OX_5, + OY_5, + OZ_5, + A_5, + B_5, + C_5, + IA_5, + IB_5, + IC_5, + OA_5, + OB_5, + OC_5, + E_5, + I, + J = 0; + + + +/** + * M168 + */ +void GcodeSuite::M168() +{ + + if (parser.seen('X')) X_5 = parser.value_float(); + + SERIAL_ECHO("X"); + SERIAL_ECHOLN(X_5); + + if (parser.seen('Y')) Y_5 = parser.value_float(); + + SERIAL_ECHO("Y"); + SERIAL_ECHOLN(Y_5); + + if (parser.seen('Z')) Z_5 = parser.value_float(); + + SERIAL_ECHO("Z"); + SERIAL_ECHOLN(Z_5); + + if (parser.seen('A')) A_5 = parser.value_float(); + + SERIAL_ECHO("A"); + SERIAL_ECHOLN(A_5); + + if (parser.seen('B')) B_5 = parser.value_float(); + + SERIAL_ECHO("B"); + SERIAL_ECHOLN(B_5); + + if (parser.seen('C')) C_5 = parser.value_float(); + + SERIAL_ECHO("C"); + SERIAL_ECHOLN(C_5); + + + #if ENABLED (A_5x) && ENABLED(B_5x) + + I=A_5; + J=B_5; + + #endif + + #if ENABLED (A_5x) && ENABLED(C_5x) + + I=A_5; + J=C_5; + + #endif + + #if ENABLED (B_5x) && ENABLED(C_5x) + + I=B_5; + J=C_5; + + #endif + + destination[X_AXIS] = X_5; + destination[Y_AXIS] = Y_5; + destination[Z_AXIS] = Z_5; + destination[E0_AXIS] = I; + destination[E1_AXIS] = J; + + prepare_move_to_destination(); + + planner.synchronize(); + + //COPY(current_position, current_position5); + + SERIAL_ECHO("I"); + SERIAL_ECHOLN(current_position[E0_AXIS]); + + SERIAL_ECHO("J"); + SERIAL_ECHOLN(current_position[E1_AXIS]); + + +} + +#endif + diff --git a/Marlin/src/lcd/menu/menu_custom.cpp b/Marlin/src/lcd/menu/menu_custom.cpp index f6178133bef9..0c67b5bab5fb 100644 --- a/Marlin/src/lcd/menu/menu_custom.cpp +++ b/Marlin/src/lcd/menu/menu_custom.cpp @@ -65,6 +65,51 @@ void menu_user() { #if defined(USER_DESC_5) && defined(USER_GCODE_5) ACTION_ITEM_P(PSTR(USER_DESC_5), []{ _lcd_user_gcode(PSTR(USER_GCODE_5 _DONE_SCRIPT)); }); #endif + #if defined(USER_DESC_6) && defined(USER_GCODE_6) + ACTION_ITEM_P(PSTR(USER_DESC_6), []{ _lcd_user_gcode(PSTR(USER_GCODE_6 _DONE_SCRIPT)); }); + #endif + #if defined(USER_DESC_7) && defined(USER_GCODE_7) + ACTION_ITEM_P(PSTR(USER_DESC_7), []{ _lcd_user_gcode(PSTR(USER_GCODE_7 _DONE_SCRIPT)); }); + #endif + #if defined(USER_DESC_8) && defined(USER_GCODE_8) + ACTION_ITEM_P(PSTR(USER_DESC_8), []{ _lcd_user_gcode(PSTR(USER_GCODE_8 _DONE_SCRIPT)); }); + #endif + #if defined(USER_DESC_9) && defined(USER_GCODE_9) + ACTION_ITEM_P(PSTR(USER_DESC_9), []{ _lcd_user_gcode(PSTR(USER_GCODE_9 _DONE_SCRIPT)); }); + #endif + #if defined(USER_DESC_10) && defined(USER_GCODE_10) + ACTION_ITEM_P(PSTR(USER_DESC_10), []{ _lcd_user_gcode(PSTR(USER_GCODE_10 _DONE_SCRIPT)); }); + #endif + #if defined(USER_DESC_11) && defined(USER_GCODE_11) + ACTION_ITEM_P(PSTR(USER_DESC_11), []{ _lcd_user_gcode(PSTR(USER_GCODE_11 _DONE_SCRIPT)); }); + #endif + #if defined(USER_DESC_12) && defined(USER_GCODE_12) + ACTION_ITEM_P(PSTR(USER_DESC_12), []{ _lcd_user_gcode(PSTR(USER_GCODE_12 _DONE_SCRIPT)); }); + #endif + #if defined(USER_DESC_13) && defined(USER_GCODE_13) + ACTION_ITEM_P(PSTR(USER_DESC_13), []{ _lcd_user_gcode(PSTR(USER_GCODE_13 _DONE_SCRIPT)); }); + #endif + #if defined(USER_DESC_14) && defined(USER_GCODE_14) + ACTION_ITEM_P(PSTR(USER_DESC_14), []{ _lcd_user_gcode(PSTR(USER_GCODE_14 _DONE_SCRIPT)); }); + #endif + #if defined(USER_DESC_15) && defined(USER_GCODE_15) + ACTION_ITEM_P(PSTR(USER_DESC_15), []{ _lcd_user_gcode(PSTR(USER_GCODE_15 _DONE_SCRIPT)); }); + #endif + #if defined(USER_DESC_16) && defined(USER_GCODE_16) + ACTION_ITEM_P(PSTR(USER_DESC_16), []{ _lcd_user_gcode(PSTR(USER_GCODE_16 _DONE_SCRIPT)); }); + #endif + #if defined(USER_DESC_17) && defined(USER_GCODE_17) + ACTION_ITEM_P(PSTR(USER_DESC_17), []{ _lcd_user_gcode(PSTR(USER_GCODE_17 _DONE_SCRIPT)); }); + #endif + #if defined(USER_DESC_18) && defined(USER_GCODE_18) + ACTION_ITEM_P(PSTR(USER_DESC_18), []{ _lcd_user_gcode(PSTR(USER_GCODE_18 _DONE_SCRIPT)); }); + #endif + #if defined(USER_DESC_19) && defined(USER_GCODE_19) + ACTION_ITEM_P(PSTR(USER_DESC_19), []{ _lcd_user_gcode(PSTR(USER_GCODE_19 _DONE_SCRIPT)); }); + #endif + #if defined(USER_DESC_20) && defined(USER_GCODE_20) + ACTION_ITEM_P(PSTR(USER_DESC_20), []{ _lcd_user_gcode(PSTR(USER_GCODE_20 _DONE_SCRIPT)); }); + #endif END_MENU(); }