diff --git a/Firmware/Configuration.h b/Firmware/Configuration.h index 629c2634..edce46e8 100644 --- a/Firmware/Configuration.h +++ b/Firmware/Configuration.h @@ -9,7 +9,7 @@ // Firmware version #define FW_version "3.1.1-RC1" -#define FW_build 128 +#define FW_build 129 //#define FW_build --BUILD-NUMBER-- #define FW_version_build FW_version " b" STR(FW_build) @@ -81,6 +81,7 @@ #define EEPROM_POWER_COUNT (EEPROM_UVLO_MESH_BED_LEVELING-17) #define EEPROM_DIR_DEPTH (EEPROM_POWER_COUNT-1) #define EEPROM_DIRS (EEPROM_DIR_DEPTH-80) //8 chars for each dir name, max 10 levels +#define EEPROM_SD_SORT (EEPROM_DIRS - 1) //0 -time, 1-alpha, 2-none //TMC2130 configuration #define EEPROM_TMC_AXIS_SIZE //axis configuration block size diff --git a/Firmware/Configuration_adv.h b/Firmware/Configuration_adv.h index e365d79d..d7ca0814 100644 --- a/Firmware/Configuration_adv.h +++ b/Firmware/Configuration_adv.h @@ -224,6 +224,52 @@ // using: //#define MENU_ADDAUTOSTART +/** +* Sort SD file listings in alphabetical order. +* +* With this option enabled, items on SD cards will be sorted +* by name for easier navigation. +* +* By default... +* +* - Use the slowest -but safest- method for sorting. +* - Folders are sorted to the top. +* - The sort key is statically allocated. +* - No added G-code (M34) support. +* - 40 item sorting limit. (Items after the first 40 are unsorted.) +* +* SD sorting uses static allocation (as set by SDSORT_LIMIT), allowing the +* compiler to calculate the worst-case usage and throw an error if the SRAM +* limit is exceeded. +* +* - SDSORT_USES_RAM provides faster sorting via a static directory buffer. +* - SDSORT_USES_STACK does the same, but uses a local stack-based buffer. +* - SDSORT_CACHE_NAMES will retain the sorted file listing in RAM. (Expensive!) +* - SDSORT_DYNAMIC_RAM only uses RAM when the SD menu is visible. (Use with caution!) +*/ + #define SDCARD_SORT_ALPHA //Alphabetical sorting of SD files menu + + // SD Card Sorting options + // In current firmware Prusa Firmware version, + // SDSORT_CACHE_NAMES and SDSORT_DYNAMIC_RAM is not supported and must be set to false. + #ifdef SDCARD_SORT_ALPHA + #define SD_SORT_TIME 0 + #define SD_SORT_ALPHA 1 + #define SD_SORT_NONE 2 + + #define SDSORT_LIMIT 100 // Maximum number of sorted items (10-256). + #define FOLDER_SORTING -1 // -1=above 0=none 1=below + #define SDSORT_GCODE false // Allow turning sorting on/off with LCD and M34 g-code. + #define SDSORT_USES_RAM false // Pre-allocate a static array for faster pre-sorting. + #define SDSORT_USES_STACK false // Prefer the stack for pre-sorting to give back some SRAM. (Negated by next 2 options.) + #define SDSORT_CACHE_NAMES false // Keep sorted items in RAM longer for speedy performance. Most expensive option. + #define SDSORT_DYNAMIC_RAM false // Use dynamic allocation (within SD menus). Least expensive option. Set SDSORT_LIMIT before use! + #endif + + #if defined(SDCARD_SORT_ALPHA) + #define HAS_FOLDER_SORTING (FOLDER_SORTING || SDSORT_GCODE) + #endif + // Show a progress bar on the LCD when printing from SD? //#define LCD_PROGRESS_BAR diff --git a/Firmware/Marlin.h b/Firmware/Marlin.h index 74455a38..51ce9dbc 100644 --- a/Firmware/Marlin.h +++ b/Firmware/Marlin.h @@ -349,6 +349,7 @@ extern bool mesh_bed_leveling_flag; extern bool mesh_bed_run_from_menu; extern float distance_from_min[2]; +extern bool sortAlpha; extern char dir_names[3][9]; diff --git a/Firmware/Marlin_main.cpp b/Firmware/Marlin_main.cpp index d14f7845..26cf6f9b 100644 --- a/Firmware/Marlin_main.cpp +++ b/Firmware/Marlin_main.cpp @@ -322,6 +322,8 @@ int fan_speed[2]; char dir_names[3][9]; +bool sortAlpha = false; + bool volumetric_enabled = false; float filament_size[EXTRUDERS] = { DEFAULT_NOMINAL_FILAMENT_DIA #if EXTRUDERS > 1 @@ -1098,6 +1100,9 @@ void setup() if (eeprom_read_byte((uint8_t*)EEPROM_UVLO) == 255) { eeprom_write_byte((uint8_t*)EEPROM_UVLO, 0); } + if (eeprom_read_byte((uint8_t*)EEPROM_SD_SORT) == 255) { + eeprom_write_byte((uint8_t*)EEPROM_SD_SORT, 0); + } check_babystep(); //checking if Z babystep is in allowed range setup_uvlo_interrupt(); diff --git a/Firmware/cardreader.cpp b/Firmware/cardreader.cpp index ed78ea23..fb006ca6 100644 --- a/Firmware/cardreader.cpp +++ b/Firmware/cardreader.cpp @@ -7,10 +7,20 @@ #ifdef SDSUPPORT - +#define LONGEST_FILENAME (longFilename[0] ? longFilename : filename) CardReader::CardReader() { + + #ifdef SDCARD_SORT_ALPHA + sort_count = 0; + #if SDSORT_GCODE + sort_alpha = true; + sort_folders = FOLDER_SORTING; + //sort_reverse = false; + #endif + #endif + filesize = 0; sdpos = 0; sdprinting = false; @@ -49,95 +59,102 @@ char *createFilename(char *buffer,const dir_t &p) //buffer>12characters return buffer; } +/** ++* Dive into a folder and recurse depth-first to perform a pre-set operation lsAction: ++* LS_Count - Add +1 to nrFiles for every file within the parent ++* LS_GetFilename - Get the filename of the file indexed by nrFiles ++* LS_SerialPrint - Print the full path and size of each file to serial output ++*/ -void CardReader::lsDive(const char *prepend, SdFile parent, const char * const match/*=NULL*/) -{ - dir_t p; - uint8_t cnt=0; - - while (parent.readDir(p, longFilename) > 0) - { - if( DIR_IS_SUBDIR(&p) && lsAction!=LS_Count && lsAction!=LS_GetFilename) // hence LS_SerialPrint - { - - char path[13*2]; - char lfilename[13]; - createFilename(lfilename,p); - - path[0]=0; - if(strlen(prepend)==0) //avoid leading / if already in prepend - { - strcat(path,"/"); - } - strcat(path,prepend); - strcat(path,lfilename); - strcat(path,"/"); - - //Serial.print(path); - - SdFile dir; - if(!dir.open(parent,lfilename, O_READ)) - { - if(lsAction==LS_SerialPrint) - { - SERIAL_ECHO_START; - SERIAL_ECHORPGM(MSG_SD_CANT_ENTER_SUBDIR); - SERIAL_ECHOLN(lfilename); - } - } - lsDive(path,dir); - //close done automatically by destructor of SdFile - - - } - else - { - char pn0 = p.name[0]; - if (pn0 == DIR_NAME_FREE) break; - if (pn0 == DIR_NAME_DELETED || pn0 == '.' || pn0 == '_') continue; - char lf0 = longFilename[0]; - if (lf0 == '.' || lf0 == '_') continue; - - if (!DIR_IS_FILE_OR_SUBDIR(&p)) continue; - // Ignore the files and directories with hidden or system attribute. - if ((p.attributes & (DIR_ATT_HIDDEN | DIR_ATT_SYSTEM)) != 0) continue; - filenameIsDir=DIR_IS_SUBDIR(&p); - - - if(!filenameIsDir) - { - if(p.name[8]!='G') continue; - if(p.name[9]=='~') continue; - } - //if(cnt++!=nr) continue; - createFilename(filename,p); - if(lsAction==LS_SerialPrint) - { - SERIAL_PROTOCOL(prepend); - SERIAL_PROTOCOLLN(filename); - } - else if(lsAction==LS_Count) - { - nrFiles++; - } - else if(lsAction==LS_GetFilename) - { - if (match != NULL) { - if (strcasecmp(match, filename) == 0) return; - } - else if (cnt == nrFiles) return; - cnt++; - - } - } - } +void CardReader::lsDive(const char *prepend, SdFile parent, const char * const match/*=NULL*/) { + dir_t p; + uint8_t cnt = 0; + // Read the next entry from a directory + while (parent.readDir(p, longFilename) > 0) { + // If the entry is a directory and the action is LS_SerialPrint + if (DIR_IS_SUBDIR(&p) && lsAction != LS_Count && lsAction != LS_GetFilename) { + // Get the short name for the item, which we know is a folder + char lfilename[FILENAME_LENGTH]; + createFilename(lfilename, p); + // Allocate enough stack space for the full path to a folder, trailing slash, and nul + bool prepend_is_empty = (prepend[0] == '\0'); + int len = (prepend_is_empty ? 1 : strlen(prepend)) + strlen(lfilename) + 1 + 1; + char path[len]; + // Append the FOLDERNAME12/ to the passed string. + // It contains the full path to the "parent" argument. + // We now have the full path to the item in this folder. + strcpy(path, prepend_is_empty ? "/" : prepend); // root slash if prepend is empty + strcat(path, lfilename); // FILENAME_LENGTH-1 characters maximum + strcat(path, "/"); // 1 character + // Serial.print(path); + // Get a new directory object using the full path + // and dive recursively into it. + SdFile dir; + if (!dir.open(parent, lfilename, O_READ)) { + if (lsAction == LS_SerialPrint) { + //SERIAL_ECHO_START(); + //SERIAL_ECHOPGM(MSG_SD_CANT_OPEN_SUBDIR); + //SERIAL_ECHOLN(lfilename); + } + } + lsDive(path, dir); + // close() is done automatically by destructor of SdFile + } + else { + uint8_t pn0 = p.name[0]; + if (pn0 == DIR_NAME_FREE) break; + if (pn0 == DIR_NAME_DELETED || pn0 == '.') continue; + if (longFilename[0] == '.') continue; + if (!DIR_IS_FILE_OR_SUBDIR(&p) || (p.attributes & DIR_ATT_HIDDEN)) continue; + filenameIsDir = DIR_IS_SUBDIR(&p); + if (!filenameIsDir && (p.name[8] != 'G' || p.name[9] == '~')) continue; + switch (lsAction) { + case LS_Count: + nrFiles++; + break; + + case LS_SerialPrint: + createFilename(filename, p); + SERIAL_PROTOCOL(prepend); + SERIAL_PROTOCOL(filename); + MYSERIAL.write(' '); + SERIAL_PROTOCOLLN(p.fileSize); + break; + + case LS_GetFilename: + //SERIAL_ECHOPGM("File: "); + createFilename(filename, p); + cluster = parent.curCluster(); + position = parent.curPosition(); + /*MYSERIAL.println(filename); + SERIAL_ECHOPGM("Write date: "); + writeDate = p.lastWriteDate; + MYSERIAL.println(writeDate); + writeTime = p.lastWriteTime; + SERIAL_ECHOPGM("Creation date: "); + MYSERIAL.println(p.creationDate); + SERIAL_ECHOPGM("Access date: "); + MYSERIAL.println(p.lastAccessDate); + SERIAL_ECHOLNPGM("");*/ + creationDate = p.creationDate; + creationTime = p.creationTime; + //writeDate = p.lastAccessDate; + if (match != NULL) { + if (strcasecmp(match, filename) == 0) return; + } + else if (cnt == nrFiles) return; + cnt++; + break; + } + } + } // while readDir } void CardReader::ls() { lsAction=LS_SerialPrint; - if(lsAction==LS_Count) - nrFiles=0; + //if(lsAction==LS_Count) + //nrFiles=0; root.rewind(); lsDive("",root); @@ -185,6 +202,11 @@ void CardReader::initsd() } workDir=root; curDir=&root; + + #ifdef SDCARD_SORT_ALPHA + presort(); + #endif + /* if(!workDir.openRoot(&volume)) { @@ -203,6 +225,9 @@ void CardReader::setroot() workDir=root; curDir=&workDir; + #ifdef SDCARD_SORT_ALPHA + presort(); + #endif } void CardReader::release() { @@ -215,6 +240,9 @@ void CardReader::startFileprint() if(cardOK) { sdprinting = true; + #ifdef SDCARD_SORT_ALPHA + //flush_presort(); + #endif } } @@ -458,6 +486,9 @@ void CardReader::removeFile(char* name) SERIAL_PROTOCOLPGM("File deleted:"); SERIAL_PROTOCOLLN(fname); sdpos = 0; + #ifdef SDCARD_SORT_ALPHA + presort(); + #endif } else { @@ -607,6 +638,15 @@ void CardReader::getfilename(uint16_t nr, const char * const match/*=NULL*/) } +void CardReader::getfilename_simple(uint32_t position, const char * const match/*=NULL*/) +{ + curDir = &workDir; + lsAction = LS_GetFilename; + nrFiles = 0; + curDir->seekSet(position); + lsDive("", *curDir, match); +} + uint16_t CardReader::getnrfilenames() { curDir=&workDir; @@ -640,6 +680,9 @@ void CardReader::chdir(const char * relpath) workDirParents[0]=*parent; } workDir=newfile; + #ifdef SDCARD_SORT_ALPHA + presort(); + #endif } } @@ -652,9 +695,296 @@ void CardReader::updir() int d; for (int d = 0; d < workDirDepth; d++) workDirParents[d] = workDirParents[d+1]; + #ifdef SDCARD_SORT_ALPHA + presort(); + #endif } } +#ifdef SDCARD_SORT_ALPHA + +/** +* Get the name of a file in the current directory by sort-index +*/ +void CardReader::getfilename_sorted(const uint16_t nr) { + getfilename( + #if SDSORT_GCODE + sort_alpha && + #endif + (nr < sort_count) ? sort_order[nr] : nr + ); +} + +/** +* Read all the files and produce a sort key +* +* We can do this in 3 ways... +* - Minimal RAM: Read two filenames at a time sorting along... +* - Some RAM: Buffer the directory just for this sort +* - Most RAM: Buffer the directory and return filenames from RAM +*/ +void CardReader::presort() { + if (farm_mode || IS_SD_INSERTED == false) return; //sorting is not used in farm mode + uint8_t sdSort = eeprom_read_byte((uint8_t*)EEPROM_SD_SORT); + + if (sdSort == SD_SORT_NONE) return; //sd sort is turned off + + #if SDSORT_GCODE + if (!sort_alpha) return; + #endif + KEEPALIVE_STATE(IN_HANDLER); + + // Throw away old sort index + flush_presort(); + + // If there are files, sort up to the limit + uint16_t fileCnt = getnrfilenames(); + if (fileCnt > 0) { + + // Never sort more than the max allowed + // If you use folders to organize, 20 may be enough + if (fileCnt > SDSORT_LIMIT) { + lcd_show_fullscreen_message_and_wait_P(MSG_FILE_CNT); + fileCnt = SDSORT_LIMIT; + } + lcd_implementation_clear(); + #if !SDSORT_USES_RAM + lcd_set_progress(); + #endif + lcd_print_at_PGM(0, 1, MSG_SORTING); + + // Sort order is always needed. May be static or dynamic. + #if SDSORT_DYNAMIC_RAM + sort_order = new uint8_t[fileCnt]; + #endif + + // Use RAM to store the entire directory during pre-sort. + // SDSORT_LIMIT should be set to prevent over-allocation. + #if SDSORT_USES_RAM + + // If using dynamic ram for names, allocate on the heap. + #if SDSORT_CACHE_NAMES + #if SDSORT_DYNAMIC_RAM + sortshort = new char*[fileCnt]; + sortnames = new char*[fileCnt]; + #endif + #elif SDSORT_USES_STACK + char sortnames[fileCnt][LONG_FILENAME_LENGTH]; + uint16_t creation_time[fileCnt]; + uint16_t creation_date[fileCnt]; + #endif + + // Folder sorting needs 1 bit per entry for flags. + #if HAS_FOLDER_SORTING + #if SDSORT_DYNAMIC_RAM + isDir = new uint8_t[(fileCnt + 7) >> 3]; + #elif SDSORT_USES_STACK + uint8_t isDir[(fileCnt + 7) >> 3]; + #endif + #endif + + #else // !SDSORT_USES_RAM + + uint32_t positions[fileCnt]; + + // By default re-read the names from SD for every compare + // retaining only two filenames at a time. This is very + // slow but is safest and uses minimal RAM. + char name1[LONG_FILENAME_LENGTH + 1]; + uint16_t creation_time_bckp; + uint16_t creation_date_bckp; + + #endif + position = 0; + if (fileCnt > 1) { + // Init sort order. + for (uint16_t i = 0; i < fileCnt; i++) { + if (!IS_SD_INSERTED) return; + manage_heater(); + sort_order[i] = i; + positions[i] = position; + getfilename(i); + // If using RAM then read all filenames now. + #if SDSORT_USES_RAM + getfilename(i); + #if SDSORT_DYNAMIC_RAM + // Use dynamic method to copy long filename + sortnames[i] = strdup(LONGEST_FILENAME); + #if SDSORT_CACHE_NAMES + // When caching also store the short name, since + // we're replacing the getfilename() behavior. + sortshort[i] = strdup(filename); + #endif + #else + // Copy filenames into the static array + strcpy(sortnames[i], LONGEST_FILENAME); + creation_time[i] = creationTime; + creation_date[i] = creationDate; + #if SDSORT_CACHE_NAMES + strcpy(sortshort[i], filename); + #endif + #endif + // char out[30]; + // sprintf_P(out, PSTR("---- %i %s %s"), i, filenameIsDir ? "D" : " ", sortnames[i]); + // SERIAL_ECHOLN(out); + #if HAS_FOLDER_SORTING + const uint16_t bit = i & 0x07, ind = i >> 3; + if (bit == 0) isDir[ind] = 0x00; + if (filenameIsDir) isDir[ind] |= _BV(bit); + #endif + #endif + } + +#ifdef QUICKSORT + quicksort(0, fileCnt - 1); +#else //Qicksort not defined, use Bubble Sort + uint32_t counter = 0; + uint16_t total = 0.5*(fileCnt - 1)*(fileCnt); + + // Compare names from the array or just the two buffered names + #if SDSORT_USES_RAM + #define _SORT_CMP_NODIR() (strcasecmp(sortnames[o1], sortnames[o2]) > 0) + #define _SORT_CMP_TIME_NODIR() (((creation_date[o1] == creation_date[o2]) && (creation_time[o1] < creation_time[o2])) || \ + (creation_date[o1] < creation_date [o2])) + #else + #define _SORT_CMP_NODIR() (strcasecmp(name1, name2) > 0) //true if lowercase(name1) > lowercase(name2) + #define _SORT_CMP_TIME_NODIR() (((creation_date_bckp == creationDate) && (creation_time_bckp > creationTime)) || \ + (creation_date_bckp > creationDate)) + + #endif + + #if HAS_FOLDER_SORTING + #if SDSORT_USES_RAM + // Folder sorting needs an index and bit to test for folder-ness. + const uint8_t ind1 = o1 >> 3, bit1 = o1 & 0x07, + ind2 = o2 >> 3, bit2 = o2 & 0x07; + #define _SORT_CMP_DIR(fs) \ + (((isDir[ind1] & _BV(bit1)) != 0) == ((isDir[ind2] & _BV(bit2)) != 0) \ + ? _SORT_CMP_NODIR() \ + : (isDir[fs > 0 ? ind1 : ind2] & (fs > 0 ? _BV(bit1) : _BV(bit2))) != 0) + #define _SORT_CMP_TIME_DIR(fs) \ + (((isDir[ind1] & _BV(bit1)) != 0) == ((isDir[ind2] & _BV(bit2)) != 0) \ + ? _SORT_CMP_TIME_NODIR() \ + : (isDir[fs > 0 ? ind1 : ind2] & (fs > 0 ? _BV(bit1) : _BV(bit2))) != 0) + #else + #define _SORT_CMP_DIR(fs) ((dir1 == filenameIsDir) ? _SORT_CMP_NODIR() : (fs > 0 ? dir1 : !dir1)) + #define _SORT_CMP_TIME_DIR(fs) ((dir1 == filenameIsDir) ? _SORT_CMP_TIME_NODIR() : (fs < 0 ? dir1 : !dir1)) + #endif + #endif + + for (uint16_t i = fileCnt; --i;) { + if (!IS_SD_INSERTED) return; + bool didSwap = false; + + #if !SDSORT_USES_RAM //show progresss bar only if slow sorting method is used + int8_t percent = (counter * 100) / total;//((counter * 100) / pow((fileCnt-1),2)); + for (int column = 0; column < 20; column++) { + if (column < (percent / 5)) lcd_implementation_print_at(column, 2, "\x01"); //simple progress bar + } + counter++; + #endif + + //MYSERIAL.println(int(i)); + for (uint16_t j = 0; j < i; ++j) { + if (!IS_SD_INSERTED) return; + manage_heater(); + const uint16_t o1 = sort_order[j], o2 = sort_order[j + 1]; + + // The most economical method reads names as-needed + // throughout the loop. Slow if there are many. + #if !SDSORT_USES_RAM + counter++; + getfilename_simple(positions[o1]); + strcpy(name1, LONGEST_FILENAME); // save (or getfilename below will trounce it) + creation_date_bckp = creationDate; + creation_time_bckp = creationTime; + #if HAS_FOLDER_SORTING + bool dir1 = filenameIsDir; + #endif + getfilename_simple(positions[o2]); + char *name2 = LONGEST_FILENAME; // use the string in-place + + #endif // !SDSORT_USES_RAM + + // Sort the current pair according to settings. + if ( + #if HAS_FOLDER_SORTING + (sdSort == SD_SORT_TIME && _SORT_CMP_TIME_DIR(FOLDER_SORTING)) || (sdSort == SD_SORT_ALPHA && _SORT_CMP_DIR(FOLDER_SORTING)) + #else + (sdSort == SD_SORT_TIME && _SORT_CMP_TIME_NODIR()) || (sdSort == SD_SORT_ALPHA && _SORT_CMP_NODIR()) + #endif + ) + { + sort_order[j] = o2; + sort_order[j + 1] = o1; + didSwap = true; + } + } + if (!didSwap) break; + } //end of bubble sort loop +#endif + // Using RAM but not keeping names around + #if (SDSORT_USES_RAM && !SDSORT_CACHE_NAMES) + #if SDSORT_DYNAMIC_RAM + for (uint16_t i = 0; i < fileCnt; ++i) free(sortnames[i]); + #if HAS_FOLDER_SORTING + free(isDir); + #endif + #endif + #endif + } + else { + sort_order[0] = 0; + #if (SDSORT_USES_RAM && SDSORT_CACHE_NAMES) + getfilename(0); + #if SDSORT_DYNAMIC_RAM + sortnames = new char*[1]; + sortnames[0] = strdup(LONGEST_FILENAME); // malloc + sortshort = new char*[1]; + sortshort[0] = strdup(filename); // malloc + isDir = new uint8_t[1]; + #else + strcpy(sortnames[0], LONGEST_FILENAME); + strcpy(sortshort[0], filename); + #endif + isDir[0] = filenameIsDir ? 0x01 : 0x00; + #endif + } + + sort_count = fileCnt; + } +#if !SDSORT_USES_RAM //show progresss bar only if slow sorting method is used + for (int column = 0; column <= 19; column++) lcd_implementation_print_at(column, 2, "\x01"); //simple progress bar + delay(300); + lcd_set_degree(); + lcd_implementation_clear(); + lcd_update(2); +#endif + lcd_update(2); + KEEPALIVE_STATE(NOT_BUSY); + lcd_timeoutToStatus = millis() + LCD_TIMEOUT_TO_STATUS; +} + +void CardReader::flush_presort() { + if (sort_count > 0) { + #if SDSORT_DYNAMIC_RAM + delete sort_order; + #if SDSORT_CACHE_NAMES + for (uint8_t i = 0; i < sort_count; ++i) { + free(sortshort[i]); // strdup + free(sortnames[i]); // strdup + } + delete sortshort; + delete sortnames; + #endif + #endif + sort_count = 0; + } +} + +#endif // SDCARD_SORT_ALPHA + + void CardReader::printingHasFinished() { @@ -678,6 +1008,9 @@ void CardReader::printingHasFinished() //enquecommand_P(PSTR(SD_FINISHED_RELEASECOMMAND)); } autotempShutdown(); + #ifdef SDCARD_SORT_ALPHA + presort(); + #endif } } diff --git a/Firmware/cardreader.h b/Firmware/cardreader.h index fa2bec75..1315ff18 100644 --- a/Firmware/cardreader.h +++ b/Firmware/cardreader.h @@ -31,6 +31,7 @@ public: void printingHasFinished(); void getfilename(uint16_t nr, const char* const match=NULL); + void getfilename_simple(uint32_t position, const char * const match = NULL); uint16_t getnrfilenames(); void getAbsFilename(char *t); @@ -43,6 +44,19 @@ public: void updir(); void setroot(); + #ifdef SDCARD_SORT_ALPHA + void presort(); + #ifdef SDSORT_QUICKSORT + void swap(uint8_t left, uint8_t right); + void quicksort(uint8_t left, uint8_t right); + #endif //SDSORT_QUICKSORT + void getfilename_sorted(const uint16_t nr); + #if SDSORT_GCODE + FORCE_INLINE void setSortOn(bool b) { sort_alpha = b; presort(); } + FORCE_INLINE void setSortFolders(int i) { sort_folders = i; presort(); } + //FORCE_INLINE void setSortReverse(bool b) { sort_reverse = b; } + #endif + #endif FORCE_INLINE bool isFileOpen() { return file.isOpen(); } FORCE_INLINE bool eof() { return sdpos>=filesize ;}; @@ -62,12 +76,60 @@ public: bool sdprinting ; bool cardOK ; char filename[13]; + uint16_t creationTime, creationDate; + uint32_t cluster, position; char longFilename[LONG_FILENAME_LENGTH]; bool filenameIsDir; int lastnr; //last number of the autostart; private: SdFile root,*curDir,workDir,workDirParents[MAX_DIR_DEPTH]; uint16_t workDirDepth; + + // Sort files and folders alphabetically. +#ifdef SDCARD_SORT_ALPHA + uint16_t sort_count; // Count of sorted items in the current directory + #if SDSORT_GCODE + bool sort_alpha; // Flag to enable / disable the feature + int sort_folders; // Flag to enable / disable folder sorting + //bool sort_reverse; // Flag to enable / disable reverse sorting + #endif + + // By default the sort index is static + #if SDSORT_DYNAMIC_RAM + uint8_t *sort_order; + #else + uint8_t sort_order[SDSORT_LIMIT]; + #endif + // Cache filenames to speed up SD menus. + #if SDSORT_USES_RAM + + // If using dynamic ram for names, allocate on the heap. + #if SDSORT_CACHE_NAMES + #if SDSORT_DYNAMIC_RAM + char **sortshort, **sortnames; + #else + char sortshort[SDSORT_LIMIT][FILENAME_LENGTH]; + char sortnames[SDSORT_LIMIT][FILENAME_LENGTH]; + #endif + #elif !SDSORT_USES_STACK + char sortnames[SDSORT_LIMIT][FILENAME_LENGTH]; + uint16_t creation_time[SDSORT_LIMIT]; + uint16_t creation_date[SDSORT_LIMIT]; + #endif + + // Folder sorting uses an isDir array when caching items. + #if HAS_FOLDER_SORTING + #if SDSORT_DYNAMIC_RAM + uint8_t *isDir; + #elif (SDSORT_CACHE_NAMES) || !(SDSORT_USES_STACK) + uint8_t isDir[(SDSORT_LIMIT + 7) >> 3]; + #endif + #endif + + #endif // SDSORT_USES_RAM + +#endif // SDCARD_SORT_ALPHA + Sd2Card card; SdVolume volume; SdFile file; @@ -87,6 +149,9 @@ private: int16_t nrFiles; //counter for the files in the current directory and recycled as position counter for getting the nrFiles'th name in the directory. char* diveDirName; void lsDive(const char *prepend, SdFile parent, const char * const match=NULL); +#ifdef SDCARD_SORT_ALPHA + void flush_presort(); +#endif }; extern CardReader card; #define IS_SD_PRINTING (card.sdprinting) diff --git a/Firmware/language_all.cpp b/Firmware/language_all.cpp index c437e1f6..e1e6f8f6 100644 --- a/Firmware/language_all.cpp +++ b/Firmware/language_all.cpp @@ -422,6 +422,11 @@ const char * const MSG_DATE_LANG_TABLE[LANG_NUM] PROGMEM = { MSG_DATE_CZ }; +const char MSG_DEFAULT_SETTINGS_LOADED_EN[] PROGMEM = "Default settings loaded"; +const char * const MSG_DEFAULT_SETTINGS_LOADED_LANG_TABLE[1] PROGMEM = { + MSG_DEFAULT_SETTINGS_LOADED_EN +}; + const char MSG_DISABLE_STEPPERS_EN[] PROGMEM = "Disable steppers"; const char MSG_DISABLE_STEPPERS_CZ[] PROGMEM = "Vypnout motory"; const char * const MSG_DISABLE_STEPPERS_LANG_TABLE[LANG_NUM] PROGMEM = { @@ -621,6 +626,13 @@ const char * const MSG_FILAMENT_LOADING_T3_LANG_TABLE[LANG_NUM] PROGMEM = { MSG_FILAMENT_LOADING_T3_CZ }; +const char MSG_FILE_CNT_EN[] PROGMEM = "Some files will not be sorted. Max. No. of files in 1 folder for sorting is 100."; +const char MSG_FILE_CNT_CZ[] PROGMEM = "Nektere soubory nebudou setrideny. Maximalni pocet souboru pro setrideni je 100."; +const char * const MSG_FILE_CNT_LANG_TABLE[LANG_NUM] PROGMEM = { + MSG_FILE_CNT_EN, + MSG_FILE_CNT_CZ +}; + const char MSG_FILE_INCOMPLETE_EN[] PROGMEM = "File incomplete. Continue anyway?"; const char MSG_FILE_INCOMPLETE_CZ[] PROGMEM = "Soubor nekompletni. Pokracovat?"; const char * const MSG_FILE_INCOMPLETE_LANG_TABLE[LANG_NUM] PROGMEM = { @@ -1750,6 +1762,32 @@ const char * const MSG_SOFTWARE_RESET_LANG_TABLE[1] PROGMEM = { MSG_SOFTWARE_RESET_EN }; +const char MSG_SORTING_EN[] PROGMEM = "Sorting files"; +const char * const MSG_SORTING_LANG_TABLE[1] PROGMEM = { + MSG_SORTING_EN +}; + +const char MSG_SORT_ALPHA_EN[] PROGMEM = "Sort: [Alphabet]"; +const char MSG_SORT_ALPHA_CZ[] PROGMEM = "Trideni [Abeceda]"; +const char * const MSG_SORT_ALPHA_LANG_TABLE[LANG_NUM] PROGMEM = { + MSG_SORT_ALPHA_EN, + MSG_SORT_ALPHA_CZ +}; + +const char MSG_SORT_NONE_EN[] PROGMEM = "Sort: [None]"; +const char MSG_SORT_NONE_CZ[] PROGMEM = "Trideni [Zadne]"; +const char * const MSG_SORT_NONE_LANG_TABLE[LANG_NUM] PROGMEM = { + MSG_SORT_NONE_EN, + MSG_SORT_NONE_CZ +}; + +const char MSG_SORT_TIME_EN[] PROGMEM = "Sort: [Time]"; +const char MSG_SORT_TIME_CZ[] PROGMEM = "Trideni [Cas]"; +const char * const MSG_SORT_TIME_LANG_TABLE[LANG_NUM] PROGMEM = { + MSG_SORT_TIME_EN, + MSG_SORT_TIME_CZ +}; + const char MSG_SPEED_EN[] PROGMEM = "Speed"; const char MSG_SPEED_CZ[] PROGMEM = "Rychlost"; const char * const MSG_SPEED_LANG_TABLE[LANG_NUM] PROGMEM = { diff --git a/Firmware/language_all.h b/Firmware/language_all.h index 6fb1275a..6ac48356 100644 --- a/Firmware/language_all.h +++ b/Firmware/language_all.h @@ -152,6 +152,8 @@ extern const char* const MSG_CURRENT_LANG_TABLE[LANG_NUM]; #define MSG_CURRENT LANG_TABLE_SELECT(MSG_CURRENT_LANG_TABLE) extern const char* const MSG_DATE_LANG_TABLE[LANG_NUM]; #define MSG_DATE LANG_TABLE_SELECT(MSG_DATE_LANG_TABLE) +extern const char* const MSG_DEFAULT_SETTINGS_LOADED_LANG_TABLE[1]; +#define MSG_DEFAULT_SETTINGS_LOADED LANG_TABLE_SELECT_EXPLICIT(MSG_DEFAULT_SETTINGS_LOADED_LANG_TABLE, 0) extern const char* const MSG_DISABLE_STEPPERS_LANG_TABLE[LANG_NUM]; #define MSG_DISABLE_STEPPERS LANG_TABLE_SELECT(MSG_DISABLE_STEPPERS_LANG_TABLE) extern const char* const MSG_DWELL_LANG_TABLE[1]; @@ -222,6 +224,8 @@ extern const char* const MSG_FILAMENT_LOADING_T2_LANG_TABLE[LANG_NUM]; #define MSG_FILAMENT_LOADING_T2 LANG_TABLE_SELECT(MSG_FILAMENT_LOADING_T2_LANG_TABLE) extern const char* const MSG_FILAMENT_LOADING_T3_LANG_TABLE[LANG_NUM]; #define MSG_FILAMENT_LOADING_T3 LANG_TABLE_SELECT(MSG_FILAMENT_LOADING_T3_LANG_TABLE) +extern const char* const MSG_FILE_CNT_LANG_TABLE[LANG_NUM]; +#define MSG_FILE_CNT LANG_TABLE_SELECT(MSG_FILE_CNT_LANG_TABLE) extern const char* const MSG_FILE_INCOMPLETE_LANG_TABLE[LANG_NUM]; #define MSG_FILE_INCOMPLETE LANG_TABLE_SELECT(MSG_FILE_INCOMPLETE_LANG_TABLE) extern const char* const MSG_FILE_PRINTED_LANG_TABLE[1]; @@ -590,6 +594,14 @@ extern const char* const MSG_SLIGHT_SKEW_LANG_TABLE[LANG_NUM]; #define MSG_SLIGHT_SKEW LANG_TABLE_SELECT(MSG_SLIGHT_SKEW_LANG_TABLE) extern const char* const MSG_SOFTWARE_RESET_LANG_TABLE[1]; #define MSG_SOFTWARE_RESET LANG_TABLE_SELECT_EXPLICIT(MSG_SOFTWARE_RESET_LANG_TABLE, 0) +extern const char* const MSG_SORTING_LANG_TABLE[1]; +#define MSG_SORTING LANG_TABLE_SELECT_EXPLICIT(MSG_SORTING_LANG_TABLE, 0) +extern const char* const MSG_SORT_ALPHA_LANG_TABLE[LANG_NUM]; +#define MSG_SORT_ALPHA LANG_TABLE_SELECT(MSG_SORT_ALPHA_LANG_TABLE) +extern const char* const MSG_SORT_NONE_LANG_TABLE[LANG_NUM]; +#define MSG_SORT_NONE LANG_TABLE_SELECT(MSG_SORT_NONE_LANG_TABLE) +extern const char* const MSG_SORT_TIME_LANG_TABLE[LANG_NUM]; +#define MSG_SORT_TIME LANG_TABLE_SELECT(MSG_SORT_TIME_LANG_TABLE) extern const char* const MSG_SPEED_LANG_TABLE[LANG_NUM]; #define MSG_SPEED LANG_TABLE_SELECT(MSG_SPEED_LANG_TABLE) extern const char* const MSG_STACK_ERROR_LANG_TABLE[1]; diff --git a/Firmware/language_cz.h b/Firmware/language_cz.h index 708cdff5..d750dd93 100644 --- a/Firmware/language_cz.h +++ b/Firmware/language_cz.h @@ -352,4 +352,8 @@ #define MSG_RECOVER_PRINT "Detekovan vypadek proudu.Obnovit tisk?" #define MSG_PRESS_TO_UNLOAD "Pro vysunuti filamentu stisknete prosim tlacitko" #define MSG_UNLOAD_SUCCESSFULL "Opakovat vysunuti filamentu?" -#define MSG_FILE_INCOMPLETE "Soubor nekompletni. Pokracovat?" \ No newline at end of file +#define MSG_FILE_INCOMPLETE "Soubor nekompletni. Pokracovat?" +#define MSG_FILE_CNT "Nektere soubory nebudou setrideny. Maximalni pocet souboru pro setrideni je 100." +#define MSG_SORT_TIME "Trideni [Cas]" +#define MSG_SORT_ALPHA "Trideni [Abeceda]" +#define MSG_SORT_NONE "Trideni [Zadne]" \ No newline at end of file diff --git a/Firmware/language_en.h b/Firmware/language_en.h index e011e12c..00ceedb1 100644 --- a/Firmware/language_en.h +++ b/Firmware/language_en.h @@ -370,3 +370,9 @@ #define(length=20, lines=2) MSG_UNLOAD_SUCCESSFULL "Repeat unloading filament?" #define(length=20, lines=2) MSG_FILE_INCOMPLETE "File incomplete. Continue anyway?" +#define(length=20, lines=4) MSG_DEFAULT_SETTINGS_LOADED "Default settings loaded" +#define(length=17, lines=1) MSG_SORT_TIME "Sort: [Time]" +#define(length=17, lines=1) MSG_SORT_ALPHA "Sort: [Alphabet]" +#define(length=17, lines=1) MSG_SORT_NONE "Sort: [None]" +#define(length=20, lines=1) MSG_SORTING "Sorting files" +#define(length=20, lines=4) MSG_FILE_CNT "Some files will not be sorted. Max. No. of files in 1 folder for sorting is 100." \ No newline at end of file diff --git a/Firmware/ultralcd.cpp b/Firmware/ultralcd.cpp index 88026526..d37f42a4 100644 --- a/Firmware/ultralcd.cpp +++ b/Firmware/ultralcd.cpp @@ -121,6 +121,10 @@ extern void crashdet_disable(); uint8_t snmm_extruder = 0; #endif +#ifdef SDCARD_SORT_ALPHA + bool presort_flag = false; +#endif + int lcd_commands_type=LCD_COMMAND_IDLE; int lcd_commands_step=0; bool isPrintPaused = false; @@ -3236,6 +3240,21 @@ void EEPROM_read(int pos, uint8_t* value, uint8_t size) } while (--size); } +#ifdef SDCARD_SORT_ALPHA +static void lcd_sort_type_set() { + uint8_t sdSort; + EEPROM_read(EEPROM_SD_SORT, (uint8_t*)&sdSort, sizeof(sdSort)); + switch (sdSort) { + case SD_SORT_TIME: sdSort = SD_SORT_ALPHA; break; + case SD_SORT_ALPHA: sdSort = SD_SORT_NONE; break; + default: sdSort = SD_SORT_TIME; + } + eeprom_update_byte((unsigned char *)EEPROM_SD_SORT, sdSort); + presort_flag = true; + lcd_goto_menu(lcd_settings_menu, 8); +} +#endif //SDCARD_SORT_ALPHA + static void lcd_silent_mode_set() { SilentModeMenu = !SilentModeMenu; eeprom_update_byte((unsigned char *)EEPROM_SILENT, SilentModeMenu); @@ -3291,6 +3310,16 @@ static void lcd_fsensor_state_set() } +#if !SDSORT_USES_RAM +void lcd_set_degree() { + lcd_set_custom_characters_degree(); +} + +void lcd_set_progress() { + lcd_set_custom_characters_progress(); +} +#endif + void lcd_force_language_selection() { eeprom_update_byte((unsigned char *)EEPROM_LANG, LANG_ID_FORCE_SELECTION); } @@ -3718,6 +3747,18 @@ static void lcd_settings_menu() } else { MENU_ITEM(function, MSG_TOSHIBA_FLASH_AIR_COMPATIBILITY_OFF, lcd_toshiba_flash_air_compatibility_toggle); } + + #ifdef SDCARD_SORT_ALPHA + if (!farm_mode) { + uint8_t sdSort; + EEPROM_read(EEPROM_SD_SORT, (uint8_t*)&sdSort, sizeof(sdSort)); + switch (sdSort) { + case SD_SORT_TIME: MENU_ITEM(function, MSG_SORT_TIME, lcd_sort_type_set); break; + case SD_SORT_ALPHA: MENU_ITEM(function, MSG_SORT_ALPHA, lcd_sort_type_set); break; + default: MENU_ITEM(function, MSG_SORT_NONE, lcd_sort_type_set); + } + } + #endif // SDCARD_SORT_ALPHA if (farm_mode) { @@ -5193,8 +5234,12 @@ void getFileDescription(char *name, char *description) { void lcd_sdcard_menu() { - - int tempScrool = 0; + uint8_t sdSort = eeprom_read_byte((uint8_t*)EEPROM_SD_SORT); + int tempScrool = 0; + if (presort_flag == true) { + presort_flag = false; + card.presort(); + } if (lcdDrawUpdate == 0 && LCD_CLICKED == 0) //delay(100); return; // nothing to do (so don't thrash the SD card) @@ -5216,22 +5261,24 @@ void lcd_sdcard_menu() { if (_menuItemNr == _lineNr) { -#ifndef SDCARD_RATHERRECENTFIRST - card.getfilename(i); -#else - card.getfilename(fileCnt - 1 - i); -#endif - if (card.filenameIsDir) - { - MENU_ITEM(sddirectory, MSG_CARD_MENU, card.filename, card.longFilename); - } else { - - MENU_ITEM(sdfile, MSG_CARD_MENU, card.filename, card.longFilename); - - - - - } + const uint16_t nr = ((sdSort == SD_SORT_NONE) || farm_mode || (sdSort == SD_SORT_TIME)) ? (fileCnt - 1 - i) : i; + /*#ifdef SDCARD_RATHERRECENTFIRST + #ifndef SDCARD_SORT_ALPHA + fileCnt - 1 - + #endif + #endif + i;*/ + #ifdef SDCARD_SORT_ALPHA + if (sdSort == SD_SORT_NONE) card.getfilename(nr); + else card.getfilename_sorted(nr); + #else + card.getfilename(nr); + #endif + + if (card.filenameIsDir) + MENU_ITEM(sddirectory, MSG_CARD_MENU, card.filename, card.longFilename); + else + MENU_ITEM(sdfile, MSG_CARD_MENU, card.filename, card.longFilename); } else { MENU_ITEM_DUMMY(); } @@ -6460,6 +6507,7 @@ void lcd_update(uint8_t lcdDrawUpdateOverride) (*currentMenu)(); menuExiting = false; } + lcd_implementation_clear(); lcd_return_to_status(); lcdDrawUpdate = 2; } diff --git a/Firmware/ultralcd.h b/Firmware/ultralcd.h index 5a4a1bf7..486c38c2 100644 --- a/Firmware/ultralcd.h +++ b/Firmware/ultralcd.h @@ -238,6 +238,9 @@ void extr_unload_all(); void extr_unload_used(); void extr_unload(); static char snmm_stop_print_menu(); +#ifdef SDCARD_SORT_ALPHA + static void lcd_sort_type_set(); +#endif static float count_e(float layer_heigth, float extrusion_width, float extrusion_length); static void lcd_babystep_z(); @@ -272,6 +275,11 @@ void display_loading(); void lcd_service_mode_show_result(); +#if !SDSORT_USES_RAM + void lcd_set_degree(); + void lcd_set_progress(); +#endif + void lcd_wizard(); void lcd_wizard(int state); diff --git a/Firmware/ultralcd_implementation_hitachi_HD44780.h b/Firmware/ultralcd_implementation_hitachi_HD44780.h index 3774bf62..4ff323b8 100644 --- a/Firmware/ultralcd_implementation_hitachi_HD44780.h +++ b/Firmware/ultralcd_implementation_hitachi_HD44780.h @@ -462,6 +462,21 @@ void lcd_set_custom_characters_arrows() lcd.createChar(1, arrdown); } +void lcd_set_custom_characters_progress() + { + byte progress[8] = { + B11111, + B11111, + B11111, + B11111, + B11111, + B11111, + B11111, + B11111, + }; + lcd.createChar(1, progress); + } + void lcd_set_custom_characters_nextpage() { @@ -847,17 +862,18 @@ if (print_sd_status) // If heating in progress, set flag if (heating_status != 0) { custom_message = true; } + if (IS_SD_PRINTING) { + if (strcmp(longFilenameOLD, card.longFilename) != 0) + { + memset(longFilenameOLD, '\0', strlen(longFilenameOLD)); + sprintf_P(longFilenameOLD, PSTR("%s"), card.longFilename); + scrollstuff = 0; + } + } + // If printing from SD, show what we are printing if ((IS_SD_PRINTING) && !custom_message) { - - if(strcmp(longFilenameOLD, card.longFilename) != 0) - { - memset(longFilenameOLD,'\0',strlen(longFilenameOLD)); - sprintf_P(longFilenameOLD, PSTR("%s"), card.longFilename); - scrollstuff = 0; - } - if(strlen(card.longFilename) > LCD_WIDTH) {