diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp index fad1e8ee20..8d8bf3e3ec 100644 --- a/xs/src/libslic3r/GCode.cpp +++ b/xs/src/libslic3r/GCode.cpp @@ -7,9 +7,6 @@ //############################################################################################################ #include "GCodeTimeEstimator.hpp" -#ifdef WIN32 -#include "enrico/wintimer.h" -#endif // WIN32 //############################################################################################################ #include @@ -383,83 +380,12 @@ bool GCode::do_export(Print *print, const char *path) boost::nowide::remove(path_tmp.c_str()); //############################################################################################################ -#ifdef WIN32 - WinTimer timer; - timer.Start(); -#endif // WIN32 + GCodeTimeEstimator timeEstimator; + timeEstimator.calculate_time_from_file(path); + float time = timeEstimator.get_time(); + std::string timeHMS = timeEstimator.get_time_hms(); - My_GCodeTimeEstimator timeEstimator; - timeEstimator.parse_file(path); -#ifdef WIN32 - double timeParse = timer.GetElapsedTimeSec(); -#endif // WIN32 - timeEstimator.calculate_time(); -#ifdef WIN32 - double timeCalculate = timer.GetElapsedTimeSec(); -#endif // WIN32 - std::cout << std::endl << ">>> estimated time: " << timeEstimator.get_time() << " seconds." << std::endl << std::endl; - -#ifdef WIN32 - std::cout << std::endl << "parse_file() -> Time: " << timeParse << std::endl << std::endl; - std::cout << std::endl << "calculate_file() -> Time: " << timeCalculate - timeParse << std::endl << std::endl; -#endif // WIN32 - -/* - unsigned int i = 0; - const My_GCodeTimeEstimator::BlocksList& blocks = timeEstimator.get_blocks(); - float maxXYZ[3] = { 0.0f, 0.0f, 0.0f }; - unsigned int maxID[3] = { 0, 0, 0 }; - for (const My_GCodeTimeEstimator::Block& block : blocks) - { - ++i; - std::cout << std::endl << "Block: " - << i - << " (" - << block.delta_pos[My_GCodeTimeEstimator::X] - << ", " - << block.delta_pos[My_GCodeTimeEstimator::Y] - << ", " - << block.delta_pos[My_GCodeTimeEstimator::Z] - << ") - f: " - << block.feedrate - << " - a: " - << block.acceleration - << " - s: (" - << block.entry_feedrate - << ", " - << block.exit_feedrate - << ")" - << std::endl; - - float dx = ::abs(block.delta_pos[My_GCodeTimeEstimator::X]); - float dy = ::abs(block.delta_pos[My_GCodeTimeEstimator::Y]); - float dz = ::abs(block.delta_pos[My_GCodeTimeEstimator::Z]); - - if (maxXYZ[My_GCodeTimeEstimator::X] < dx) - { - maxXYZ[My_GCodeTimeEstimator::X] = dx; - maxID[My_GCodeTimeEstimator::X] = i; - } - - if (maxXYZ[My_GCodeTimeEstimator::Y] < dy) - { - maxXYZ[My_GCodeTimeEstimator::Y] = dy; - maxID[My_GCodeTimeEstimator::Y] = i; - } - - if (maxXYZ[My_GCodeTimeEstimator::Z] < dz) - { - maxXYZ[My_GCodeTimeEstimator::Z] = dz; - maxID[My_GCodeTimeEstimator::Z] = i; - } - } - - std::cout << std::endl << "MAX DX: " << maxID[My_GCodeTimeEstimator::X] << " - " << maxXYZ[My_GCodeTimeEstimator::X] << std::endl; - std::cout << std::endl << "MAX DY: " << maxID[My_GCodeTimeEstimator::Y] << " - " << maxXYZ[My_GCodeTimeEstimator::Y] << std::endl; - std::cout << std::endl << "MAX DZ: " << maxID[My_GCodeTimeEstimator::Z] << " - " << maxXYZ[My_GCodeTimeEstimator::Z] << std::endl; - - timeEstimator.print_counters(); -*/ + std::cout << std::endl << ">>> estimated time: " << timeHMS << " (" << time << " seconds)" << std::endl << std::endl; //############################################################################################################ return result; diff --git a/xs/src/libslic3r/GCodeTimeEstimator.cpp b/xs/src/libslic3r/GCodeTimeEstimator.cpp index b75745757d..d612980bad 100644 --- a/xs/src/libslic3r/GCodeTimeEstimator.cpp +++ b/xs/src/libslic3r/GCodeTimeEstimator.cpp @@ -2,8 +2,6 @@ #include #include -//########################################################################################################### -#include static const std::string AXIS_STR = "XYZE"; static const float MMMIN_TO_MMSEC = 1.0f / 60.0f; static const float MILLISEC_TO_SEC = 0.001f; @@ -13,205 +11,258 @@ static const float DEFAULT_ACCELERATION = 3000.0f; static const float DEFAULT_AXIS_MAX_FEEDRATE[] = { 600.0f, 600.0f, 40.0f, 25.0f }; static const float DEFAULT_AXIS_MAX_ACCELERATION[] = { 9000.0f, 9000.0f, 100.0f, 10000.0f }; -static const float DEFAULT_AXIS_MAX_JERK[] = { 10.0f, 10.0f, 0.2f, 2.5f }; // from firmware -// static const float DEFAULT_AXIS_MAX_JERK[] = { 20.0f, 20.0f, 0.4f, 5.0f }; / from CURA +static const float DEFAULT_AXIS_MAX_JERK[] = { 10.0f, 10.0f, 0.2f, 2.5f }; // from Firmware +//static const float DEFAULT_AXIS_MAX_JERK[] = { 20.0f, 20.0f, 0.4f, 5.0f }; // from Cura -static const float MINIMUM_FEEDRATE = 0.01f; -static const float MINIMUM_PLANNER_SPEED = 0.05f; // <<<<<<<< WHAT IS THIS ??? -static const float FEEDRATE_THRESHOLD = 0.0001f; -//########################################################################################################### +static const float DEFAULT_MINIMUM_FEEDRATE = 0.0f; // from Firmware +//static const float DEFAULT_MINIMUM_FEEDRATE = 0.01f; // from Cura + +#if USE_CURA_JUNCTION_VMAX +static const float MINIMUM_PLANNER_SPEED = 0.05f; // from Cura <<<<<<<< WHAT IS THIS ??? +#endif // USE_CURA_JUNCTION_VMAX + +static const float PREVIOUS_FEEDRATE_THRESHOLD = 0.0001f; namespace Slic3r { -//########################################################################################################### - float My_GCodeTimeEstimator::Block::move_length() const + 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) + { + return ::sqrt(sqr(initial_feedrate) + 2.0f * acceleration * distance); + } + + 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 : ::abs(delta_pos[E]); } - void My_GCodeTimeEstimator::Block::calculate_trapezoid() + float GCodeTimeEstimator::Block::acceleration_time() const { - float accelerate_distance = estimate_acceleration_distance(entry_feedrate, feedrate, acceleration); - float decelerate_distance = estimate_acceleration_distance(feedrate, exit_feedrate, -acceleration); + 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(); - float plateau_distance = distance - accelerate_distance - decelerate_distance; + 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 (plateau_distance < 0.0f) + if (cruise_distance < 0.0f) { - accelerate_distance = clamp(0.0f, distance, intersection_distance(entry_feedrate, exit_feedrate, acceleration, distance)); - plateau_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.distance = distance; trapezoid.accelerate_until = accelerate_distance; - trapezoid.decelerate_after = accelerate_distance + plateau_distance; - trapezoid.entry_feedrate = entry_feedrate; - trapezoid.exit_feedrate = exit_feedrate; + trapezoid.decelerate_after = accelerate_distance + cruise_distance; } - float My_GCodeTimeEstimator::Block::max_allowable_speed(float acceleration, float target_velocity, float distance) + float GCodeTimeEstimator::Block::max_allowable_speed(float acceleration, float target_velocity, float distance) { return ::sqrt(sqr(target_velocity) - 2.0f * acceleration * distance); } - float My_GCodeTimeEstimator::Block::estimate_acceleration_distance(float initial_rate, float target_rate, float acceleration) + 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 My_GCodeTimeEstimator::Block::intersection_distance(float initial_rate, float final_rate, float acceleration, float distance) + 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); } - float My_GCodeTimeEstimator::Block::acceleration_time_from_distance(float initial_feedrate, float distance, float acceleration) - { - float discriminant = sqr(initial_feedrate) + 2.0f * acceleration * distance; - - // If discriminant is negative, we're moving in the wrong direction. - // Making the discriminant 0 then gives the extremum of the parabola instead of the intersection. - discriminant = std::max(0.0f, discriminant); - return (-initial_feedrate + ::sqrt(discriminant)) / acceleration; - } - - My_GCodeTimeEstimator::My_GCodeTimeEstimator() + GCodeTimeEstimator::GCodeTimeEstimator() { } - void My_GCodeTimeEstimator::parse(const std::string& gcode) + void GCodeTimeEstimator::calculate_time_from_text(const std::string& gcode) { _reset(); - GCodeReader::parse(gcode, boost::bind(&My_GCodeTimeEstimator::_process_gcode_line, this, _1, _2)); + _parser.parse(gcode, boost::bind(&GCodeTimeEstimator::_process_gcode_line, this, _1, _2)); + _calculate_time(); } - void My_GCodeTimeEstimator::parse_file(const std::string& file) + void GCodeTimeEstimator::calculate_time_from_file(const std::string& file) { _reset(); - GCodeReader::parse_file(file, boost::bind(&My_GCodeTimeEstimator::_process_gcode_line, this, _1, _2)); + _parser.parse_file(file, boost::bind(&GCodeTimeEstimator::_process_gcode_line, this, _1, _2)); + _calculate_time(); } - void My_GCodeTimeEstimator::calculate_time() - { - _time = get_additional_time(); - - for (const Block& block : _blocks) - { - const Block::Trapezoid& trapezoid = block.trapezoid; - float plateau_distance = trapezoid.decelerate_after - trapezoid.accelerate_until; - - _time += Block::acceleration_time_from_distance(block.entry_feedrate, trapezoid.accelerate_until, block.acceleration); - _time += plateau_distance / block.feedrate; - _time += Block::acceleration_time_from_distance(block.exit_feedrate, (trapezoid.distance - trapezoid.decelerate_after), block.acceleration); - } - } - - void My_GCodeTimeEstimator::set_axis_position(EAxis axis, float position) + void GCodeTimeEstimator::set_axis_position(EAxis axis, float position) { _state.axis[axis].position = position; } - void My_GCodeTimeEstimator::set_axis_max_feedrate(EAxis axis, float feedrate_mm_sec) + void GCodeTimeEstimator::set_axis_max_feedrate(EAxis axis, float feedrate_mm_sec) { _state.axis[axis].max_feedrate = feedrate_mm_sec; } - void My_GCodeTimeEstimator::set_axis_max_acceleration(EAxis axis, float acceleration) + void GCodeTimeEstimator::set_axis_max_acceleration(EAxis axis, float acceleration) { _state.axis[axis].max_acceleration = acceleration; } - void My_GCodeTimeEstimator::set_axis_max_jerk(EAxis axis, float jerk) + void GCodeTimeEstimator::set_axis_max_jerk(EAxis axis, float jerk) { _state.axis[axis].max_jerk = jerk; } - float My_GCodeTimeEstimator::get_axis_position(EAxis axis) const + float GCodeTimeEstimator::get_axis_position(EAxis axis) const { return _state.axis[axis].position; } - float My_GCodeTimeEstimator::get_axis_max_feedrate(EAxis axis) const + float GCodeTimeEstimator::get_axis_max_feedrate(EAxis axis) const { return _state.axis[axis].max_feedrate; } - float My_GCodeTimeEstimator::get_axis_max_acceleration(EAxis axis) const + float GCodeTimeEstimator::get_axis_max_acceleration(EAxis axis) const { return _state.axis[axis].max_acceleration; } - float My_GCodeTimeEstimator::get_axis_max_jerk(EAxis axis) const + float GCodeTimeEstimator::get_axis_max_jerk(EAxis axis) const { return _state.axis[axis].max_jerk; } - void My_GCodeTimeEstimator::set_feedrate(float feedrate_mm_sec) + void GCodeTimeEstimator::set_feedrate(float feedrate_mm_sec) { - _state.feedrate = std::max(feedrate_mm_sec, MINIMUM_FEEDRATE); + _state.feedrate = feedrate_mm_sec; } - float My_GCodeTimeEstimator::get_feedrate() const + float GCodeTimeEstimator::get_feedrate() const { return _state.feedrate; } - void My_GCodeTimeEstimator::set_acceleration(float acceleration) + void GCodeTimeEstimator::set_acceleration(float acceleration) { _state.acceleration = acceleration; } - float My_GCodeTimeEstimator::get_acceleration() const + float GCodeTimeEstimator::get_acceleration() const { return _state.acceleration; } - void My_GCodeTimeEstimator::set_dialect(My_GCodeTimeEstimator::EDialect dialect) + 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_dialect(GCodeTimeEstimator::EDialect dialect) { _state.dialect = dialect; } - My_GCodeTimeEstimator::EDialect My_GCodeTimeEstimator::get_dialect() const + GCodeTimeEstimator::EDialect GCodeTimeEstimator::get_dialect() const { return _state.dialect; } - void My_GCodeTimeEstimator::set_units(My_GCodeTimeEstimator::EUnits units) + void GCodeTimeEstimator::set_units(GCodeTimeEstimator::EUnits units) { _state.units = units; } - My_GCodeTimeEstimator::EUnits My_GCodeTimeEstimator::get_units() const + GCodeTimeEstimator::EUnits GCodeTimeEstimator::get_units() const { return _state.units; } - void My_GCodeTimeEstimator::set_positioningType(My_GCodeTimeEstimator::EPositioningType type) + void GCodeTimeEstimator::set_positioningType(GCodeTimeEstimator::EPositioningType type) { _state.positioningType = type; } - My_GCodeTimeEstimator::EPositioningType My_GCodeTimeEstimator::get_positioningType() const + GCodeTimeEstimator::EPositioningType GCodeTimeEstimator::get_positioningType() const { return _state.positioningType; } - void My_GCodeTimeEstimator::add_additional_time(float timeSec) + void GCodeTimeEstimator::add_additional_time(float timeSec) { _state.additional_time += timeSec; } - float My_GCodeTimeEstimator::get_additional_time() const + void GCodeTimeEstimator::set_additional_time(float timeSec) + { + _state.additional_time = timeSec; + } + + float GCodeTimeEstimator::get_additional_time() const { return _state.additional_time; } - void My_GCodeTimeEstimator::set_default() + void GCodeTimeEstimator::set_default() { set_units(Millimeters); set_dialect(Unknown); @@ -219,6 +270,7 @@ namespace Slic3r { set_feedrate(DEFAULT_FEEDRATE); set_acceleration(DEFAULT_ACCELERATION); + set_minimum_feedrate(DEFAULT_MINIMUM_FEEDRATE); for (unsigned char a = X; a < Num_Axis; ++a) { @@ -229,40 +281,59 @@ namespace Slic3r { } } - float My_GCodeTimeEstimator::get_time() const + float GCodeTimeEstimator::get_time() const { return _time; } - const My_GCodeTimeEstimator::BlocksList& My_GCodeTimeEstimator::get_blocks() const + std::string GCodeTimeEstimator::get_time_hms() const { - return _blocks; + float timeinsecs = get_time(); + int hours = (int)(timeinsecs / 3600.0f); + timeinsecs -= (float)hours * 3600.0f; + int minutes = (int)(timeinsecs / 60.0f); + timeinsecs -= (float)minutes * 60.0f; + + char buf[16]; + ::sprintf(buf, "%02d:%02d:%02d", hours, minutes, (int)timeinsecs); + return buf; } -// void My_GCodeTimeEstimator::print_counters() const -// { -// std::cout << std::endl; -// for (const CmdToCounterMap::value_type& counter : _cmdCounters) -// { -// std::cout << counter.first << " : " << counter.second << std::endl; -// } -// } - - void My_GCodeTimeEstimator::_reset() + void GCodeTimeEstimator::_reset() { -// _cmdCounters.clear(); - _blocks.clear(); + _curr.reset(); + _prev.reset(); + set_default(); + set_axis_position(X, 0.0f); set_axis_position(Y, 0.0f); set_axis_position(Z, 0.0f); - _state.additional_time = 0.0f; + set_additional_time(0.0f); } - void My_GCodeTimeEstimator::_process_gcode_line(GCodeReader&, const GCodeReader::GCodeLine& line) + void GCodeTimeEstimator::_calculate_time() + { +#if ENABLE_BLOCKS_PRE_PROCESSING + forward_pass(); + reverse_pass(); + recalculate_trapezoids(); +#endif // ENABLE_BLOCKS_PRE_PROCESSING + + _time = get_additional_time(); + + for (const Block& block : _blocks) + { + _time += block.acceleration_time(); + _time += block.cruise_time(); + _time += block.deceleration_time(); + } + } + + void GCodeTimeEstimator::_process_gcode_line(GCodeReader&, const GCodeReader::GCodeLine& line) { if (line.cmd.length() > 1) { @@ -335,6 +406,11 @@ namespace Slic3r { _processM204(line); break; } + case 205: // Advanced settings + { + _processM205(line); + break; + } case 566: // Set allowable instantaneous speed change { _processM566(line); @@ -345,16 +421,10 @@ namespace Slic3r { break; } } - -// CmdToCounterMap::iterator it = _cmdCounters.find(line.cmd); -// if (it == _cmdCounters.end()) -// _cmdCounters.insert(CmdToCounterMap::value_type(line.cmd, 1)); -// else -// ++it->second; } } - void My_GCodeTimeEstimator::_processG1(const GCodeReader::GCodeLine& line) + void GCodeTimeEstimator::_processG1(const GCodeReader::GCodeLine& line) { float lengthsScaleFactor = (get_units() == Inches) ? INCHES_TO_MM : 1.0f; @@ -379,7 +449,7 @@ namespace Slic3r { // updates feedrate from line, if present if (line.has('F')) - set_feedrate(line.get_float('F') * MMMIN_TO_MMSEC); + set_feedrate(std::max(line.get_float('F') * MMMIN_TO_MMSEC, get_minimum_feedrate())); // fills block data Block block; @@ -397,24 +467,26 @@ namespace Slic3r { return; // calculates block feedrate - float feedrate = get_feedrate(); + _curr.feedrate = std::max(get_feedrate(), get_minimum_feedrate()); float distance = block.move_length(); float invDistance = 1.0f / distance; - float axis_feedrate[Num_Axis]; float min_feedrate_factor = 1.0f; for (unsigned char a = X; a < Num_Axis; ++a) { - axis_feedrate[a] = feedrate * ::abs(block.delta_pos[a]) * invDistance; - if (axis_feedrate[a] > 0.0f) - min_feedrate_factor = std::min(min_feedrate_factor, get_axis_max_feedrate((EAxis)a) / axis_feedrate[a]); + _curr.axis_feedrate[a] = _curr.feedrate * block.delta_pos[a] * invDistance; + _curr.abs_axis_feedrate[a] = ::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 = min_feedrate_factor * feedrate; + block.feedrate.cruise = min_feedrate_factor * _curr.feedrate; + for (unsigned char a = X; a < Num_Axis; ++a) { - axis_feedrate[a] *= min_feedrate_factor; + _curr.axis_feedrate[a] *= min_feedrate_factor; + _curr.abs_axis_feedrate[a] *= min_feedrate_factor; } // calculates block acceleration @@ -430,27 +502,28 @@ namespace Slic3r { block.acceleration = acceleration; // calculates block exit feedrate - float exit_feedrate = block.feedrate; + _curr.safe_feedrate = block.feedrate.cruise; for (unsigned char a = X; a < Num_Axis; ++a) { - float half_axis_max_jerk = 0.5f * get_axis_max_jerk((EAxis)a); - if (axis_feedrate[a] > half_axis_max_jerk) - exit_feedrate = std::min(exit_feedrate, half_axis_max_jerk); + 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.exit_feedrate = exit_feedrate; + block.feedrate.exit = _curr.safe_feedrate; // calculates block entry feedrate - float vmax_junction = exit_feedrate; - if (!_blocks.empty() && (_prev.feedrate > FEEDRATE_THRESHOLD)) +#if USE_CURA_JUNCTION_VMAX + float vmax_junction = _curr.safe_feedrate; + if (!_blocks.empty() && (_prev.feedrate > PREVIOUS_FEEDRATE_THRESHOLD)) { - vmax_junction = block.feedrate; + vmax_junction = block.feedrate.cruise; float vmax_junction_factor = 1.0f; for (unsigned char a = X; a < Num_Axis; ++a) { - float abs_delta_axis_feedrate = ::abs(axis_feedrate[a] - _prev.axis_feedrate[a]); + float abs_delta_axis_feedrate = ::abs(_curr.axis_feedrate[a] - _prev.axis_feedrate[a]); float axis_max_jerk = get_axis_max_jerk((EAxis)a); if (abs_delta_axis_feedrate > axis_max_jerk) vmax_junction_factor = std::min(vmax_junction_factor, axis_max_jerk / abs_delta_axis_feedrate); @@ -460,17 +533,94 @@ namespace Slic3r { vmax_junction = std::min(_prev.feedrate, vmax_junction * vmax_junction_factor); } - block.entry_feedrate = std::min(vmax_junction, Block::max_allowable_speed(-acceleration, MINIMUM_PLANNER_SPEED, distance)); +#if ENABLE_BLOCKS_PRE_PROCESSING + float v_allowable = Block::max_allowable_speed(-acceleration, MINIMUM_PLANNER_SPEED, distance); + block.feedrate.entry = std::min(vmax_junction, v_allowable); +#else + block.feedrate.entry = std::min(vmax_junction, Block::max_allowable_speed(-acceleration, MINIMUM_PLANNER_SPEED, distance)); +#endif // ENABLE_BLOCKS_PRE_PROCESSING +#else + 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; + } + +#if ENABLE_BLOCKS_PRE_PROCESSING + float v_allowable = Block::max_allowable_speed(-acceleration, _curr.safe_feedrate, distance); + block.feedrate.entry = std::min(vmax_junction, v_allowable); +#else + block.feedrate.entry = std::min(vmax_junction, Block::max_allowable_speed(-acceleration, _curr.safe_feedrate, distance)); +#endif // ENABLE_BLOCKS_PRE_PROCESSING +#endif // USE_CURA_JUNCTION_VMAX + +#if ENABLE_BLOCKS_PRE_PROCESSING + 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; +#endif // ENABLE_BLOCKS_PRE_PROCESSING // calculates block trapezoid block.calculate_trapezoid(); - // updates previous cache - _prev.feedrate = feedrate; - for (unsigned char a = X; a < Num_Axis; ++a) - { - _prev.axis_feedrate[a] = axis_feedrate[a]; - } + // updates previous + _prev = _curr; // updates axis positions for (unsigned char a = X; a < Num_Axis; ++a) @@ -482,7 +632,7 @@ namespace Slic3r { _blocks.push_back(block); } - void My_GCodeTimeEstimator::_processG4(const GCodeReader::GCodeLine& line) + void GCodeTimeEstimator::_processG4(const GCodeReader::GCodeLine& line) { EDialect dialect = get_dialect(); @@ -500,44 +650,44 @@ namespace Slic3r { } } - void My_GCodeTimeEstimator::_processG20(const GCodeReader::GCodeLine& line) + void GCodeTimeEstimator::_processG20(const GCodeReader::GCodeLine& line) { set_units(Inches); } - void My_GCodeTimeEstimator::_processG21(const GCodeReader::GCodeLine& line) + void GCodeTimeEstimator::_processG21(const GCodeReader::GCodeLine& line) { set_units(Millimeters); } - void My_GCodeTimeEstimator::_processG28(const GCodeReader::GCodeLine& line) + void GCodeTimeEstimator::_processG28(const GCodeReader::GCodeLine& line) { // todo } - void My_GCodeTimeEstimator::_processG90(const GCodeReader::GCodeLine& line) + void GCodeTimeEstimator::_processG90(const GCodeReader::GCodeLine& line) { set_positioningType(Absolute); } - void My_GCodeTimeEstimator::_processG91(const GCodeReader::GCodeLine& line) + void GCodeTimeEstimator::_processG91(const GCodeReader::GCodeLine& line) { // >>>>>>>> THERE ARE DIALECT VARIANTS set_positioningType(Relative); } - void My_GCodeTimeEstimator::_processG92(const GCodeReader::GCodeLine& line) + void GCodeTimeEstimator::_processG92(const GCodeReader::GCodeLine& line) { // todo } - void My_GCodeTimeEstimator::_processM109(const GCodeReader::GCodeLine& line) + void GCodeTimeEstimator::_processM109(const GCodeReader::GCodeLine& line) { // todo } - void My_GCodeTimeEstimator::_processM203(const GCodeReader::GCodeLine& line) + void GCodeTimeEstimator::_processM203(const GCodeReader::GCodeLine& line) { EDialect dialect = get_dialect(); @@ -561,7 +711,7 @@ namespace Slic3r { set_axis_max_feedrate(E, line.get_float('E') * factor); } - void My_GCodeTimeEstimator::_processM204(const GCodeReader::GCodeLine& line) + void GCodeTimeEstimator::_processM204(const GCodeReader::GCodeLine& line) { if (line.has('S')) set_acceleration(line.get_float('S')); // <<<< Is this correct ? @@ -572,7 +722,29 @@ namespace Slic3r { } } - void My_GCodeTimeEstimator::_processM566(const GCodeReader::GCodeLine& line) + void GCodeTimeEstimator::_processM205(const GCodeReader::GCodeLine& line) + { + if (line.has('X')) + { + float max_jerk = line.get_float('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.get_float('Y')); + + if (line.has('Z')) + set_axis_max_jerk(Z, line.get_float('Z')); + + if (line.has('E')) + set_axis_max_jerk(E, line.get_float('E')); + + if (line.has('S')) + set_minimum_feedrate(line.get_float('S')); + } + + void GCodeTimeEstimator::_processM566(const GCodeReader::GCodeLine& line) { if (line.has('X')) set_axis_max_jerk(X, line.get_float('X') * MMMIN_TO_MMSEC); @@ -586,77 +758,119 @@ namespace Slic3r { if (line.has('E')) set_axis_max_jerk(E, line.get_float('E') * MMMIN_TO_MMSEC); } -//########################################################################################################### -void -GCodeTimeEstimator::parse(const std::string &gcode) -{ - GCodeReader::parse(gcode, boost::bind(&GCodeTimeEstimator::_parser, this, _1, _2)); -} +#if ENABLE_BLOCKS_PRE_PROCESSING + void GCodeTimeEstimator::forward_pass() + { + Block* block[2] = { nullptr, nullptr }; -void -GCodeTimeEstimator::parse_file(const std::string &file) -{ - GCodeReader::parse_file(file, boost::bind(&GCodeTimeEstimator::_parser, this, _1, _2)); -} - -void -GCodeTimeEstimator::_parser(GCodeReader&, const GCodeReader::GCodeLine &line) -{ - // std::cout << "[" << this->time << "] " << line.raw << std::endl; - if (line.cmd == "G1") { - const float dist_XY = line.dist_XY(); - const float new_F = line.new_F(); - - if (dist_XY > 0) { - //this->time += dist_XY / new_F * 60; - this->time += _accelerated_move(dist_XY, new_F/60, this->acceleration); - } else { - //this->time += std::abs(line.dist_E()) / new_F * 60; - this->time += _accelerated_move(std::abs(line.dist_E()), new_F/60, this->acceleration); - } - //this->time += std::abs(line.dist_Z()) / new_F * 60; - this->time += _accelerated_move(std::abs(line.dist_Z()), new_F/60, this->acceleration); - } else if (line.cmd == "M204" && line.has('S')) { - this->acceleration = line.get_float('S'); - } else if (line.cmd == "G4") { // swell - if (line.has('S')) { - this->time += line.get_float('S'); - } else if (line.has('P')) { - this->time += line.get_float('P')/1000; - } + for (Block& b : _blocks) + { + block[0] = block[1]; + block[1] = &b; + planner_forward_pass_kernel(block[0], block[1]); } -} -// Wildly optimistic acceleration "bell" curve modeling. -// Returns an estimate of how long the move with a given accel -// takes in seconds. -// It is assumed that the movement is smooth and uniform. -float -GCodeTimeEstimator::_accelerated_move(double length, double v, double acceleration) -{ - // for half of the move, there are 2 zones, where the speed is increasing/decreasing and - // where the speed is constant. - // Since the slowdown is assumed to be uniform, calculate the average velocity for half of the - // expected displacement. - // final velocity v = a*t => a * (dx / 0.5v) => v^2 = 2*a*dx - // v_avg = 0.5v => 2*v_avg = v - // d_x = v_avg*t => t = d_x / v_avg - acceleration = (acceleration == 0.0 ? 4000.0 : acceleration); // Set a default accel to use for print time in case it's 0 somehow. - auto half_length = length / 2.0; - auto t_init = v / acceleration; // time to final velocity - auto dx_init = (0.5*v*t_init); // Initial displacement for the time to get to final velocity - auto t = 0.0; - if (half_length >= dx_init) { - half_length -= (0.5*v*t_init); - t += t_init; - t += (half_length / v); // rest of time is at constant speed. - } else { - // If too much displacement for the expected final velocity, we don't hit the max, so reduce - // the average velocity to fit the displacement we actually are looking for. - t += std::sqrt(std::abs(length) * 2.0 * acceleration) / acceleration; + planner_forward_pass_kernel(block[1], nullptr); + } + + void GCodeTimeEstimator::reverse_pass() + { + Block* block[2] = { nullptr, nullptr }; + + for (int i = (int)_blocks.size() - 1; i >= 0; --i) + { + block[1] = block[0]; + block[0] = &_blocks[i]; + planner_reverse_pass_kernel(block[0], block[1]); } - return 2.0*t; // cut in half before, so double to get full time spent. -} + } + + void GCodeTimeEstimator::planner_forward_pass_kernel(Block* prev, Block* curr) + { + if (prev == nullptr) + return; + + // 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 ((curr == nullptr) || (next == nullptr)) + return; + + // 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() + { + Block* curr = nullptr; + Block* next = nullptr; + + for (Block& b : _blocks) + { + 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. Exit speed is set with MINIMUM_PLANNER_SPEED. Always recalculated. + if (next != nullptr) + { + Block block = *next; +#if USE_CURA_JUNCTION_VMAX + block.feedrate.exit = MINIMUM_PLANNER_SPEED; +#else + block.feedrate.exit = next->safe_feedrate; +#endif // USE_CURA_JUNCTION_VMAX + block.calculate_trapezoid(); + next->trapezoid = block.trapezoid; + next->flags.recalculate = false; + } + } +#endif // ENABLE_BLOCKS_PRE_PROCESSING } diff --git a/xs/src/libslic3r/GCodeTimeEstimator.hpp b/xs/src/libslic3r/GCodeTimeEstimator.hpp index 6e3de493f7..06d5c50e42 100644 --- a/xs/src/libslic3r/GCodeTimeEstimator.hpp +++ b/xs/src/libslic3r/GCodeTimeEstimator.hpp @@ -4,10 +4,12 @@ #include "libslic3r.h" #include "GCodeReader.hpp" +#define USE_CURA_JUNCTION_VMAX 0 +#define ENABLE_BLOCKS_PRE_PROCESSING 1 + namespace Slic3r { -//########################################################################################################### - class My_GCodeTimeEstimator : public GCodeReader + class GCodeTimeEstimator { public: enum EUnits : unsigned char @@ -51,6 +53,16 @@ namespace Slic3r { float max_jerk; // mm/s }; + struct Feedrates + { + float feedrate; // mm/s + float axis_feedrate[Num_Axis]; // mm/s + float abs_axis_feedrate[Num_Axis]; // mm/s + float safe_feedrate; // mm/s + + void reset(); + }; + struct State { EDialect dialect; @@ -60,37 +72,74 @@ namespace Slic3r { float feedrate; // mm/s float acceleration; // mm/s^2 float additional_time; // s - }; - - struct PreviousBlockCache - { - float feedrate; // mm/s - float axis_feedrate[Num_Axis]; // mm/s + float minimum_feedrate; // mm/s }; public: struct Block { + struct FeedrateProfile + { + float entry; // mm/s + float cruise; // mm/s + float exit; // mm/s + }; + struct Trapezoid { float distance; // mm float accelerate_until; // mm float decelerate_after; // mm - float entry_feedrate; // mm/s - float exit_feedrate; // mm/s + FeedrateProfile feedrate; + + float acceleration_time(float acceleration) const; + float cruise_time() const; + float deceleration_time(float acceleration) const; + float cruise_distance() const; + + // This function gives the time needed to accelerate from an initial speed to reach a final distance. + static float acceleration_time_from_distance(float initial_feedrate, float distance, float acceleration); + + // This function gives the final speed while accelerating at the given constant acceleration from the given initial speed along the given distance. + static float speed_from_distance(float initial_feedrate, float distance, float acceleration); }; - float delta_pos[Num_Axis]; // mm - float feedrate; // mm/s - float acceleration; // mm/s^2 - float entry_feedrate; // mm/s - float exit_feedrate; // mm/s +#if ENABLE_BLOCKS_PRE_PROCESSING + struct Flags + { + bool recalculate; + bool nominal_length; + }; + Flags flags; +#endif // ENABLE_BLOCKS_PRE_PROCESSING + + float delta_pos[Num_Axis]; // mm + float acceleration; // mm/s^2 +#if ENABLE_BLOCKS_PRE_PROCESSING + float max_entry_speed; // mm/s + float safe_feedrate; // mm/s +#endif // ENABLE_BLOCKS_PRE_PROCESSING + + FeedrateProfile feedrate; Trapezoid trapezoid; // Returns the length of the move covered by this block, in mm float move_length() const; + // Returns the time spent accelerating toward cruise speed, in seconds + float acceleration_time() const; + + // Returns the time spent at cruise speed, in seconds + float cruise_time() const; + + // Returns the time spent decelerating from cruise speed, in seconds + float deceleration_time() const; + + // Returns the distance covered at cruise speed, in mm + float cruise_distance() const; + + // Calculates this block's trapezoid void calculate_trapezoid(); // Calculates the maximum allowable speed at this point when you must be able to reach target_velocity using the @@ -105,29 +154,26 @@ namespace Slic3r { // a total travel of distance. This can be used to compute the intersection point between acceleration and // deceleration in the cases where the trapezoid has no plateau (i.e. never reaches maximum speed) static float intersection_distance(float initial_rate, float final_rate, float acceleration, float distance); - - // This function gives the time it needs to accelerate from an initial speed to reach a final distance. - static float acceleration_time_from_distance(float initial_feedrate, float distance, float acceleration); }; typedef std::vector BlocksList; private: -// typedef std::map CmdToCounterMap; -// CmdToCounterMap _cmdCounters; - + GCodeReader _parser; State _state; - PreviousBlockCache _prev; + Feedrates _curr; + Feedrates _prev; BlocksList _blocks; float _time; // s public: - My_GCodeTimeEstimator(); + GCodeTimeEstimator(); - void parse(const std::string& gcode); - void parse_file(const std::string& file); + // Calculates the time estimate from the given gcode in string format + void calculate_time_from_text(const std::string& gcode); - void calculate_time(); + // Calculates the time estimate from the gcode contained in the file with the given filename + void calculate_time_from_file(const std::string& file); void set_axis_position(EAxis axis, float position); void set_axis_max_feedrate(EAxis axis, float feedrate_mm_sec); @@ -145,6 +191,9 @@ namespace Slic3r { void set_acceleration(float acceleration); float get_acceleration() const; + void set_minimum_feedrate(float feedrate_mm_sec); + float get_minimum_feedrate() const; + void set_dialect(EDialect dialect); EDialect get_dialect() const; @@ -155,20 +204,23 @@ namespace Slic3r { EPositioningType get_positioningType() const; void add_additional_time(float timeSec); + void set_additional_time(float timeSec); float get_additional_time() const; void set_default(); - // returns estimated time in seconds + // Returns the estimated time, in seconds float get_time() const; - const BlocksList& get_blocks() const; - -// void print_counters() const; + // Returns the estimated time, in format HH:MM:SS + std::string get_time_hms() const; private: void _reset(); + // Calculates the time estimate + void _calculate_time(); + // Processes GCode line void _process_gcode_line(GCodeReader&, const GCodeReader::GCodeLine& line); @@ -205,23 +257,22 @@ namespace Slic3r { // Set default acceleration void _processM204(const GCodeReader::GCodeLine& line); + // Advanced settings + void _processM205(const GCodeReader::GCodeLine& line); + // Set allowable instantaneous speed change void _processM566(const GCodeReader::GCodeLine& line); - }; -//########################################################################################################### -class GCodeTimeEstimator : public GCodeReader { - public: - float time = 0; // in seconds - - void parse(const std::string &gcode); - void parse_file(const std::string &file); - - protected: - float acceleration = 9000; - void _parser(GCodeReader&, const GCodeReader::GCodeLine &line); - static float _accelerated_move(double length, double v, double acceleration); -}; +#if ENABLE_BLOCKS_PRE_PROCESSING + void forward_pass(); + void reverse_pass(); + + void planner_forward_pass_kernel(Block* prev, Block* curr); + void planner_reverse_pass_kernel(Block* curr, Block* next); + + void recalculate_trapezoids(); +#endif // ENABLE_BLOCKS_PRE_PROCESSING + }; } /* namespace Slic3r */