diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h index 9090eb5a61..278853ac60 100644 --- a/Marlin/Configuration_adv.h +++ b/Marlin/Configuration_adv.h @@ -1675,6 +1675,7 @@ //#define NO_SD_AUTOSTART // Remove auto#.g file support completely to save some Flash, SRAM //#define MENU_ADDAUTOSTART // Add a menu option to run auto#.g files + //#define ONE_CLICK_PRINT // Prompt to print the newest file on inserted media //#define BROWSE_MEDIA_ON_INSERT // Open the file browser when media is inserted //#define MEDIA_MENU_AT_TOP // Force the media menu to be listed on the top of the main menu diff --git a/Marlin/src/inc/SanityCheck.h b/Marlin/src/inc/SanityCheck.h index 53333d8727..2a9fdde2dc 100644 --- a/Marlin/src/inc/SanityCheck.h +++ b/Marlin/src/inc/SanityCheck.h @@ -4044,6 +4044,19 @@ static_assert(_PLUS_TEST(3), "DEFAULT_MAX_ACCELERATION values must be positive." // Multi-Stepping Limit static_assert(WITHIN(MULTISTEPPING_LIMIT, 1, 128) && IS_POWER_OF_2(MULTISTEPPING_LIMIT), "MULTISTEPPING_LIMIT must be 1, 2, 4, 8, 16, 32, 64, or 128."); +// One Click Print +#if ENABLED(ONE_CLICK_PRINT) + #if !HAS_MEDIA + #error "SD Card or Flash Drive is required for ONE_CLICK_PRINT." + #elif ENABLED(BROWSE_MEDIA_ON_INSERT) + #error "ONE_CLICK_PRINT is incompatible with BROWSE_MEDIA_ON_INSERT." + #elif DISABLED(NO_SD_AUTOSTART) + #error "NO_SD_AUTOSTART must be enabled for ONE_CLICK_PRINT." + #elif !defined(HAS_MARLINUI_MENU) + #error "ONE_CLICK_PRINT needs a display that has Marlin UI menus." + #endif +#endif + // Misc. Cleanup #undef _TEST_PWM #undef _NUM_AXES_STR diff --git a/Marlin/src/lcd/marlinui.cpp b/Marlin/src/lcd/marlinui.cpp index 638a133184..e8956ab9e1 100644 --- a/Marlin/src/lcd/marlinui.cpp +++ b/Marlin/src/lcd/marlinui.cpp @@ -424,7 +424,7 @@ void MarlinUI::init() { #if !HAS_GRAPHICAL_TFT - void _wrap_string(uint8_t &col, uint8_t &row, const char * const string, read_byte_cb_t cb_read_byte, bool wordwrap/*=false*/) { + void _wrap_string(uint8_t &col, uint8_t &row, const char * const string, read_byte_cb_t cb_read_byte, const bool wordwrap/*=false*/) { SETCURSOR(col, row); if (!string) return; diff --git a/Marlin/src/lcd/menu/menu.h b/Marlin/src/lcd/menu/menu.h index 5a70ddd3a2..1face4774b 100644 --- a/Marlin/src/lcd/menu/menu.h +++ b/Marlin/src/lcd/menu/menu.h @@ -259,6 +259,10 @@ void _lcd_draw_homing(); void touch_screen_calibration(); #endif +#if ENABLED(ONE_CLICK_PRINT) + void one_click_print(); +#endif + extern uint8_t screen_history_depth; inline void clear_menu_history() { screen_history_depth = 0; } diff --git a/Marlin/src/lcd/menu/menu_media.cpp b/Marlin/src/lcd/menu/menu_media.cpp index e32f41a9a6..795ac2052b 100644 --- a/Marlin/src/lcd/menu/menu_media.cpp +++ b/Marlin/src/lcd/menu/menu_media.cpp @@ -73,14 +73,11 @@ class MenuItem_sdfile : public MenuItem_sdbase { #endif #if ENABLED(SD_MENU_CONFIRM_START) MenuItem_submenu::action(fstr, []{ - char * const longest = card.longest_filename(); - char buffer[strlen(longest) + 2]; - buffer[0] = ' '; - strcpy(buffer + 1, longest); + char * const filename = card.longest_filename(); MenuItem_confirm::select_screen( GET_TEXT_F(MSG_BUTTON_PRINT), GET_TEXT_F(MSG_BUTTON_CANCEL), sdcard_start_selected_file, nullptr, - GET_TEXT_F(MSG_START_PRINT), buffer, F("?") + GET_TEXT_F(MSG_START_PRINT), filename, F("?") ); }); #else diff --git a/Marlin/src/lcd/menu/menu_one_click_print.cpp b/Marlin/src/lcd/menu/menu_one_click_print.cpp new file mode 100644 index 0000000000..f1ed92d0d5 --- /dev/null +++ b/Marlin/src/lcd/menu/menu_one_click_print.cpp @@ -0,0 +1,44 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2023 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/MarlinConfigPre.h" + +#if ENABLED(ONE_CLICK_PRINT) + +#include "menu.h" + +void one_click_print() { + ui.goto_screen([]{ + char * const filename = card.longest_filename(); + MenuItem_confirm::select_screen( + GET_TEXT_F(MSG_BUTTON_PRINT), GET_TEXT_F(MSG_BUTTON_CANCEL), + []{ + card.openAndPrintFile(card.filename); + ui.return_to_status(); + ui.reset_status(); + }, nullptr, + GET_TEXT_F(MSG_START_PRINT), filename, F("?") + ); + }); +} + +#endif // ONE_CLICK_PRINT diff --git a/Marlin/src/sd/cardreader.cpp b/Marlin/src/sd/cardreader.cpp index 4e864e5672..cd3d1d2cd6 100644 --- a/Marlin/src/sd/cardreader.cpp +++ b/Marlin/src/sd/cardreader.cpp @@ -56,6 +56,10 @@ #include "../feature/pause.h" #endif +#if ENABLED(ONE_CLICK_PRINT) + #include "../../src/lcd/menu/menu.h" +#endif + #define DEBUG_OUT EITHER(DEBUG_CARDREADER, MARLIN_DEV_MODE) #include "../core/debug_out.h" #include "../libs/hex_print.h" @@ -290,7 +294,7 @@ void CardReader::printListing(MediaFile parent, const char * const prepend, cons while (parent.readDir(&p, longFilename) > 0) { if (DIR_IS_SUBDIR(&p)) { - size_t lenPrepend = prepend ? strlen(prepend) + 1 : 0; + const size_t lenPrepend = prepend ? strlen(prepend) + 1 : 0; // Allocate enough stack space for the full path including / separator char path[lenPrepend + FILENAME_LENGTH]; if (prepend) { strcpy(path, prepend); path[lenPrepend - 1] = '/'; } @@ -545,20 +549,28 @@ void CardReader::manage_media() { if (!stat) return; // Exit if no media is present - if (old_stat != 2) return; // First mount? - - DEBUG_ECHOLNPGM("First mount."); - - // Load settings the first time media is inserted (not just during init) - TERN_(SDCARD_EEPROM_EMULATION, settings.first_load()); - bool do_auto = true; UNUSED(do_auto); - // Check for PLR file. - TERN_(POWER_LOSS_RECOVERY, if (recovery.check()) do_auto = false); + // First mount on boot? Load emulated EEPROM and look for PLR file. + if (old_stat == 2) { + DEBUG_ECHOLNPGM("First mount."); - // Look for auto0.g on the next idle() - IF_DISABLED(NO_SD_AUTOSTART, if (do_auto) autofile_begin()); + // Load settings the first time media is inserted (not just during init) + TERN_(SDCARD_EEPROM_EMULATION, settings.first_load()); + + // Check for PLR file. Skip One-Click and auto#.g if found + TERN_(POWER_LOSS_RECOVERY, if (recovery.check()) do_auto = false); + } + + // Find the newest file and prompt to print it. + TERN_(ONE_CLICK_PRINT, if (do_auto && one_click_check()) do_auto = false); + + // Also for the first mount run auto#.g for machine init. + // (Skip if PLR or One-Click Print was invoked.) + if (old_stat == 2) { + // Look for auto0.g on the next idle() + IF_DISABLED(NO_SD_AUTOSTART, if (do_auto) autofile_begin()); + } } /** @@ -887,6 +899,81 @@ void CardReader::write_command(char * const buf) { } #endif +#if ENABLED(ONE_CLICK_PRINT) + + /** + * Select the newest file and ask the user if they want to print it. + */ + bool CardReader::one_click_check() { + const bool found = selectNewestFile(); + if (found) { + //SERIAL_ECHO_MSG(" OCP File: ", longest_filename(), "\n"); + //ui.init(); + one_click_print(); + } + return found; + } + + /** + * Recurse the entire directory to find the newest file. + * This may take a very long time so watch out for watchdog reset. + * It may be best to only look at root for reasonable boot and mount times. + */ + void CardReader::diveToNewestFile(MediaFile parent, uint32_t &compareDateTime, MediaFile &outdir, char * const outname) { + // Iterate the given parent dir + parent.rewind(); + for (dir_t p; parent.readDir(&p, longFilename) > 0;) { + + // If the item is a dir, recurse into it + if (DIR_IS_SUBDIR(&p)) { + // Get the name of the dir for opening + char dirname[FILENAME_LENGTH]; + createFilename(dirname, p); + + // Open the item in a new MediaFile + MediaFile child; // child.close() in destructor + if (child.open(&parent, dirname, O_READ)) + diveToNewestFile(child, compareDateTime, outdir, outname); + } + else if (is_visible_entity(p)) { + // Get the newer of the modified/created date and time + const uint32_t modDateTime = uint32_t(p.lastWriteDate) << 16 | p.lastWriteTime, + createDateTime = uint32_t(p.creationDate) << 16 | p.creationTime, + newerDateTime = _MAX(modDateTime, createDateTime); + // If a newer item is found overwrite the outdir and outname + if (newerDateTime > compareDateTime) { + compareDateTime = newerDateTime; + outdir = parent; + createFilename(outname, p); + } + } + } + } + + /** + * Recurse the entire directory to find the newest file. + * Make the found file the current selection. + */ + bool CardReader::selectNewestFile() { + uint32_t dateTimeStorage = 0; + MediaFile foundDir; + char foundName[FILENAME_LENGTH]; + foundName[0] = '\0'; + + diveToNewestFile(root, dateTimeStorage, foundDir, foundName); + + if (foundName[0]) { + workDir = foundDir; + workDir.rewind(); + selectByName(workDir, foundName); + //workDir.close(); // Not needed? + return true; + } + return false; + } + +#endif // ONE_CLICK_PRINT + void CardReader::closefile(const bool store_location/*=false*/) { file.sync(); file.close(); diff --git a/Marlin/src/sd/cardreader.h b/Marlin/src/sd/cardreader.h index 3b7b7debcf..4baaa73e1a 100644 --- a/Marlin/src/sd/cardreader.h +++ b/Marlin/src/sd/cardreader.h @@ -128,6 +128,12 @@ public: static void autofile_cancel() { autofile_index = 0; } #endif + #if ENABLED(ONE_CLICK_PRINT) + static bool one_click_check(); // Check for the newest file and prompt to run it. + static void diveToNewestFile(MediaFile parent, uint32_t &compareDateTime, MediaFile &outdir, char * const outname); + static bool selectNewestFile(); + #endif + // Basic file ops static void openFileRead(const char * const path, const uint8_t subcall=0); static void openFileWrite(const char * const path); diff --git a/buildroot/share/PlatformIO/scripts/common-dependencies.h b/buildroot/share/PlatformIO/scripts/common-dependencies.h index 4438b4efe1..c75d9a3d67 100644 --- a/buildroot/share/PlatformIO/scripts/common-dependencies.h +++ b/buildroot/share/PlatformIO/scripts/common-dependencies.h @@ -104,6 +104,9 @@ #if ENABLED(AUTO_BED_LEVELING_UBL) #define HAS_MENU_UBL #endif + #if ENABLED(ONE_CLICK_PRINT) + #define HAS_MENU_ONE_CLICK_PRINT + #endif #endif #if HAS_GRAPHICAL_TFT diff --git a/buildroot/tests/mks_tinybee b/buildroot/tests/mks_tinybee index 9dcc33ede7..0351946d0a 100755 --- a/buildroot/tests/mks_tinybee +++ b/buildroot/tests/mks_tinybee @@ -25,8 +25,8 @@ opt_set MOTHERBOARD BOARD_MKS_TINYBEE \ LCD_INFO_SCREEN_STYLE 0 \ DISPLAY_CHARSET_HD44780 WESTERN \ NEOPIXEL_TYPE NEO_RGB -opt_enable FYSETC_MINI_12864_2_1 SDSUPPORT -opt_enable LED_CONTROL_MENU LED_USER_PRESET_STARTUP LED_COLOR_PRESETS NEOPIXEL_LED +opt_enable FYSETC_MINI_12864_2_1 SDSUPPORT ONE_CLICK_PRINT NO_SD_AUTOSTART \ + NEOPIXEL_LED LED_CONTROL_MENU LED_USER_PRESET_STARTUP LED_COLOR_PRESETS exec_test $1 $2 "MKS TinyBee with NeoPixel LCD, SD and Speaker" "$3" # cleanup diff --git a/ini/features.ini b/ini/features.ini index 17d2ece04a..86e7d2e396 100644 --- a/ini/features.ini +++ b/ini/features.ini @@ -81,6 +81,7 @@ HAS_MENU_LED = build_src_filter=+ HAS_MENU_MIXER = build_src_filter=+ HAS_MENU_MMU2 = build_src_filter=+ +HAS_MENU_ONE_CLICK_PRINT = build_src_filter=+ HAS_MENU_PASSWORD = build_src_filter=+ HAS_MENU_POWER_MONITOR = build_src_filter=+ HAS_MENU_CUTTER = build_src_filter=+