diff options
author | bubnikv <bubnikv@gmail.com> | 2018-09-19 12:02:24 +0300 |
---|---|---|
committer | bubnikv <bubnikv@gmail.com> | 2018-09-19 12:02:24 +0300 |
commit | 0558b53493a77bae44831cf87bb0f59359828ef5 (patch) | |
tree | c3e8dbdf7d91a051c12d9ebbf7606d41047fea96 /src/libslic3r/GCodeTimeEstimator.cpp | |
parent | 3ddaccb6410478ad02d8c0e02d6d8e6eb1785b9f (diff) |
WIP: Moved sources int src/, separated most of the source code from Perl.mass_rename
The XS was left only for the unit / integration tests, and it links
libslic3r only. No wxWidgets are allowed to be used from Perl starting
from now.
Diffstat (limited to 'src/libslic3r/GCodeTimeEstimator.cpp')
-rw-r--r-- | src/libslic3r/GCodeTimeEstimator.cpp | 1505 |
1 files changed, 1505 insertions, 0 deletions
diff --git a/src/libslic3r/GCodeTimeEstimator.cpp b/src/libslic3r/GCodeTimeEstimator.cpp new file mode 100644 index 000000000..f97265ee3 --- /dev/null +++ b/src/libslic3r/GCodeTimeEstimator.cpp @@ -0,0 +1,1505 @@ +#include "GCodeTimeEstimator.hpp" +#include "Utils.hpp" +#include <boost/bind.hpp> +#include <cmath> + +#include <Shiny/Shiny.h> + +#include <boost/nowide/fstream.hpp> +#include <boost/nowide/cstdio.hpp> +#include <boost/algorithm/string/predicate.hpp> + +static const float MMMIN_TO_MMSEC = 1.0f / 60.0f; +static const float MILLISEC_TO_SEC = 0.001f; +static const float INCHES_TO_MM = 25.4f; + +static const float DEFAULT_FEEDRATE = 1500.0f; // from Prusa Firmware (Marlin_main.cpp) +static const float DEFAULT_ACCELERATION = 1500.0f; // Prusa Firmware 1_75mm_MK2 +static const float DEFAULT_RETRACT_ACCELERATION = 1500.0f; // Prusa Firmware 1_75mm_MK2 +static const float DEFAULT_AXIS_MAX_FEEDRATE[] = { 500.0f, 500.0f, 12.0f, 120.0f }; // Prusa Firmware 1_75mm_MK2 +static const float DEFAULT_AXIS_MAX_ACCELERATION[] = { 9000.0f, 9000.0f, 500.0f, 10000.0f }; // Prusa Firmware 1_75mm_MK2 +static const float DEFAULT_AXIS_MAX_JERK[] = { 10.0f, 10.0f, 0.4f, 2.5f }; // from Prusa Firmware (Configuration.h) +static const float DEFAULT_MINIMUM_FEEDRATE = 0.0f; // from Prusa Firmware (Configuration_adv.h) +static const float DEFAULT_MINIMUM_TRAVEL_FEEDRATE = 0.0f; // from Prusa Firmware (Configuration_adv.h) +static const float DEFAULT_EXTRUDE_FACTOR_OVERRIDE_PERCENTAGE = 1.0f; // 100 percent + +static const float PREVIOUS_FEEDRATE_THRESHOLD = 0.0001f; + +#if ENABLE_MOVE_STATS +static const std::string MOVE_TYPE_STR[Slic3r::GCodeTimeEstimator::Block::Num_Types] = +{ + "Noop", + "Retract", + "Unretract", + "Tool_change", + "Move", + "Extrude" +}; +#endif // ENABLE_MOVE_STATS + +namespace Slic3r { + + void GCodeTimeEstimator::Feedrates::reset() + { + feedrate = 0.0f; + safe_feedrate = 0.0f; + ::memset(axis_feedrate, 0, Num_Axis * sizeof(float)); + ::memset(abs_axis_feedrate, 0, Num_Axis * sizeof(float)); + } + + float GCodeTimeEstimator::Block::Trapezoid::acceleration_time(float acceleration) const + { + return acceleration_time_from_distance(feedrate.entry, accelerate_until, acceleration); + } + + float GCodeTimeEstimator::Block::Trapezoid::cruise_time() const + { + return (feedrate.cruise != 0.0f) ? cruise_distance() / feedrate.cruise : 0.0f; + } + + float GCodeTimeEstimator::Block::Trapezoid::deceleration_time(float acceleration) const + { + return acceleration_time_from_distance(feedrate.cruise, (distance - decelerate_after), -acceleration); + } + + float GCodeTimeEstimator::Block::Trapezoid::cruise_distance() const + { + return decelerate_after - accelerate_until; + } + + float GCodeTimeEstimator::Block::Trapezoid::acceleration_time_from_distance(float initial_feedrate, float distance, float acceleration) + { + return (acceleration != 0.0f) ? (speed_from_distance(initial_feedrate, distance, acceleration) - initial_feedrate) / acceleration : 0.0f; + } + + float GCodeTimeEstimator::Block::Trapezoid::speed_from_distance(float initial_feedrate, float distance, float acceleration) + { + // to avoid invalid negative numbers due to numerical imprecision + float value = std::max(0.0f, sqr(initial_feedrate) + 2.0f * acceleration * distance); + return ::sqrt(value); + } + + GCodeTimeEstimator::Block::Block() + { + } + + float GCodeTimeEstimator::Block::move_length() const + { + float length = ::sqrt(sqr(delta_pos[X]) + sqr(delta_pos[Y]) + sqr(delta_pos[Z])); + return (length > 0.0f) ? length : std::abs(delta_pos[E]); + } + + float GCodeTimeEstimator::Block::is_extruder_only_move() const + { + return (delta_pos[X] == 0.0f) && (delta_pos[Y] == 0.0f) && (delta_pos[Z] == 0.0f) && (delta_pos[E] != 0.0f); + } + + float GCodeTimeEstimator::Block::is_travel_move() const + { + return delta_pos[E] == 0.0f; + } + + float GCodeTimeEstimator::Block::acceleration_time() const + { + return trapezoid.acceleration_time(acceleration); + } + + float GCodeTimeEstimator::Block::cruise_time() const + { + return trapezoid.cruise_time(); + } + + float GCodeTimeEstimator::Block::deceleration_time() const + { + return trapezoid.deceleration_time(acceleration); + } + + float GCodeTimeEstimator::Block::cruise_distance() const + { + return trapezoid.cruise_distance(); + } + + void GCodeTimeEstimator::Block::calculate_trapezoid() + { + float distance = move_length(); + + trapezoid.distance = distance; + trapezoid.feedrate = feedrate; + + float accelerate_distance = estimate_acceleration_distance(feedrate.entry, feedrate.cruise, acceleration); + float decelerate_distance = estimate_acceleration_distance(feedrate.cruise, feedrate.exit, -acceleration); + float cruise_distance = distance - accelerate_distance - decelerate_distance; + + // Not enough space to reach the nominal feedrate. + // This means no cruising, and we'll have to use intersection_distance() to calculate when to abort acceleration + // and start braking in order to reach the exit_feedrate exactly at the end of this block. + if (cruise_distance < 0.0f) + { + accelerate_distance = clamp(0.0f, distance, intersection_distance(feedrate.entry, feedrate.exit, acceleration, distance)); + cruise_distance = 0.0f; + trapezoid.feedrate.cruise = Trapezoid::speed_from_distance(feedrate.entry, accelerate_distance, acceleration); + } + + trapezoid.accelerate_until = accelerate_distance; + trapezoid.decelerate_after = accelerate_distance + cruise_distance; + } + + float GCodeTimeEstimator::Block::max_allowable_speed(float acceleration, float target_velocity, float distance) + { + // to avoid invalid negative numbers due to numerical imprecision + float value = std::max(0.0f, sqr(target_velocity) - 2.0f * acceleration * distance); + return ::sqrt(value); + } + + float GCodeTimeEstimator::Block::estimate_acceleration_distance(float initial_rate, float target_rate, float acceleration) + { + return (acceleration == 0.0f) ? 0.0f : (sqr(target_rate) - sqr(initial_rate)) / (2.0f * acceleration); + } + + float GCodeTimeEstimator::Block::intersection_distance(float initial_rate, float final_rate, float acceleration, float distance) + { + return (acceleration == 0.0f) ? 0.0f : (2.0f * acceleration * distance - sqr(initial_rate) + sqr(final_rate)) / (4.0f * acceleration); + } + +#if ENABLE_MOVE_STATS + GCodeTimeEstimator::MoveStats::MoveStats() + : count(0) + , time(0.0f) + { + } +#endif // ENABLE_MOVE_STATS + + const std::string GCodeTimeEstimator::Normal_First_M73_Output_Placeholder_Tag = "; NORMAL_FIRST_M73_OUTPUT_PLACEHOLDER"; + const std::string GCodeTimeEstimator::Silent_First_M73_Output_Placeholder_Tag = "; SILENT_FIRST_M73_OUTPUT_PLACEHOLDER"; + + GCodeTimeEstimator::GCodeTimeEstimator(EMode mode) + : _mode(mode) + { + reset(); + set_default(); + } + + void GCodeTimeEstimator::add_gcode_line(const std::string& gcode_line) + { + PROFILE_FUNC(); + _parser.parse_line(gcode_line, + [this](GCodeReader &reader, const GCodeReader::GCodeLine &line) + { this->_process_gcode_line(reader, line); }); + } + + void GCodeTimeEstimator::add_gcode_block(const char *ptr) + { + PROFILE_FUNC(); + GCodeReader::GCodeLine gline; + auto action = [this](GCodeReader &reader, const GCodeReader::GCodeLine &line) + { this->_process_gcode_line(reader, line); }; + for (; *ptr != 0;) { + gline.reset(); + ptr = _parser.parse_line(ptr, gline, action); + } + } + + void GCodeTimeEstimator::calculate_time(bool start_from_beginning) + { + PROFILE_FUNC(); + if (start_from_beginning) + { + _reset_time(); + _last_st_synchronized_block_id = -1; + } + _calculate_time(); + +#if ENABLE_MOVE_STATS + _log_moves_stats(); +#endif // ENABLE_MOVE_STATS + } + + void GCodeTimeEstimator::calculate_time_from_text(const std::string& gcode) + { + reset(); + + _parser.parse_buffer(gcode, + [this](GCodeReader &reader, const GCodeReader::GCodeLine &line) + { this->_process_gcode_line(reader, line); }); + + _calculate_time(); + +#if ENABLE_MOVE_STATS + _log_moves_stats(); +#endif // ENABLE_MOVE_STATS + } + + void GCodeTimeEstimator::calculate_time_from_file(const std::string& file) + { + reset(); + + _parser.parse_file(file, boost::bind(&GCodeTimeEstimator::_process_gcode_line, this, _1, _2)); + _calculate_time(); + +#if ENABLE_MOVE_STATS + _log_moves_stats(); +#endif // ENABLE_MOVE_STATS + } + + void GCodeTimeEstimator::calculate_time_from_lines(const std::vector<std::string>& gcode_lines) + { + reset(); + + auto action = [this](GCodeReader &reader, const GCodeReader::GCodeLine &line) + { this->_process_gcode_line(reader, line); }; + for (const std::string& line : gcode_lines) + _parser.parse_line(line, action); + _calculate_time(); + +#if ENABLE_MOVE_STATS + _log_moves_stats(); +#endif // ENABLE_MOVE_STATS + } + + bool GCodeTimeEstimator::post_process_remaining_times(const std::string& filename, float interval) + { + boost::nowide::ifstream in(filename); + if (!in.good()) + throw std::runtime_error(std::string("Remaining times export failed.\nCannot open file for reading.\n")); + + std::string path_tmp = filename + ".times"; + + FILE* out = boost::nowide::fopen(path_tmp.c_str(), "wb"); + if (out == nullptr) + throw std::runtime_error(std::string("Remaining times export failed.\nCannot open file for writing.\n")); + + std::string time_mask; + switch (_mode) + { + default: + case Normal: + { + time_mask = "M73 P%s R%s\n"; + break; + } + case Silent: + { + time_mask = "M73 Q%s S%s\n"; + break; + } + } + + unsigned int g1_lines_count = 0; + float last_recorded_time = 0.0f; + std::string gcode_line; + // buffer line to export only when greater than 64K to reduce writing calls + std::string export_line; + char time_line[64]; + while (std::getline(in, gcode_line)) + { + if (!in.good()) + { + fclose(out); + throw std::runtime_error(std::string("Remaining times export failed.\nError while reading from file.\n")); + } + + // replaces placeholders for initial line M73 with the real lines + if (((_mode == Normal) && (gcode_line == Normal_First_M73_Output_Placeholder_Tag)) || + ((_mode == Silent) && (gcode_line == Silent_First_M73_Output_Placeholder_Tag))) + { + sprintf(time_line, time_mask.c_str(), "0", _get_time_minutes(_time).c_str()); + gcode_line = time_line; + } + else + gcode_line += "\n"; + + // add remaining time lines where needed + _parser.parse_line(gcode_line, + [this, &g1_lines_count, &last_recorded_time, &time_line, &gcode_line, time_mask, interval](GCodeReader& reader, const GCodeReader::GCodeLine& line) + { + if (line.cmd_is("G1")) + { + ++g1_lines_count; + + if (!line.has_e()) + return; + + G1LineIdToBlockIdMap::const_iterator it = _g1_line_ids.find(g1_lines_count); + if ((it != _g1_line_ids.end()) && (it->second < (unsigned int)_blocks.size())) + { + const Block& block = _blocks[it->second]; + if (block.elapsed_time != -1.0f) + { + float block_remaining_time = _time - block.elapsed_time; + if (std::abs(last_recorded_time - block_remaining_time) > interval) + { + sprintf(time_line, time_mask.c_str(), std::to_string((int)(100.0f * block.elapsed_time / _time)).c_str(), _get_time_minutes(block_remaining_time).c_str()); + gcode_line += time_line; + + last_recorded_time = block_remaining_time; + } + } + } + } + }); + + export_line += gcode_line; + if (export_line.length() > 65535) + { + fwrite((const void*)export_line.c_str(), 1, export_line.length(), out); + if (ferror(out)) + { + in.close(); + fclose(out); + boost::nowide::remove(path_tmp.c_str()); + throw std::runtime_error(std::string("Remaining times export failed.\nIs the disk full?\n")); + } + export_line.clear(); + } + } + + if (export_line.length() > 0) + { + fwrite((const void*)export_line.c_str(), 1, export_line.length(), out); + if (ferror(out)) + { + in.close(); + fclose(out); + boost::nowide::remove(path_tmp.c_str()); + throw std::runtime_error(std::string("Remaining times export failed.\nIs the disk full?\n")); + } + } + + fclose(out); + in.close(); + + if (rename_file(path_tmp, filename) != 0) + throw std::runtime_error(std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + filename + '\n' + + "Is " + path_tmp + " locked?" + '\n'); + + return true; + } + + void GCodeTimeEstimator::set_axis_position(EAxis axis, float position) + { + _state.axis[axis].position = position; + } + + void GCodeTimeEstimator::set_axis_max_feedrate(EAxis axis, float feedrate_mm_sec) + { + _state.axis[axis].max_feedrate = feedrate_mm_sec; + } + + void GCodeTimeEstimator::set_axis_max_acceleration(EAxis axis, float acceleration) + { + _state.axis[axis].max_acceleration = acceleration; + } + + void GCodeTimeEstimator::set_axis_max_jerk(EAxis axis, float jerk) + { + _state.axis[axis].max_jerk = jerk; + } + + float GCodeTimeEstimator::get_axis_position(EAxis axis) const + { + return _state.axis[axis].position; + } + + float GCodeTimeEstimator::get_axis_max_feedrate(EAxis axis) const + { + return _state.axis[axis].max_feedrate; + } + + float GCodeTimeEstimator::get_axis_max_acceleration(EAxis axis) const + { + return _state.axis[axis].max_acceleration; + } + + float GCodeTimeEstimator::get_axis_max_jerk(EAxis axis) const + { + return _state.axis[axis].max_jerk; + } + + void GCodeTimeEstimator::set_feedrate(float feedrate_mm_sec) + { + _state.feedrate = feedrate_mm_sec; + } + + float GCodeTimeEstimator::get_feedrate() const + { + return _state.feedrate; + } + + void GCodeTimeEstimator::set_acceleration(float acceleration_mm_sec2) + { + _state.acceleration = (_state.max_acceleration == 0) ? + acceleration_mm_sec2 : + // Clamp the acceleration with the maximum. + std::min(_state.max_acceleration, acceleration_mm_sec2); + } + + float GCodeTimeEstimator::get_acceleration() const + { + return _state.acceleration; + } + + void GCodeTimeEstimator::set_max_acceleration(float acceleration_mm_sec2) + { + _state.max_acceleration = acceleration_mm_sec2; + if (acceleration_mm_sec2 > 0) + _state.acceleration = acceleration_mm_sec2; + } + + float GCodeTimeEstimator::get_max_acceleration() const + { + return _state.max_acceleration; + } + + void GCodeTimeEstimator::set_retract_acceleration(float acceleration_mm_sec2) + { + _state.retract_acceleration = acceleration_mm_sec2; + } + + float GCodeTimeEstimator::get_retract_acceleration() const + { + return _state.retract_acceleration; + } + + void GCodeTimeEstimator::set_minimum_feedrate(float feedrate_mm_sec) + { + _state.minimum_feedrate = feedrate_mm_sec; + } + + float GCodeTimeEstimator::get_minimum_feedrate() const + { + return _state.minimum_feedrate; + } + + void GCodeTimeEstimator::set_minimum_travel_feedrate(float feedrate_mm_sec) + { + _state.minimum_travel_feedrate = feedrate_mm_sec; + } + + float GCodeTimeEstimator::get_minimum_travel_feedrate() const + { + return _state.minimum_travel_feedrate; + } + + void GCodeTimeEstimator::set_filament_load_times(const std::vector<double> &filament_load_times) + { + _state.filament_load_times.clear(); + for (double t : filament_load_times) + _state.filament_load_times.push_back(t); + } + + void GCodeTimeEstimator::set_filament_unload_times(const std::vector<double> &filament_unload_times) + { + _state.filament_unload_times.clear(); + for (double t : filament_unload_times) + _state.filament_unload_times.push_back(t); + } + + float GCodeTimeEstimator::get_filament_load_time(unsigned int id_extruder) + { + return + (_state.filament_load_times.empty() || id_extruder == _state.extruder_id_unloaded) ? + 0 : + (_state.filament_load_times.size() <= id_extruder) ? + _state.filament_load_times.front() : + _state.filament_load_times[id_extruder]; + } + + float GCodeTimeEstimator::get_filament_unload_time(unsigned int id_extruder) + { + return + (_state.filament_unload_times.empty() || id_extruder == _state.extruder_id_unloaded) ? + 0 : + (_state.filament_unload_times.size() <= id_extruder) ? + _state.filament_unload_times.front() : + _state.filament_unload_times[id_extruder]; + } + + void GCodeTimeEstimator::set_extrude_factor_override_percentage(float percentage) + { + _state.extrude_factor_override_percentage = percentage; + } + + float GCodeTimeEstimator::get_extrude_factor_override_percentage() const + { + return _state.extrude_factor_override_percentage; + } + + void GCodeTimeEstimator::set_dialect(GCodeFlavor dialect) + { + _state.dialect = dialect; + } + + GCodeFlavor GCodeTimeEstimator::get_dialect() const + { + PROFILE_FUNC(); + return _state.dialect; + } + + void GCodeTimeEstimator::set_units(GCodeTimeEstimator::EUnits units) + { + _state.units = units; + } + + GCodeTimeEstimator::EUnits GCodeTimeEstimator::get_units() const + { + return _state.units; + } + + void GCodeTimeEstimator::set_global_positioning_type(GCodeTimeEstimator::EPositioningType type) + { + _state.global_positioning_type = type; + } + + GCodeTimeEstimator::EPositioningType GCodeTimeEstimator::get_global_positioning_type() const + { + return _state.global_positioning_type; + } + + void GCodeTimeEstimator::set_e_local_positioning_type(GCodeTimeEstimator::EPositioningType type) + { + _state.e_local_positioning_type = type; + } + + GCodeTimeEstimator::EPositioningType GCodeTimeEstimator::get_e_local_positioning_type() const + { + return _state.e_local_positioning_type; + } + + int GCodeTimeEstimator::get_g1_line_id() const + { + return _state.g1_line_id; + } + + void GCodeTimeEstimator::increment_g1_line_id() + { + ++_state.g1_line_id; + } + + void GCodeTimeEstimator::reset_g1_line_id() + { + _state.g1_line_id = 0; + } + + void GCodeTimeEstimator::set_extruder_id(unsigned int id) + { + _state.extruder_id = id; + } + + unsigned int GCodeTimeEstimator::get_extruder_id() const + { + return _state.extruder_id; + } + + void GCodeTimeEstimator::reset_extruder_id() + { + // Set the initial extruder ID to unknown. For the multi-material setup it means + // that all the filaments are parked in the MMU and no filament is loaded yet. + _state.extruder_id = _state.extruder_id_unloaded; + } + + void GCodeTimeEstimator::add_additional_time(float timeSec) + { + PROFILE_FUNC(); + _state.additional_time += timeSec; + } + + void GCodeTimeEstimator::set_additional_time(float timeSec) + { + _state.additional_time = timeSec; + } + + float GCodeTimeEstimator::get_additional_time() const + { + return _state.additional_time; + } + + void GCodeTimeEstimator::set_default() + { + set_units(Millimeters); + set_dialect(gcfRepRap); + set_global_positioning_type(Absolute); + set_e_local_positioning_type(Absolute); + + set_feedrate(DEFAULT_FEEDRATE); + // Setting the maximum acceleration to zero means that the there is no limit and the G-code + // is allowed to set excessive values. + set_max_acceleration(0); + set_acceleration(DEFAULT_ACCELERATION); + set_retract_acceleration(DEFAULT_RETRACT_ACCELERATION); + set_minimum_feedrate(DEFAULT_MINIMUM_FEEDRATE); + set_minimum_travel_feedrate(DEFAULT_MINIMUM_TRAVEL_FEEDRATE); + set_extrude_factor_override_percentage(DEFAULT_EXTRUDE_FACTOR_OVERRIDE_PERCENTAGE); + + for (unsigned char a = X; a < Num_Axis; ++a) + { + EAxis axis = (EAxis)a; + set_axis_max_feedrate(axis, DEFAULT_AXIS_MAX_FEEDRATE[a]); + set_axis_max_acceleration(axis, DEFAULT_AXIS_MAX_ACCELERATION[a]); + set_axis_max_jerk(axis, DEFAULT_AXIS_MAX_JERK[a]); + } + + _state.filament_load_times.clear(); + _state.filament_unload_times.clear(); + } + + void GCodeTimeEstimator::reset() + { + _reset_time(); +#if ENABLE_MOVE_STATS + _moves_stats.clear(); +#endif // ENABLE_MOVE_STATS + _reset_blocks(); + _reset(); + } + + float GCodeTimeEstimator::get_time() const + { + return _time; + } + + std::string GCodeTimeEstimator::get_time_dhms() const + { + return _get_time_dhms(get_time()); + } + + std::string GCodeTimeEstimator::get_time_minutes() const + { + return _get_time_minutes(get_time()); + } + + void GCodeTimeEstimator::_reset() + { + _curr.reset(); + _prev.reset(); + + set_axis_position(X, 0.0f); + set_axis_position(Y, 0.0f); + set_axis_position(Z, 0.0f); + + set_additional_time(0.0f); + + reset_extruder_id(); + reset_g1_line_id(); + _g1_line_ids.clear(); + + _last_st_synchronized_block_id = -1; + } + + void GCodeTimeEstimator::_reset_time() + { + _time = 0.0f; + } + + void GCodeTimeEstimator::_reset_blocks() + { + _blocks.clear(); + } + + + void GCodeTimeEstimator::_calculate_time() + { + PROFILE_FUNC(); + _forward_pass(); + _reverse_pass(); + _recalculate_trapezoids(); + + _time += get_additional_time(); + + for (int i = _last_st_synchronized_block_id + 1; i < (int)_blocks.size(); ++i) + { + Block& block = _blocks[i]; + +#if ENABLE_MOVE_STATS + float block_time = 0.0f; + block_time += block.acceleration_time(); + block_time += block.cruise_time(); + block_time += block.deceleration_time(); + _time += block_time; + block.elapsed_time = _time; + + MovesStatsMap::iterator it = _moves_stats.find(block.move_type); + if (it == _moves_stats.end()) + it = _moves_stats.insert(MovesStatsMap::value_type(block.move_type, MoveStats())).first; + + it->second.count += 1; + it->second.time += block_time; +#else + _time += block.acceleration_time(); + _time += block.cruise_time(); + _time += block.deceleration_time(); + block.elapsed_time = _time; +#endif // ENABLE_MOVE_STATS + } + + _last_st_synchronized_block_id = _blocks.size() - 1; + // The additional time has been consumed (added to the total time), reset it to zero. + set_additional_time(0.); + } + + void GCodeTimeEstimator::_process_gcode_line(GCodeReader&, const GCodeReader::GCodeLine& line) + { + PROFILE_FUNC(); + std::string cmd = line.cmd(); + if (cmd.length() > 1) + { + switch (::toupper(cmd[0])) + { + case 'G': + { + switch (::atoi(&cmd[1])) + { + case 1: // Move + { + _processG1(line); + break; + } + case 4: // Dwell + { + _processG4(line); + break; + } + case 20: // Set Units to Inches + { + _processG20(line); + break; + } + case 21: // Set Units to Millimeters + { + _processG21(line); + break; + } + case 28: // Move to Origin (Home) + { + _processG28(line); + break; + } + case 90: // Set to Absolute Positioning + { + _processG90(line); + break; + } + case 91: // Set to Relative Positioning + { + _processG91(line); + break; + } + case 92: // Set Position + { + _processG92(line); + break; + } + } + + break; + } + case 'M': + { + switch (::atoi(&cmd[1])) + { + case 1: // Sleep or Conditional stop + { + _processM1(line); + break; + } + case 82: // Set extruder to absolute mode + { + _processM82(line); + break; + } + case 83: // Set extruder to relative mode + { + _processM83(line); + break; + } + case 109: // Set Extruder Temperature and Wait + { + _processM109(line); + break; + } + case 201: // Set max printing acceleration + { + _processM201(line); + break; + } + case 203: // Set maximum feedrate + { + _processM203(line); + break; + } + case 204: // Set default acceleration + { + _processM204(line); + break; + } + case 205: // Advanced settings + { + _processM205(line); + break; + } + case 221: // Set extrude factor override percentage + { + _processM221(line); + break; + } + case 566: // Set allowable instantaneous speed change + { + _processM566(line); + break; + } + case 702: // MK3 MMU2: Process the final filament unload. + { + _processM702(line); + break; + } + } + + break; + } + case 'T': // Select Tools + { + _processT(line); + break; + } + } + } + } + + // Returns the new absolute position on the given axis in dependence of the given parameters + float axis_absolute_position_from_G1_line(GCodeTimeEstimator::EAxis axis, const GCodeReader::GCodeLine& lineG1, GCodeTimeEstimator::EUnits units, bool is_relative, float current_absolute_position) + { + float lengthsScaleFactor = (units == GCodeTimeEstimator::Inches) ? INCHES_TO_MM : 1.0f; + if (lineG1.has(Slic3r::Axis(axis))) + { + float ret = lineG1.value(Slic3r::Axis(axis)) * lengthsScaleFactor; + return is_relative ? current_absolute_position + ret : ret; + } + else + return current_absolute_position; + } + + void GCodeTimeEstimator::_processG1(const GCodeReader::GCodeLine& line) + { + PROFILE_FUNC(); + increment_g1_line_id(); + + // updates axes positions from line + EUnits units = get_units(); + float new_pos[Num_Axis]; + for (unsigned char a = X; a < Num_Axis; ++a) + { + bool is_relative = (get_global_positioning_type() == Relative); + if (a == E) + is_relative |= (get_e_local_positioning_type() == Relative); + + new_pos[a] = axis_absolute_position_from_G1_line((EAxis)a, line, units, is_relative, get_axis_position((EAxis)a)); + } + + // updates feedrate from line, if present + if (line.has_f()) + set_feedrate(std::max(line.f() * MMMIN_TO_MMSEC, get_minimum_feedrate())); + + // fills block data + Block block; + + // calculates block movement deltas + float max_abs_delta = 0.0f; + for (unsigned char a = X; a < Num_Axis; ++a) + { + block.delta_pos[a] = new_pos[a] - get_axis_position((EAxis)a); + max_abs_delta = std::max(max_abs_delta, std::abs(block.delta_pos[a])); + } + + // is it a move ? + if (max_abs_delta == 0.0f) + return; + + // calculates block feedrate + _curr.feedrate = std::max(get_feedrate(), block.is_travel_move() ? get_minimum_travel_feedrate() : get_minimum_feedrate()); + + float distance = block.move_length(); + float invDistance = 1.0f / distance; + + float min_feedrate_factor = 1.0f; + for (unsigned char a = X; a < Num_Axis; ++a) + { + _curr.axis_feedrate[a] = _curr.feedrate * block.delta_pos[a] * invDistance; + if (a == E) + _curr.axis_feedrate[a] *= get_extrude_factor_override_percentage(); + + _curr.abs_axis_feedrate[a] = std::abs(_curr.axis_feedrate[a]); + if (_curr.abs_axis_feedrate[a] > 0.0f) + min_feedrate_factor = std::min(min_feedrate_factor, get_axis_max_feedrate((EAxis)a) / _curr.abs_axis_feedrate[a]); + } + + block.feedrate.cruise = min_feedrate_factor * _curr.feedrate; + + if (min_feedrate_factor < 1.0f) + { + for (unsigned char a = X; a < Num_Axis; ++a) + { + _curr.axis_feedrate[a] *= min_feedrate_factor; + _curr.abs_axis_feedrate[a] *= min_feedrate_factor; + } + } + + // calculates block acceleration + float acceleration = block.is_extruder_only_move() ? get_retract_acceleration() : get_acceleration(); + + for (unsigned char a = X; a < Num_Axis; ++a) + { + float axis_max_acceleration = get_axis_max_acceleration((EAxis)a); + if (acceleration * std::abs(block.delta_pos[a]) * invDistance > axis_max_acceleration) + acceleration = axis_max_acceleration; + } + + block.acceleration = acceleration; + + // calculates block exit feedrate + _curr.safe_feedrate = block.feedrate.cruise; + + for (unsigned char a = X; a < Num_Axis; ++a) + { + float axis_max_jerk = get_axis_max_jerk((EAxis)a); + if (_curr.abs_axis_feedrate[a] > axis_max_jerk) + _curr.safe_feedrate = std::min(_curr.safe_feedrate, axis_max_jerk); + } + + block.feedrate.exit = _curr.safe_feedrate; + + // calculates block entry feedrate + float vmax_junction = _curr.safe_feedrate; + if (!_blocks.empty() && (_prev.feedrate > PREVIOUS_FEEDRATE_THRESHOLD)) + { + bool prev_speed_larger = _prev.feedrate > block.feedrate.cruise; + float smaller_speed_factor = prev_speed_larger ? (block.feedrate.cruise / _prev.feedrate) : (_prev.feedrate / block.feedrate.cruise); + // Pick the smaller of the nominal speeds. Higher speed shall not be achieved at the junction during coasting. + vmax_junction = prev_speed_larger ? block.feedrate.cruise : _prev.feedrate; + + float v_factor = 1.0f; + bool limited = false; + + for (unsigned char a = X; a < Num_Axis; ++a) + { + // Limit an axis. We have to differentiate coasting from the reversal of an axis movement, or a full stop. + float v_exit = _prev.axis_feedrate[a]; + float v_entry = _curr.axis_feedrate[a]; + + if (prev_speed_larger) + v_exit *= smaller_speed_factor; + + if (limited) + { + v_exit *= v_factor; + v_entry *= v_factor; + } + + // Calculate the jerk depending on whether the axis is coasting in the same direction or reversing a direction. + float jerk = + (v_exit > v_entry) ? + (((v_entry > 0.0f) || (v_exit < 0.0f)) ? + // coasting + (v_exit - v_entry) : + // axis reversal + std::max(v_exit, -v_entry)) : + // v_exit <= v_entry + (((v_entry < 0.0f) || (v_exit > 0.0f)) ? + // coasting + (v_entry - v_exit) : + // axis reversal + std::max(-v_exit, v_entry)); + + float axis_max_jerk = get_axis_max_jerk((EAxis)a); + if (jerk > axis_max_jerk) + { + v_factor *= axis_max_jerk / jerk; + limited = true; + } + } + + if (limited) + vmax_junction *= v_factor; + + // Now the transition velocity is known, which maximizes the shared exit / entry velocity while + // respecting the jerk factors, it may be possible, that applying separate safe exit / entry velocities will achieve faster prints. + float vmax_junction_threshold = vmax_junction * 0.99f; + + // Not coasting. The machine will stop and start the movements anyway, better to start the segment from start. + if ((_prev.safe_feedrate > vmax_junction_threshold) && (_curr.safe_feedrate > vmax_junction_threshold)) + vmax_junction = _curr.safe_feedrate; + } + + float v_allowable = Block::max_allowable_speed(-acceleration, _curr.safe_feedrate, distance); + block.feedrate.entry = std::min(vmax_junction, v_allowable); + + block.max_entry_speed = vmax_junction; + block.flags.nominal_length = (block.feedrate.cruise <= v_allowable); + block.flags.recalculate = true; + block.safe_feedrate = _curr.safe_feedrate; + + // calculates block trapezoid + block.calculate_trapezoid(); + + // updates previous + _prev = _curr; + + // updates axis positions + for (unsigned char a = X; a < Num_Axis; ++a) + { + set_axis_position((EAxis)a, new_pos[a]); + } + +#if ENABLE_MOVE_STATS + // detects block move type + block.move_type = Block::Noop; + + if (block.delta_pos[E] < 0.0f) + { + if ((block.delta_pos[X] != 0.0f) || (block.delta_pos[Y] != 0.0f) || (block.delta_pos[Z] != 0.0f)) + block.move_type = Block::Move; + else + block.move_type = Block::Retract; + } + else if (block.delta_pos[E] > 0.0f) + { + if ((block.delta_pos[X] == 0.0f) && (block.delta_pos[Y] == 0.0f) && (block.delta_pos[Z] == 0.0f)) + block.move_type = Block::Unretract; + else if ((block.delta_pos[X] != 0.0f) || (block.delta_pos[Y] != 0.0f)) + block.move_type = Block::Extrude; + } + else if ((block.delta_pos[X] != 0.0f) || (block.delta_pos[Y] != 0.0f) || (block.delta_pos[Z] != 0.0f)) + block.move_type = Block::Move; +#endif // ENABLE_MOVE_STATS + + // adds block to blocks list + _blocks.emplace_back(block); + _g1_line_ids.insert(G1LineIdToBlockIdMap::value_type(get_g1_line_id(), (unsigned int)_blocks.size() - 1)); + } + + void GCodeTimeEstimator::_processG4(const GCodeReader::GCodeLine& line) + { + PROFILE_FUNC(); + GCodeFlavor dialect = get_dialect(); + + float value; + if (line.has_value('P', value)) + add_additional_time(value * MILLISEC_TO_SEC); + + // see: http://reprap.org/wiki/G-code#G4:_Dwell + if ((dialect == gcfRepetier) || + (dialect == gcfMarlin) || + (dialect == gcfSmoothie) || + (dialect == gcfRepRap)) + { + if (line.has_value('S', value)) + add_additional_time(value); + } + + _simulate_st_synchronize(); + } + + void GCodeTimeEstimator::_processG20(const GCodeReader::GCodeLine& line) + { + PROFILE_FUNC(); + set_units(Inches); + } + + void GCodeTimeEstimator::_processG21(const GCodeReader::GCodeLine& line) + { + PROFILE_FUNC(); + set_units(Millimeters); + } + + void GCodeTimeEstimator::_processG28(const GCodeReader::GCodeLine& line) + { + PROFILE_FUNC(); + // TODO + } + + void GCodeTimeEstimator::_processG90(const GCodeReader::GCodeLine& line) + { + PROFILE_FUNC(); + set_global_positioning_type(Absolute); + } + + void GCodeTimeEstimator::_processG91(const GCodeReader::GCodeLine& line) + { + PROFILE_FUNC(); + set_global_positioning_type(Relative); + } + + void GCodeTimeEstimator::_processG92(const GCodeReader::GCodeLine& line) + { + PROFILE_FUNC(); + float lengthsScaleFactor = (get_units() == Inches) ? INCHES_TO_MM : 1.0f; + bool anyFound = false; + + if (line.has_x()) + { + set_axis_position(X, line.x() * lengthsScaleFactor); + anyFound = true; + } + + if (line.has_y()) + { + set_axis_position(Y, line.y() * lengthsScaleFactor); + anyFound = true; + } + + if (line.has_z()) + { + set_axis_position(Z, line.z() * lengthsScaleFactor); + anyFound = true; + } + + if (line.has_e()) + { + set_axis_position(E, line.e() * lengthsScaleFactor); + anyFound = true; + } + else + _simulate_st_synchronize(); + + if (!anyFound) + { + for (unsigned char a = X; a < Num_Axis; ++a) + { + set_axis_position((EAxis)a, 0.0f); + } + } + } + + void GCodeTimeEstimator::_processM1(const GCodeReader::GCodeLine& line) + { + PROFILE_FUNC(); + _simulate_st_synchronize(); + } + + void GCodeTimeEstimator::_processM82(const GCodeReader::GCodeLine& line) + { + PROFILE_FUNC(); + set_e_local_positioning_type(Absolute); + } + + void GCodeTimeEstimator::_processM83(const GCodeReader::GCodeLine& line) + { + PROFILE_FUNC(); + set_e_local_positioning_type(Relative); + } + + void GCodeTimeEstimator::_processM109(const GCodeReader::GCodeLine& line) + { + PROFILE_FUNC(); + // TODO + } + + void GCodeTimeEstimator::_processM201(const GCodeReader::GCodeLine& line) + { + PROFILE_FUNC(); + GCodeFlavor dialect = get_dialect(); + + // see http://reprap.org/wiki/G-code#M201:_Set_max_printing_acceleration + float factor = ((dialect != gcfRepRap) && (get_units() == GCodeTimeEstimator::Inches)) ? INCHES_TO_MM : 1.0f; + + if (line.has_x()) + set_axis_max_acceleration(X, line.x() * factor); + + if (line.has_y()) + set_axis_max_acceleration(Y, line.y() * factor); + + if (line.has_z()) + set_axis_max_acceleration(Z, line.z() * factor); + + if (line.has_e()) + set_axis_max_acceleration(E, line.e() * factor); + } + + void GCodeTimeEstimator::_processM203(const GCodeReader::GCodeLine& line) + { + PROFILE_FUNC(); + GCodeFlavor dialect = get_dialect(); + + // see http://reprap.org/wiki/G-code#M203:_Set_maximum_feedrate + if (dialect == gcfRepetier) + return; + + // see http://reprap.org/wiki/G-code#M203:_Set_maximum_feedrate + float factor = (dialect == gcfMarlin) ? 1.0f : MMMIN_TO_MMSEC; + + if (line.has_x()) + set_axis_max_feedrate(X, line.x() * factor); + + if (line.has_y()) + set_axis_max_feedrate(Y, line.y() * factor); + + if (line.has_z()) + set_axis_max_feedrate(Z, line.z() * factor); + + if (line.has_e()) + set_axis_max_feedrate(E, line.e() * factor); + } + + void GCodeTimeEstimator::_processM204(const GCodeReader::GCodeLine& line) + { + PROFILE_FUNC(); + float value; + if (line.has_value('S', value)) { + // Legacy acceleration format. This format is used by the legacy Marlin, MK2 or MK3 firmware, + // and it is also generated by Slic3r to control acceleration per extrusion type + // (there is a separate acceleration settings in Slicer for perimeter, first layer etc). + set_acceleration(value); + if (line.has_value('T', value)) + set_retract_acceleration(value); + } else { + // New acceleration format, compatible with the upstream Marlin. + if (line.has_value('P', value)) + set_acceleration(value); + if (line.has_value('R', value)) + set_retract_acceleration(value); + if (line.has_value('T', value)) { + // Interpret the T value as the travel acceleration in the new Marlin format. + //FIXME Prusa3D firmware currently does not support travel acceleration value independent from the extruding acceleration value. + // set_travel_acceleration(value); + } + } + } + + void GCodeTimeEstimator::_processM205(const GCodeReader::GCodeLine& line) + { + PROFILE_FUNC(); + if (line.has_x()) + { + float max_jerk = line.x(); + set_axis_max_jerk(X, max_jerk); + set_axis_max_jerk(Y, max_jerk); + } + + if (line.has_y()) + set_axis_max_jerk(Y, line.y()); + + if (line.has_z()) + set_axis_max_jerk(Z, line.z()); + + if (line.has_e()) + set_axis_max_jerk(E, line.e()); + + float value; + if (line.has_value('S', value)) + set_minimum_feedrate(value); + + if (line.has_value('T', value)) + set_minimum_travel_feedrate(value); + } + + void GCodeTimeEstimator::_processM221(const GCodeReader::GCodeLine& line) + { + PROFILE_FUNC(); + float value_s; + float value_t; + if (line.has_value('S', value_s) && !line.has_value('T', value_t)) + set_extrude_factor_override_percentage(value_s * 0.01f); + } + + void GCodeTimeEstimator::_processM566(const GCodeReader::GCodeLine& line) + { + PROFILE_FUNC(); + if (line.has_x()) + set_axis_max_jerk(X, line.x() * MMMIN_TO_MMSEC); + + if (line.has_y()) + set_axis_max_jerk(Y, line.y() * MMMIN_TO_MMSEC); + + if (line.has_z()) + set_axis_max_jerk(Z, line.z() * MMMIN_TO_MMSEC); + + if (line.has_e()) + set_axis_max_jerk(E, line.e() * MMMIN_TO_MMSEC); + } + + void GCodeTimeEstimator::_processM702(const GCodeReader::GCodeLine& line) + { + PROFILE_FUNC(); + if (line.has('C')) { + // MK3 MMU2 specific M code: + // M702 C is expected to be sent by the custom end G-code when finalizing a print. + // The MK3 unit shall unload and park the active filament into the MMU2 unit. + add_additional_time(get_filament_unload_time(get_extruder_id())); + reset_extruder_id(); + _simulate_st_synchronize(); + } + } + + void GCodeTimeEstimator::_processT(const GCodeReader::GCodeLine& line) + { + std::string cmd = line.cmd(); + if (cmd.length() > 1) + { + unsigned int id = (unsigned int)::strtol(cmd.substr(1).c_str(), nullptr, 10); + if (get_extruder_id() != id) + { + // Specific to the MK3 MMU2: The initial extruder ID is set to -1 indicating + // that the filament is parked in the MMU2 unit and there is nothing to be unloaded yet. + add_additional_time(get_filament_unload_time(get_extruder_id())); + set_extruder_id(id); + add_additional_time(get_filament_load_time(get_extruder_id())); + _simulate_st_synchronize(); + } + } + } + + void GCodeTimeEstimator::_simulate_st_synchronize() + { + PROFILE_FUNC(); + _calculate_time(); + } + + void GCodeTimeEstimator::_forward_pass() + { + PROFILE_FUNC(); + if (_blocks.size() > 1) + { + for (int i = _last_st_synchronized_block_id + 1; i < (int)_blocks.size() - 1; ++i) + { + _planner_forward_pass_kernel(_blocks[i], _blocks[i + 1]); + } + } + } + + void GCodeTimeEstimator::_reverse_pass() + { + PROFILE_FUNC(); + if (_blocks.size() > 1) + { + for (int i = (int)_blocks.size() - 1; i >= _last_st_synchronized_block_id + 2; --i) + { + _planner_reverse_pass_kernel(_blocks[i - 1], _blocks[i]); + } + } + } + + void GCodeTimeEstimator::_planner_forward_pass_kernel(Block& prev, Block& curr) + { + PROFILE_FUNC(); + // If the previous block is an acceleration block, but it is not long enough to complete the + // full speed change within the block, we need to adjust the entry speed accordingly. Entry + // speeds have already been reset, maximized, and reverse planned by reverse planner. + // If nominal length is true, max junction speed is guaranteed to be reached. No need to recheck. + if (!prev.flags.nominal_length) + { + if (prev.feedrate.entry < curr.feedrate.entry) + { + float entry_speed = std::min(curr.feedrate.entry, Block::max_allowable_speed(-prev.acceleration, prev.feedrate.entry, prev.move_length())); + + // Check for junction speed change + if (curr.feedrate.entry != entry_speed) + { + curr.feedrate.entry = entry_speed; + curr.flags.recalculate = true; + } + } + } + } + + void GCodeTimeEstimator::_planner_reverse_pass_kernel(Block& curr, Block& next) + { + // If entry speed is already at the maximum entry speed, no need to recheck. Block is cruising. + // If not, block in state of acceleration or deceleration. Reset entry speed to maximum and + // check for maximum allowable speed reductions to ensure maximum possible planned speed. + if (curr.feedrate.entry != curr.max_entry_speed) + { + // If nominal length true, max junction speed is guaranteed to be reached. Only compute + // for max allowable speed if block is decelerating and nominal length is false. + if (!curr.flags.nominal_length && (curr.max_entry_speed > next.feedrate.entry)) + curr.feedrate.entry = std::min(curr.max_entry_speed, Block::max_allowable_speed(-curr.acceleration, next.feedrate.entry, curr.move_length())); + else + curr.feedrate.entry = curr.max_entry_speed; + + curr.flags.recalculate = true; + } + } + + void GCodeTimeEstimator::_recalculate_trapezoids() + { + PROFILE_FUNC(); + Block* curr = nullptr; + Block* next = nullptr; + + for (int i = _last_st_synchronized_block_id + 1; i < (int)_blocks.size(); ++i) + { + Block& b = _blocks[i]; + + curr = next; + next = &b; + + if (curr != nullptr) + { + // Recalculate if current block entry or exit junction speed has changed. + if (curr->flags.recalculate || next->flags.recalculate) + { + // NOTE: Entry and exit factors always > 0 by all previous logic operations. + Block block = *curr; + block.feedrate.exit = next->feedrate.entry; + block.calculate_trapezoid(); + curr->trapezoid = block.trapezoid; + curr->flags.recalculate = false; // Reset current only to ensure next trapezoid is computed + } + } + } + + // Last/newest block in buffer. Always recalculated. + if (next != nullptr) + { + Block block = *next; + block.feedrate.exit = next->safe_feedrate; + block.calculate_trapezoid(); + next->trapezoid = block.trapezoid; + next->flags.recalculate = false; + } + } + + std::string GCodeTimeEstimator::_get_time_dhms(float time_in_secs) + { + int days = (int)(time_in_secs / 86400.0f); + time_in_secs -= (float)days * 86400.0f; + int hours = (int)(time_in_secs / 3600.0f); + time_in_secs -= (float)hours * 3600.0f; + int minutes = (int)(time_in_secs / 60.0f); + time_in_secs -= (float)minutes * 60.0f; + + char buffer[64]; + if (days > 0) + ::sprintf(buffer, "%dd %dh %dm %ds", days, hours, minutes, (int)time_in_secs); + else if (hours > 0) + ::sprintf(buffer, "%dh %dm %ds", hours, minutes, (int)time_in_secs); + else if (minutes > 0) + ::sprintf(buffer, "%dm %ds", minutes, (int)time_in_secs); + else + ::sprintf(buffer, "%ds", (int)time_in_secs); + + return buffer; + } + + std::string GCodeTimeEstimator::_get_time_minutes(float time_in_secs) + { + return std::to_string((int)(::roundf(time_in_secs / 60.0f))); + } + +#if ENABLE_MOVE_STATS + void GCodeTimeEstimator::_log_moves_stats() const + { + float moves_count = 0.0f; + for (const MovesStatsMap::value_type& move : _moves_stats) + { + moves_count += (float)move.second.count; + } + + for (const MovesStatsMap::value_type& move : _moves_stats) + { + std::cout << MOVE_TYPE_STR[move.first]; + std::cout << ": count " << move.second.count << " (" << 100.0f * (float)move.second.count / moves_count << "%)"; + std::cout << " - time: " << move.second.time << "s (" << 100.0f * move.second.time / _time << "%)"; + std::cout << std::endl; + } + std::cout << std::endl; + } +#endif // ENABLE_MOVE_STATS +} |