diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index dec0a7a19..c02d24a4e 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1281,13 +1281,16 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu print.throw_if_canceled(); // adds tags for time estimators -#if !ENABLE_GCODE_VIEWER +#if ENABLE_GCODE_VIEWER + if (print.config().remaining_times.value) + _writeln(file, GCodeProcessor::First_M73_Output_Placeholder_Tag); +#else if (print.config().remaining_times.value) { _writeln(file, GCodeTimeEstimator::Normal_First_M73_Output_Placeholder_Tag); if (m_silent_time_estimator_enabled) _writeln(file, GCodeTimeEstimator::Silent_First_M73_Output_Placeholder_Tag); } -#endif // !ENABLE_GCODE_VIEWER +#endif // ENABLE_GCODE_VIEWER // Prepare the helper object for replacing placeholders in custom G-code and output filename. m_placeholder_parser = print.placeholder_parser(); @@ -1582,14 +1585,16 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu _write(file, m_writer.postamble()); // adds tags for time estimators -#if !ENABLE_GCODE_VIEWER +#if ENABLE_GCODE_VIEWER if (print.config().remaining_times.value) - { + _writeln(file, GCodeProcessor::Last_M73_Output_Placeholder_Tag); +#else + if (print.config().remaining_times.value) { _writeln(file, GCodeTimeEstimator::Normal_Last_M73_Output_Placeholder_Tag); if (m_silent_time_estimator_enabled) _writeln(file, GCodeTimeEstimator::Silent_Last_M73_Output_Placeholder_Tag); } -#endif // !ENABLE_GCODE_VIEWER +#endif // ENABLE_GCODE_VIEWER print.throw_if_canceled(); @@ -2087,7 +2092,7 @@ void GCode::process_layer( #if ENABLE_GCODE_VIEWER // export layer z char buf[64]; - sprintf(buf, ";Z%g\n", print_z); + sprintf(buf, ";Z:%g\n", print_z); gcode += buf; // export layer height float height = first_layer ? static_cast(print_z) : static_cast(print_z) - m_last_layer_z; diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 7612af0e9..87bcde9d0 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -4,6 +4,7 @@ #include "GCodeProcessor.hpp" #include +#include #include #include @@ -28,6 +29,9 @@ const std::string GCodeProcessor::Color_Change_Tag = "Color change"; const std::string GCodeProcessor::Pause_Print_Tag = "Pause print"; const std::string GCodeProcessor::Custom_Code_Tag = "Custom gcode"; +const std::string GCodeProcessor::First_M73_Output_Placeholder_Tag = "; _GP_FIRST_M73_OUTPUT_PLACEHOLDER"; +const std::string GCodeProcessor::Last_M73_Output_Placeholder_Tag = "; _GP_LAST_M73_OUTPUT_PLACEHOLDER"; + static bool is_valid_extrusion_role(int value) { return (static_cast(erNone) <= value) && (value <= static_cast(erMixed)); @@ -161,6 +165,7 @@ void GCodeProcessor::TimeMachine::reset() prev.reset(); gcode_time.reset(); blocks = std::vector(); + g1_times_cache = std::vector(); std::fill(moves_time.begin(), moves_time.end(), 0.0f); std::fill(roles_time.begin(), roles_time.end(), 0.0f); } @@ -264,7 +269,6 @@ void GCodeProcessor::TimeMachine::calculate_time(size_t keep_last_n_blocks) recalculate_trapezoids(blocks); size_t n_blocks_process = blocks.size() - keep_last_n_blocks; -// m_g1_times.reserve(m_g1_times.size() + n_blocks_process); for (size_t i = 0; i < n_blocks_process; ++i) { const TimeBlock& block = blocks[i]; float block_time = block.time(); @@ -272,9 +276,7 @@ void GCodeProcessor::TimeMachine::calculate_time(size_t keep_last_n_blocks) gcode_time.cache += block_time; moves_time[static_cast(block.move_type)] += block_time; roles_time[static_cast(block.role)] += block_time; - -// if (block.g1_line_id >= 0) -// m_g1_times.emplace_back(block.g1_line_id, time); + g1_times_cache.push_back(time); } if (keep_last_n_blocks) @@ -286,6 +288,7 @@ void GCodeProcessor::TimeMachine::calculate_time(size_t keep_last_n_blocks) void GCodeProcessor::TimeProcessor::reset() { extruder_unloaded = true; + export_remaining_time_enabled = false; machine_limits = MachineEnvelopeConfig(); filament_load_times = std::vector(); filament_unload_times = std::vector(); @@ -295,6 +298,136 @@ void GCodeProcessor::TimeProcessor::reset() machines[static_cast(ETimeMode::Normal)].enabled = true; } +void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) +{ + boost::nowide::ifstream in(filename); + if (!in.good()) + throw std::runtime_error(std::string("Time estimator post process export failed.\nCannot open file for reading.\n")); + + // temporary file to contain modified gcode + std::string out_path = filename + ".postprocess"; + FILE* out = boost::nowide::fopen(out_path.c_str(), "wb"); + if (out == nullptr) + throw std::runtime_error(std::string("Time estimator post process export failed.\nCannot open file for writing.\n")); + + auto time_in_minutes = [](float time_in_seconds) { + return int(::roundf(time_in_seconds / 60.0f)); + }; + + auto format_line_M73 = [](const std::string& mask, int percent, int time) { + char line_M73[64]; + sprintf(line_M73, mask.c_str(), + std::to_string(percent).c_str(), + std::to_string(time).c_str()); + return std::string(line_M73); + }; + + GCodeReader parser; + std::string gcode_line; + size_t g1_lines_counter = 0; + // keeps track of last exported pair + std::array, static_cast(ETimeMode::Count)> last_exported; + for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { + last_exported[i] = { 0, time_in_minutes(machines[i].time) }; + } + + // buffer line to export only when greater than 64K to reduce writing calls + std::string export_line; + + // replace placeholder lines with the proper final value + auto process_placeholders = [&](const std::string& gcode_line) { + std::string ret; + // remove trailing '\n' + std::string line = gcode_line.substr(0, gcode_line.length() - 1); + if (line == First_M73_Output_Placeholder_Tag || line == Last_M73_Output_Placeholder_Tag) { + for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { + const TimeMachine& machine = machines[i]; + if (machine.enabled) { + ret += format_line_M73(machine.line_m73_mask.c_str(), + (line == First_M73_Output_Placeholder_Tag) ? 0 : 100, + (line == First_M73_Output_Placeholder_Tag) ? time_in_minutes(machines[i].time) : 0); + } + } + } + return std::make_pair(!ret.empty(), ret.empty() ? gcode_line : ret); + }; + + // add lines M73 to exported gcode + auto process_line_G1 = [&]() { + for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { + const TimeMachine& machine = machines[i]; + if (machine.enabled && g1_lines_counter < machine.g1_times_cache.size()) { + float elapsed_time = machine.g1_times_cache[g1_lines_counter]; + std::pair to_export = { int(::roundf(100.0f * elapsed_time / machine.time)), + time_in_minutes(machine.time - elapsed_time) }; + if (last_exported[i] != to_export) { + export_line += format_line_M73(machine.line_m73_mask.c_str(), + to_export.first, to_export.second); + last_exported[i] = to_export; + } + } + } + }; + + // helper function to write to disk + auto write_string = [&](const std::string& str) { + fwrite((const void*)export_line.c_str(), 1, export_line.length(), out); + if (ferror(out)) { + in.close(); + fclose(out); + boost::nowide::remove(out_path.c_str()); + throw std::runtime_error(std::string("Time estimator post process export failed.\nIs the disk full?\n")); + } + export_line.clear(); + }; + + while (std::getline(in, gcode_line)) { + if (!in.good()) { + fclose(out); + throw std::runtime_error(std::string("Time estimator post process export failed.\nError while reading from file.\n")); + } + + gcode_line += "\n"; + auto [processed, result] = process_placeholders(gcode_line); + gcode_line = result; + if (!processed) { + parser.parse_line(gcode_line, + [&](GCodeReader& reader, const GCodeReader::GCodeLine& line) { + if (line.cmd_is("G1")) { + process_line_G1(); + ++g1_lines_counter; + } + }); + } + + export_line += gcode_line; + if (export_line.length() > 65535) + write_string(export_line); + } + + for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { + const TimeMachine& machine = machines[i]; + ETimeMode mode = static_cast(i); + if (machine.enabled) { + char line[128]; + sprintf(line, "; estimated printing time (%s mode) = %s\n", + (mode == ETimeMode::Normal) ? "normal" : "silent", + get_time_dhms(machine.time).c_str()); + export_line += line; + } + } + + if (!export_line.empty()) + write_string(export_line); + + fclose(out); + in.close(); + + if (rename_file(out_path, filename)) + throw std::runtime_error(std::string("Failed to rename the output G-code file from ") + out_path + " to " + filename + '\n' + + "Is " + out_path + " locked?" + '\n'); +} + const std::vector> GCodeProcessor::Producers = { { EProducer::PrusaSlicer, "PrusaSlicer" }, { EProducer::Cura, "Cura_SteamEngine" }, @@ -305,6 +438,13 @@ const std::vector> GCodeProces unsigned int GCodeProcessor::s_result_id = 0; +GCodeProcessor::GCodeProcessor() +{ + reset(); + m_time_processor.machines[static_cast(ETimeMode::Normal)].line_m73_mask = "M73 P%s R%s\n"; + m_time_processor.machines[static_cast(ETimeMode::Stealth)].line_m73_mask = "M73 Q%s S%s\n"; +} + void GCodeProcessor::apply_config(const PrintConfig& config) { m_parser.apply_config(config); @@ -346,6 +486,8 @@ void GCodeProcessor::apply_config(const PrintConfig& config) float max_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_extruding, i); m_time_processor.machines[i].acceleration = (max_acceleration > 0.0f) ? max_acceleration : DEFAULT_ACCELERATION; } + + m_time_processor.export_remaining_time_enabled = config.remaining_times.value; } void GCodeProcessor::apply_config(const DynamicPrintConfig& config) @@ -572,6 +714,10 @@ void GCodeProcessor::process_file(const std::string& filename) update_estimated_times_stats(); + // post-process to add M73 lines into the gcode + if (m_time_processor.export_remaining_time_enabled) + m_time_processor.post_process(filename); + #if ENABLE_GCODE_VIEWER_STATISTICS m_result.time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); #endif // ENABLE_GCODE_VIEWER_STATISTICS @@ -1525,13 +1671,13 @@ void GCodeProcessor::process_M201(const GCodeReader::GCodeLine& line) if (line.has_x()) set_option_value(m_time_processor.machine_limits.machine_max_acceleration_x, i, line.x() * factor); - if (line.has_y() && i < m_time_processor.machine_limits.machine_max_acceleration_y.values.size()) + if (line.has_y()) set_option_value(m_time_processor.machine_limits.machine_max_acceleration_y, i, line.y() * factor); - if (line.has_z() && i < m_time_processor.machine_limits.machine_max_acceleration_z.values.size()) + if (line.has_z()) set_option_value(m_time_processor.machine_limits.machine_max_acceleration_z, i, line.z() * factor); - if (line.has_e() && i < m_time_processor.machine_limits.machine_max_acceleration_e.values.size()) + if (line.has_e()) set_option_value(m_time_processor.machine_limits.machine_max_acceleration_e, i, line.e() * factor); } } diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index e9c71038f..840b5373b 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -72,6 +72,8 @@ namespace Slic3r { static const std::string Color_Change_Tag; static const std::string Pause_Print_Tag; static const std::string Custom_Code_Tag; + static const std::string First_M73_Output_Placeholder_Tag; + static const std::string Last_M73_Output_Placeholder_Tag; private: using AxisCoords = std::array; @@ -182,10 +184,12 @@ namespace Slic3r { float acceleration; // mm/s^2 float extrude_factor_override_percentage; float time; // s + std::string line_m73_mask; State curr; State prev; CustomGCodeTime gcode_time; std::vector blocks; + std::vector g1_times_cache; std::array(EMoveType::Count)> moves_time; std::array(ExtrusionRole::erCount)> roles_time; @@ -212,6 +216,7 @@ namespace Slic3r { // This is currently only really used by the MK3 MMU2: // extruder_unloaded = true means no filament is loaded yet, all the filaments are parked in the MK3 MMU2 unit. bool extruder_unloaded; + bool export_remaining_time_enabled; MachineEnvelopeConfig machine_limits; // Additional load / unload times for a filament exchange sequence. std::vector filament_load_times; @@ -219,6 +224,9 @@ namespace Slic3r { std::array(ETimeMode::Count)> machines; void reset(); + + // post process the file with the given filename to add remaining time lines M73 + void post_process(const std::string& filename); }; public: @@ -312,7 +320,7 @@ namespace Slic3r { static unsigned int s_result_id; public: - GCodeProcessor() { reset(); } + GCodeProcessor(); void apply_config(const PrintConfig& config); void apply_config(const DynamicPrintConfig& config);