From abf76490e4c38b41e609b7099252f9fea068b839 Mon Sep 17 00:00:00 2001 From: Matias Alejandro Yocca Date: Sun, 20 Sep 2026 23:40:08 -0300 Subject: [PATCH] feature: Add granular print time placeholders for days, hours, minutes, and seconds (#13063) --- .../Creality Ender-3 V3 SE 0.2 nozzle.json | 2 +- .../Creality Ender-3 V3 SE 0.4 nozzle.json | 2 +- .../Creality Ender-3 V3 SE 0.6 nozzle.json | 2 +- .../Creality Ender-3 V3 SE 0.8 nozzle.json | 2 +- .../machine/fdm_adventurer5m_common.json | 1 + src/libslic3r/GCode.cpp | 8 ++ src/libslic3r/GCode/GCodeProcessor.cpp | 78 +++++++++++++++++-- src/libslic3r/GCode/GCodeProcessor.hpp | 6 +- src/libslic3r/PrintConfig.cpp | 22 +++++- 9 files changed, 106 insertions(+), 17 deletions(-) diff --git a/resources/profiles/Creality/machine/Creality Ender-3 V3 SE 0.2 nozzle.json b/resources/profiles/Creality/machine/Creality Ender-3 V3 SE 0.2 nozzle.json index dc04436c04..378dca3a2d 100644 --- a/resources/profiles/Creality/machine/Creality Ender-3 V3 SE 0.2 nozzle.json +++ b/resources/profiles/Creality/machine/Creality Ender-3 V3 SE 0.2 nozzle.json @@ -120,7 +120,7 @@ "default_filament_profile": [ "Generic PLA @Creality Ender-3V3-all" ], - "file_start_gcode": ";FLAVOR:Marlin\\n;TIME:{print_time_sec}\\n;Filament used:{used_filament_length}m\\n;Layer height:{layer_height}", + "file_start_gcode": ";FLAVOR:Marlin\\n;TIME:{print_time_total_sec}\\n;Filament used:{used_filament_length}m\\n;Layer height:{layer_height}", "machine_start_gcode": "M220 S100 ;Reset Feedrate \nM221 S100 ;Reset Flowrate \n \nM104 S[nozzle_temperature_initial_layer] ;Set final nozzle temp \nM190 S[bed_temperature_initial_layer_single] ;Set and wait for bed temp to stabilize \nG28 ;Home \nG92 E0 ;Reset Extruder \nG1 Z2.0 F3000 ;Move Z Axis up \nG1 X-2.1 Y20 Z0.28 F5000.0 ;Move to start position \nM109 S[nozzle_temperature_initial_layer] ;Wait for nozzle temp to stabilize \nG1 X-2.1 Y145.0 Z0.28 F1500.0 E15 ;Draw the first line \nG1 X-2.4 Y145.0 Z0.28 F5000.0 ;Move to side a little \nG1 X-2.4 Y20 Z0.28 F1500.0 E30 ;Draw the second line \nG92 E0 ;Reset Extruder \nG1 E-1.0000 F1800 ;Retract a bit \nG1 Z2.0 F3000 ;Move Z Axis up \nG1 E0.0000 F1800", "machine_end_gcode": "G91 ;Relative positionning \nG1 E-2 F2700 ;Retract a bit \nG1 E-2 Z0.2 F2400 ;Retract and raise Z \nG1 X5 Y5 F3000 ;Wipe out \nG1 Z10 ;Raise Z more \nG90 ;Absolute positionning \n \nG1 X0 Y220 ;Present print \nM106 S0 ;Turn-off fan \nM104 S0 ;Turn-off hotend \nM140 S0 ;Turn-off bed \n \nM84 X Y E ;Disable all steppers but Z", "scan_first_layer": "0", diff --git a/resources/profiles/Creality/machine/Creality Ender-3 V3 SE 0.4 nozzle.json b/resources/profiles/Creality/machine/Creality Ender-3 V3 SE 0.4 nozzle.json index 22df39dd1d..97ef822dc7 100644 --- a/resources/profiles/Creality/machine/Creality Ender-3 V3 SE 0.4 nozzle.json +++ b/resources/profiles/Creality/machine/Creality Ender-3 V3 SE 0.4 nozzle.json @@ -120,7 +120,7 @@ "default_filament_profile": [ "Generic PLA @Creality Ender-3V3-all" ], - "file_start_gcode": ";FLAVOR:Marlin\\n;TIME:{print_time_sec}\\n;Filament used:{used_filament_length}m\\n;Layer height:{layer_height}", + "file_start_gcode": ";FLAVOR:Marlin\\n;TIME:{print_time_total_sec}\\n;Filament used:{used_filament_length}m\\n;Layer height:{layer_height}", "machine_start_gcode": "M220 S100 ;Reset Feedrate \nM221 S100 ;Reset Flowrate \n \nM104 S[nozzle_temperature_initial_layer] ;Set final nozzle temp \nM190 S[bed_temperature_initial_layer_single] ;Set and wait for bed temp to stabilize \nG28 ;Home \nG92 E0 ;Reset Extruder \nG1 Z2.0 F3000 ;Move Z Axis up \nG1 X-2.1 Y20 Z0.28 F5000.0 ;Move to start position \nM109 S[nozzle_temperature_initial_layer] ;Wait for nozzle temp to stabilize \nG1 X-2.1 Y145.0 Z0.28 F1500.0 E15 ;Draw the first line \nG1 X-2.4 Y145.0 Z0.28 F5000.0 ;Move to side a little \nG1 X-2.4 Y20 Z0.28 F1500.0 E30 ;Draw the second line \nG92 E0 ;Reset Extruder \nG1 E-1.0000 F1800 ;Retract a bit \nG1 Z2.0 F3000 ;Move Z Axis up \nG1 E0.0000 F1800", "machine_end_gcode": "G91 ;Relative positionning \nG1 E-2 F2700 ;Retract a bit \nG1 E-2 Z0.2 F2400 ;Retract and raise Z \nG1 X5 Y5 F3000 ;Wipe out \nG1 Z10 ;Raise Z more \nG90 ;Absolute positionning \n \nG1 X0 Y220 ;Present print \nM106 S0 ;Turn-off fan \nM104 S0 ;Turn-off hotend \nM140 S0 ;Turn-off bed \n \nM84 X Y E ;Disable all steppers but Z", "scan_first_layer": "0", diff --git a/resources/profiles/Creality/machine/Creality Ender-3 V3 SE 0.6 nozzle.json b/resources/profiles/Creality/machine/Creality Ender-3 V3 SE 0.6 nozzle.json index 881a46fc3a..7023c5a95a 100644 --- a/resources/profiles/Creality/machine/Creality Ender-3 V3 SE 0.6 nozzle.json +++ b/resources/profiles/Creality/machine/Creality Ender-3 V3 SE 0.6 nozzle.json @@ -120,7 +120,7 @@ "default_filament_profile": [ "Generic PLA @Creality Ender-3V3-all" ], - "file_start_gcode": ";FLAVOR:Marlin\\n;TIME:{print_time_sec}\\n;Filament used:{used_filament_length}m\\n;Layer height:{layer_height}", + "file_start_gcode": ";FLAVOR:Marlin\\n;TIME:{print_time_total_sec}\\n;Filament used:{used_filament_length}m\\n;Layer height:{layer_height}", "machine_start_gcode": "M220 S100 ;Reset Feedrate \nM221 S100 ;Reset Flowrate \n \nM104 S[nozzle_temperature_initial_layer] ;Set final nozzle temp \nM190 S[bed_temperature_initial_layer_single] ;Set and wait for bed temp to stabilize \nG28 ;Home \nG92 E0 ;Reset Extruder \nG1 Z2.0 F3000 ;Move Z Axis up \nG1 X-2.1 Y20 Z0.28 F5000.0 ;Move to start position \nM109 S[nozzle_temperature_initial_layer] ;Wait for nozzle temp to stabilize \nG1 X-2.1 Y145.0 Z0.28 F1500.0 E15 ;Draw the first line \nG1 X-2.4 Y145.0 Z0.28 F5000.0 ;Move to side a little \nG1 X-2.4 Y20 Z0.28 F1500.0 E30 ;Draw the second line \nG92 E0 ;Reset Extruder \nG1 E-1.0000 F1800 ;Retract a bit \nG1 Z2.0 F3000 ;Move Z Axis up \nG1 E0.0000 F1800", "machine_end_gcode": "G91 ;Relative positionning \nG1 E-2 F2700 ;Retract a bit \nG1 E-2 Z0.2 F2400 ;Retract and raise Z \nG1 X5 Y5 F3000 ;Wipe out \nG1 Z10 ;Raise Z more \nG90 ;Absolute positionning \n \nG1 X0 Y220 ;Present print \nM106 S0 ;Turn-off fan \nM104 S0 ;Turn-off hotend \nM140 S0 ;Turn-off bed \n \nM84 X Y E ;Disable all steppers but Z", "scan_first_layer": "0", diff --git a/resources/profiles/Creality/machine/Creality Ender-3 V3 SE 0.8 nozzle.json b/resources/profiles/Creality/machine/Creality Ender-3 V3 SE 0.8 nozzle.json index 44e9b97afb..acec590a1f 100644 --- a/resources/profiles/Creality/machine/Creality Ender-3 V3 SE 0.8 nozzle.json +++ b/resources/profiles/Creality/machine/Creality Ender-3 V3 SE 0.8 nozzle.json @@ -120,7 +120,7 @@ "default_filament_profile": [ "Generic PLA @Creality Ender-3V3-all" ], - "file_start_gcode": ";FLAVOR:Marlin\\n;TIME:{print_time_sec}\\n;Filament used:{used_filament_length}m\\n;Layer height:{layer_height}", + "file_start_gcode": ";FLAVOR:Marlin\\n;TIME:{print_time_total_sec}\\n;Filament used:{used_filament_length}m\\n;Layer height:{layer_height}", "machine_start_gcode": "M220 S100 ;Reset Feedrate \nM221 S100 ;Reset Flowrate \n \nM104 S[nozzle_temperature_initial_layer] ;Set final nozzle temp \nM190 S[bed_temperature_initial_layer_single] ;Set and wait for bed temp to stabilize \nG28 ;Home \nG92 E0 ;Reset Extruder \nG1 Z2.0 F3000 ;Move Z Axis up \nG1 X-2.1 Y20 Z0.28 F5000.0 ;Move to start position \nM109 S[nozzle_temperature_initial_layer] ;Wait for nozzle temp to stabilize \nG1 X-2.1 Y145.0 Z0.28 F1500.0 E15 ;Draw the first line \nG1 X-2.4 Y145.0 Z0.28 F5000.0 ;Move to side a little \nG1 X-2.4 Y20 Z0.28 F1500.0 E30 ;Draw the second line \nG92 E0 ;Reset Extruder \nG1 E-1.0000 F1800 ;Retract a bit \nG1 Z2.0 F3000 ;Move Z Axis up \nG1 E0.0000 F1800", "machine_end_gcode": "G91 ;Relative positionning \nG1 E-2 F2700 ;Retract a bit \nG1 E-2 Z0.2 F2400 ;Retract and raise Z \nG1 X5 Y5 F3000 ;Wipe out \nG1 Z10 ;Raise Z more \nG90 ;Absolute positionning \n \nG1 X0 Y220 ;Present print \nM106 S0 ;Turn-off fan \nM104 S0 ;Turn-off hotend \nM140 S0 ;Turn-off bed \n \nM84 X Y E ;Disable all steppers but Z", "scan_first_layer": "0", diff --git a/resources/profiles/Flashforge/machine/fdm_adventurer5m_common.json b/resources/profiles/Flashforge/machine/fdm_adventurer5m_common.json index 2808e866e3..1ab9ef81e8 100644 --- a/resources/profiles/Flashforge/machine/fdm_adventurer5m_common.json +++ b/resources/profiles/Flashforge/machine/fdm_adventurer5m_common.json @@ -96,6 +96,7 @@ "default_filament_profile": [ "Generic PLA @Flashforge" ], + "file_start_gcode": "; estimated printing time (normal mode) = {print_time_day}d {print_time_hour}h {print_time_minute}m {print_time_sec}s", "machine_start_gcode": "M190 S[bed_temperature_initial_layer_single]\nM109 S[nozzle_temperature_initial_layer]\nG90\nM83\nG1 Z5 F6000\nG1 E-0.2 F800\nG1 X110 Y-110 F6000\nG1 E2 F800\nG1 Y-110 X55 Z0.25 F4800\nG1 X-55 E8 F2400\nG1 Y-109.6 F2400\nG1 X55 E5 F2400\nG1 Y-110 X55 Z0.45 F4800\nG1 X-55 E8 F2400\nG1 Y-109.6 F2400\nG1 X55 E5 F2400\nG92 E0", "machine_end_gcode": "G1 E-3 F3600\nG0 X50 Y50 F30000\nM104 S0 ; turn off temperature", "before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]", diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index e5d3e58554..3e8a6478c6 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -3032,6 +3032,10 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato // file_start_gcode runs before the parser copy that normally restores these, so set them here. PlaceholderParser::update_timestamp(top_config); PlaceholderParser::update_user_name(top_config); + top_config.set_key_value("print_time_total_sec", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Total_Sec_Placeholder))); + top_config.set_key_value("print_time_day", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Day_Placeholder))); + top_config.set_key_value("print_time_hour", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Hour_Placeholder))); + top_config.set_key_value("print_time_minute", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Minute_Placeholder))); top_config.set_key_value("print_time_sec", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Sec_Placeholder))); top_config.set_key_value("used_filament_length", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Used_Filament_Length_Placeholder))); std::string top_gcode = print.placeholder_parser().process(top_gcode_template, 0, &top_config); @@ -3661,6 +3665,10 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato this->placeholder_parser().set("hold_chamber_temp_for_flat_print", new ConfigOptionBool(hold_chamber_temp_for_flat_print)); } + this->placeholder_parser().set("print_time_total_sec", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Total_Sec_Placeholder))); + this->placeholder_parser().set("print_time_day", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Day_Placeholder))); + this->placeholder_parser().set("print_time_hour", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Hour_Placeholder))); + this->placeholder_parser().set("print_time_minute", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Minute_Placeholder))); this->placeholder_parser().set("print_time_sec", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Sec_Placeholder))); this->placeholder_parser().set("used_filament_length", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Used_Filament_Length_Placeholder))); diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 7932bc11a6..e03be228d0 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -75,6 +75,10 @@ const std::vector GCodeProcessor::Reserved_Tags = { " WIPE_TOWER_START", " WIPE_TOWER_END", " PA_CHANGE:", + "@PRINT_TIME_TOTAL_SEC@", + "@PRINT_TIME_DAY@", + "@PRINT_TIME_HOUR@", + "@PRINT_TIME_MINUTE@", "@PRINT_TIME_SEC@", "@USED_FILAMENT_LENGTH@" }; @@ -98,6 +102,10 @@ const std::vector GCodeProcessor::Reserved_Tags_compatible = { " WIPE_TOWER_START", " WIPE_TOWER_END", " PA_CHANGE:", + "@PRINT_TIME_TOTAL_SEC@", + "@PRINT_TIME_DAY@", + "@PRINT_TIME_HOUR@", + "@PRINT_TIME_MINUTE@", "@PRINT_TIME_SEC@", "@USED_FILAMENT_LENGTH@" }; @@ -1215,22 +1223,76 @@ void GCodeProcessor::run_post_process() return ret; }; - // Process inline placeholders (print_time_sec and used_filament_length) + // Process inline placeholders (print_time_total_sec, print_time_day, print_time_hour, print_time_minute, print_time_sec and used_filament_length) auto process_inline_placeholders = [&](std::string& gcode_line) { bool processed = false; - const std::string& print_time_placeholder = reserved_tag(ETags::Print_Time_Sec_Placeholder); + const std::string& print_time_total_placeholder = reserved_tag(ETags::Print_Time_Total_Sec_Placeholder); + const std::string& print_time_day_placeholder = reserved_tag(ETags::Print_Time_Day_Placeholder); + const std::string& print_time_hour_placeholder = reserved_tag(ETags::Print_Time_Hour_Placeholder); + const std::string& print_time_minute_placeholder = reserved_tag(ETags::Print_Time_Minute_Placeholder); + const std::string& print_time_sec_placeholder = reserved_tag(ETags::Print_Time_Sec_Placeholder); const std::string& used_filament_placeholder = reserved_tag(ETags::Used_Filament_Length_Placeholder); - // Replace print_time_sec - size_t pos = gcode_line.find(print_time_placeholder); + double print_time_total_sec = m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].time; + if (print_time_total_sec < 0.0) + print_time_total_sec = 0.0; + + int total_seconds = static_cast(print_time_total_sec); + int print_time_day = total_seconds / 86400; + int day_remainder_seconds = total_seconds % 86400; + int print_time_hour = day_remainder_seconds / 3600; + int print_time_minute = (day_remainder_seconds % 3600) / 60; + int print_time_sec = day_remainder_seconds % 60; + + // Replace print_time_total_sec + size_t pos = gcode_line.find(print_time_total_placeholder); while (pos != std::string::npos) { - double print_time_sec = m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].time; char buf[64]; - sprintf(buf, "%.2f", print_time_sec); - gcode_line.replace(pos, print_time_placeholder.length(), buf); + sprintf(buf, "%.2f", print_time_total_sec); + gcode_line.replace(pos, print_time_total_placeholder.length(), buf); processed = true; - pos = gcode_line.find(print_time_placeholder, pos + strlen(buf)); + pos = gcode_line.find(print_time_total_placeholder, pos + strlen(buf)); + } + + // Replace print_time_day + pos = gcode_line.find(print_time_day_placeholder); + while (pos != std::string::npos) { + char buf[64]; + sprintf(buf, "%d", print_time_day); + gcode_line.replace(pos, print_time_day_placeholder.length(), buf); + processed = true; + pos = gcode_line.find(print_time_day_placeholder, pos + strlen(buf)); + } + + // Replace print_time_hour + pos = gcode_line.find(print_time_hour_placeholder); + while (pos != std::string::npos) { + char buf[64]; + sprintf(buf, "%d", print_time_hour); + gcode_line.replace(pos, print_time_hour_placeholder.length(), buf); + processed = true; + pos = gcode_line.find(print_time_hour_placeholder, pos + strlen(buf)); + } + + // Replace print_time_minute + pos = gcode_line.find(print_time_minute_placeholder); + while (pos != std::string::npos) { + char buf[64]; + sprintf(buf, "%d", print_time_minute); + gcode_line.replace(pos, print_time_minute_placeholder.length(), buf); + processed = true; + pos = gcode_line.find(print_time_minute_placeholder, pos + strlen(buf)); + } + + // Replace print_time_sec + pos = gcode_line.find(print_time_sec_placeholder); + while (pos != std::string::npos) { + char buf[64]; + sprintf(buf, "%d", print_time_sec); + gcode_line.replace(pos, print_time_sec_placeholder.length(), buf); + processed = true; + pos = gcode_line.find(print_time_sec_placeholder, pos + strlen(buf)); } // Replace used_filament_length diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index a677912add..192a80a43d 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -512,6 +512,10 @@ class Print; Wipe_Tower_Start, Wipe_Tower_End, PA_Change, + Print_Time_Total_Sec_Placeholder, + Print_Time_Day_Placeholder, + Print_Time_Hour_Placeholder, + Print_Time_Minute_Placeholder, Print_Time_Sec_Placeholder, Used_Filament_Length_Placeholder, }; @@ -1543,5 +1547,3 @@ class Print; } /* namespace Slic3r */ #endif /* slic3r_GCodeProcessor_hpp_ */ - - diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 75df1a42a6..92e7067ae5 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -6610,7 +6610,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("G-code written at the very top of the output file, before any other content. " "Useful for adding metadata that printer firmware reads from the first lines of the file " "(e.g. estimated print time, filament usage). " - "Supports placeholders like {print_time_sec} and {used_filament_length}."); + "Supports placeholders like {print_time_total_sec}, {print_time_day}, {print_time_hour}, {print_time_minute}, {print_time_sec} and {used_filament_length}."); def->multiline = true; def->full_width = true; def->height = 8; @@ -12559,10 +12559,26 @@ PrintStatisticsConfigDef::PrintStatisticsConfigDef() def->label = L("Used filament"); def->tooltip = L("Total length of filament used in the print."); - def = this->add("print_time_sec", coString); - def->label = L("Print time (seconds)"); + def = this->add("print_time_total_sec", coString); + def->label = L("Print time (total seconds)"); def->tooltip = L("Total estimated print time in seconds. Replaced with actual value during post-processing."); + def = this->add("print_time_day", coString); + def->label = L("Print time (days component)"); + def->tooltip = L("Estimated print time day component (normal mode). Replaced with actual value during post-processing."); + + def = this->add("print_time_hour", coString); + def->label = L("Print time (hours component)"); + def->tooltip = L("Estimated print time hour component (normal mode). Replaced with actual value during post-processing."); + + def = this->add("print_time_minute", coString); + def->label = L("Print time (minutes component)"); + def->tooltip = L("Estimated print time minute component (normal mode). Replaced with actual value during post-processing."); + + def = this->add("print_time_sec", coString); + def->label = L("Print time (seconds component)"); + def->tooltip = L("Estimated print time second component (normal mode). Replaced with actual value during post-processing."); + def = this->add("used_filament_length", coString); def->label = L("Filament length (meters)"); def->tooltip = L("Total filament length used in meters. Replaced with actual value during post-processing.");