mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-30 20:21:12 -06:00 
			
		
		
		
	GCodeProcessor -> Export remaining time (lines M73) to gcode
This commit is contained in:
		
							parent
							
								
									64001c0fe5
								
							
						
					
					
						commit
						6ed2cb661d
					
				
					 3 changed files with 173 additions and 14 deletions
				
			
		|  | @ -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<float>(print_z) : static_cast<float>(print_z) - m_last_layer_z; | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ | |||
| #include "GCodeProcessor.hpp" | ||||
| 
 | ||||
| #include <boost/log/trivial.hpp> | ||||
| #include <boost/nowide/fstream.hpp> | ||||
| 
 | ||||
| #include <float.h> | ||||
| #include <assert.h> | ||||
|  | @ -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<int>(erNone) <= value) && (value <= static_cast<int>(erMixed)); | ||||
|  | @ -161,6 +165,7 @@ void GCodeProcessor::TimeMachine::reset() | |||
|     prev.reset(); | ||||
|     gcode_time.reset(); | ||||
|     blocks = std::vector<TimeBlock>(); | ||||
|     g1_times_cache = std::vector<float>(); | ||||
|     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<size_t>(block.move_type)] += block_time; | ||||
|         roles_time[static_cast<size_t>(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<float>(); | ||||
|     filament_unload_times = std::vector<float>(); | ||||
|  | @ -295,6 +298,136 @@ void GCodeProcessor::TimeProcessor::reset() | |||
|     machines[static_cast<size_t>(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 <percent, remaining time>
 | ||||
|     std::array<std::pair<int, int>, static_cast<size_t>(ETimeMode::Count)> last_exported; | ||||
|     for (size_t i = 0; i < static_cast<size_t>(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<size_t>(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<size_t>(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<int, int> 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<size_t>(ETimeMode::Count); ++i) { | ||||
|         const TimeMachine& machine = machines[i]; | ||||
|         ETimeMode mode = static_cast<ETimeMode>(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<std::pair<GCodeProcessor::EProducer, std::string>> GCodeProcessor::Producers = { | ||||
|     { EProducer::PrusaSlicer, "PrusaSlicer" }, | ||||
|     { EProducer::Cura,        "Cura_SteamEngine" }, | ||||
|  | @ -305,6 +438,13 @@ const std::vector<std::pair<GCodeProcessor::EProducer, std::string>> GCodeProces | |||
| 
 | ||||
| unsigned int GCodeProcessor::s_result_id = 0; | ||||
| 
 | ||||
| GCodeProcessor::GCodeProcessor() | ||||
| { | ||||
|     reset(); | ||||
|     m_time_processor.machines[static_cast<size_t>(ETimeMode::Normal)].line_m73_mask = "M73 P%s R%s\n"; | ||||
|     m_time_processor.machines[static_cast<size_t>(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::milliseconds>(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); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -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<float, 4>; | ||||
|  | @ -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<TimeBlock> blocks; | ||||
|             std::vector<float> g1_times_cache; | ||||
|             std::array<float, static_cast<size_t>(EMoveType::Count)> moves_time; | ||||
|             std::array<float, static_cast<size_t>(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<float> filament_load_times; | ||||
|  | @ -219,6 +224,9 @@ namespace Slic3r { | |||
|             std::array<TimeMachine, static_cast<size_t>(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); | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 enricoturri1966
						enricoturri1966