diff --git a/CMakeLists.txt b/CMakeLists.txt index 3097f056c5..8a486000fc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -414,6 +414,7 @@ if(SLIC3R_STATIC) endif() set(TBB_DEBUG 1) find_package(TBB REQUIRED) +slic3r_remap_configs(TBB::tbb RelWithDebInfo Release) # include_directories(${TBB_INCLUDE_DIRS}) # add_definitions(${TBB_DEFINITIONS}) # if(MSVC) diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index 8124b23e3e..8227ea4ad6 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -581,11 +581,6 @@ static void process_arrangeable(const ArrangePolygon &arrpoly, const Vec2crd &offs = arrpoly.translation; double rotation = arrpoly.rotation; - // This fixes: - // https://github.com/prusa3d/PrusaSlicer/issues/2209 - if (p.points.size() < 3) - return; - outp.emplace_back(std::move(p)); outp.back().rotation(rotation); outp.back().translation({offs.x(), offs.y()}); diff --git a/src/libslic3r/Format/SL1.cpp b/src/libslic3r/Format/SL1.cpp index df10ce0465..2f324accda 100644 --- a/src/libslic3r/Format/SL1.cpp +++ b/src/libslic3r/Format/SL1.cpp @@ -503,21 +503,19 @@ static void write_thumbnail(Zipper &zipper, const ThumbnailData &data) } } -void SL1Archive::export_print(const std::string fname, +void SL1Archive::export_print(Zipper &zipper, const SLAPrint &print, const ThumbnailsList &thumbnails, const std::string &prjname) { - Zipper zipper{fname}; - std::string project = prjname.empty() ? boost::filesystem::path(zipper.get_filename()).stem().string() : prjname; - + ConfMap iniconf, slicerconf; fill_iniconf(iniconf, print); - + iniconf["jobDir"] = project; fill_slicerconf(slicerconf, print); @@ -527,13 +525,13 @@ void SL1Archive::export_print(const std::string fname, zipper << to_ini(iniconf); zipper.add_entry("prusaslicer.ini"); zipper << to_ini(slicerconf); - + size_t i = 0; for (const sla::EncodedRaster &rst : m_layers) { std::string imgname = project + string_printf("%.5d", i++) + "." + rst.extension(); - + zipper.add_entry(imgname.c_str(), rst.data(), rst.size()); } @@ -549,4 +547,14 @@ void SL1Archive::export_print(const std::string fname, } } +void SL1Archive::export_print(const std::string fname, + const SLAPrint &print, + const ThumbnailsList &thumbnails, + const std::string &prjname) +{ + Zipper zipper{fname, Zipper::FAST_COMPRESSION}; + + export_print(zipper, print, thumbnails, prjname); +} + } // namespace Slic3r diff --git a/src/libslic3r/Format/SL1.hpp b/src/libslic3r/Format/SL1.hpp index 493550db4b..0a662cc1e0 100644 --- a/src/libslic3r/Format/SL1.hpp +++ b/src/libslic3r/Format/SL1.hpp @@ -22,8 +22,13 @@ protected: SLAPrinterConfig & cfg() { return m_cfg; } const SLAPrinterConfig & cfg() const { return m_cfg; } + void export_print(Zipper &, + const SLAPrint &print, + const ThumbnailsList &thumbnails, + const std::string &projectname); + public: - + SL1Archive() = default; explicit SL1Archive(const SLAPrinterConfig &cfg): m_cfg(cfg) {} explicit SL1Archive(SLAPrinterConfig &&cfg): m_cfg(std::move(cfg)) {} diff --git a/src/libslic3r/Format/SL1_SVG.cpp b/src/libslic3r/Format/SL1_SVG.cpp index 372348283a..d138a72ba8 100644 --- a/src/libslic3r/Format/SL1_SVG.cpp +++ b/src/libslic3r/Format/SL1_SVG.cpp @@ -224,4 +224,14 @@ sla::RasterEncoder SL1_SVGArchive::get_encoder() const return nullptr; } +void SL1_SVGArchive::export_print(const std::string fname, + const SLAPrint &print, + const ThumbnailsList &thumbnails, + const std::string &projectname) +{ + Zipper zipper{fname, Zipper::TIGHT_COMPRESSION}; + + SL1Archive::export_print(zipper, print, thumbnails, projectname); +} + } // namespace Slic3r diff --git a/src/libslic3r/Format/SL1_SVG.hpp b/src/libslic3r/Format/SL1_SVG.hpp index a3afbcdfff..a764f1a4c0 100644 --- a/src/libslic3r/Format/SL1_SVG.hpp +++ b/src/libslic3r/Format/SL1_SVG.hpp @@ -14,6 +14,11 @@ protected: public: + void export_print(const std::string fname, + const SLAPrint &print, + const ThumbnailsList &thumbnails, + const std::string &projectname = "") override; + using SL1Archive::SL1Archive; }; diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index a46103d6c8..f4cf12064d 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -1,3742 +1,4140 @@ -#include "libslic3r/libslic3r.h" -#include "libslic3r/Utils.hpp" -#include "libslic3r/Print.hpp" -#include "libslic3r/LocalesUtils.hpp" -#include "libslic3r/format.hpp" -#include "GCodeProcessor.hpp" - -#include -#include -#include -#include -#include -#include - -#include -#include - -#if __has_include() - #include - #include -#endif - -#include - -static const float DEFAULT_TOOLPATH_WIDTH = 0.4f; -static const float DEFAULT_TOOLPATH_HEIGHT = 0.2f; - -static const float INCHES_TO_MM = 25.4f; -static const float MMMIN_TO_MMSEC = 1.0f / 60.0f; -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_TRAVEL_ACCELERATION = 1250.0f; - -static const size_t MIN_EXTRUDERS_COUNT = 5; -static const float DEFAULT_FILAMENT_DIAMETER = 1.75f; -static const float DEFAULT_FILAMENT_DENSITY = 1.245f; -static const Slic3r::Vec3f DEFAULT_EXTRUDER_OFFSET = Slic3r::Vec3f::Zero(); - -#if ENABLE_PROCESS_G2_G3_LINES -static const std::string INTERNAL_G2G3_TAG = "!#!#! internal only - from G2/G3 expansion !#!#!"; -#endif // ENABLE_PROCESS_G2_G3_LINES - -namespace Slic3r { - -const std::vector GCodeProcessor::Reserved_Tags = { - "TYPE:", - "WIPE_START", - "WIPE_END", - "HEIGHT:", - "WIDTH:", - "LAYER_CHANGE", - "COLOR_CHANGE", - "PAUSE_PRINT", - "CUSTOM_GCODE", - "_GP_FIRST_LINE_M73_PLACEHOLDER", - "_GP_LAST_LINE_M73_PLACEHOLDER", - "_GP_ESTIMATED_PRINTING_TIME_PLACEHOLDER" -}; - -const float GCodeProcessor::Wipe_Width = 0.05f; -const float GCodeProcessor::Wipe_Height = 0.05f; - -#if ENABLE_GCODE_VIEWER_DATA_CHECKING -const std::string GCodeProcessor::Mm3_Per_Mm_Tag = "MM3_PER_MM:"; -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING - -static void set_option_value(ConfigOptionFloats& option, size_t id, float value) -{ - if (id < option.values.size()) - option.values[id] = static_cast(value); -}; - -static float get_option_value(const ConfigOptionFloats& option, size_t id) -{ - return option.values.empty() ? 0.0f : - ((id < option.values.size()) ? static_cast(option.values[id]) : static_cast(option.values.back())); -} - -static float estimated_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); -} - -static float 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); -} - -static float speed_from_distance(float initial_feedrate, float distance, float acceleration) -{ - // to avoid invalid negative numbers due to numerical errors - float value = std::max(0.0f, sqr(initial_feedrate) + 2.0f * acceleration * distance); - return ::sqrt(value); -} - -// Calculates the maximum allowable speed at this point when you must be able to reach target_velocity using the -// acceleration within the allotted distance. -static float max_allowable_speed(float acceleration, float target_velocity, float distance) -{ - // to avoid invalid negative numbers due to numerical errors - float value = std::max(0.0f, sqr(target_velocity) - 2.0f * acceleration * distance); - return std::sqrt(value); -} - -static float 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; -} - -void GCodeProcessor::CachedPosition::reset() -{ - std::fill(position.begin(), position.end(), FLT_MAX); - feedrate = FLT_MAX; -} - -void GCodeProcessor::CpColor::reset() -{ - counter = 0; - current = 0; -} - -float GCodeProcessor::Trapezoid::acceleration_time(float entry_feedrate, float acceleration) const -{ - return acceleration_time_from_distance(entry_feedrate, accelerate_until, acceleration); -} - -float GCodeProcessor::Trapezoid::cruise_time() const -{ - return (cruise_feedrate != 0.0f) ? cruise_distance() / cruise_feedrate : 0.0f; -} - -float GCodeProcessor::Trapezoid::deceleration_time(float distance, float acceleration) const -{ - return acceleration_time_from_distance(cruise_feedrate, (distance - decelerate_after), -acceleration); -} - -float GCodeProcessor::Trapezoid::cruise_distance() const -{ - return decelerate_after - accelerate_until; -} - -void GCodeProcessor::TimeBlock::calculate_trapezoid() -{ - trapezoid.cruise_feedrate = feedrate_profile.cruise; - - float accelerate_distance = std::max(0.0f, estimated_acceleration_distance(feedrate_profile.entry, feedrate_profile.cruise, acceleration)); - float decelerate_distance = std::max(0.0f, estimated_acceleration_distance(feedrate_profile.cruise, feedrate_profile.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 = std::clamp(intersection_distance(feedrate_profile.entry, feedrate_profile.exit, acceleration, distance), 0.0f, distance); - cruise_distance = 0.0f; - trapezoid.cruise_feedrate = speed_from_distance(feedrate_profile.entry, accelerate_distance, acceleration); - } - - trapezoid.accelerate_until = accelerate_distance; - trapezoid.decelerate_after = accelerate_distance + cruise_distance; -} - -float GCodeProcessor::TimeBlock::time() const -{ - return trapezoid.acceleration_time(feedrate_profile.entry, acceleration) - + trapezoid.cruise_time() - + trapezoid.deceleration_time(distance, acceleration); -} - -void GCodeProcessor::TimeMachine::State::reset() -{ - feedrate = 0.0f; - safe_feedrate = 0.0f; - axis_feedrate = { 0.0f, 0.0f, 0.0f, 0.0f }; - abs_axis_feedrate = { 0.0f, 0.0f, 0.0f, 0.0f }; -} - -void GCodeProcessor::TimeMachine::CustomGCodeTime::reset() -{ - needed = false; - cache = 0.0f; - times = std::vector>(); -} - -void GCodeProcessor::TimeMachine::reset() -{ - enabled = false; - acceleration = 0.0f; - max_acceleration = 0.0f; - retract_acceleration = 0.0f; - max_retract_acceleration = 0.0f; - travel_acceleration = 0.0f; - max_travel_acceleration = 0.0f; - extrude_factor_override_percentage = 1.0f; - time = 0.0f; -#if ENABLE_TRAVEL_TIME - travel_time = 0.0f; -#endif // ENABLE_TRAVEL_TIME - stop_times = std::vector(); - curr.reset(); - prev.reset(); - gcode_time.reset(); - blocks = std::vector(); - g1_times_cache = std::vector(); - std::fill(moves_time.begin(), moves_time.end(), 0.0f); - std::fill(roles_time.begin(), roles_time.end(), 0.0f); - layers_time = std::vector(); -} - -void GCodeProcessor::TimeMachine::simulate_st_synchronize(float additional_time) -{ - if (!enabled) - return; - - calculate_time(0, additional_time); -} - -static void planner_forward_pass_kernel(GCodeProcessor::TimeBlock& prev, GCodeProcessor::TimeBlock& curr) -{ - // 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_profile.entry < curr.feedrate_profile.entry) { - float entry_speed = std::min(curr.feedrate_profile.entry, max_allowable_speed(-prev.acceleration, prev.feedrate_profile.entry, prev.distance)); - - // Check for junction speed change - if (curr.feedrate_profile.entry != entry_speed) { - curr.feedrate_profile.entry = entry_speed; - curr.flags.recalculate = true; - } - } - } -} - -void planner_reverse_pass_kernel(GCodeProcessor::TimeBlock& curr, GCodeProcessor::TimeBlock& 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_profile.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_profile.entry) - curr.feedrate_profile.entry = std::min(curr.max_entry_speed, max_allowable_speed(-curr.acceleration, next.feedrate_profile.entry, curr.distance)); - else - curr.feedrate_profile.entry = curr.max_entry_speed; - - curr.flags.recalculate = true; - } -} - -static void recalculate_trapezoids(std::vector& blocks) -{ - GCodeProcessor::TimeBlock* curr = nullptr; - GCodeProcessor::TimeBlock* next = nullptr; - - for (size_t i = 0; i < blocks.size(); ++i) { - GCodeProcessor::TimeBlock& 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. - GCodeProcessor::TimeBlock block = *curr; - block.feedrate_profile.exit = next->feedrate_profile.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) { - GCodeProcessor::TimeBlock block = *next; - block.feedrate_profile.exit = next->safe_feedrate; - block.calculate_trapezoid(); - next->trapezoid = block.trapezoid; - next->flags.recalculate = false; - } -} - -void GCodeProcessor::TimeMachine::calculate_time(size_t keep_last_n_blocks, float additional_time) -{ - if (!enabled || blocks.size() < 2) - return; - - assert(keep_last_n_blocks <= blocks.size()); - - // forward_pass - for (size_t i = 0; i + 1 < blocks.size(); ++i) { - planner_forward_pass_kernel(blocks[i], blocks[i + 1]); - } - - // reverse_pass - for (int i = static_cast(blocks.size()) - 1; i > 0; --i) - planner_reverse_pass_kernel(blocks[i - 1], blocks[i]); - - recalculate_trapezoids(blocks); - - size_t n_blocks_process = blocks.size() - keep_last_n_blocks; - for (size_t i = 0; i < n_blocks_process; ++i) { - const TimeBlock& block = blocks[i]; - float block_time = block.time(); - if (i == 0) - block_time += additional_time; - - time += block_time; -#if ENABLE_TRAVEL_TIME - if (block.move_type == EMoveType::Travel) - travel_time += block_time; - else - roles_time[static_cast(block.role)] += block_time; -#endif // ENABLE_TRAVEL_TIME - gcode_time.cache += block_time; - moves_time[static_cast(block.move_type)] += block_time; -#if !ENABLE_TRAVEL_TIME - roles_time[static_cast(block.role)] += block_time; -#endif // !ENABLE_TRAVEL_TIME - if (block.layer_id >= layers_time.size()) { - const size_t curr_size = layers_time.size(); - layers_time.resize(block.layer_id); - for (size_t i = curr_size; i < layers_time.size(); ++i) { - layers_time[i] = 0.0f; - } - } - layers_time[block.layer_id - 1] += block_time; - g1_times_cache.push_back({ block.g1_line_id, time }); - // update times for remaining time to printer stop placeholders - auto it_stop_time = std::lower_bound(stop_times.begin(), stop_times.end(), block.g1_line_id, - [](const StopTime& t, unsigned int value) { return t.g1_line_id < value; }); - if (it_stop_time != stop_times.end() && it_stop_time->g1_line_id == block.g1_line_id) - it_stop_time->elapsed_time = time; - } - - if (keep_last_n_blocks) - blocks.erase(blocks.begin(), blocks.begin() + n_blocks_process); - else - blocks.clear(); -} - -void GCodeProcessor::TimeProcessor::reset() -{ - extruder_unloaded = true; - export_remaining_time_enabled = false; - machine_envelope_processing_enabled = false; - machine_limits = MachineEnvelopeConfig(); - filament_load_times = std::vector(); - filament_unload_times = std::vector(); - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - machines[i].reset(); - } - machines[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].enabled = true; -} - -void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, std::vector& moves, std::vector& lines_ends) -{ - FilePtr in{ boost::nowide::fopen(filename.c_str(), "rb") }; - if (in.f == nullptr) - throw Slic3r::RuntimeError(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"; - FilePtr out{ boost::nowide::fopen(out_path.c_str(), "wb") }; - if (out.f == nullptr) { - throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nCannot open file for writing.\n")); - } - - auto time_in_minutes = [](float time_in_seconds) { - assert(time_in_seconds >= 0.f); - return int((time_in_seconds + 0.5f) / 60.0f); - }; - - auto time_in_last_minute = [](float time_in_seconds) { - assert(time_in_seconds <= 60.0f); - return time_in_seconds / 60.0f; - }; - - auto format_line_M73_main = [](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); - }; - - auto format_line_M73_stop_int = [](const std::string& mask, int time) { - char line_M73[64]; - sprintf(line_M73, mask.c_str(), std::to_string(time).c_str()); - return std::string(line_M73); - }; - - auto format_time_float = [](float time) { - return Slic3r::float_to_string_decimal_point(time, 2); - }; - - auto format_line_M73_stop_float = [format_time_float](const std::string& mask, float time) { - char line_M73[64]; - sprintf(line_M73, mask.c_str(), format_time_float(time).c_str()); - return std::string(line_M73); - }; - - std::string gcode_line; - size_t g1_lines_counter = 0; - // keeps track of last exported pair - std::array, static_cast(PrintEstimatedStatistics::ETimeMode::Count)> last_exported_main; - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - last_exported_main[i] = { 0, time_in_minutes(machines[i].time) }; - } - - // keeps track of last exported remaining time to next printer stop - std::array(PrintEstimatedStatistics::ETimeMode::Count)> last_exported_stop; - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - last_exported_stop[i] = 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 - // gcode_line is in/out parameter, to reduce expensive memory allocation - auto process_placeholders = [&](std::string& gcode_line) { - unsigned int extra_lines_count = 0; - - // remove trailing '\n' - auto line = std::string_view(gcode_line).substr(0, gcode_line.length() - 1); - - std::string ret; - if (line.length() > 1) { - line = line.substr(1); - if (export_remaining_time_enabled && - (line == reserved_tag(ETags::First_Line_M73_Placeholder) || line == reserved_tag(ETags::Last_Line_M73_Placeholder))) { - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - const TimeMachine& machine = machines[i]; - if (machine.enabled) { - // export pair - ret += format_line_M73_main(machine.line_m73_main_mask.c_str(), - (line == reserved_tag(ETags::First_Line_M73_Placeholder)) ? 0 : 100, - (line == reserved_tag(ETags::First_Line_M73_Placeholder)) ? time_in_minutes(machine.time) : 0); - ++extra_lines_count; - - // export remaining time to next printer stop - if (line == reserved_tag(ETags::First_Line_M73_Placeholder) && !machine.stop_times.empty()) { - int to_export_stop = time_in_minutes(machine.stop_times.front().elapsed_time); - ret += format_line_M73_stop_int(machine.line_m73_stop_mask.c_str(), to_export_stop); - last_exported_stop[i] = to_export_stop; - ++extra_lines_count; - } - } - } - } - else if (line == reserved_tag(ETags::Estimated_Printing_Time_Placeholder)) { - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - const TimeMachine& machine = machines[i]; - PrintEstimatedStatistics::ETimeMode mode = static_cast(i); - if (mode == PrintEstimatedStatistics::ETimeMode::Normal || machine.enabled) { - char buf[128]; - sprintf(buf, "; estimated printing time (%s mode) = %s\n", - (mode == PrintEstimatedStatistics::ETimeMode::Normal) ? "normal" : "silent", - get_time_dhms(machine.time).c_str()); - ret += buf; - } - } - } - } - - if (! ret.empty()) - // Not moving the move operator on purpose, so that the gcode_line allocation will grow and it will not be reallocated after handful of lines are processed. - gcode_line = ret; - return std::tuple(!ret.empty(), (extra_lines_count == 0) ? extra_lines_count : extra_lines_count - 1); - }; - - // check for temporary lines - auto is_temporary_decoration = [](const std::string_view gcode_line) { - // remove trailing '\n' - assert(! gcode_line.empty()); - assert(gcode_line.back() == '\n'); - - // return true for decorations which are used in processing the gcode but that should not be exported into the final gcode - // i.e.: - // bool ret = gcode_line.substr(0, gcode_line.length() - 1) == ";" + Layer_Change_Tag; - // ... - // return ret; - return false; - }; - - // Iterators for the normal and silent cached time estimate entry recently processed, used by process_line_G1. - auto g1_times_cache_it = Slic3r::reserve_vector::const_iterator>(machines.size()); - for (const auto& machine : machines) - g1_times_cache_it.emplace_back(machine.g1_times_cache.begin()); - - // add lines M73 to exported gcode - auto process_line_G1 = [ - // Lambdas, mostly for string formatting, all with an empty capture block. - time_in_minutes, format_time_float, format_line_M73_main, format_line_M73_stop_int, format_line_M73_stop_float, time_in_last_minute, - &self = std::as_const(*this), - // Caches, to be modified - &g1_times_cache_it, &last_exported_main, &last_exported_stop, - // String output - &export_line] - (const size_t g1_lines_counter) { - unsigned int exported_lines_count = 0; - if (self.export_remaining_time_enabled) { - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - const TimeMachine& machine = self.machines[i]; - if (machine.enabled) { - // export pair - // Skip all machine.g1_times_cache below g1_lines_counter. - auto& it = g1_times_cache_it[i]; - while (it != machine.g1_times_cache.end() && it->id < g1_lines_counter) - ++it; - if (it != machine.g1_times_cache.end() && it->id == g1_lines_counter) { - std::pair to_export_main = { int(100.0f * it->elapsed_time / machine.time), - time_in_minutes(machine.time - it->elapsed_time) }; - if (last_exported_main[i] != to_export_main) { - export_line += format_line_M73_main(machine.line_m73_main_mask.c_str(), - to_export_main.first, to_export_main.second); - last_exported_main[i] = to_export_main; - ++exported_lines_count; - } - // export remaining time to next printer stop - auto it_stop = std::upper_bound(machine.stop_times.begin(), machine.stop_times.end(), it->elapsed_time, - [](float value, const TimeMachine::StopTime& t) { return value < t.elapsed_time; }); - if (it_stop != machine.stop_times.end()) { - int to_export_stop = time_in_minutes(it_stop->elapsed_time - it->elapsed_time); - if (last_exported_stop[i] != to_export_stop) { - if (to_export_stop > 0) { - if (last_exported_stop[i] != to_export_stop) { - export_line += format_line_M73_stop_int(machine.line_m73_stop_mask.c_str(), to_export_stop); - last_exported_stop[i] = to_export_stop; - ++exported_lines_count; - } - } - else { - bool is_last = false; - auto next_it = it + 1; - is_last |= (next_it == machine.g1_times_cache.end()); - - if (next_it != machine.g1_times_cache.end()) { - auto next_it_stop = std::upper_bound(machine.stop_times.begin(), machine.stop_times.end(), next_it->elapsed_time, - [](float value, const TimeMachine::StopTime& t) { return value < t.elapsed_time; }); - is_last |= (next_it_stop != it_stop); - - std::string time_float_str = format_time_float(time_in_last_minute(it_stop->elapsed_time - it->elapsed_time)); - std::string next_time_float_str = format_time_float(time_in_last_minute(it_stop->elapsed_time - next_it->elapsed_time)); - is_last |= (string_to_double_decimal_point(time_float_str) > 0. && string_to_double_decimal_point(next_time_float_str) == 0.); - } - - if (is_last) { - if (std::distance(machine.stop_times.begin(), it_stop) == static_cast(machine.stop_times.size() - 1)) - export_line += format_line_M73_stop_int(machine.line_m73_stop_mask.c_str(), to_export_stop); - else - export_line += format_line_M73_stop_float(machine.line_m73_stop_mask.c_str(), time_in_last_minute(it_stop->elapsed_time - it->elapsed_time)); - - last_exported_stop[i] = to_export_stop; - ++exported_lines_count; - } - } - } - } - } - } - } - } - return exported_lines_count; - }; - - // helper function to write to disk - size_t out_file_pos = 0; - lines_ends.clear(); - auto write_string = [&export_line, &out, &out_path, &out_file_pos, &lines_ends](const std::string& str) { - fwrite((const void*)export_line.c_str(), 1, export_line.length(), out.f); - if (ferror(out.f)) { - out.close(); - boost::nowide::remove(out_path.c_str()); - throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nIs the disk full?\n")); - } - for (size_t i = 0; i < export_line.size(); ++ i) - if (export_line[i] == '\n') - lines_ends.emplace_back(out_file_pos + i + 1); - out_file_pos += export_line.size(); - export_line.clear(); - }; - - unsigned int line_id = 0; - std::vector> offsets; - - { - // Read the input stream 64kB at a time, extract lines and process them. - std::vector buffer(65536 * 10, 0); - // Line buffer. - assert(gcode_line.empty()); - for (;;) { - size_t cnt_read = ::fread(buffer.data(), 1, buffer.size(), in.f); - if (::ferror(in.f)) - throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nError while reading from file.\n")); - bool eof = cnt_read == 0; - auto it = buffer.begin(); - auto it_bufend = buffer.begin() + cnt_read; - while (it != it_bufend || (eof && ! gcode_line.empty())) { - // Find end of line. - bool eol = false; - auto it_end = it; - for (; it_end != it_bufend && ! (eol = *it_end == '\r' || *it_end == '\n'); ++ it_end) ; - // End of line is indicated also if end of file was reached. - eol |= eof && it_end == it_bufend; - gcode_line.insert(gcode_line.end(), it, it_end); - if (eol) { - ++line_id; - - gcode_line += "\n"; - // replace placeholder lines - auto [processed, lines_added_count] = process_placeholders(gcode_line); - if (processed && lines_added_count > 0) - offsets.push_back({ line_id, lines_added_count }); - if (! processed && ! is_temporary_decoration(gcode_line) && GCodeReader::GCodeLine::cmd_is(gcode_line, "G1")) { - // remove temporary lines, add lines M73 where needed - unsigned int extra_lines_count = process_line_G1(g1_lines_counter ++); - if (extra_lines_count > 0) - offsets.push_back({ line_id, extra_lines_count }); - } - - export_line += gcode_line; - if (export_line.length() > 65535) - write_string(export_line); - gcode_line.clear(); - } - // Skip EOL. - it = it_end; - if (it != it_bufend && *it == '\r') - ++ it; - if (it != it_bufend && *it == '\n') - ++ it; - } - if (eof) - break; - } - } - - if (!export_line.empty()) - write_string(export_line); - - out.close(); - in.close(); - - // updates moves' gcode ids which have been modified by the insertion of the M73 lines - unsigned int curr_offset_id = 0; - unsigned int total_offset = 0; - for (GCodeProcessorResult::MoveVertex& move : moves) { - while (curr_offset_id < static_cast(offsets.size()) && offsets[curr_offset_id].first <= move.gcode_id) { - total_offset += offsets[curr_offset_id].second; - ++curr_offset_id; - } - move.gcode_id += total_offset; - } - - if (rename_file(out_path, filename)) - throw Slic3r::RuntimeError(std::string("Failed to rename the output G-code file from ") + out_path + " to " + filename + '\n' + - "Is " + out_path + " locked?" + '\n'); -} - -void GCodeProcessor::UsedFilaments::reset() -{ - color_change_cache = 0.0f; - volumes_per_color_change = std::vector(); - - tool_change_cache = 0.0f; - volumes_per_extruder.clear(); - - role_cache = 0.0f; - filaments_per_role.clear(); -} - -void GCodeProcessor::UsedFilaments::increase_caches(double extruded_volume) -{ - color_change_cache += extruded_volume; - tool_change_cache += extruded_volume; - role_cache += extruded_volume; -} - -void GCodeProcessor::UsedFilaments::process_color_change_cache() -{ - if (color_change_cache != 0.0f) { - volumes_per_color_change.push_back(color_change_cache); - color_change_cache = 0.0f; - } -} - -void GCodeProcessor::UsedFilaments::process_extruder_cache(GCodeProcessor* processor) -{ - size_t active_extruder_id = processor->m_extruder_id; - if (tool_change_cache != 0.0f) { - if (volumes_per_extruder.find(active_extruder_id) != volumes_per_extruder.end()) - volumes_per_extruder[active_extruder_id] += tool_change_cache; - else - volumes_per_extruder[active_extruder_id] = tool_change_cache; - tool_change_cache = 0.0f; - } -} - -void GCodeProcessor::UsedFilaments::process_role_cache(GCodeProcessor* processor) -{ - if (role_cache != 0.0f) { - std::pair filament = { 0.0f, 0.0f }; - - double s = PI * sqr(0.5 * processor->m_result.filament_diameters[processor->m_extruder_id]); - filament.first = role_cache / s * 0.001; - filament.second = role_cache * processor->m_result.filament_densities[processor->m_extruder_id] * 0.001; - - ExtrusionRole active_role = processor->m_extrusion_role; - if (filaments_per_role.find(active_role) != filaments_per_role.end()) { - filaments_per_role[active_role].first += filament.first; - filaments_per_role[active_role].second += filament.second; - } - else - filaments_per_role[active_role] = filament; - role_cache = 0.0f; - } -} - -void GCodeProcessor::UsedFilaments::process_caches(GCodeProcessor* processor) -{ - process_color_change_cache(); - process_extruder_cache(processor); - process_role_cache(processor); -} - -#if ENABLE_GCODE_VIEWER_STATISTICS -void GCodeProcessorResult::reset() { - moves = std::vector(); - bed_shape = Pointfs(); - max_print_height = 0.0f; - settings_ids.reset(); - extruders_count = 0; - extruder_colors = std::vector(); - filament_diameters = std::vector(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_DIAMETER); - filament_densities = std::vector(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_DENSITY); - custom_gcode_per_print_z = std::vector(); - spiral_vase_layers = std::vector>>(); - time = 0; -} -#else -void GCodeProcessorResult::reset() { - - moves.clear(); - lines_ends.clear(); - bed_shape = Pointfs(); - max_print_height = 0.0f; - settings_ids.reset(); - extruders_count = 0; - extruder_colors = std::vector(); - filament_diameters = std::vector(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_DIAMETER); - filament_densities = std::vector(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_DENSITY); - custom_gcode_per_print_z = std::vector(); - spiral_vase_layers = std::vector>>(); -} -#endif // ENABLE_GCODE_VIEWER_STATISTICS - -const std::vector> GCodeProcessor::Producers = { - { EProducer::PrusaSlicer, "generated by PrusaSlicer" }, - { EProducer::Slic3rPE, "generated by Slic3r Prusa Edition" }, - { EProducer::Slic3r, "generated by Slic3r" }, - { EProducer::SuperSlicer, "generated by SuperSlicer" }, - { EProducer::Cura, "Cura_SteamEngine" }, - { EProducer::Simplify3D, "G-Code generated by Simplify3D(R)" }, - { EProducer::CraftWare, "CraftWare" }, - { EProducer::ideaMaker, "ideaMaker" }, - { EProducer::KissSlicer, "KISSlicer" } -}; - -unsigned int GCodeProcessor::s_result_id = 0; - -bool GCodeProcessor::contains_reserved_tag(const std::string& gcode, std::string& found_tag) -{ - bool ret = false; - - GCodeReader parser; - parser.parse_buffer(gcode, [&ret, &found_tag](GCodeReader& parser, const GCodeReader::GCodeLine& line) { - std::string comment = line.raw(); - if (comment.length() > 2 && comment.front() == ';') { - comment = comment.substr(1); - for (const std::string& s : Reserved_Tags) { - if (boost::starts_with(comment, s)) { - ret = true; - found_tag = comment; - parser.quit_parsing(); - return; - } - } - } - }); - - return ret; -} - -bool GCodeProcessor::contains_reserved_tags(const std::string& gcode, unsigned int max_count, std::vector& found_tag) -{ - max_count = std::max(max_count, 1U); - - bool ret = false; - - CNumericLocalesSetter locales_setter; - - GCodeReader parser; - parser.parse_buffer(gcode, [&ret, &found_tag, max_count](GCodeReader& parser, const GCodeReader::GCodeLine& line) { - std::string comment = line.raw(); - if (comment.length() > 2 && comment.front() == ';') { - comment = comment.substr(1); - for (const std::string& s : Reserved_Tags) { - if (boost::starts_with(comment, s)) { - ret = true; - found_tag.push_back(comment); - if (found_tag.size() == max_count) { - parser.quit_parsing(); - return; - } - } - } - } - }); - - return ret; -} - -GCodeProcessor::GCodeProcessor() -: m_options_z_corrector(m_result) -{ - reset(); - m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].line_m73_main_mask = "M73 P%s R%s\n"; - m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].line_m73_stop_mask = "M73 C%s\n"; - m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].line_m73_main_mask = "M73 Q%s S%s\n"; - m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].line_m73_stop_mask = "M73 D%s\n"; -} - -void GCodeProcessor::apply_config(const PrintConfig& config) -{ - m_parser.apply_config(config); - - m_flavor = config.gcode_flavor; - - size_t extruders_count = config.nozzle_diameter.values.size(); - m_result.extruders_count = extruders_count; - - m_extruder_offsets.resize(extruders_count); - m_extruder_colors.resize(extruders_count); - m_result.filament_diameters.resize(extruders_count); - m_result.filament_densities.resize(extruders_count); - m_extruder_temps.resize(extruders_count); - - for (size_t i = 0; i < extruders_count; ++ i) { - m_extruder_offsets[i] = to_3d(config.extruder_offset.get_at(i).cast().eval(), 0.f); - m_extruder_colors[i] = static_cast(i); - m_result.filament_diameters[i] = static_cast(config.filament_diameter.get_at(i)); - m_result.filament_densities[i] = static_cast(config.filament_density.get_at(i)); - } - - if ((m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware || m_flavor == gcfRepRapFirmware) && config.machine_limits_usage.value != MachineLimitsUsage::Ignore) { - m_time_processor.machine_limits = reinterpret_cast(config); - if (m_flavor == gcfMarlinLegacy) { - // Legacy Marlin does not have separate travel acceleration, it uses the 'extruding' value instead. - m_time_processor.machine_limits.machine_max_acceleration_travel = m_time_processor.machine_limits.machine_max_acceleration_extruding; - } - if (m_flavor == gcfRepRapFirmware) { - // RRF does not support setting min feedrates. Set them to zero. - m_time_processor.machine_limits.machine_min_travel_rate.values.assign(m_time_processor.machine_limits.machine_min_travel_rate.size(), 0.); - m_time_processor.machine_limits.machine_min_extruding_rate.values.assign(m_time_processor.machine_limits.machine_min_extruding_rate.size(), 0.); - } - } - - // Filament load / unload times are not specific to a firmware flavor. Let anybody use it if they find it useful. - // As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they - // are considered to be active for the single extruder multi-material printers only. - m_time_processor.filament_load_times.resize(config.filament_load_time.values.size()); - for (size_t i = 0; i < config.filament_load_time.values.size(); ++i) { - m_time_processor.filament_load_times[i] = static_cast(config.filament_load_time.values[i]); - } - m_time_processor.filament_unload_times.resize(config.filament_unload_time.values.size()); - for (size_t i = 0; i < config.filament_unload_time.values.size(); ++i) { - m_time_processor.filament_unload_times[i] = static_cast(config.filament_unload_time.values[i]); - } - - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - float max_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_extruding, i); - m_time_processor.machines[i].max_acceleration = max_acceleration; - m_time_processor.machines[i].acceleration = (max_acceleration > 0.0f) ? max_acceleration : DEFAULT_ACCELERATION; - float max_retract_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_retracting, i); - m_time_processor.machines[i].max_retract_acceleration = max_retract_acceleration; - m_time_processor.machines[i].retract_acceleration = (max_retract_acceleration > 0.0f) ? max_retract_acceleration : DEFAULT_RETRACT_ACCELERATION; - float max_travel_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_travel, i); - m_time_processor.machines[i].max_travel_acceleration = max_travel_acceleration; - m_time_processor.machines[i].travel_acceleration = (max_travel_acceleration > 0.0f) ? max_travel_acceleration : DEFAULT_TRAVEL_ACCELERATION; - } - - m_time_processor.export_remaining_time_enabled = config.remaining_times.value; - m_use_volumetric_e = config.use_volumetric_e; - - const ConfigOptionFloatOrPercent* first_layer_height = config.option("first_layer_height"); - if (first_layer_height != nullptr) - m_first_layer_height = std::abs(first_layer_height->value); - - m_result.max_print_height = config.max_print_height; - - const ConfigOptionBool* spiral_vase = config.option("spiral_vase"); - if (spiral_vase != nullptr) - m_spiral_vase_active = spiral_vase->value; - - const ConfigOptionFloat* z_offset = config.option("z_offset"); - if (z_offset != nullptr) - m_z_offset = z_offset->value; -} - -void GCodeProcessor::apply_config(const DynamicPrintConfig& config) -{ - m_parser.apply_config(config); - - const ConfigOptionEnum* gcode_flavor = config.option>("gcode_flavor"); - if (gcode_flavor != nullptr) - m_flavor = gcode_flavor->value; - - const ConfigOptionPoints* bed_shape = config.option("bed_shape"); - if (bed_shape != nullptr) - m_result.bed_shape = bed_shape->values; - - const ConfigOptionString* print_settings_id = config.option("print_settings_id"); - if (print_settings_id != nullptr) - m_result.settings_ids.print = print_settings_id->value; - - const ConfigOptionStrings* filament_settings_id = config.option("filament_settings_id"); - if (filament_settings_id != nullptr) - m_result.settings_ids.filament = filament_settings_id->values; - - const ConfigOptionString* printer_settings_id = config.option("printer_settings_id"); - if (printer_settings_id != nullptr) - m_result.settings_ids.printer = printer_settings_id->value; - - m_result.extruders_count = config.option("nozzle_diameter")->values.size(); - - const ConfigOptionFloats* filament_diameters = config.option("filament_diameter"); - if (filament_diameters != nullptr) { - m_result.filament_diameters.clear(); - m_result.filament_diameters.resize(filament_diameters->values.size()); - for (size_t i = 0; i < filament_diameters->values.size(); ++i) { - m_result.filament_diameters[i] = static_cast(filament_diameters->values[i]); - } - } - - if (m_result.filament_diameters.size() < m_result.extruders_count) { - for (size_t i = m_result.filament_diameters.size(); i < m_result.extruders_count; ++i) { - m_result.filament_diameters.emplace_back(DEFAULT_FILAMENT_DIAMETER); - } - } - - const ConfigOptionFloats* filament_densities = config.option("filament_density"); - if (filament_densities != nullptr) { - m_result.filament_densities.clear(); - m_result.filament_densities.resize(filament_densities->values.size()); - for (size_t i = 0; i < filament_densities->values.size(); ++i) { - m_result.filament_densities[i] = static_cast(filament_densities->values[i]); - } - } - - if (m_result.filament_densities.size() < m_result.extruders_count) { - for (size_t i = m_result.filament_densities.size(); i < m_result.extruders_count; ++i) { - m_result.filament_densities.emplace_back(DEFAULT_FILAMENT_DENSITY); - } - } - - const ConfigOptionPoints* extruder_offset = config.option("extruder_offset"); - if (extruder_offset != nullptr) { - m_extruder_offsets.resize(extruder_offset->values.size()); - for (size_t i = 0; i < extruder_offset->values.size(); ++i) { - Vec2f offset = extruder_offset->values[i].cast(); - m_extruder_offsets[i] = { offset(0), offset(1), 0.0f }; - } - } - - if (m_extruder_offsets.size() < m_result.extruders_count) { - for (size_t i = m_extruder_offsets.size(); i < m_result.extruders_count; ++i) { - m_extruder_offsets.emplace_back(DEFAULT_EXTRUDER_OFFSET); - } - } - - const ConfigOptionStrings* extruder_colour = config.option("extruder_colour"); - if (extruder_colour != nullptr) { - // takes colors from config - m_result.extruder_colors = extruder_colour->values; - // try to replace missing values with filament colors - const ConfigOptionStrings* filament_colour = config.option("filament_colour"); - if (filament_colour != nullptr && filament_colour->values.size() == m_result.extruder_colors.size()) { - for (size_t i = 0; i < m_result.extruder_colors.size(); ++i) { - if (m_result.extruder_colors[i].empty()) - m_result.extruder_colors[i] = filament_colour->values[i]; - } - } - } - - if (m_result.extruder_colors.size() < m_result.extruders_count) { - for (size_t i = m_result.extruder_colors.size(); i < m_result.extruders_count; ++i) { - m_result.extruder_colors.emplace_back(std::string()); - } - } - - // replace missing values with default - for (size_t i = 0; i < m_result.extruder_colors.size(); ++i) { - if (m_result.extruder_colors[i].empty()) - m_result.extruder_colors[i] = "#FF8000"; - } - - m_extruder_colors.resize(m_result.extruder_colors.size()); - for (size_t i = 0; i < m_result.extruder_colors.size(); ++i) { - m_extruder_colors[i] = static_cast(i); - } - - m_extruder_temps.resize(m_result.extruders_count); - - const ConfigOptionFloats* filament_load_time = config.option("filament_load_time"); - if (filament_load_time != nullptr) { - m_time_processor.filament_load_times.resize(filament_load_time->values.size()); - for (size_t i = 0; i < filament_load_time->values.size(); ++i) { - m_time_processor.filament_load_times[i] = static_cast(filament_load_time->values[i]); - } - } - - const ConfigOptionFloats* filament_unload_time = config.option("filament_unload_time"); - if (filament_unload_time != nullptr) { - m_time_processor.filament_unload_times.resize(filament_unload_time->values.size()); - for (size_t i = 0; i < filament_unload_time->values.size(); ++i) { - m_time_processor.filament_unload_times[i] = static_cast(filament_unload_time->values[i]); - } - } - - bool use_machine_limits = false; - const ConfigOptionEnum* machine_limits_usage = config.option>("machine_limits_usage"); - if (machine_limits_usage != nullptr) - use_machine_limits = machine_limits_usage->value != MachineLimitsUsage::Ignore; - - if (use_machine_limits && (m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware || m_flavor == gcfRepRapFirmware)) { - const ConfigOptionFloats* machine_max_acceleration_x = config.option("machine_max_acceleration_x"); - if (machine_max_acceleration_x != nullptr) - m_time_processor.machine_limits.machine_max_acceleration_x.values = machine_max_acceleration_x->values; - - const ConfigOptionFloats* machine_max_acceleration_y = config.option("machine_max_acceleration_y"); - if (machine_max_acceleration_y != nullptr) - m_time_processor.machine_limits.machine_max_acceleration_y.values = machine_max_acceleration_y->values; - - const ConfigOptionFloats* machine_max_acceleration_z = config.option("machine_max_acceleration_z"); - if (machine_max_acceleration_z != nullptr) - m_time_processor.machine_limits.machine_max_acceleration_z.values = machine_max_acceleration_z->values; - - const ConfigOptionFloats* machine_max_acceleration_e = config.option("machine_max_acceleration_e"); - if (machine_max_acceleration_e != nullptr) - m_time_processor.machine_limits.machine_max_acceleration_e.values = machine_max_acceleration_e->values; - - const ConfigOptionFloats* machine_max_feedrate_x = config.option("machine_max_feedrate_x"); - if (machine_max_feedrate_x != nullptr) - m_time_processor.machine_limits.machine_max_feedrate_x.values = machine_max_feedrate_x->values; - - const ConfigOptionFloats* machine_max_feedrate_y = config.option("machine_max_feedrate_y"); - if (machine_max_feedrate_y != nullptr) - m_time_processor.machine_limits.machine_max_feedrate_y.values = machine_max_feedrate_y->values; - - const ConfigOptionFloats* machine_max_feedrate_z = config.option("machine_max_feedrate_z"); - if (machine_max_feedrate_z != nullptr) - m_time_processor.machine_limits.machine_max_feedrate_z.values = machine_max_feedrate_z->values; - - const ConfigOptionFloats* machine_max_feedrate_e = config.option("machine_max_feedrate_e"); - if (machine_max_feedrate_e != nullptr) - m_time_processor.machine_limits.machine_max_feedrate_e.values = machine_max_feedrate_e->values; - - const ConfigOptionFloats* machine_max_jerk_x = config.option("machine_max_jerk_x"); - if (machine_max_jerk_x != nullptr) - m_time_processor.machine_limits.machine_max_jerk_x.values = machine_max_jerk_x->values; - - const ConfigOptionFloats* machine_max_jerk_y = config.option("machine_max_jerk_y"); - if (machine_max_jerk_y != nullptr) - m_time_processor.machine_limits.machine_max_jerk_y.values = machine_max_jerk_y->values; - - const ConfigOptionFloats* machine_max_jerk_z = config.option("machine_max_jerkz"); - if (machine_max_jerk_z != nullptr) - m_time_processor.machine_limits.machine_max_jerk_z.values = machine_max_jerk_z->values; - - const ConfigOptionFloats* machine_max_jerk_e = config.option("machine_max_jerk_e"); - if (machine_max_jerk_e != nullptr) - m_time_processor.machine_limits.machine_max_jerk_e.values = machine_max_jerk_e->values; - - const ConfigOptionFloats* machine_max_acceleration_extruding = config.option("machine_max_acceleration_extruding"); - if (machine_max_acceleration_extruding != nullptr) - m_time_processor.machine_limits.machine_max_acceleration_extruding.values = machine_max_acceleration_extruding->values; - - const ConfigOptionFloats* machine_max_acceleration_retracting = config.option("machine_max_acceleration_retracting"); - if (machine_max_acceleration_retracting != nullptr) - m_time_processor.machine_limits.machine_max_acceleration_retracting.values = machine_max_acceleration_retracting->values; - - - // Legacy Marlin does not have separate travel acceleration, it uses the 'extruding' value instead. - const ConfigOptionFloats* machine_max_acceleration_travel = config.option(m_flavor == gcfMarlinLegacy - ? "machine_max_acceleration_extruding" - : "machine_max_acceleration_travel"); - if (machine_max_acceleration_travel != nullptr) - m_time_processor.machine_limits.machine_max_acceleration_travel.values = machine_max_acceleration_travel->values; - - - const ConfigOptionFloats* machine_min_extruding_rate = config.option("machine_min_extruding_rate"); - if (machine_min_extruding_rate != nullptr) { - m_time_processor.machine_limits.machine_min_extruding_rate.values = machine_min_extruding_rate->values; - if (m_flavor == gcfRepRapFirmware) { - // RRF does not support setting min feedrates. Set zero. - m_time_processor.machine_limits.machine_min_extruding_rate.values.assign(m_time_processor.machine_limits.machine_min_extruding_rate.size(), 0.); - } - } - - const ConfigOptionFloats* machine_min_travel_rate = config.option("machine_min_travel_rate"); - if (machine_min_travel_rate != nullptr) { - m_time_processor.machine_limits.machine_min_travel_rate.values = machine_min_travel_rate->values; - if (m_flavor == gcfRepRapFirmware) { - // RRF does not support setting min feedrates. Set zero. - m_time_processor.machine_limits.machine_min_travel_rate.values.assign(m_time_processor.machine_limits.machine_min_travel_rate.size(), 0.); - } - } - } - - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - float max_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_extruding, i); - m_time_processor.machines[i].max_acceleration = max_acceleration; - m_time_processor.machines[i].acceleration = (max_acceleration > 0.0f) ? max_acceleration : DEFAULT_ACCELERATION; - float max_retract_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_retracting, i); - m_time_processor.machines[i].max_retract_acceleration = max_retract_acceleration; - m_time_processor.machines[i].retract_acceleration = (max_retract_acceleration > 0.0f) ? max_retract_acceleration : DEFAULT_RETRACT_ACCELERATION; - float max_travel_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_travel, i); - m_time_processor.machines[i].max_travel_acceleration = max_travel_acceleration; - m_time_processor.machines[i].travel_acceleration = (max_travel_acceleration > 0.0f) ? max_travel_acceleration : DEFAULT_TRAVEL_ACCELERATION; - } - - if (m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware) { - const ConfigOptionBool* silent_mode = config.option("silent_mode"); - if (silent_mode != nullptr) { - if (silent_mode->value && m_time_processor.machine_limits.machine_max_acceleration_x.values.size() > 1) - enable_stealth_time_estimator(true); - } - } - - const ConfigOptionBool* use_volumetric_e = config.option("use_volumetric_e"); - if (use_volumetric_e != nullptr) - m_use_volumetric_e = use_volumetric_e->value; - - const ConfigOptionFloatOrPercent* first_layer_height = config.option("first_layer_height"); - if (first_layer_height != nullptr) - m_first_layer_height = std::abs(first_layer_height->value); - - const ConfigOptionFloat* max_print_height = config.option("max_print_height"); - if (max_print_height != nullptr) - m_result.max_print_height = max_print_height->value; - - const ConfigOptionBool* spiral_vase = config.option("spiral_vase"); - if (spiral_vase != nullptr) - m_spiral_vase_active = spiral_vase->value; - - const ConfigOptionFloat* z_offset = config.option("z_offset"); - if (z_offset != nullptr) - m_z_offset = z_offset->value; -} - -void GCodeProcessor::enable_stealth_time_estimator(bool enabled) -{ - m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].enabled = enabled; -} - -void GCodeProcessor::reset() -{ - m_units = EUnits::Millimeters; - m_global_positioning_type = EPositioningType::Absolute; - m_e_local_positioning_type = EPositioningType::Absolute; - m_extruder_offsets = std::vector(MIN_EXTRUDERS_COUNT, Vec3f::Zero()); - m_flavor = gcfRepRapSprinter; - - m_start_position = { 0.0f, 0.0f, 0.0f, 0.0f }; - m_end_position = { 0.0f, 0.0f, 0.0f, 0.0f }; - m_saved_position = { 0.0f, 0.0f, 0.0f, 0.0f }; - m_origin = { 0.0f, 0.0f, 0.0f, 0.0f }; - m_cached_position.reset(); - m_wiping = false; - - m_line_id = 0; - m_last_line_id = 0; - m_feedrate = 0.0f; - m_feed_multiply.reset(); - m_width = 0.0f; - m_height = 0.0f; - m_forced_width = 0.0f; - m_forced_height = 0.0f; - m_mm3_per_mm = 0.0f; - m_fan_speed = 0.0f; - m_z_offset = 0.0f; - - m_extrusion_role = erNone; - m_extruder_id = 0; - m_extruder_colors.resize(MIN_EXTRUDERS_COUNT); - for (size_t i = 0; i < MIN_EXTRUDERS_COUNT; ++i) { - m_extruder_colors[i] = static_cast(i); - } - m_extruder_temps.resize(MIN_EXTRUDERS_COUNT); - for (size_t i = 0; i < MIN_EXTRUDERS_COUNT; ++i) { - m_extruder_temps[i] = 0.0f; - } - - m_extruded_last_z = 0.0f; - m_first_layer_height = 0.0f; - m_g1_line_id = 0; - m_layer_id = 0; - m_cp_color.reset(); - - m_producer = EProducer::Unknown; - - m_time_processor.reset(); - m_used_filaments.reset(); - - m_result.reset(); - m_result.id = ++s_result_id; - - m_use_volumetric_e = false; - m_last_default_color_id = 0; - - m_options_z_corrector.reset(); - - m_spiral_vase_active = false; - -#if ENABLE_GCODE_VIEWER_DATA_CHECKING - m_mm3_per_mm_compare.reset(); - m_height_compare.reset(); - m_width_compare.reset(); -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING -} - -static inline const char* skip_whitespaces(const char *begin, const char *end) { - for (; begin != end && (*begin == ' ' || *begin == '\t'); ++ begin); - return begin; -} - -static inline const char* remove_eols(const char *begin, const char *end) { - for (; begin != end && (*(end - 1) == '\r' || *(end - 1) == '\n'); -- end); - return end; -} - -// Load a G-code into a stand-alone G-code viewer. -// throws CanceledException through print->throw_if_canceled() (sent by the caller as callback). -void GCodeProcessor::process_file(const std::string& filename, std::function cancel_callback) -{ - CNumericLocalesSetter locales_setter; - -#if ENABLE_GCODE_VIEWER_STATISTICS - m_start_time = std::chrono::high_resolution_clock::now(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - // pre-processing - // parse the gcode file to detect its producer - { - m_parser.parse_file_raw(filename, [this](GCodeReader& reader, const char *begin, const char *end) { - begin = skip_whitespaces(begin, end); - if (begin != end && *begin == ';') { - // Comment. - begin = skip_whitespaces(++ begin, end); - end = remove_eols(begin, end); - if (begin != end && detect_producer(std::string_view(begin, end - begin))) - m_parser.quit_parsing(); - } - }); - m_parser.reset(); - - // if the gcode was produced by PrusaSlicer, - // extract the config from it - if (m_producer == EProducer::PrusaSlicer || m_producer == EProducer::Slic3rPE || m_producer == EProducer::Slic3r) { - DynamicPrintConfig config; - config.apply(FullPrintConfig::defaults()); - // Silently substitute unknown values by new ones for loading configurations from PrusaSlicer's own G-code. - // Showing substitution log or errors may make sense, but we are not really reading many values from the G-code config, - // thus a probability of incorrect substitution is low and the G-code viewer is a consumer-only anyways. - config.load_from_gcode_file(filename, ForwardCompatibilitySubstitutionRule::EnableSilent); - apply_config(config); - } - else if (m_producer == EProducer::Simplify3D) - apply_config_simplify3d(filename); - else if (m_producer == EProducer::SuperSlicer) - apply_config_superslicer(filename); - } - - // process gcode - m_result.filename = filename; - m_result.id = ++s_result_id; - // 1st move must be a dummy move - m_result.moves.emplace_back(GCodeProcessorResult::MoveVertex()); - size_t parse_line_callback_cntr = 10000; - m_parser.parse_file(filename, [this, cancel_callback, &parse_line_callback_cntr](GCodeReader& reader, const GCodeReader::GCodeLine& line) { - if (-- parse_line_callback_cntr == 0) { - // Don't call the cancel_callback() too often, do it every at every 10000'th line. - parse_line_callback_cntr = 10000; - if (cancel_callback) - cancel_callback(); - } - this->process_gcode_line(line, true); - }, m_result.lines_ends); - - // Don't post-process the G-code to update time stamps. - this->finalize(false); -} - -void GCodeProcessor::initialize(const std::string& filename) -{ - assert(is_decimal_separator_point()); - -#if ENABLE_GCODE_VIEWER_STATISTICS - m_start_time = std::chrono::high_resolution_clock::now(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - // process gcode - m_result.filename = filename; - m_result.id = ++s_result_id; - // 1st move must be a dummy move - m_result.moves.emplace_back(GCodeProcessorResult::MoveVertex()); -} - -void GCodeProcessor::process_buffer(const std::string &buffer) -{ - //FIXME maybe cache GCodeLine gline to be over multiple parse_buffer() invocations. - m_parser.parse_buffer(buffer, [this](GCodeReader&, const GCodeReader::GCodeLine& line) { - this->process_gcode_line(line, false); - }); -} - -void GCodeProcessor::finalize(bool post_process) -{ - // update width/height of wipe moves - for (GCodeProcessorResult::MoveVertex& move : m_result.moves) { - if (move.type == EMoveType::Wipe) { - move.width = Wipe_Width; - move.height = Wipe_Height; - } - } - - // process the time blocks - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - TimeMachine& machine = m_time_processor.machines[i]; - TimeMachine::CustomGCodeTime& gcode_time = machine.gcode_time; - machine.calculate_time(); - if (gcode_time.needed && gcode_time.cache != 0.0f) - gcode_time.times.push_back({ CustomGCode::ColorChange, gcode_time.cache }); - } - - m_used_filaments.process_caches(this); - - update_estimated_times_stats(); - -#if ENABLE_GCODE_VIEWER_DATA_CHECKING - std::cout << "\n"; - m_mm3_per_mm_compare.output(); - m_height_compare.output(); - m_width_compare.output(); -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING - - if (post_process) - m_time_processor.post_process(m_result.filename, m_result.moves, m_result.lines_ends); -#if ENABLE_GCODE_VIEWER_STATISTICS - m_result.time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - m_start_time).count(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS -} - -float GCodeProcessor::get_time(PrintEstimatedStatistics::ETimeMode mode) const -{ - return (mode < PrintEstimatedStatistics::ETimeMode::Count) ? m_time_processor.machines[static_cast(mode)].time : 0.0f; -} - -std::string GCodeProcessor::get_time_dhm(PrintEstimatedStatistics::ETimeMode mode) const -{ - return (mode < PrintEstimatedStatistics::ETimeMode::Count) ? short_time(get_time_dhms(m_time_processor.machines[static_cast(mode)].time)) : std::string("N/A"); -} - -#if ENABLE_TRAVEL_TIME -float GCodeProcessor::get_travel_time(PrintEstimatedStatistics::ETimeMode mode) const -{ - return (mode < PrintEstimatedStatistics::ETimeMode::Count) ? m_time_processor.machines[static_cast(mode)].travel_time : 0.0f; -} - -std::string GCodeProcessor::get_travel_time_dhm(PrintEstimatedStatistics::ETimeMode mode) const -{ - return (mode < PrintEstimatedStatistics::ETimeMode::Count) ? short_time(get_time_dhms(m_time_processor.machines[static_cast(mode)].travel_time)) : std::string("N/A"); -} -#endif // ENABLE_TRAVEL_TIME - -std::vector>> GCodeProcessor::get_custom_gcode_times(PrintEstimatedStatistics::ETimeMode mode, bool include_remaining) const -{ - std::vector>> ret; - if (mode < PrintEstimatedStatistics::ETimeMode::Count) { - const TimeMachine& machine = m_time_processor.machines[static_cast(mode)]; - float total_time = 0.0f; - for (const auto& [type, time] : machine.gcode_time.times) { - float remaining = include_remaining ? machine.time - total_time : 0.0f; - ret.push_back({ type, { time, remaining } }); - total_time += time; - } - } - return ret; -} - -std::vector> GCodeProcessor::get_moves_time(PrintEstimatedStatistics::ETimeMode mode) const -{ - std::vector> ret; - if (mode < PrintEstimatedStatistics::ETimeMode::Count) { - for (size_t i = 0; i < m_time_processor.machines[static_cast(mode)].moves_time.size(); ++i) { - float time = m_time_processor.machines[static_cast(mode)].moves_time[i]; - if (time > 0.0f) - ret.push_back({ static_cast(i), time }); - } - } - return ret; -} - -std::vector> GCodeProcessor::get_roles_time(PrintEstimatedStatistics::ETimeMode mode) const -{ - std::vector> ret; - if (mode < PrintEstimatedStatistics::ETimeMode::Count) { - for (size_t i = 0; i < m_time_processor.machines[static_cast(mode)].roles_time.size(); ++i) { - float time = m_time_processor.machines[static_cast(mode)].roles_time[i]; - if (time > 0.0f) - ret.push_back({ static_cast(i), time }); - } - } - return ret; -} - -ConfigSubstitutions load_from_superslicer_gcode_file(const std::string& filename, DynamicPrintConfig& config, ForwardCompatibilitySubstitutionRule compatibility_rule) -{ - // for reference, see: ConfigBase::load_from_gcode_file() - - boost::nowide::ifstream ifs(filename); - - auto header_end_pos = ifs.tellg(); - ConfigSubstitutionContext substitutions_ctxt(compatibility_rule); - size_t key_value_pairs = 0; - - ifs.seekg(0, ifs.end); - auto file_length = ifs.tellg(); - auto data_length = std::min(65535, file_length - header_end_pos); - ifs.seekg(file_length - data_length, ifs.beg); - std::vector data(size_t(data_length) + 1, 0); - ifs.read(data.data(), data_length); - ifs.close(); - key_value_pairs = ConfigBase::load_from_gcode_string_legacy(config, data.data(), substitutions_ctxt); - - if (key_value_pairs < 80) - throw Slic3r::RuntimeError(format("Suspiciously low number of configuration values extracted from %1%: %2%", filename, key_value_pairs)); - - return std::move(substitutions_ctxt.substitutions); -} - -void GCodeProcessor::apply_config_superslicer(const std::string& filename) -{ - DynamicPrintConfig config; - config.apply(FullPrintConfig::defaults()); - load_from_superslicer_gcode_file(filename, config, ForwardCompatibilitySubstitutionRule::EnableSilent); - apply_config(config); -} - -std::vector GCodeProcessor::get_layers_time(PrintEstimatedStatistics::ETimeMode mode) const -{ - return (mode < PrintEstimatedStatistics::ETimeMode::Count) ? - m_time_processor.machines[static_cast(mode)].layers_time : - std::vector(); -} - -void GCodeProcessor::apply_config_simplify3d(const std::string& filename) -{ - struct BedSize - { - double x{ 0.0 }; - double y{ 0.0 }; - - bool is_defined() const { return x > 0.0 && y > 0.0; } - }; - - BedSize bed_size; - bool producer_detected = false; - - m_parser.parse_file_raw(filename, [this, &bed_size, &producer_detected](GCodeReader& reader, const char* begin, const char* end) { - - auto extract_double = [](const std::string_view cmt, const std::string& key, double& out) { - size_t pos = cmt.find(key); - if (pos != cmt.npos) { - pos = cmt.find(',', pos); - if (pos != cmt.npos) { - out = string_to_double_decimal_point(cmt.substr(pos+1)); - return true; - } - } - return false; - }; - - auto extract_floats = [](const std::string_view cmt, const std::string& key, std::vector& out) { - size_t pos = cmt.find(key); - if (pos != cmt.npos) { - pos = cmt.find(',', pos); - if (pos != cmt.npos) { - const std::string_view data_str = cmt.substr(pos + 1); - std::vector values_str; - boost::split(values_str, data_str, boost::is_any_of("|,"), boost::token_compress_on); - for (const std::string& s : values_str) { - out.emplace_back(static_cast(string_to_double_decimal_point(s))); - } - return true; - } - } - return false; - }; - - begin = skip_whitespaces(begin, end); - end = remove_eols(begin, end); - if (begin != end) { - if (*begin == ';') { - // Comment. - begin = skip_whitespaces(++ begin, end); - if (begin != end) { - std::string_view comment(begin, end - begin); - if (producer_detected) { - if (bed_size.x == 0.0 && comment.find("strokeXoverride") != comment.npos) - extract_double(comment, "strokeXoverride", bed_size.x); - else if (bed_size.y == 0.0 && comment.find("strokeYoverride") != comment.npos) - extract_double(comment, "strokeYoverride", bed_size.y); - else if (comment.find("filamentDiameters") != comment.npos) { - m_result.filament_diameters.clear(); - extract_floats(comment, "filamentDiameters", m_result.filament_diameters); - } else if (comment.find("filamentDensities") != comment.npos) { - m_result.filament_densities.clear(); - extract_floats(comment, "filamentDensities", m_result.filament_densities); - } else if (comment.find("extruderDiameter") != comment.npos) { - std::vector extruder_diameters; - extract_floats(comment, "extruderDiameter", extruder_diameters); - m_result.extruders_count = extruder_diameters.size(); - } - } else if (boost::starts_with(comment, "G-Code generated by Simplify3D(R)")) - producer_detected = true; - } - } else { - // Some non-empty G-code line detected, stop parsing config comments. - reader.quit_parsing(); - } - } - }); - - if (m_result.extruders_count == 0) - m_result.extruders_count = std::max(1, std::min(m_result.filament_diameters.size(), m_result.filament_densities.size())); - - if (bed_size.is_defined()) { - m_result.bed_shape = { - { 0.0, 0.0 }, - { bed_size.x, 0.0 }, - { bed_size.x, bed_size.y }, - { 0.0, bed_size.y } - }; - } -} - -void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line, bool producers_enabled) -{ -/* std::cout << line.raw() << std::endl; */ - - ++m_line_id; - - // update start position - m_start_position = m_end_position; - - const std::string_view cmd = line.cmd(); - if (cmd.length() > 1) { - // process command lines - switch (cmd[0]) - { - case 'g': - case 'G': - switch (cmd.size()) { - case 2: - switch (cmd[1]) { - case '0': { process_G0(line); break; } // Move - case '1': { process_G1(line); break; } // Move -#if ENABLE_PROCESS_G2_G3_LINES - case '2': { process_G2_G3(line, true); break; } // CW Arc Move - case '3': { process_G2_G3(line, false); break; } // CCW Arc Move -#endif // ENABLE_PROCESS_G2_G3_LINES - default: break; - } - break; - case 3: - switch (cmd[1]) { - case '1': - switch (cmd[2]) { - case '0': { process_G10(line); break; } // Retract - case '1': { process_G11(line); break; } // Unretract - default: break; - } - break; - case '2': - switch (cmd[2]) { - case '0': { process_G20(line); break; } // Set Units to Inches - case '1': { process_G21(line); break; } // Set Units to Millimeters - case '2': { process_G22(line); break; } // Firmware controlled retract - case '3': { process_G23(line); break; } // Firmware controlled unretract - case '8': { process_G28(line); break; } // Move to origin - default: break; - } - break; - case '6': - switch (cmd[2]) { - case '0': { process_G60(line); break; } // Save Current Position - case '1': { process_G61(line); break; } // Return to Saved Position - default: break; - } - break; - case '9': - switch (cmd[2]) { - case '0': { process_G90(line); break; } // Set to Absolute Positioning - case '1': { process_G91(line); break; } // Set to Relative Positioning - case '2': { process_G92(line); break; } // Set Position - default: break; - } - break; - } - break; - default: - break; - } - break; - case 'm': - case 'M': - switch (cmd.size()) { - case 2: - switch (cmd[1]) { - case '1': { process_M1(line); break; } // Sleep or Conditional stop - default: break; - } - break; - case 3: - switch (cmd[1]) { - case '8': - switch (cmd[2]) { - case '2': { process_M82(line); break; } // Set extruder to absolute mode - case '3': { process_M83(line); break; } // Set extruder to relative mode - default: break; - } - break; - default: - break; - } - break; - case 4: - switch (cmd[1]) { - case '1': - switch (cmd[2]) { - case '0': - switch (cmd[3]) { - case '4': { process_M104(line); break; } // Set extruder temperature - case '6': { process_M106(line); break; } // Set fan speed - case '7': { process_M107(line); break; } // Disable fan - case '8': { process_M108(line); break; } // Set tool (Sailfish) - case '9': { process_M109(line); break; } // Set extruder temperature and wait - default: break; - } - break; - case '3': - switch (cmd[3]) { - case '2': { process_M132(line); break; } // Recall stored home offsets - case '5': { process_M135(line); break; } // Set tool (MakerWare) - default: break; - } - break; - default: - break; - } - break; - case '2': - switch (cmd[2]) { - case '0': - switch (cmd[3]) { - case '1': { process_M201(line); break; } // Set max printing acceleration - case '3': { process_M203(line); break; } // Set maximum feedrate - case '4': { process_M204(line); break; } // Set default acceleration - case '5': { process_M205(line); break; } // Advanced settings - default: break; - } - break; - case '2': - switch (cmd[3]) { - case '0': { process_M220(line); break; } // Set Feedrate Percentage - case '1': { process_M221(line); break; } // Set extrude factor override percentage - default: break; - } - break; - default: - break; - } - break; - case '4': - switch (cmd[2]) { - case '0': - switch (cmd[3]) { - case '1': { process_M401(line); break; } // Repetier: Store x, y and z position - case '2': { process_M402(line); break; } // Repetier: Go to stored position - default: break; - } - break; - default: - break; - } - break; - case '5': - switch (cmd[2]) { - case '6': - switch (cmd[3]) { - case '6': { process_M566(line); break; } // Set allowable instantaneous speed change - default: break; - } - break; - default: - break; - } - break; - case '7': - switch (cmd[2]) { - case '0': - switch (cmd[3]) { - case '2': { process_M702(line); break; } // Unload the current filament into the MK3 MMU2 unit at the end of print. - default: break; - } - break; - default: - break; - } - break; - default: - break; - } - break; - default: - break; - } - break; - case 't': - case 'T': - process_T(line); // Select Tool - break; - default: - break; - } - } - else { - const std::string &comment = line.raw(); - if (comment.length() > 2 && comment.front() == ';') - // Process tags embedded into comments. Tag comments always start at the start of a line - // with a comment and continue with a tag without any whitespace separator. - process_tags(comment.substr(1), producers_enabled); - } -} - -#if __has_include() - template - struct is_from_chars_convertible : std::false_type {}; - template - struct is_from_chars_convertible(), std::declval(), std::declval()))>> : std::true_type {}; -#endif - -// Returns true if the number was parsed correctly into out and the number spanned the whole input string. -template -[[nodiscard]] static inline bool parse_number(const std::string_view sv, T &out) -{ - // https://www.bfilipek.com/2019/07/detect-overload-from-chars.html#example-stdfromchars -#if __has_include() - // Visual Studio 19 supports from_chars all right. - // OSX compiler that we use only implements std::from_chars just for ints. - // GCC that we compile on does not provide at all. - if constexpr (is_from_chars_convertible::value) { - auto str_end = sv.data() + sv.size(); - auto [end_ptr, error_code] = std::from_chars(sv.data(), str_end, out); - return error_code == std::errc() && end_ptr == str_end; - } - else -#endif - { - // Legacy conversion, which is costly due to having to make a copy of the string before conversion. - try { - assert(sv.size() < 1024); - assert(sv.data() != nullptr); - std::string str { sv }; - size_t read = 0; - if constexpr (std::is_same_v) - out = std::stoi(str, &read); - else if constexpr (std::is_same_v) - out = std::stol(str, &read); - else if constexpr (std::is_same_v) - out = string_to_double_decimal_point(str, &read); - else if constexpr (std::is_same_v) - out = string_to_double_decimal_point(str, &read); - return str.size() == read; - } catch (...) { - return false; - } - } -} - -void GCodeProcessor::process_tags(const std::string_view comment, bool producers_enabled) -{ - // producers tags - if (producers_enabled && process_producers_tags(comment)) - return; - - // extrusion role tag - if (boost::starts_with(comment, reserved_tag(ETags::Role))) { - set_extrusion_role(ExtrusionEntity::string_to_role(comment.substr(reserved_tag(ETags::Role).length()))); - if (m_extrusion_role == erExternalPerimeter) - m_seams_detector.activate(true); - return; - } - - // wipe start tag - if (boost::starts_with(comment, reserved_tag(ETags::Wipe_Start))) { - m_wiping = true; - return; - } - - // wipe end tag - if (boost::starts_with(comment, reserved_tag(ETags::Wipe_End))) { - m_wiping = false; - return; - } - - if (!producers_enabled || m_producer == EProducer::PrusaSlicer) { - // height tag - if (boost::starts_with(comment, reserved_tag(ETags::Height))) { - if (!parse_number(comment.substr(reserved_tag(ETags::Height).size()), m_forced_height)) - BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ")."; - return; - } - // width tag - if (boost::starts_with(comment, reserved_tag(ETags::Width))) { - if (!parse_number(comment.substr(reserved_tag(ETags::Width).size()), m_forced_width)) - BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Width (" << comment << ")."; - return; - } - } - - // color change tag - if (boost::starts_with(comment, reserved_tag(ETags::Color_Change))) { - unsigned char extruder_id = 0; - static std::vector Default_Colors = { - "#0B2C7A", // { 0.043f, 0.173f, 0.478f }, // bluish - "#1C8891", // { 0.110f, 0.533f, 0.569f }, - "#AAF200", // { 0.667f, 0.949f, 0.000f }, - "#F5CE0A", // { 0.961f, 0.808f, 0.039f }, - "#D16830", // { 0.820f, 0.408f, 0.188f }, - "#942616", // { 0.581f, 0.149f, 0.087f } // reddish - }; - - std::string color = Default_Colors[0]; - auto is_valid_color = [](const std::string& color) { - auto is_hex_digit = [](char c) { - return ((c >= '0' && c <= '9') || - (c >= 'A' && c <= 'F') || - (c >= 'a' && c <= 'f')); - }; - - if (color[0] != '#' || color.length() != 7) - return false; - for (int i = 1; i <= 6; ++i) { - if (!is_hex_digit(color[i])) - return false; - } - return true; - }; - - std::vector tokens; - boost::split(tokens, comment, boost::is_any_of(","), boost::token_compress_on); - if (tokens.size() > 1) { - if (tokens[1][0] == 'T') { - int eid; - if (!parse_number(tokens[1].substr(1), eid) || eid < 0 || eid > 255) { - BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Color_Change (" << comment << ")."; - return; - } - extruder_id = static_cast(eid); - } - } - if (tokens.size() > 2) { - if (is_valid_color(tokens[2])) - color = tokens[2]; - } - else { - color = Default_Colors[m_last_default_color_id]; - ++m_last_default_color_id; - if (m_last_default_color_id == Default_Colors.size()) - m_last_default_color_id = 0; - } - - if (extruder_id < m_extruder_colors.size()) - m_extruder_colors[extruder_id] = static_cast(m_extruder_offsets.size()) + m_cp_color.counter; // color_change position in list of color for preview - ++m_cp_color.counter; - if (m_cp_color.counter == UCHAR_MAX) - m_cp_color.counter = 0; - - if (m_extruder_id == extruder_id) { - m_cp_color.current = m_extruder_colors[extruder_id]; - store_move_vertex(EMoveType::Color_change); - CustomGCode::Item item = { static_cast(m_end_position[2]), CustomGCode::ColorChange, extruder_id + 1, color, "" }; - m_result.custom_gcode_per_print_z.emplace_back(item); - m_options_z_corrector.set(); - process_custom_gcode_time(CustomGCode::ColorChange); - process_filaments(CustomGCode::ColorChange); - } - - return; - } - - // pause print tag - if (comment == reserved_tag(ETags::Pause_Print)) { - store_move_vertex(EMoveType::Pause_Print); - CustomGCode::Item item = { static_cast(m_end_position[2]), CustomGCode::PausePrint, m_extruder_id + 1, "", "" }; - m_result.custom_gcode_per_print_z.emplace_back(item); - m_options_z_corrector.set(); - process_custom_gcode_time(CustomGCode::PausePrint); - return; - } - - // custom code tag - if (comment == reserved_tag(ETags::Custom_Code)) { - store_move_vertex(EMoveType::Custom_GCode); - CustomGCode::Item item = { static_cast(m_end_position[2]), CustomGCode::Custom, m_extruder_id + 1, "", "" }; - m_result.custom_gcode_per_print_z.emplace_back(item); - m_options_z_corrector.set(); - return; - } - - // layer change tag - if (comment == reserved_tag(ETags::Layer_Change)) { - ++m_layer_id; - if (m_spiral_vase_active) { - if (m_result.moves.empty()) - m_result.spiral_vase_layers.push_back({ m_first_layer_height, { 0, 0 } }); - else { - const size_t move_id = m_result.moves.size() - 1; - if (!m_result.spiral_vase_layers.empty() && m_end_position[Z] == m_result.spiral_vase_layers.back().first) - m_result.spiral_vase_layers.back().second.second = move_id; - else - m_result.spiral_vase_layers.push_back({ static_cast(m_end_position[Z]), { move_id, move_id } }); - } - } - return; - } - -#if ENABLE_GCODE_VIEWER_DATA_CHECKING - // mm3_per_mm print tag - if (boost::starts_with(comment, Mm3_Per_Mm_Tag)) { - if (! parse_number(comment.substr(Mm3_Per_Mm_Tag.size()), m_mm3_per_mm_compare.last_tag_value)) - BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Mm3_Per_Mm (" << comment << ")."; - return; - } -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING -} - -bool GCodeProcessor::process_producers_tags(const std::string_view comment) -{ - switch (m_producer) - { - case EProducer::Slic3rPE: - case EProducer::Slic3r: - case EProducer::SuperSlicer: - case EProducer::PrusaSlicer: { return process_prusaslicer_tags(comment); } - case EProducer::Cura: { return process_cura_tags(comment); } - case EProducer::Simplify3D: { return process_simplify3d_tags(comment); } - case EProducer::CraftWare: { return process_craftware_tags(comment); } - case EProducer::ideaMaker: { return process_ideamaker_tags(comment); } - case EProducer::KissSlicer: { return process_kissslicer_tags(comment); } - default: { return false; } - } -} - -bool GCodeProcessor::process_prusaslicer_tags(const std::string_view comment) -{ - return false; -} - -bool GCodeProcessor::process_cura_tags(const std::string_view comment) -{ - // TYPE -> extrusion role - std::string tag = "TYPE:"; - size_t pos = comment.find(tag); - if (pos != comment.npos) { - const std::string_view type = comment.substr(pos + tag.length()); - if (type == "SKIRT") - set_extrusion_role(erSkirt); - else if (type == "WALL-OUTER") - set_extrusion_role(erExternalPerimeter); - else if (type == "WALL-INNER") - set_extrusion_role(erPerimeter); - else if (type == "SKIN") - set_extrusion_role(erSolidInfill); - else if (type == "FILL") - set_extrusion_role(erInternalInfill); - else if (type == "SUPPORT") - set_extrusion_role(erSupportMaterial); - else if (type == "SUPPORT-INTERFACE") - set_extrusion_role(erSupportMaterialInterface); - else if (type == "PRIME-TOWER") - set_extrusion_role(erWipeTower); - else { - set_extrusion_role(erNone); - BOOST_LOG_TRIVIAL(warning) << "GCodeProcessor found unknown extrusion role: " << type; - } - - if (m_extrusion_role == erExternalPerimeter) - m_seams_detector.activate(true); - - return true; - } - - // flavor - tag = "FLAVOR:"; - pos = comment.find(tag); - if (pos != comment.npos) { - const std::string_view flavor = comment.substr(pos + tag.length()); - if (flavor == "BFB") - m_flavor = gcfMarlinLegacy; // is this correct ? - else if (flavor == "Mach3") - m_flavor = gcfMach3; - else if (flavor == "Makerbot") - m_flavor = gcfMakerWare; - else if (flavor == "UltiGCode") - m_flavor = gcfMarlinLegacy; // is this correct ? - else if (flavor == "Marlin(Volumetric)") - m_flavor = gcfMarlinLegacy; // is this correct ? - else if (flavor == "Griffin") - m_flavor = gcfMarlinLegacy; // is this correct ? - else if (flavor == "Repetier") - m_flavor = gcfRepetier; - else if (flavor == "RepRap") - m_flavor = gcfRepRapFirmware; - else if (flavor == "Marlin") - m_flavor = gcfMarlinLegacy; - else - BOOST_LOG_TRIVIAL(warning) << "GCodeProcessor found unknown flavor: " << flavor; - - return true; - } - - // layer - tag = "LAYER:"; - pos = comment.find(tag); - if (pos != comment.npos) { - ++m_layer_id; - return true; - } - - return false; -} - -bool GCodeProcessor::process_simplify3d_tags(const std::string_view comment) -{ - // extrusion roles - - // in older versions the comments did not contain the key 'feature' - std::string_view cmt = comment; - size_t pos = cmt.find(" feature"); - if (pos == 0) - cmt.remove_prefix(8); - - // ; skirt - pos = cmt.find(" skirt"); - if (pos == 0) { - set_extrusion_role(erSkirt); - return true; - } - - // ; outer perimeter - pos = cmt.find(" outer perimeter"); - if (pos == 0) { - set_extrusion_role(erExternalPerimeter); - m_seams_detector.activate(true); - return true; - } - - // ; inner perimeter - pos = cmt.find(" inner perimeter"); - if (pos == 0) { - set_extrusion_role(erPerimeter); - return true; - } - - // ; gap fill - pos = cmt.find(" gap fill"); - if (pos == 0) { - set_extrusion_role(erGapFill); - return true; - } - - // ; infill - pos = cmt.find(" infill"); - if (pos == 0) { - set_extrusion_role(erInternalInfill); - return true; - } - - // ; solid layer - pos = cmt.find(" solid layer"); - if (pos == 0) { - set_extrusion_role(erSolidInfill); - return true; - } - - // ; bridge - pos = cmt.find(" bridge"); - if (pos == 0) { - set_extrusion_role(erBridgeInfill); - return true; - } - - // ; support - pos = cmt.find(" support"); - if (pos == 0) { - set_extrusion_role(erSupportMaterial); - return true; - } - - // ; dense support - pos = cmt.find(" dense support"); - if (pos == 0) { - set_extrusion_role(erSupportMaterialInterface); - return true; - } - - // ; prime pillar - pos = cmt.find(" prime pillar"); - if (pos == 0) { - set_extrusion_role(erWipeTower); - return true; - } - - // ; ooze shield - pos = cmt.find(" ooze shield"); - if (pos == 0) { - set_extrusion_role(erNone); // Missing mapping - return true; - } - - // ; raft - pos = cmt.find(" raft"); - if (pos == 0) { - set_extrusion_role(erSupportMaterial); - return true; - } - - // ; internal single extrusion - pos = cmt.find(" internal single extrusion"); - if (pos == 0) { - set_extrusion_role(erNone); // Missing mapping - return true; - } - - // geometry - // ; tool - std::string tag = " tool"; - pos = cmt.find(tag); - if (pos == 0) { - const std::string_view data = cmt.substr(pos + tag.length()); - std::string h_tag = "H"; - size_t h_start = data.find(h_tag); - size_t h_end = data.find_first_of(' ', h_start); - std::string w_tag = "W"; - size_t w_start = data.find(w_tag); - size_t w_end = data.find_first_of(' ', w_start); - if (h_start != data.npos) { - if (!parse_number(data.substr(h_start + 1, (h_end != data.npos) ? h_end - h_start - 1 : h_end), m_forced_height)) - BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ")."; - } - if (w_start != data.npos) { - if (!parse_number(data.substr(w_start + 1, (w_end != data.npos) ? w_end - w_start - 1 : w_end), m_forced_width)) - BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Width (" << comment << ")."; - } - - return true; - } - - // ; layer - tag = " layer"; - pos = cmt.find(tag); - if (pos == 0) { - // skip lines "; layer end" - const std::string_view data = cmt.substr(pos + tag.length()); - size_t end_start = data.find("end"); - if (end_start == data.npos) - ++m_layer_id; - - return true; - } - - return false; -} - -bool GCodeProcessor::process_craftware_tags(const std::string_view comment) -{ - // segType -> extrusion role - std::string tag = "segType:"; - size_t pos = comment.find(tag); - if (pos != comment.npos) { - const std::string_view type = comment.substr(pos + tag.length()); - if (type == "Skirt") - set_extrusion_role(erSkirt); - else if (type == "Perimeter") - set_extrusion_role(erExternalPerimeter); - else if (type == "HShell") - set_extrusion_role(erNone); // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - else if (type == "InnerHair") - set_extrusion_role(erNone); // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - else if (type == "Loop") - set_extrusion_role(erNone); // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - else if (type == "Infill") - set_extrusion_role(erInternalInfill); - else if (type == "Raft") - set_extrusion_role(erSkirt); - else if (type == "Support") - set_extrusion_role(erSupportMaterial); - else if (type == "SupportTouch") - set_extrusion_role(erSupportMaterial); - else if (type == "SoftSupport") - set_extrusion_role(erSupportMaterialInterface); - else if (type == "Pillar") - set_extrusion_role(erWipeTower); - else { - set_extrusion_role(erNone); - BOOST_LOG_TRIVIAL(warning) << "GCodeProcessor found unknown extrusion role: " << type; - } - - if (m_extrusion_role == erExternalPerimeter) - m_seams_detector.activate(true); - - return true; - } - - // layer - pos = comment.find(" Layer #"); - if (pos == 0) { - ++m_layer_id; - return true; - } - - return false; -} - -bool GCodeProcessor::process_ideamaker_tags(const std::string_view comment) -{ - // TYPE -> extrusion role - std::string tag = "TYPE:"; - size_t pos = comment.find(tag); - if (pos != comment.npos) { - const std::string_view type = comment.substr(pos + tag.length()); - if (type == "RAFT") - set_extrusion_role(erSkirt); - else if (type == "WALL-OUTER") - set_extrusion_role(erExternalPerimeter); - else if (type == "WALL-INNER") - set_extrusion_role(erPerimeter); - else if (type == "SOLID-FILL") - set_extrusion_role(erSolidInfill); - else if (type == "FILL") - set_extrusion_role(erInternalInfill); - else if (type == "BRIDGE") - set_extrusion_role(erBridgeInfill); - else if (type == "SUPPORT") - set_extrusion_role(erSupportMaterial); - else { - set_extrusion_role(erNone); - BOOST_LOG_TRIVIAL(warning) << "GCodeProcessor found unknown extrusion role: " << type; - } - - if (m_extrusion_role == erExternalPerimeter) - m_seams_detector.activate(true); - - return true; - } - - // geometry - // width - tag = "WIDTH:"; - pos = comment.find(tag); - if (pos != comment.npos) { - if (!parse_number(comment.substr(pos + tag.length()), m_forced_width)) - BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Width (" << comment << ")."; - return true; - } - - // height - tag = "HEIGHT:"; - pos = comment.find(tag); - if (pos != comment.npos) { - if (!parse_number(comment.substr(pos + tag.length()), m_forced_height)) - BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ")."; - return true; - } - - // layer - pos = comment.find("LAYER:"); - if (pos == 0) { - ++m_layer_id; - return true; - } - - return false; -} - -bool GCodeProcessor::process_kissslicer_tags(const std::string_view comment) -{ - // extrusion roles - - // ; 'Raft Path' - size_t pos = comment.find(" 'Raft Path'"); - if (pos == 0) { - set_extrusion_role(erSkirt); - return true; - } - - // ; 'Support Interface Path' - pos = comment.find(" 'Support Interface Path'"); - if (pos == 0) { - set_extrusion_role(erSupportMaterialInterface); - return true; - } - - // ; 'Travel/Ironing Path' - pos = comment.find(" 'Travel/Ironing Path'"); - if (pos == 0) { - set_extrusion_role(erIroning); - return true; - } - - // ; 'Support (may Stack) Path' - pos = comment.find(" 'Support (may Stack) Path'"); - if (pos == 0) { - set_extrusion_role(erSupportMaterial); - return true; - } - - // ; 'Perimeter Path' - pos = comment.find(" 'Perimeter Path'"); - if (pos == 0) { - set_extrusion_role(erExternalPerimeter); - m_seams_detector.activate(true); - return true; - } - - // ; 'Pillar Path' - pos = comment.find(" 'Pillar Path'"); - if (pos == 0) { - set_extrusion_role(erNone); // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - return true; - } - - // ; 'Destring/Wipe/Jump Path' - pos = comment.find(" 'Destring/Wipe/Jump Path'"); - if (pos == 0) { - set_extrusion_role(erNone); // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - return true; - } - - // ; 'Prime Pillar Path' - pos = comment.find(" 'Prime Pillar Path'"); - if (pos == 0) { - set_extrusion_role(erNone); // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - return true; - } - - // ; 'Loop Path' - pos = comment.find(" 'Loop Path'"); - if (pos == 0) { - set_extrusion_role(erNone); // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - return true; - } - - // ; 'Crown Path' - pos = comment.find(" 'Crown Path'"); - if (pos == 0) { - set_extrusion_role(erNone); // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - return true; - } - - // ; 'Solid Path' - pos = comment.find(" 'Solid Path'"); - if (pos == 0) { - set_extrusion_role(erNone); - return true; - } - - // ; 'Stacked Sparse Infill Path' - pos = comment.find(" 'Stacked Sparse Infill Path'"); - if (pos == 0) { - set_extrusion_role(erInternalInfill); - return true; - } - - // ; 'Sparse Infill Path' - pos = comment.find(" 'Sparse Infill Path'"); - if (pos == 0) { - set_extrusion_role(erSolidInfill); - return true; - } - - // geometry - - // layer - pos = comment.find(" BEGIN_LAYER_"); - if (pos == 0) { - ++m_layer_id; - return true; - } - - return false; -} - -bool GCodeProcessor::detect_producer(const std::string_view comment) -{ - for (const auto& [id, search_string] : Producers) { - size_t pos = comment.find(search_string); - if (pos != comment.npos) { - m_producer = id; - BOOST_LOG_TRIVIAL(info) << "Detected gcode producer: " << search_string; - return true; - } - } - return false; -} - -void GCodeProcessor::process_G0(const GCodeReader::GCodeLine& line) -{ - process_G1(line); -} - -void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) -{ - const float filament_diameter = (static_cast(m_extruder_id) < m_result.filament_diameters.size()) ? m_result.filament_diameters[m_extruder_id] : m_result.filament_diameters.back(); - const float filament_radius = 0.5f * filament_diameter; - const float area_filament_cross_section = static_cast(M_PI) * sqr(filament_radius); -#if !ENABLE_PROCESS_G2_G3_LINES - auto absolute_position = [this, area_filament_cross_section](Axis axis, const GCodeReader::GCodeLine& lineG1) { - bool is_relative = (m_global_positioning_type == EPositioningType::Relative); - if (axis == E) - is_relative |= (m_e_local_positioning_type == EPositioningType::Relative); - - if (lineG1.has(Slic3r::Axis(axis))) { - float lengthsScaleFactor = (m_units == EUnits::Inches) ? INCHES_TO_MM : 1.0f; - float ret = lineG1.value(Slic3r::Axis(axis)) * lengthsScaleFactor; - if (axis == E && m_use_volumetric_e) - ret /= area_filament_cross_section; - return is_relative ? m_start_position[axis] + ret : m_origin[axis] + ret; - } - else - return m_start_position[axis]; - }; -#endif // !ENABLE_PROCESS_G2_G3_LINES - - auto move_type = [this](const AxisCoords& delta_pos) { - EMoveType type = EMoveType::Noop; - - if (m_wiping) - type = EMoveType::Wipe; - else if (delta_pos[E] < 0.0f) - type = (delta_pos[X] != 0.0f || delta_pos[Y] != 0.0f || delta_pos[Z] != 0.0f) ? EMoveType::Travel : EMoveType::Retract; - else if (delta_pos[E] > 0.0f) { - if (delta_pos[X] == 0.0f && delta_pos[Y] == 0.0f) - type = (delta_pos[Z] == 0.0f) ? EMoveType::Unretract : EMoveType::Travel; - else if (delta_pos[X] != 0.0f || delta_pos[Y] != 0.0f) - type = EMoveType::Extrude; - } - else if (delta_pos[X] != 0.0f || delta_pos[Y] != 0.0f || delta_pos[Z] != 0.0f) - type = EMoveType::Travel; - - return type; - }; - - ++m_g1_line_id; - - // enable processing of lines M201/M203/M204/M205 - m_time_processor.machine_envelope_processing_enabled = true; - - // updates axes positions from line - for (unsigned char a = X; a <= E; ++a) { -#if ENABLE_PROCESS_G2_G3_LINES - m_end_position[a] = extract_absolute_position_on_axis((Axis)a, line, double(area_filament_cross_section)); -#else - m_end_position[a] = absolute_position((Axis)a, line); -#endif // ENABLE_PROCESS_G2_G3_LINES - } - - // updates feedrate from line, if present - if (line.has_f()) - m_feedrate = m_feed_multiply.current * line.f() * MMMIN_TO_MMSEC; - - // calculates movement deltas - float max_abs_delta = 0.0f; - AxisCoords delta_pos; - for (unsigned char a = X; a <= E; ++a) { - delta_pos[a] = m_end_position[a] - m_start_position[a]; - max_abs_delta = std::max(max_abs_delta, std::abs(delta_pos[a])); - } - - // no displacement, return - if (max_abs_delta == 0.0f) - return; - - const EMoveType type = move_type(delta_pos); - if (type == EMoveType::Extrude) { - const float delta_xyz = std::sqrt(sqr(delta_pos[X]) + sqr(delta_pos[Y]) + sqr(delta_pos[Z])); - const float volume_extruded_filament = area_filament_cross_section * delta_pos[E]; - const float area_toolpath_cross_section = volume_extruded_filament / delta_xyz; - - // save extruded volume to the cache - m_used_filaments.increase_caches(volume_extruded_filament); - - // volume extruded filament / tool displacement = area toolpath cross section - m_mm3_per_mm = area_toolpath_cross_section; -#if ENABLE_GCODE_VIEWER_DATA_CHECKING - m_mm3_per_mm_compare.update(area_toolpath_cross_section, m_extrusion_role); -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING - -#if ENABLE_PROCESS_G2_G3_LINES - if (m_forced_height > 0.0f) - m_height = m_forced_height; - else if (m_layer_id == 0) - m_height = (m_end_position[Z] <= double(m_first_layer_height)) ? m_end_position[Z] : m_first_layer_height; - else if (line.comment() != INTERNAL_G2G3_TAG){ - if (m_end_position[Z] > m_extruded_last_z + EPSILON && delta_pos[Z] == 0.0) - m_height = m_end_position[Z] - m_extruded_last_z; - } -#else - if (m_forced_height > 0.0f) - m_height = m_forced_height; - else if (m_layer_id == 0) - m_height = (m_end_position[Z] <= double(m_first_layer_height)) ? m_end_position[Z] : m_first_layer_height; - else { - if (m_end_position[Z] > m_extruded_last_z + EPSILON) - m_height = m_end_position[Z] - m_extruded_last_z; - } -#endif // ENABLE_PROCESS_G2_G3_LINES - - if (m_height == 0.0f) - m_height = DEFAULT_TOOLPATH_HEIGHT; - - if (m_end_position[Z] == 0.0f) - m_end_position[Z] = m_height; - -#if ENABLE_PROCESS_G2_G3_LINES - if (line.comment() != INTERNAL_G2G3_TAG) -#endif // ENABLE_PROCESS_G2_G3_LINES - m_extruded_last_z = m_end_position[Z]; - m_options_z_corrector.update(m_height); - -#if ENABLE_GCODE_VIEWER_DATA_CHECKING - m_height_compare.update(m_height, m_extrusion_role); -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING - - if (m_forced_width > 0.0f) - m_width = m_forced_width; - else if (m_extrusion_role == erExternalPerimeter) - // cross section: rectangle - m_width = delta_pos[E] * static_cast(M_PI * sqr(1.05f * filament_radius)) / (delta_xyz * m_height); - else if (m_extrusion_role == erBridgeInfill || m_extrusion_role == erNone) - // cross section: circle - m_width = static_cast(m_result.filament_diameters[m_extruder_id]) * std::sqrt(delta_pos[E] / delta_xyz); - else - // cross section: rectangle + 2 semicircles - m_width = delta_pos[E] * static_cast(M_PI * sqr(filament_radius)) / (delta_xyz * m_height) + static_cast(1.0 - 0.25 * M_PI) * m_height; - - if (m_width == 0.0f) - m_width = DEFAULT_TOOLPATH_WIDTH; - - // clamp width to avoid artifacts which may arise from wrong values of m_height - m_width = std::min(m_width, std::max(2.0f, 4.0f * m_height)); - -#if ENABLE_GCODE_VIEWER_DATA_CHECKING - m_width_compare.update(m_width, m_extrusion_role); -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING - } - - // time estimate section - auto move_length = [](const AxisCoords& delta_pos) { - float sq_xyz_length = sqr(delta_pos[X]) + sqr(delta_pos[Y]) + sqr(delta_pos[Z]); - return (sq_xyz_length > 0.0f) ? std::sqrt(sq_xyz_length) : std::abs(delta_pos[E]); - }; - - auto is_extrusion_only_move = [](const AxisCoords& delta_pos) { - return delta_pos[X] == 0.0f && delta_pos[Y] == 0.0f && delta_pos[Z] == 0.0f && delta_pos[E] != 0.0f; - }; - - const float distance = move_length(delta_pos); - assert(distance != 0.0f); - const float inv_distance = 1.0f / distance; - - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - TimeMachine& machine = m_time_processor.machines[i]; - if (!machine.enabled) - continue; - - TimeMachine::State& curr = machine.curr; - TimeMachine::State& prev = machine.prev; - std::vector& blocks = machine.blocks; - - curr.feedrate = (delta_pos[E] == 0.0f) ? - minimum_travel_feedrate(static_cast(i), m_feedrate) : - minimum_feedrate(static_cast(i), m_feedrate); - - TimeBlock block; - block.move_type = type; - block.role = m_extrusion_role; - block.distance = distance; - block.g1_line_id = m_g1_line_id; - block.layer_id = std::max(1, m_layer_id); - - // calculates block cruise feedrate - float min_feedrate_factor = 1.0f; - for (unsigned char a = X; a <= E; ++a) { - curr.axis_feedrate[a] = curr.feedrate * delta_pos[a] * inv_distance; - if (a == E) - curr.axis_feedrate[a] *= machine.extrude_factor_override_percentage; - - curr.abs_axis_feedrate[a] = std::abs(curr.axis_feedrate[a]); - if (curr.abs_axis_feedrate[a] != 0.0f) { - const float axis_max_feedrate = get_axis_max_feedrate(static_cast(i), static_cast(a)); - if (axis_max_feedrate != 0.0f) - min_feedrate_factor = std::min(min_feedrate_factor, axis_max_feedrate / curr.abs_axis_feedrate[a]); - } - } - - block.feedrate_profile.cruise = min_feedrate_factor * curr.feedrate; - - if (min_feedrate_factor < 1.0f) { - for (unsigned char a = X; a <= E; ++a) { - curr.axis_feedrate[a] *= min_feedrate_factor; - curr.abs_axis_feedrate[a] *= min_feedrate_factor; - } - } - - // calculates block acceleration - float acceleration = - (type == EMoveType::Travel) ? get_travel_acceleration(static_cast(i)) : - (is_extrusion_only_move(delta_pos) ? - get_retract_acceleration(static_cast(i)) : - get_acceleration(static_cast(i))); - - for (unsigned char a = X; a <= E; ++a) { - const float axis_max_acceleration = get_axis_max_acceleration(static_cast(i), static_cast(a)); - if (acceleration * std::abs(delta_pos[a]) * inv_distance > axis_max_acceleration) - acceleration = axis_max_acceleration; - } - - block.acceleration = acceleration; - - // calculates block exit feedrate - curr.safe_feedrate = block.feedrate_profile.cruise; - - for (unsigned char a = X; a <= E; ++a) { - const float axis_max_jerk = get_axis_max_jerk(static_cast(i), static_cast(a)); - if (curr.abs_axis_feedrate[a] > axis_max_jerk) - curr.safe_feedrate = std::min(curr.safe_feedrate, axis_max_jerk); - } - - block.feedrate_profile.exit = curr.safe_feedrate; - - static const float PREVIOUS_FEEDRATE_THRESHOLD = 0.0001f; - - // 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_profile.cruise; - float smaller_speed_factor = prev_speed_larger ? (block.feedrate_profile.cruise / prev.feedrate) : (prev.feedrate / block.feedrate_profile.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_profile.cruise : prev.feedrate; - - float v_factor = 1.0f; - bool limited = false; - - for (unsigned char a = X; a <= E; ++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. - const 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)); - - const float axis_max_jerk = get_axis_max_jerk(static_cast(i), static_cast(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. - const 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; - } - - const float v_allowable = max_allowable_speed(-acceleration, curr.safe_feedrate, block.distance); - block.feedrate_profile.entry = std::min(vmax_junction, v_allowable); - - block.max_entry_speed = vmax_junction; - block.flags.nominal_length = (block.feedrate_profile.cruise <= v_allowable); - block.flags.recalculate = true; - block.safe_feedrate = curr.safe_feedrate; - - // calculates block trapezoid - block.calculate_trapezoid(); - - // updates previous - prev = curr; - - blocks.push_back(block); - - if (blocks.size() > TimeProcessor::Planner::refresh_threshold) - machine.calculate_time(TimeProcessor::Planner::queue_size); - } - - if (m_seams_detector.is_active()) { - // check for seam starting vertex - if (type == EMoveType::Extrude && m_extrusion_role == erExternalPerimeter && !m_seams_detector.has_first_vertex()) - m_seams_detector.set_first_vertex(m_result.moves.back().position - m_extruder_offsets[m_extruder_id]); - // check for seam ending vertex and store the resulting move - else if ((type != EMoveType::Extrude || (m_extrusion_role != erExternalPerimeter && m_extrusion_role != erOverhangPerimeter)) && m_seams_detector.has_first_vertex()) { - auto set_end_position = [this](const Vec3f& pos) { - m_end_position[X] = pos.x(); m_end_position[Y] = pos.y(); m_end_position[Z] = pos.z(); - }; - - const Vec3f curr_pos(m_end_position[X], m_end_position[Y], m_end_position[Z]); - const Vec3f new_pos = m_result.moves.back().position - m_extruder_offsets[m_extruder_id]; - const std::optional first_vertex = m_seams_detector.get_first_vertex(); - // the threshold value = 0.0625f == 0.25 * 0.25 is arbitrary, we may find some smarter condition later - - if ((new_pos - *first_vertex).squaredNorm() < 0.0625f) { - set_end_position(0.5f * (new_pos + *first_vertex) + m_z_offset * Vec3f::UnitZ()); - store_move_vertex(EMoveType::Seam); - set_end_position(curr_pos); - } - - m_seams_detector.activate(false); - } - } - else if (type == EMoveType::Extrude && m_extrusion_role == erExternalPerimeter) { - m_seams_detector.activate(true); - m_seams_detector.set_first_vertex(m_result.moves.back().position - m_extruder_offsets[m_extruder_id]); - } - - if (m_spiral_vase_active && !m_result.spiral_vase_layers.empty() && !m_result.moves.empty()) - m_result.spiral_vase_layers.back().second.second = m_result.moves.size() - 1; - - // store move -#if ENABLE_PROCESS_G2_G3_LINES - store_move_vertex(type, line.comment() == INTERNAL_G2G3_TAG); -#else - store_move_vertex(type); -#endif // ENABLE_PROCESS_G2_G3_LINES -} - -#if ENABLE_PROCESS_G2_G3_LINES -void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line, bool clockwise) -{ - if (!line.has('X') || !line.has('Y') || !line.has('I') || !line.has('J')) - return; - - // relative center - Vec3f rel_center = Vec3f::Zero(); - if (!line.has_value('I', rel_center.x()) || !line.has_value('J', rel_center.y())) - return; - - // scale center, if needed - if (m_units == EUnits::Inches) - rel_center *= INCHES_TO_MM; - - struct Arc - { - Vec3d start{ Vec3d::Zero() }; - Vec3d end{ Vec3d::Zero() }; - Vec3d center{ Vec3d::Zero() }; - - double angle{ 0.0 }; - double delta_x() const { return end.x() - start.x(); } - double delta_y() const { return end.y() - start.y(); } - double delta_z() const { return end.z() - start.z(); } - - double length() const { return angle * start_radius(); } - double travel_length() const { return std::sqrt(sqr(length() + sqr(delta_z()))); } - double start_radius() const { return (start - center).norm(); } - double end_radius() const { return (end - center).norm(); } - - Vec3d relative_start() const { return start - center; } - Vec3d relative_end() const { return end - center; } - - bool closed() const { return end.isApprox(start); } - }; - - Arc arc; - - // arc start endpoint - arc.start = Vec3d(m_start_position[X], m_start_position[Y], m_start_position[Z]); - - // arc center - arc.center = arc.start + rel_center.cast(); - - const float filament_diameter = (static_cast(m_extruder_id) < m_result.filament_diameters.size()) ? m_result.filament_diameters[m_extruder_id] : m_result.filament_diameters.back(); - const float filament_radius = 0.5f * filament_diameter; - const float area_filament_cross_section = static_cast(M_PI) * sqr(filament_radius); - - AxisCoords end_position = m_start_position; - for (unsigned char a = X; a <= E; ++a) { - end_position[a] = extract_absolute_position_on_axis((Axis)a, line, double(area_filament_cross_section)); - } - - // arc end endpoint - arc.end = Vec3d(end_position[X], end_position[Y], end_position[Z]); - - // radii - if (std::abs(arc.end_radius() - arc.start_radius()) > EPSILON) { - // what to do ??? - } - - // updates feedrate from line - std::optional feedrate; - if (line.has_f()) - feedrate = m_feed_multiply.current * line.f() * MMMIN_TO_MMSEC; - - // updates extrusion from line - std::optional extrusion; - if (line.has_e()) - extrusion = end_position[E] - m_start_position[E]; - - // relative arc endpoints - const Vec3d rel_arc_start = arc.relative_start(); - const Vec3d rel_arc_end = arc.relative_end(); - - // arc angle - if (arc.closed()) - arc.angle = 2.0 * PI; - else { - arc.angle = std::atan2(rel_arc_start.x() * rel_arc_end.y() - rel_arc_start.y() * rel_arc_end.x(), - rel_arc_start.x() * rel_arc_end.x() + rel_arc_start.y() * rel_arc_end.y()); - if (arc.angle < 0.0) - arc.angle += 2.0 * PI; - if (clockwise) - arc.angle -= 2.0 * PI; - } - - const double travel_length = arc.travel_length(); - if (travel_length < 0.001) - return; - - auto adjust_target = [this, area_filament_cross_section](const AxisCoords& target, const AxisCoords& prev_position) { - AxisCoords ret = target; - if (m_global_positioning_type == EPositioningType::Relative) { - for (unsigned char a = X; a <= E; ++a) { - ret[a] -= prev_position[a]; - } - } - else if (m_e_local_positioning_type == EPositioningType::Relative) - ret[E] -= prev_position[E]; - - if (m_use_volumetric_e) - ret[E] *= area_filament_cross_section; - - const double lengthsScaleFactor = (m_units == EUnits::Inches) ? double(INCHES_TO_MM) : 1.0; - for (unsigned char a = X; a <= E; ++a) { - ret[a] /= lengthsScaleFactor; - } - return ret; - }; - - auto internal_only_g1_line = [](const AxisCoords& target, bool has_z, const std::optional& feedrate, const std::optional& extrusion) { - std::string ret = (boost::format("G1 X%1% Y%2%") % target[X] % target[Y]).str(); - if (has_z) - ret += (boost::format(" Z%1%") % target[Z]).str(); - if (feedrate.has_value()) - ret += (boost::format(" F%1%") % *feedrate).str(); - if (extrusion.has_value()) - ret += (boost::format(" E%1%") % target[E]).str(); - - ret += (boost::format(" ;%1%\n") % INTERNAL_G2G3_TAG).str(); - - return ret; - }; - - // calculate arc segments - // reference: - // Prusa-Firmware\Firmware\motion_control.cpp - mc_arc() - - // segments count - static const double MM_PER_ARC_SEGMENT = 1.0; - const size_t segments = std::max(std::floor(travel_length / MM_PER_ARC_SEGMENT), 1); - - const double theta_per_segment = arc.angle / double(segments); - const double z_per_segment = arc.delta_z() / double(segments); - const double extruder_per_segment = (extrusion.has_value()) ? *extrusion / double(segments) : 0.0; - - double cos_T = 1.0 - 0.5 * sqr(theta_per_segment); // Small angle approximation - double sin_T = theta_per_segment; - - AxisCoords prev_target = m_start_position; - AxisCoords arc_target; - double sin_Ti; - double cos_Ti; - double r_axisi; - size_t count = 0; - - // Initialize the linear axis - arc_target[Z] = m_start_position[Z]; - - // Initialize the extruder axis - arc_target[E] = m_start_position[E]; - - static const size_t N_ARC_CORRECTION = 25; - - Vec3d curr_rel_arc_start = arc.relative_start(); - - std::string gcode; - - for (size_t i = 1; i < segments; ++i) { // Increment (segments-1) - if (count < N_ARC_CORRECTION) { - // Apply vector rotation matrix - r_axisi = curr_rel_arc_start.x() * sin_T + curr_rel_arc_start.y() * cos_T; - curr_rel_arc_start.x() = curr_rel_arc_start.x() * cos_T - curr_rel_arc_start.y() * sin_T; - curr_rel_arc_start.y() = r_axisi; - count++; - } - else { - // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments. - // Compute exact location by applying transformation matrix from initial radius vector(=-offset). - cos_Ti = ::cos(double(i) * theta_per_segment); - sin_Ti = ::sin(double(i) * theta_per_segment); - curr_rel_arc_start.x() = -double(rel_center.x()) * cos_Ti + double(rel_center.y()) * sin_Ti; - curr_rel_arc_start.y() = -double(rel_center.x()) * sin_Ti - double(rel_center.y()) * cos_Ti; - count = 0; - } - - // Update arc_target location - arc_target[X] = arc.center.x() + curr_rel_arc_start.x(); - arc_target[Y] = arc.center.y() + curr_rel_arc_start.y(); - arc_target[Z] += z_per_segment; - arc_target[E] += extruder_per_segment; - - gcode += internal_only_g1_line(adjust_target(arc_target, prev_target), z_per_segment != 0.0, feedrate, extrusion); - prev_target = arc_target; - - // feedrate is constant, we do not need to repeat it - feedrate.reset(); - } - - // Ensure last segment arrives at target location. - gcode += internal_only_g1_line(adjust_target(end_position, prev_target), arc.delta_z() != 0.0, feedrate, extrusion); - - // process fake gcode lines - GCodeReader parser; - parser.parse_buffer(gcode, [this](GCodeReader&, const GCodeReader::GCodeLine& line) { - // force all lines to share the same id - --m_line_id; - process_gcode_line(line, false); - }); -} -#endif // ENABLE_PROCESS_G2_G3_LINES - -void GCodeProcessor::process_G10(const GCodeReader::GCodeLine& line) -{ - // stores retract move - store_move_vertex(EMoveType::Retract); -} - -void GCodeProcessor::process_G11(const GCodeReader::GCodeLine& line) -{ - // stores unretract move - store_move_vertex(EMoveType::Unretract); -} - -void GCodeProcessor::process_G20(const GCodeReader::GCodeLine& line) -{ - m_units = EUnits::Inches; -} - -void GCodeProcessor::process_G21(const GCodeReader::GCodeLine& line) -{ - m_units = EUnits::Millimeters; -} - -void GCodeProcessor::process_G22(const GCodeReader::GCodeLine& line) -{ - // stores retract move - store_move_vertex(EMoveType::Retract); -} - -void GCodeProcessor::process_G23(const GCodeReader::GCodeLine& line) -{ - // stores unretract move - store_move_vertex(EMoveType::Unretract); -} - -void GCodeProcessor::process_G28(const GCodeReader::GCodeLine& line) -{ - std::string_view cmd = line.cmd(); - std::string new_line_raw = { cmd.data(), cmd.size() }; - bool found = false; - if (line.has('X')) { - new_line_raw += " X0"; - found = true; - } - if (line.has('Y')) { - new_line_raw += " Y0"; - found = true; - } - if (line.has('Z')) { - new_line_raw += " Z0"; - found = true; - } - if (!found) - new_line_raw += " X0 Y0 Z0"; - - GCodeReader::GCodeLine new_gline; - GCodeReader reader; - reader.parse_line(new_line_raw, [&](GCodeReader& reader, const GCodeReader::GCodeLine& gline) { new_gline = gline; }); - process_G1(new_gline); -} - -void GCodeProcessor::process_G60(const GCodeReader::GCodeLine& line) -{ - if (m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware) - m_saved_position = m_end_position; -} - -void GCodeProcessor::process_G61(const GCodeReader::GCodeLine& line) -{ - if (m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware) { - bool modified = false; - if (line.has_x()) { - m_end_position[X] = m_saved_position[X]; - modified = true; - } - if (line.has_y()) { - m_end_position[Y] = m_saved_position[Y]; - modified = true; - } - if (line.has_z()) { - m_end_position[Z] = m_saved_position[Z]; - modified = true; - } - if (line.has_e()) { - m_end_position[E] = m_saved_position[E]; - modified = true; - } - if (line.has_f()) - m_feedrate = m_feed_multiply.current * line.f(); - - if (!modified) - m_end_position = m_saved_position; - - - store_move_vertex(EMoveType::Travel); - } -} - -void GCodeProcessor::process_G90(const GCodeReader::GCodeLine& line) -{ - m_global_positioning_type = EPositioningType::Absolute; -} - -void GCodeProcessor::process_G91(const GCodeReader::GCodeLine& line) -{ - m_global_positioning_type = EPositioningType::Relative; -} - -void GCodeProcessor::process_G92(const GCodeReader::GCodeLine& line) -{ - float lengths_scale_factor = (m_units == EUnits::Inches) ? INCHES_TO_MM : 1.0f; - bool any_found = false; - - if (line.has_x()) { - m_origin[X] = m_end_position[X] - line.x() * lengths_scale_factor; - any_found = true; - } - - if (line.has_y()) { - m_origin[Y] = m_end_position[Y] - line.y() * lengths_scale_factor; - any_found = true; - } - - if (line.has_z()) { - m_origin[Z] = m_end_position[Z] - line.z() * lengths_scale_factor; - any_found = true; - } - - if (line.has_e()) { - // extruder coordinate can grow to the point where its float representation does not allow for proper addition with small increments, - // we set the value taken from the G92 line as the new current position for it - m_end_position[E] = line.e() * lengths_scale_factor; - any_found = true; - } - else - simulate_st_synchronize(); - - if (!any_found && !line.has_unknown_axis()) { - // The G92 may be called for axes that PrusaSlicer does not recognize, for example see GH issue #3510, - // where G92 A0 B0 is called although the extruder axis is till E. - for (unsigned char a = X; a <= E; ++a) { - m_origin[a] = m_end_position[a]; - } - } -} - -void GCodeProcessor::process_M1(const GCodeReader::GCodeLine& line) -{ - simulate_st_synchronize(); -} - -void GCodeProcessor::process_M82(const GCodeReader::GCodeLine& line) -{ - m_e_local_positioning_type = EPositioningType::Absolute; -} - -void GCodeProcessor::process_M83(const GCodeReader::GCodeLine& line) -{ - m_e_local_positioning_type = EPositioningType::Relative; -} - -void GCodeProcessor::process_M104(const GCodeReader::GCodeLine& line) -{ - float new_temp; - if (line.has_value('S', new_temp)) - m_extruder_temps[m_extruder_id] = new_temp; -} - -void GCodeProcessor::process_M106(const GCodeReader::GCodeLine& line) -{ - if (!line.has('P')) { - // The absence of P means the print cooling fan, so ignore anything else. - float new_fan_speed; - if (line.has_value('S', new_fan_speed)) - m_fan_speed = (100.0f / 255.0f) * new_fan_speed; - else - m_fan_speed = 100.0f; - } -} - -void GCodeProcessor::process_M107(const GCodeReader::GCodeLine& line) -{ - m_fan_speed = 0.0f; -} - -void GCodeProcessor::process_M108(const GCodeReader::GCodeLine& line) -{ - // These M-codes are used by Sailfish to change active tool. - // They have to be processed otherwise toolchanges will be unrecognised - // by the analyzer - see https://github.com/prusa3d/PrusaSlicer/issues/2566 - - if (m_flavor != gcfSailfish) - return; - - std::string cmd = line.raw(); - size_t pos = cmd.find("T"); - if (pos != std::string::npos) - process_T(cmd.substr(pos)); -} - -void GCodeProcessor::process_M109(const GCodeReader::GCodeLine& line) -{ - float new_temp; - if (line.has_value('R', new_temp)) { - float val; - if (line.has_value('T', val)) { - size_t eid = static_cast(val); - if (eid < m_extruder_temps.size()) - m_extruder_temps[eid] = new_temp; - } - else - m_extruder_temps[m_extruder_id] = new_temp; - } - else if (line.has_value('S', new_temp)) - m_extruder_temps[m_extruder_id] = new_temp; -} - -void GCodeProcessor::process_M132(const GCodeReader::GCodeLine& line) -{ - // This command is used by Makerbot to load the current home position from EEPROM - // see: https://github.com/makerbot/s3g/blob/master/doc/GCodeProtocol.md - // Using this command to reset the axis origin to zero helps in fixing: https://github.com/prusa3d/PrusaSlicer/issues/3082 - - if (line.has('X')) - m_origin[X] = 0.0f; - - if (line.has('Y')) - m_origin[Y] = 0.0f; - - if (line.has('Z')) - m_origin[Z] = 0.0f; - - if (line.has('E')) - m_origin[E] = 0.0f; -} - -void GCodeProcessor::process_M135(const GCodeReader::GCodeLine& line) -{ - // These M-codes are used by MakerWare to change active tool. - // They have to be processed otherwise toolchanges will be unrecognised - // by the analyzer - see https://github.com/prusa3d/PrusaSlicer/issues/2566 - - if (m_flavor != gcfMakerWare) - return; - - std::string cmd = line.raw(); - size_t pos = cmd.find("T"); - if (pos != std::string::npos) - process_T(cmd.substr(pos)); -} - -void GCodeProcessor::process_M201(const GCodeReader::GCodeLine& line) -{ - // see http://reprap.org/wiki/G-code#M201:_Set_max_printing_acceleration - float factor = ((m_flavor != gcfRepRapSprinter && m_flavor != gcfRepRapFirmware) && m_units == EUnits::Inches) ? INCHES_TO_MM : 1.0f; - - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - if (static_cast(i) == PrintEstimatedStatistics::ETimeMode::Normal || - m_time_processor.machine_envelope_processing_enabled) { - if (line.has_x()) - set_option_value(m_time_processor.machine_limits.machine_max_acceleration_x, i, line.x() * factor); - - if (line.has_y()) - set_option_value(m_time_processor.machine_limits.machine_max_acceleration_y, i, line.y() * factor); - - if (line.has_z()) - set_option_value(m_time_processor.machine_limits.machine_max_acceleration_z, i, line.z() * factor); - - if (line.has_e()) - set_option_value(m_time_processor.machine_limits.machine_max_acceleration_e, i, line.e() * factor); - } - } -} - -void GCodeProcessor::process_M203(const GCodeReader::GCodeLine& line) -{ - // see http://reprap.org/wiki/G-code#M203:_Set_maximum_feedrate - if (m_flavor == gcfRepetier) - return; - - // see http://reprap.org/wiki/G-code#M203:_Set_maximum_feedrate - // http://smoothieware.org/supported-g-codes - float factor = (m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware || m_flavor == gcfSmoothie) ? 1.0f : MMMIN_TO_MMSEC; - - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - if (static_cast(i) == PrintEstimatedStatistics::ETimeMode::Normal || - m_time_processor.machine_envelope_processing_enabled) { - if (line.has_x()) - set_option_value(m_time_processor.machine_limits.machine_max_feedrate_x, i, line.x() * factor); - - if (line.has_y()) - set_option_value(m_time_processor.machine_limits.machine_max_feedrate_y, i, line.y() * factor); - - if (line.has_z()) - set_option_value(m_time_processor.machine_limits.machine_max_feedrate_z, i, line.z() * factor); - - if (line.has_e()) - set_option_value(m_time_processor.machine_limits.machine_max_feedrate_e, i, line.e() * factor); - } - } -} - -void GCodeProcessor::process_M204(const GCodeReader::GCodeLine& line) -{ - float value; - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - if (static_cast(i) == PrintEstimatedStatistics::ETimeMode::Normal || - m_time_processor.machine_envelope_processing_enabled) { - if (line.has_value('S', value)) { - // Legacy acceleration format. This format is used by the legacy Marlin, MK2 or MK3 firmware - // It is also generated by PrusaSlicer to control acceleration per extrusion type - // (perimeters, first layer etc) when 'Marlin (legacy)' flavor is used. - set_acceleration(static_cast(i), value); - set_travel_acceleration(static_cast(i), value); - if (line.has_value('T', value)) - set_retract_acceleration(static_cast(i), value); - } - else { - // New acceleration format, compatible with the upstream Marlin. - if (line.has_value('P', value)) - set_acceleration(static_cast(i), value); - if (line.has_value('R', value)) - set_retract_acceleration(static_cast(i), value); - if (line.has_value('T', value)) - // Interpret the T value as the travel acceleration in the new Marlin format. - set_travel_acceleration(static_cast(i), value); - } - } - } -} - -void GCodeProcessor::process_M205(const GCodeReader::GCodeLine& line) -{ - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - if (static_cast(i) == PrintEstimatedStatistics::ETimeMode::Normal || - m_time_processor.machine_envelope_processing_enabled) { - if (line.has_x()) { - float max_jerk = line.x(); - set_option_value(m_time_processor.machine_limits.machine_max_jerk_x, i, max_jerk); - set_option_value(m_time_processor.machine_limits.machine_max_jerk_y, i, max_jerk); - } - - if (line.has_y()) - set_option_value(m_time_processor.machine_limits.machine_max_jerk_y, i, line.y()); - - if (line.has_z()) - set_option_value(m_time_processor.machine_limits.machine_max_jerk_z, i, line.z()); - - if (line.has_e()) - set_option_value(m_time_processor.machine_limits.machine_max_jerk_e, i, line.e()); - - float value; - if (line.has_value('S', value)) - set_option_value(m_time_processor.machine_limits.machine_min_extruding_rate, i, value); - - if (line.has_value('T', value)) - set_option_value(m_time_processor.machine_limits.machine_min_travel_rate, i, value); - } - } -} - -void GCodeProcessor::process_M220(const GCodeReader::GCodeLine& line) -{ - if (m_flavor != gcfMarlinLegacy && m_flavor != gcfMarlinFirmware) - return; - - if (line.has('B')) - m_feed_multiply.saved = m_feed_multiply.current; - float value; - if (line.has_value('S', value)) - m_feed_multiply.current = value * 0.01f; - if (line.has('R')) - m_feed_multiply.current = m_feed_multiply.saved; -} - -void GCodeProcessor::process_M221(const GCodeReader::GCodeLine& line) -{ - float value_s; - float value_t; - if (line.has_value('S', value_s) && !line.has_value('T', value_t)) { - value_s *= 0.01f; - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - m_time_processor.machines[i].extrude_factor_override_percentage = value_s; - } - } -} - -void GCodeProcessor::process_M401(const GCodeReader::GCodeLine& line) -{ - if (m_flavor != gcfRepetier) - return; - - for (unsigned char a = 0; a <= 3; ++a) { - m_cached_position.position[a] = m_start_position[a]; - } - m_cached_position.feedrate = m_feedrate; -} - -void GCodeProcessor::process_M402(const GCodeReader::GCodeLine& line) -{ - if (m_flavor != gcfRepetier) - return; - - // see for reference: - // https://github.com/repetier/Repetier-Firmware/blob/master/src/ArduinoAVR/Repetier/Printer.cpp - // void Printer::GoToMemoryPosition(bool x, bool y, bool z, bool e, float feed) - - bool has_xyz = !(line.has('X') || line.has('Y') || line.has('Z')); - - float p = FLT_MAX; - for (unsigned char a = X; a <= Z; ++a) { - if (has_xyz || line.has(a)) { - p = m_cached_position.position[a]; - if (p != FLT_MAX) - m_start_position[a] = p; - } - } - - p = m_cached_position.position[E]; - if (p != FLT_MAX) - m_start_position[E] = p; - - p = FLT_MAX; - if (!line.has_value(4, p)) - p = m_cached_position.feedrate; - - if (p != FLT_MAX) - m_feedrate = p; -} - -void GCodeProcessor::process_M566(const GCodeReader::GCodeLine& line) -{ - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - if (line.has_x()) - set_option_value(m_time_processor.machine_limits.machine_max_jerk_x, i, line.x() * MMMIN_TO_MMSEC); - - if (line.has_y()) - set_option_value(m_time_processor.machine_limits.machine_max_jerk_y, i, line.y() * MMMIN_TO_MMSEC); - - if (line.has_z()) - set_option_value(m_time_processor.machine_limits.machine_max_jerk_z, i, line.z() * MMMIN_TO_MMSEC); - - if (line.has_e()) - set_option_value(m_time_processor.machine_limits.machine_max_jerk_e, i, line.e() * MMMIN_TO_MMSEC); - } -} - -void GCodeProcessor::process_M702(const GCodeReader::GCodeLine& line) -{ - 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. - m_time_processor.extruder_unloaded = true; - simulate_st_synchronize(get_filament_unload_time(m_extruder_id)); - } -} - -void GCodeProcessor::process_T(const GCodeReader::GCodeLine& line) -{ - process_T(line.cmd()); -} - -void GCodeProcessor::process_T(const std::string_view command) -{ - if (command.length() > 1) { - int eid = 0; - if (! parse_number(command.substr(1), eid) || eid < 0 || eid > 255) { - // Specific to the MMU2 V2 (see https://www.help.prusa3d.com/en/article/prusa-specific-g-codes_112173): - if ((m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware) && (command == "Tx" || command == "Tc" || command == "T?")) - return; - - // T-1 is a valid gcode line for RepRap Firmwares (used to deselects all tools) see https://github.com/prusa3d/PrusaSlicer/issues/5677 - if ((m_flavor != gcfRepRapFirmware && m_flavor != gcfRepRapSprinter) || eid != -1) - BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid toolchange (" << command << ")."; - } else { - unsigned char id = static_cast(eid); - if (m_extruder_id != id) { - if (id >= m_result.extruders_count) - BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid toolchange, maybe from a custom gcode."; - else { - unsigned char old_extruder_id = m_extruder_id; - process_filaments(CustomGCode::ToolChange); - m_extruder_id = id; - m_cp_color.current = m_extruder_colors[id]; - // Specific to the MK3 MMU2: - // The initial value of extruder_unloaded is set to true indicating - // that the filament is parked in the MMU2 unit and there is nothing to be unloaded yet. - float extra_time = get_filament_unload_time(static_cast(old_extruder_id)); - m_time_processor.extruder_unloaded = false; - extra_time += get_filament_load_time(static_cast(m_extruder_id)); - simulate_st_synchronize(extra_time); - } - - // store tool change move - store_move_vertex(EMoveType::Tool_change); - } - } - } -} - -#if ENABLE_PROCESS_G2_G3_LINES -void GCodeProcessor::store_move_vertex(EMoveType type, bool internal_only) -#else -void GCodeProcessor::store_move_vertex(EMoveType type) -#endif // ENABLE_PROCESS_G2_G3_LINES -{ - m_last_line_id = (type == EMoveType::Color_change || type == EMoveType::Pause_Print || type == EMoveType::Custom_GCode) ? - m_line_id + 1 : - ((type == EMoveType::Seam) ? m_last_line_id : m_line_id); - - m_result.moves.push_back({ - m_last_line_id, - type, - m_extrusion_role, - m_extruder_id, - m_cp_color.current, - Vec3f(m_end_position[X], m_end_position[Y], m_end_position[Z] - m_z_offset) + m_extruder_offsets[m_extruder_id], - static_cast(m_end_position[E] - m_start_position[E]), - m_feedrate, - m_width, - m_height, - m_mm3_per_mm, - m_fan_speed, - m_extruder_temps[m_extruder_id], -#if ENABLE_PROCESS_G2_G3_LINES - static_cast(m_result.moves.size()), - internal_only -#else - static_cast(m_result.moves.size()) -#endif // ENABLE_PROCESS_G2_G3_LINES - }); - - // stores stop time placeholders for later use - if (type == EMoveType::Color_change || type == EMoveType::Pause_Print) { - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - TimeMachine& machine = m_time_processor.machines[i]; - if (!machine.enabled) - continue; - - machine.stop_times.push_back({ m_g1_line_id, 0.0f }); - } - } -} - -void GCodeProcessor::set_extrusion_role(ExtrusionRole role) -{ - m_used_filaments.process_role_cache(this); - m_extrusion_role = role; -} - -float GCodeProcessor::minimum_feedrate(PrintEstimatedStatistics::ETimeMode mode, float feedrate) const -{ - if (m_time_processor.machine_limits.machine_min_extruding_rate.empty()) - return feedrate; - - return std::max(feedrate, get_option_value(m_time_processor.machine_limits.machine_min_extruding_rate, static_cast(mode))); -} - -float GCodeProcessor::minimum_travel_feedrate(PrintEstimatedStatistics::ETimeMode mode, float feedrate) const -{ - if (m_time_processor.machine_limits.machine_min_travel_rate.empty()) - return feedrate; - - return std::max(feedrate, get_option_value(m_time_processor.machine_limits.machine_min_travel_rate, static_cast(mode))); -} - -float GCodeProcessor::get_axis_max_feedrate(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const -{ - switch (axis) - { - case X: { return get_option_value(m_time_processor.machine_limits.machine_max_feedrate_x, static_cast(mode)); } - case Y: { return get_option_value(m_time_processor.machine_limits.machine_max_feedrate_y, static_cast(mode)); } - case Z: { return get_option_value(m_time_processor.machine_limits.machine_max_feedrate_z, static_cast(mode)); } - case E: { return get_option_value(m_time_processor.machine_limits.machine_max_feedrate_e, static_cast(mode)); } - default: { return 0.0f; } - } -} - -float GCodeProcessor::get_axis_max_acceleration(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const -{ - switch (axis) - { - case X: { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_x, static_cast(mode)); } - case Y: { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_y, static_cast(mode)); } - case Z: { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_z, static_cast(mode)); } - case E: { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_e, static_cast(mode)); } - default: { return 0.0f; } - } -} - -float GCodeProcessor::get_axis_max_jerk(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const -{ - switch (axis) - { - case X: { return get_option_value(m_time_processor.machine_limits.machine_max_jerk_x, static_cast(mode)); } - case Y: { return get_option_value(m_time_processor.machine_limits.machine_max_jerk_y, static_cast(mode)); } - case Z: { return get_option_value(m_time_processor.machine_limits.machine_max_jerk_z, static_cast(mode)); } - case E: { return get_option_value(m_time_processor.machine_limits.machine_max_jerk_e, static_cast(mode)); } - default: { return 0.0f; } - } -} - -float GCodeProcessor::get_retract_acceleration(PrintEstimatedStatistics::ETimeMode mode) const -{ - size_t id = static_cast(mode); - return (id < m_time_processor.machines.size()) ? m_time_processor.machines[id].retract_acceleration : DEFAULT_RETRACT_ACCELERATION; -} - -void GCodeProcessor::set_retract_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value) -{ - size_t id = static_cast(mode); - if (id < m_time_processor.machines.size()) { - m_time_processor.machines[id].retract_acceleration = (m_time_processor.machines[id].max_retract_acceleration == 0.0f) ? value : - // Clamp the acceleration with the maximum. - std::min(value, m_time_processor.machines[id].max_retract_acceleration); - } -} - -float GCodeProcessor::get_acceleration(PrintEstimatedStatistics::ETimeMode mode) const -{ - size_t id = static_cast(mode); - return (id < m_time_processor.machines.size()) ? m_time_processor.machines[id].acceleration : DEFAULT_ACCELERATION; -} - -void GCodeProcessor::set_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value) -{ - size_t id = static_cast(mode); - if (id < m_time_processor.machines.size()) { - m_time_processor.machines[id].acceleration = (m_time_processor.machines[id].max_acceleration == 0.0f) ? value : - // Clamp the acceleration with the maximum. - std::min(value, m_time_processor.machines[id].max_acceleration); - } -} - -float GCodeProcessor::get_travel_acceleration(PrintEstimatedStatistics::ETimeMode mode) const -{ - size_t id = static_cast(mode); - return (id < m_time_processor.machines.size()) ? m_time_processor.machines[id].travel_acceleration : DEFAULT_TRAVEL_ACCELERATION; -} - -void GCodeProcessor::set_travel_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value) -{ - size_t id = static_cast(mode); - if (id < m_time_processor.machines.size()) { - m_time_processor.machines[id].travel_acceleration = (m_time_processor.machines[id].max_travel_acceleration == 0.0f) ? value : - // Clamp the acceleration with the maximum. - std::min(value, m_time_processor.machines[id].max_travel_acceleration); - } -} - -float GCodeProcessor::get_filament_load_time(size_t extruder_id) -{ - return (m_time_processor.filament_load_times.empty() || m_time_processor.extruder_unloaded) ? - 0.0f : - ((extruder_id < m_time_processor.filament_load_times.size()) ? - m_time_processor.filament_load_times[extruder_id] : m_time_processor.filament_load_times.front()); -} - -float GCodeProcessor::get_filament_unload_time(size_t extruder_id) -{ - return (m_time_processor.filament_unload_times.empty() || m_time_processor.extruder_unloaded) ? - 0.0f : - ((extruder_id < m_time_processor.filament_unload_times.size()) ? - m_time_processor.filament_unload_times[extruder_id] : m_time_processor.filament_unload_times.front()); -} - -void GCodeProcessor::process_custom_gcode_time(CustomGCode::Type code) -{ - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - TimeMachine& machine = m_time_processor.machines[i]; - if (!machine.enabled) - continue; - - TimeMachine::CustomGCodeTime& gcode_time = machine.gcode_time; - gcode_time.needed = true; - //FIXME this simulates st_synchronize! is it correct? - // The estimated time may be longer than the real print time. - machine.simulate_st_synchronize(); - if (gcode_time.cache != 0.0f) { - gcode_time.times.push_back({ code, gcode_time.cache }); - gcode_time.cache = 0.0f; - } - } -} - -void GCodeProcessor::process_filaments(CustomGCode::Type code) -{ - if (code == CustomGCode::ColorChange) - m_used_filaments.process_color_change_cache(); - - if (code == CustomGCode::ToolChange) - m_used_filaments.process_extruder_cache(this); -} - -void GCodeProcessor::simulate_st_synchronize(float additional_time) -{ - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - m_time_processor.machines[i].simulate_st_synchronize(additional_time); - } -} - -void GCodeProcessor::update_estimated_times_stats() -{ - auto update_mode = [this](PrintEstimatedStatistics::ETimeMode mode) { - PrintEstimatedStatistics::Mode& data = m_result.print_statistics.modes[static_cast(mode)]; - data.time = get_time(mode); -#if ENABLE_TRAVEL_TIME - data.travel_time = get_travel_time(mode); -#endif // ENABLE_TRAVEL_TIME - data.custom_gcode_times = get_custom_gcode_times(mode, true); - data.moves_times = get_moves_time(mode); - data.roles_times = get_roles_time(mode); - data.layers_times = get_layers_time(mode); - }; - - update_mode(PrintEstimatedStatistics::ETimeMode::Normal); - if (m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].enabled) - update_mode(PrintEstimatedStatistics::ETimeMode::Stealth); - else - m_result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].reset(); - - m_result.print_statistics.volumes_per_color_change = m_used_filaments.volumes_per_color_change; - m_result.print_statistics.volumes_per_extruder = m_used_filaments.volumes_per_extruder; - m_result.print_statistics.used_filaments_per_role = m_used_filaments.filaments_per_role; -} - -#if ENABLE_PROCESS_G2_G3_LINES -double GCodeProcessor::extract_absolute_position_on_axis(Axis axis, const GCodeReader::GCodeLine& line, double area_filament_cross_section) -{ - if (line.has(Slic3r::Axis(axis))) { - bool is_relative = (m_global_positioning_type == EPositioningType::Relative); - if (axis == E) - is_relative |= (m_e_local_positioning_type == EPositioningType::Relative); - - const double lengthsScaleFactor = (m_units == EUnits::Inches) ? double(INCHES_TO_MM) : 1.0; - double ret = line.value(Slic3r::Axis(axis)) * lengthsScaleFactor; - if (axis == E && m_use_volumetric_e) - ret /= area_filament_cross_section; - return is_relative ? m_start_position[axis] + ret : m_origin[axis] + ret; - } - else - return m_start_position[axis]; -} -#endif // ENABLE_PROCESS_G2_G3_LINES - -} /* namespace Slic3r */ - +#include "libslic3r/libslic3r.h" +#include "libslic3r/Utils.hpp" +#include "libslic3r/Print.hpp" +#include "libslic3r/LocalesUtils.hpp" +#include "libslic3r/format.hpp" +#include "GCodeProcessor.hpp" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#if __has_include() + #include + #include +#endif + +#include + +static const float DEFAULT_TOOLPATH_WIDTH = 0.4f; +static const float DEFAULT_TOOLPATH_HEIGHT = 0.2f; + +static const float INCHES_TO_MM = 25.4f; +static const float MMMIN_TO_MMSEC = 1.0f / 60.0f; +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_TRAVEL_ACCELERATION = 1250.0f; + +static const size_t MIN_EXTRUDERS_COUNT = 5; +static const float DEFAULT_FILAMENT_DIAMETER = 1.75f; +static const float DEFAULT_FILAMENT_DENSITY = 1.245f; +#if ENABLE_USED_FILAMENT_POST_PROCESS +static const float DEFAULT_FILAMENT_COST = 0.0f; +#endif // ENABLE_USED_FILAMENT_POST_PROCESS +static const Slic3r::Vec3f DEFAULT_EXTRUDER_OFFSET = Slic3r::Vec3f::Zero(); + +#if ENABLE_PROCESS_G2_G3_LINES +static const std::string INTERNAL_G2G3_TAG = "!#!#! internal only - from G2/G3 expansion !#!#!"; +#endif // ENABLE_PROCESS_G2_G3_LINES + +namespace Slic3r { + +const std::vector GCodeProcessor::Reserved_Tags = { + "TYPE:", + "WIPE_START", + "WIPE_END", + "HEIGHT:", + "WIDTH:", + "LAYER_CHANGE", + "COLOR_CHANGE", + "PAUSE_PRINT", + "CUSTOM_GCODE", + "_GP_FIRST_LINE_M73_PLACEHOLDER", + "_GP_LAST_LINE_M73_PLACEHOLDER", + "_GP_ESTIMATED_PRINTING_TIME_PLACEHOLDER" +}; + +const float GCodeProcessor::Wipe_Width = 0.05f; +const float GCodeProcessor::Wipe_Height = 0.05f; + +#if ENABLE_GCODE_VIEWER_DATA_CHECKING +const std::string GCodeProcessor::Mm3_Per_Mm_Tag = "MM3_PER_MM:"; +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING + +static void set_option_value(ConfigOptionFloats& option, size_t id, float value) +{ + if (id < option.values.size()) + option.values[id] = static_cast(value); +}; + +static float get_option_value(const ConfigOptionFloats& option, size_t id) +{ + return option.values.empty() ? 0.0f : + ((id < option.values.size()) ? static_cast(option.values[id]) : static_cast(option.values.back())); +} + +static float estimated_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); +} + +static float 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); +} + +static float speed_from_distance(float initial_feedrate, float distance, float acceleration) +{ + // to avoid invalid negative numbers due to numerical errors + float value = std::max(0.0f, sqr(initial_feedrate) + 2.0f * acceleration * distance); + return ::sqrt(value); +} + +// Calculates the maximum allowable speed at this point when you must be able to reach target_velocity using the +// acceleration within the allotted distance. +static float max_allowable_speed(float acceleration, float target_velocity, float distance) +{ + // to avoid invalid negative numbers due to numerical errors + float value = std::max(0.0f, sqr(target_velocity) - 2.0f * acceleration * distance); + return std::sqrt(value); +} + +static float 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; +} + +void GCodeProcessor::CachedPosition::reset() +{ + std::fill(position.begin(), position.end(), FLT_MAX); + feedrate = FLT_MAX; +} + +void GCodeProcessor::CpColor::reset() +{ + counter = 0; + current = 0; +} + +float GCodeProcessor::Trapezoid::acceleration_time(float entry_feedrate, float acceleration) const +{ + return acceleration_time_from_distance(entry_feedrate, accelerate_until, acceleration); +} + +float GCodeProcessor::Trapezoid::cruise_time() const +{ + return (cruise_feedrate != 0.0f) ? cruise_distance() / cruise_feedrate : 0.0f; +} + +float GCodeProcessor::Trapezoid::deceleration_time(float distance, float acceleration) const +{ + return acceleration_time_from_distance(cruise_feedrate, (distance - decelerate_after), -acceleration); +} + +float GCodeProcessor::Trapezoid::cruise_distance() const +{ + return decelerate_after - accelerate_until; +} + +void GCodeProcessor::TimeBlock::calculate_trapezoid() +{ + trapezoid.cruise_feedrate = feedrate_profile.cruise; + + float accelerate_distance = std::max(0.0f, estimated_acceleration_distance(feedrate_profile.entry, feedrate_profile.cruise, acceleration)); + float decelerate_distance = std::max(0.0f, estimated_acceleration_distance(feedrate_profile.cruise, feedrate_profile.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 = std::clamp(intersection_distance(feedrate_profile.entry, feedrate_profile.exit, acceleration, distance), 0.0f, distance); + cruise_distance = 0.0f; + trapezoid.cruise_feedrate = speed_from_distance(feedrate_profile.entry, accelerate_distance, acceleration); + } + + trapezoid.accelerate_until = accelerate_distance; + trapezoid.decelerate_after = accelerate_distance + cruise_distance; +} + +float GCodeProcessor::TimeBlock::time() const +{ + return trapezoid.acceleration_time(feedrate_profile.entry, acceleration) + + trapezoid.cruise_time() + + trapezoid.deceleration_time(distance, acceleration); +} + +void GCodeProcessor::TimeMachine::State::reset() +{ + feedrate = 0.0f; + safe_feedrate = 0.0f; + axis_feedrate = { 0.0f, 0.0f, 0.0f, 0.0f }; + abs_axis_feedrate = { 0.0f, 0.0f, 0.0f, 0.0f }; +} + +void GCodeProcessor::TimeMachine::CustomGCodeTime::reset() +{ + needed = false; + cache = 0.0f; + times = std::vector>(); +} + +void GCodeProcessor::TimeMachine::reset() +{ + enabled = false; + acceleration = 0.0f; + max_acceleration = 0.0f; + retract_acceleration = 0.0f; + max_retract_acceleration = 0.0f; + travel_acceleration = 0.0f; + max_travel_acceleration = 0.0f; + extrude_factor_override_percentage = 1.0f; + time = 0.0f; +#if ENABLE_TRAVEL_TIME + travel_time = 0.0f; +#endif // ENABLE_TRAVEL_TIME + stop_times = std::vector(); + curr.reset(); + prev.reset(); + gcode_time.reset(); + blocks = std::vector(); + g1_times_cache = std::vector(); + std::fill(moves_time.begin(), moves_time.end(), 0.0f); + std::fill(roles_time.begin(), roles_time.end(), 0.0f); + layers_time = std::vector(); +} + +void GCodeProcessor::TimeMachine::simulate_st_synchronize(float additional_time) +{ + if (!enabled) + return; + + calculate_time(0, additional_time); +} + +static void planner_forward_pass_kernel(GCodeProcessor::TimeBlock& prev, GCodeProcessor::TimeBlock& curr) +{ + // 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_profile.entry < curr.feedrate_profile.entry) { + float entry_speed = std::min(curr.feedrate_profile.entry, max_allowable_speed(-prev.acceleration, prev.feedrate_profile.entry, prev.distance)); + + // Check for junction speed change + if (curr.feedrate_profile.entry != entry_speed) { + curr.feedrate_profile.entry = entry_speed; + curr.flags.recalculate = true; + } + } + } +} + +void planner_reverse_pass_kernel(GCodeProcessor::TimeBlock& curr, GCodeProcessor::TimeBlock& 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_profile.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_profile.entry) + curr.feedrate_profile.entry = std::min(curr.max_entry_speed, max_allowable_speed(-curr.acceleration, next.feedrate_profile.entry, curr.distance)); + else + curr.feedrate_profile.entry = curr.max_entry_speed; + + curr.flags.recalculate = true; + } +} + +static void recalculate_trapezoids(std::vector& blocks) +{ + GCodeProcessor::TimeBlock* curr = nullptr; + GCodeProcessor::TimeBlock* next = nullptr; + + for (size_t i = 0; i < blocks.size(); ++i) { + GCodeProcessor::TimeBlock& 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. + GCodeProcessor::TimeBlock block = *curr; + block.feedrate_profile.exit = next->feedrate_profile.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) { + GCodeProcessor::TimeBlock block = *next; + block.feedrate_profile.exit = next->safe_feedrate; + block.calculate_trapezoid(); + next->trapezoid = block.trapezoid; + next->flags.recalculate = false; + } +} + +void GCodeProcessor::TimeMachine::calculate_time(size_t keep_last_n_blocks, float additional_time) +{ + if (!enabled || blocks.size() < 2) + return; + + assert(keep_last_n_blocks <= blocks.size()); + + // forward_pass + for (size_t i = 0; i + 1 < blocks.size(); ++i) { + planner_forward_pass_kernel(blocks[i], blocks[i + 1]); + } + + // reverse_pass + for (int i = static_cast(blocks.size()) - 1; i > 0; --i) + planner_reverse_pass_kernel(blocks[i - 1], blocks[i]); + + recalculate_trapezoids(blocks); + + size_t n_blocks_process = blocks.size() - keep_last_n_blocks; + for (size_t i = 0; i < n_blocks_process; ++i) { + const TimeBlock& block = blocks[i]; + float block_time = block.time(); + if (i == 0) + block_time += additional_time; + + time += block_time; +#if ENABLE_TRAVEL_TIME + if (block.move_type == EMoveType::Travel) + travel_time += block_time; + else + roles_time[static_cast(block.role)] += block_time; +#endif // ENABLE_TRAVEL_TIME + gcode_time.cache += block_time; + moves_time[static_cast(block.move_type)] += block_time; +#if !ENABLE_TRAVEL_TIME + roles_time[static_cast(block.role)] += block_time; +#endif // !ENABLE_TRAVEL_TIME + if (block.layer_id >= layers_time.size()) { + const size_t curr_size = layers_time.size(); + layers_time.resize(block.layer_id); + for (size_t i = curr_size; i < layers_time.size(); ++i) { + layers_time[i] = 0.0f; + } + } + layers_time[block.layer_id - 1] += block_time; + g1_times_cache.push_back({ block.g1_line_id, time }); + // update times for remaining time to printer stop placeholders + auto it_stop_time = std::lower_bound(stop_times.begin(), stop_times.end(), block.g1_line_id, + [](const StopTime& t, unsigned int value) { return t.g1_line_id < value; }); + if (it_stop_time != stop_times.end() && it_stop_time->g1_line_id == block.g1_line_id) + it_stop_time->elapsed_time = time; + } + + if (keep_last_n_blocks) + blocks.erase(blocks.begin(), blocks.begin() + n_blocks_process); + else + blocks.clear(); +} + +void GCodeProcessor::TimeProcessor::reset() +{ + extruder_unloaded = true; + export_remaining_time_enabled = false; + machine_envelope_processing_enabled = false; + machine_limits = MachineEnvelopeConfig(); + filament_load_times = std::vector(); + filament_unload_times = std::vector(); + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + machines[i].reset(); + } + machines[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].enabled = true; +} + +#if !ENABLE_USED_FILAMENT_POST_PROCESS +void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, std::vector& moves, std::vector& lines_ends) +{ + FilePtr in{ boost::nowide::fopen(filename.c_str(), "rb") }; + if (in.f == nullptr) + throw Slic3r::RuntimeError(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"; + FilePtr out{ boost::nowide::fopen(out_path.c_str(), "wb") }; + if (out.f == nullptr) { + throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nCannot open file for writing.\n")); + } + + auto time_in_minutes = [](float time_in_seconds) { + assert(time_in_seconds >= 0.f); + return int((time_in_seconds + 0.5f) / 60.0f); + }; + + auto time_in_last_minute = [](float time_in_seconds) { + assert(time_in_seconds <= 60.0f); + return time_in_seconds / 60.0f; + }; + + auto format_line_M73_main = [](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); + }; + + auto format_line_M73_stop_int = [](const std::string& mask, int time) { + char line_M73[64]; + sprintf(line_M73, mask.c_str(), std::to_string(time).c_str()); + return std::string(line_M73); + }; + + auto format_time_float = [](float time) { + return Slic3r::float_to_string_decimal_point(time, 2); + }; + + auto format_line_M73_stop_float = [format_time_float](const std::string& mask, float time) { + char line_M73[64]; + sprintf(line_M73, mask.c_str(), format_time_float(time).c_str()); + return std::string(line_M73); + }; + + std::string gcode_line; + size_t g1_lines_counter = 0; + // keeps track of last exported pair + std::array, static_cast(PrintEstimatedStatistics::ETimeMode::Count)> last_exported_main; + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + last_exported_main[i] = { 0, time_in_minutes(machines[i].time) }; + } + + // keeps track of last exported remaining time to next printer stop + std::array(PrintEstimatedStatistics::ETimeMode::Count)> last_exported_stop; + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + last_exported_stop[i] = 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 + // gcode_line is in/out parameter, to reduce expensive memory allocation + auto process_placeholders = [&](std::string& gcode_line) { + unsigned int extra_lines_count = 0; + + // remove trailing '\n' + auto line = std::string_view(gcode_line).substr(0, gcode_line.length() - 1); + + std::string ret; + if (line.length() > 1) { + line = line.substr(1); + if (export_remaining_time_enabled && + (line == reserved_tag(ETags::First_Line_M73_Placeholder) || line == reserved_tag(ETags::Last_Line_M73_Placeholder))) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + const TimeMachine& machine = machines[i]; + if (machine.enabled) { + // export pair + ret += format_line_M73_main(machine.line_m73_main_mask.c_str(), + (line == reserved_tag(ETags::First_Line_M73_Placeholder)) ? 0 : 100, + (line == reserved_tag(ETags::First_Line_M73_Placeholder)) ? time_in_minutes(machine.time) : 0); + ++extra_lines_count; + + // export remaining time to next printer stop + if (line == reserved_tag(ETags::First_Line_M73_Placeholder) && !machine.stop_times.empty()) { + int to_export_stop = time_in_minutes(machine.stop_times.front().elapsed_time); + ret += format_line_M73_stop_int(machine.line_m73_stop_mask.c_str(), to_export_stop); + last_exported_stop[i] = to_export_stop; + ++extra_lines_count; + } + } + } + } + else if (line == reserved_tag(ETags::Estimated_Printing_Time_Placeholder)) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + const TimeMachine& machine = machines[i]; + PrintEstimatedStatistics::ETimeMode mode = static_cast(i); + if (mode == PrintEstimatedStatistics::ETimeMode::Normal || machine.enabled) { + char buf[128]; + sprintf(buf, "; estimated printing time (%s mode) = %s\n", + (mode == PrintEstimatedStatistics::ETimeMode::Normal) ? "normal" : "silent", + get_time_dhms(machine.time).c_str()); + ret += buf; + } + } + } + } + + if (! ret.empty()) + // Not moving the move operator on purpose, so that the gcode_line allocation will grow and it will not be reallocated after handful of lines are processed. + gcode_line = ret; + return std::tuple(!ret.empty(), (extra_lines_count == 0) ? extra_lines_count : extra_lines_count - 1); + }; + + // check for temporary lines + auto is_temporary_decoration = [](const std::string_view gcode_line) { + // remove trailing '\n' + assert(! gcode_line.empty()); + assert(gcode_line.back() == '\n'); + + // return true for decorations which are used in processing the gcode but that should not be exported into the final gcode + // i.e.: + // bool ret = gcode_line.substr(0, gcode_line.length() - 1) == ";" + Layer_Change_Tag; + // ... + // return ret; + return false; + }; + + // Iterators for the normal and silent cached time estimate entry recently processed, used by process_line_G1. + auto g1_times_cache_it = Slic3r::reserve_vector::const_iterator>(machines.size()); + for (const auto& machine : machines) + g1_times_cache_it.emplace_back(machine.g1_times_cache.begin()); + + // add lines M73 to exported gcode + auto process_line_G1 = [ + // Lambdas, mostly for string formatting, all with an empty capture block. + time_in_minutes, format_time_float, format_line_M73_main, format_line_M73_stop_int, format_line_M73_stop_float, time_in_last_minute, + &self = std::as_const(*this), + // Caches, to be modified + &g1_times_cache_it, &last_exported_main, &last_exported_stop, + // String output + &export_line] + (const size_t g1_lines_counter) { + unsigned int exported_lines_count = 0; + if (self.export_remaining_time_enabled) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + const TimeMachine& machine = self.machines[i]; + if (machine.enabled) { + // export pair + // Skip all machine.g1_times_cache below g1_lines_counter. + auto& it = g1_times_cache_it[i]; + while (it != machine.g1_times_cache.end() && it->id < g1_lines_counter) + ++it; + if (it != machine.g1_times_cache.end() && it->id == g1_lines_counter) { + std::pair to_export_main = { int(100.0f * it->elapsed_time / machine.time), + time_in_minutes(machine.time - it->elapsed_time) }; + if (last_exported_main[i] != to_export_main) { + export_line += format_line_M73_main(machine.line_m73_main_mask.c_str(), + to_export_main.first, to_export_main.second); + last_exported_main[i] = to_export_main; + ++exported_lines_count; + } + // export remaining time to next printer stop + auto it_stop = std::upper_bound(machine.stop_times.begin(), machine.stop_times.end(), it->elapsed_time, + [](float value, const TimeMachine::StopTime& t) { return value < t.elapsed_time; }); + if (it_stop != machine.stop_times.end()) { + int to_export_stop = time_in_minutes(it_stop->elapsed_time - it->elapsed_time); + if (last_exported_stop[i] != to_export_stop) { + if (to_export_stop > 0) { + if (last_exported_stop[i] != to_export_stop) { + export_line += format_line_M73_stop_int(machine.line_m73_stop_mask.c_str(), to_export_stop); + last_exported_stop[i] = to_export_stop; + ++exported_lines_count; + } + } + else { + bool is_last = false; + auto next_it = it + 1; + is_last |= (next_it == machine.g1_times_cache.end()); + + if (next_it != machine.g1_times_cache.end()) { + auto next_it_stop = std::upper_bound(machine.stop_times.begin(), machine.stop_times.end(), next_it->elapsed_time, + [](float value, const TimeMachine::StopTime& t) { return value < t.elapsed_time; }); + is_last |= (next_it_stop != it_stop); + + std::string time_float_str = format_time_float(time_in_last_minute(it_stop->elapsed_time - it->elapsed_time)); + std::string next_time_float_str = format_time_float(time_in_last_minute(it_stop->elapsed_time - next_it->elapsed_time)); + is_last |= (string_to_double_decimal_point(time_float_str) > 0. && string_to_double_decimal_point(next_time_float_str) == 0.); + } + + if (is_last) { + if (std::distance(machine.stop_times.begin(), it_stop) == static_cast(machine.stop_times.size() - 1)) + export_line += format_line_M73_stop_int(machine.line_m73_stop_mask.c_str(), to_export_stop); + else + export_line += format_line_M73_stop_float(machine.line_m73_stop_mask.c_str(), time_in_last_minute(it_stop->elapsed_time - it->elapsed_time)); + + last_exported_stop[i] = to_export_stop; + ++exported_lines_count; + } + } + } + } + } + } + } + } + return exported_lines_count; + }; + + // helper function to write to disk + size_t out_file_pos = 0; + lines_ends.clear(); + auto write_string = [&export_line, &out, &out_path, &out_file_pos, &lines_ends](const std::string& str) { + fwrite((const void*)export_line.c_str(), 1, export_line.length(), out.f); + if (ferror(out.f)) { + out.close(); + boost::nowide::remove(out_path.c_str()); + throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nIs the disk full?\n")); + } + for (size_t i = 0; i < export_line.size(); ++ i) + if (export_line[i] == '\n') + lines_ends.emplace_back(out_file_pos + i + 1); + out_file_pos += export_line.size(); + export_line.clear(); + }; + + unsigned int line_id = 0; + std::vector> offsets; + + { + // Read the input stream 64kB at a time, extract lines and process them. + std::vector buffer(65536 * 10, 0); + // Line buffer. + assert(gcode_line.empty()); + for (;;) { + size_t cnt_read = ::fread(buffer.data(), 1, buffer.size(), in.f); + if (::ferror(in.f)) + throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nError while reading from file.\n")); + bool eof = cnt_read == 0; + auto it = buffer.begin(); + auto it_bufend = buffer.begin() + cnt_read; + while (it != it_bufend || (eof && ! gcode_line.empty())) { + // Find end of line. + bool eol = false; + auto it_end = it; + for (; it_end != it_bufend && ! (eol = *it_end == '\r' || *it_end == '\n'); ++ it_end) ; + // End of line is indicated also if end of file was reached. + eol |= eof && it_end == it_bufend; + gcode_line.insert(gcode_line.end(), it, it_end); + if (eol) { + ++line_id; + + gcode_line += "\n"; + // replace placeholder lines + auto [processed, lines_added_count] = process_placeholders(gcode_line); + if (processed && lines_added_count > 0) + offsets.push_back({ line_id, lines_added_count }); + if (! processed && ! is_temporary_decoration(gcode_line) && GCodeReader::GCodeLine::cmd_is(gcode_line, "G1")) { + // remove temporary lines, add lines M73 where needed + unsigned int extra_lines_count = process_line_G1(g1_lines_counter ++); + if (extra_lines_count > 0) + offsets.push_back({ line_id, extra_lines_count }); + } + + export_line += gcode_line; + if (export_line.length() > 65535) + write_string(export_line); + gcode_line.clear(); + } + // Skip EOL. + it = it_end; + if (it != it_bufend && *it == '\r') + ++ it; + if (it != it_bufend && *it == '\n') + ++ it; + } + if (eof) + break; + } + } + + if (!export_line.empty()) + write_string(export_line); + + out.close(); + in.close(); + + // updates moves' gcode ids which have been modified by the insertion of the M73 lines + unsigned int curr_offset_id = 0; + unsigned int total_offset = 0; + for (GCodeProcessorResult::MoveVertex& move : moves) { + while (curr_offset_id < static_cast(offsets.size()) && offsets[curr_offset_id].first <= move.gcode_id) { + total_offset += offsets[curr_offset_id].second; + ++curr_offset_id; + } + move.gcode_id += total_offset; + } + + if (rename_file(out_path, filename)) + throw Slic3r::RuntimeError(std::string("Failed to rename the output G-code file from ") + out_path + " to " + filename + '\n' + + "Is " + out_path + " locked?" + '\n'); +} +#endif // !ENABLE_USED_FILAMENT_POST_PROCESS + +void GCodeProcessor::UsedFilaments::reset() +{ + color_change_cache = 0.0; + volumes_per_color_change = std::vector(); + + tool_change_cache = 0.0; + volumes_per_extruder.clear(); + + role_cache = 0.0; + filaments_per_role.clear(); +} + +void GCodeProcessor::UsedFilaments::increase_caches(double extruded_volume) +{ + color_change_cache += extruded_volume; + tool_change_cache += extruded_volume; + role_cache += extruded_volume; +} + +void GCodeProcessor::UsedFilaments::process_color_change_cache() +{ + if (color_change_cache != 0.0f) { + volumes_per_color_change.push_back(color_change_cache); + color_change_cache = 0.0f; + } +} + +void GCodeProcessor::UsedFilaments::process_extruder_cache(GCodeProcessor* processor) +{ + size_t active_extruder_id = processor->m_extruder_id; + if (tool_change_cache != 0.0) { + if (volumes_per_extruder.find(active_extruder_id) != volumes_per_extruder.end()) + volumes_per_extruder[active_extruder_id] += tool_change_cache; + else + volumes_per_extruder[active_extruder_id] = tool_change_cache; + tool_change_cache = 0.0; + } +} + +void GCodeProcessor::UsedFilaments::process_role_cache(GCodeProcessor* processor) +{ + if (role_cache != 0.0) { + std::pair filament = { 0.0f, 0.0f }; + + const double s = PI * sqr(0.5 * processor->m_result.filament_diameters[processor->m_extruder_id]); + filament.first = role_cache / s * 0.001; + filament.second = role_cache * processor->m_result.filament_densities[processor->m_extruder_id] * 0.001; + + ExtrusionRole active_role = processor->m_extrusion_role; + if (filaments_per_role.find(active_role) != filaments_per_role.end()) { + filaments_per_role[active_role].first += filament.first; + filaments_per_role[active_role].second += filament.second; + } + else + filaments_per_role[active_role] = filament; + role_cache = 0.0; + } +} + +void GCodeProcessor::UsedFilaments::process_caches(GCodeProcessor* processor) +{ + process_color_change_cache(); + process_extruder_cache(processor); + process_role_cache(processor); +} + +#if ENABLE_GCODE_VIEWER_STATISTICS +void GCodeProcessorResult::reset() { + moves = std::vector(); + bed_shape = Pointfs(); + max_print_height = 0.0f; + settings_ids.reset(); + extruders_count = 0; + extruder_colors = std::vector(); + filament_diameters = std::vector(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_DIAMETER); + filament_densities = std::vector(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_DENSITY); +#if ENABLE_USED_FILAMENT_POST_PROCESS + filament_cost = std::vector(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_COST); +#endif // ENABLE_USED_FILAMENT_POST_PROCESS + custom_gcode_per_print_z = std::vector(); + spiral_vase_layers = std::vector>>(); + time = 0; +} +#else +void GCodeProcessorResult::reset() { + + moves.clear(); + lines_ends.clear(); + bed_shape = Pointfs(); + max_print_height = 0.0f; + settings_ids.reset(); + extruders_count = 0; + extruder_colors = std::vector(); + filament_diameters = std::vector(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_DIAMETER); + filament_densities = std::vector(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_DENSITY); +#if ENABLE_USED_FILAMENT_POST_PROCESS + filament_cost = std::vector(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_COST); +#endif // ENABLE_USED_FILAMENT_POST_PROCESS + custom_gcode_per_print_z = std::vector(); + spiral_vase_layers = std::vector>>(); +} +#endif // ENABLE_GCODE_VIEWER_STATISTICS + +const std::vector> GCodeProcessor::Producers = { + { EProducer::PrusaSlicer, "generated by PrusaSlicer" }, + { EProducer::Slic3rPE, "generated by Slic3r Prusa Edition" }, + { EProducer::Slic3r, "generated by Slic3r" }, + { EProducer::SuperSlicer, "generated by SuperSlicer" }, + { EProducer::Cura, "Cura_SteamEngine" }, + { EProducer::Simplify3D, "G-Code generated by Simplify3D(R)" }, + { EProducer::CraftWare, "CraftWare" }, + { EProducer::ideaMaker, "ideaMaker" }, + { EProducer::KissSlicer, "KISSlicer" } +}; + +unsigned int GCodeProcessor::s_result_id = 0; + +bool GCodeProcessor::contains_reserved_tag(const std::string& gcode, std::string& found_tag) +{ + bool ret = false; + + GCodeReader parser; + parser.parse_buffer(gcode, [&ret, &found_tag](GCodeReader& parser, const GCodeReader::GCodeLine& line) { + std::string comment = line.raw(); + if (comment.length() > 2 && comment.front() == ';') { + comment = comment.substr(1); + for (const std::string& s : Reserved_Tags) { + if (boost::starts_with(comment, s)) { + ret = true; + found_tag = comment; + parser.quit_parsing(); + return; + } + } + } + }); + + return ret; +} + +bool GCodeProcessor::contains_reserved_tags(const std::string& gcode, unsigned int max_count, std::vector& found_tag) +{ + max_count = std::max(max_count, 1U); + + bool ret = false; + + CNumericLocalesSetter locales_setter; + + GCodeReader parser; + parser.parse_buffer(gcode, [&ret, &found_tag, max_count](GCodeReader& parser, const GCodeReader::GCodeLine& line) { + std::string comment = line.raw(); + if (comment.length() > 2 && comment.front() == ';') { + comment = comment.substr(1); + for (const std::string& s : Reserved_Tags) { + if (boost::starts_with(comment, s)) { + ret = true; + found_tag.push_back(comment); + if (found_tag.size() == max_count) { + parser.quit_parsing(); + return; + } + } + } + } + }); + + return ret; +} + +GCodeProcessor::GCodeProcessor() +: m_options_z_corrector(m_result) +{ + reset(); + m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].line_m73_main_mask = "M73 P%s R%s\n"; + m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].line_m73_stop_mask = "M73 C%s\n"; + m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].line_m73_main_mask = "M73 Q%s S%s\n"; + m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].line_m73_stop_mask = "M73 D%s\n"; +} + +void GCodeProcessor::apply_config(const PrintConfig& config) +{ + m_parser.apply_config(config); + + m_flavor = config.gcode_flavor; + + size_t extruders_count = config.nozzle_diameter.values.size(); + m_result.extruders_count = extruders_count; + + m_extruder_offsets.resize(extruders_count); + m_extruder_colors.resize(extruders_count); + m_result.filament_diameters.resize(extruders_count); + m_result.filament_densities.resize(extruders_count); +#if ENABLE_USED_FILAMENT_POST_PROCESS + m_result.filament_cost.resize(extruders_count); +#endif // ENABLE_USED_FILAMENT_POST_PROCESS + m_extruder_temps.resize(extruders_count); + + for (size_t i = 0; i < extruders_count; ++ i) { + m_extruder_offsets[i] = to_3d(config.extruder_offset.get_at(i).cast().eval(), 0.f); + m_extruder_colors[i] = static_cast(i); + m_result.filament_diameters[i] = static_cast(config.filament_diameter.get_at(i)); + m_result.filament_densities[i] = static_cast(config.filament_density.get_at(i)); +#if ENABLE_USED_FILAMENT_POST_PROCESS + m_result.filament_cost[i] = static_cast(config.filament_cost.get_at(i)); +#endif // ENABLE_USED_FILAMENT_POST_PROCESS + } + + if ((m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware || m_flavor == gcfRepRapFirmware) && config.machine_limits_usage.value != MachineLimitsUsage::Ignore) { + m_time_processor.machine_limits = reinterpret_cast(config); + if (m_flavor == gcfMarlinLegacy) { + // Legacy Marlin does not have separate travel acceleration, it uses the 'extruding' value instead. + m_time_processor.machine_limits.machine_max_acceleration_travel = m_time_processor.machine_limits.machine_max_acceleration_extruding; + } + if (m_flavor == gcfRepRapFirmware) { + // RRF does not support setting min feedrates. Set them to zero. + m_time_processor.machine_limits.machine_min_travel_rate.values.assign(m_time_processor.machine_limits.machine_min_travel_rate.size(), 0.); + m_time_processor.machine_limits.machine_min_extruding_rate.values.assign(m_time_processor.machine_limits.machine_min_extruding_rate.size(), 0.); + } + } + + // Filament load / unload times are not specific to a firmware flavor. Let anybody use it if they find it useful. + // As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they + // are considered to be active for the single extruder multi-material printers only. + m_time_processor.filament_load_times.resize(config.filament_load_time.values.size()); + for (size_t i = 0; i < config.filament_load_time.values.size(); ++i) { + m_time_processor.filament_load_times[i] = static_cast(config.filament_load_time.values[i]); + } + m_time_processor.filament_unload_times.resize(config.filament_unload_time.values.size()); + for (size_t i = 0; i < config.filament_unload_time.values.size(); ++i) { + m_time_processor.filament_unload_times[i] = static_cast(config.filament_unload_time.values[i]); + } + + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + float max_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_extruding, i); + m_time_processor.machines[i].max_acceleration = max_acceleration; + m_time_processor.machines[i].acceleration = (max_acceleration > 0.0f) ? max_acceleration : DEFAULT_ACCELERATION; + float max_retract_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_retracting, i); + m_time_processor.machines[i].max_retract_acceleration = max_retract_acceleration; + m_time_processor.machines[i].retract_acceleration = (max_retract_acceleration > 0.0f) ? max_retract_acceleration : DEFAULT_RETRACT_ACCELERATION; + float max_travel_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_travel, i); + m_time_processor.machines[i].max_travel_acceleration = max_travel_acceleration; + m_time_processor.machines[i].travel_acceleration = (max_travel_acceleration > 0.0f) ? max_travel_acceleration : DEFAULT_TRAVEL_ACCELERATION; + } + + m_time_processor.export_remaining_time_enabled = config.remaining_times.value; + m_use_volumetric_e = config.use_volumetric_e; + + const ConfigOptionFloatOrPercent* first_layer_height = config.option("first_layer_height"); + if (first_layer_height != nullptr) + m_first_layer_height = std::abs(first_layer_height->value); + + m_result.max_print_height = config.max_print_height; + + const ConfigOptionBool* spiral_vase = config.option("spiral_vase"); + if (spiral_vase != nullptr) + m_spiral_vase_active = spiral_vase->value; + + const ConfigOptionFloat* z_offset = config.option("z_offset"); + if (z_offset != nullptr) + m_z_offset = z_offset->value; +} + +void GCodeProcessor::apply_config(const DynamicPrintConfig& config) +{ + m_parser.apply_config(config); + + const ConfigOptionEnum* gcode_flavor = config.option>("gcode_flavor"); + if (gcode_flavor != nullptr) + m_flavor = gcode_flavor->value; + + const ConfigOptionPoints* bed_shape = config.option("bed_shape"); + if (bed_shape != nullptr) + m_result.bed_shape = bed_shape->values; + + const ConfigOptionString* print_settings_id = config.option("print_settings_id"); + if (print_settings_id != nullptr) + m_result.settings_ids.print = print_settings_id->value; + + const ConfigOptionStrings* filament_settings_id = config.option("filament_settings_id"); + if (filament_settings_id != nullptr) + m_result.settings_ids.filament = filament_settings_id->values; + + const ConfigOptionString* printer_settings_id = config.option("printer_settings_id"); + if (printer_settings_id != nullptr) + m_result.settings_ids.printer = printer_settings_id->value; + + m_result.extruders_count = config.option("nozzle_diameter")->values.size(); + + const ConfigOptionFloats* filament_diameters = config.option("filament_diameter"); + if (filament_diameters != nullptr) { + m_result.filament_diameters.clear(); + m_result.filament_diameters.resize(filament_diameters->values.size()); + for (size_t i = 0; i < filament_diameters->values.size(); ++i) { + m_result.filament_diameters[i] = static_cast(filament_diameters->values[i]); + } + } + + if (m_result.filament_diameters.size() < m_result.extruders_count) { + for (size_t i = m_result.filament_diameters.size(); i < m_result.extruders_count; ++i) { + m_result.filament_diameters.emplace_back(DEFAULT_FILAMENT_DIAMETER); + } + } + + const ConfigOptionFloats* filament_densities = config.option("filament_density"); + if (filament_densities != nullptr) { + m_result.filament_densities.clear(); + m_result.filament_densities.resize(filament_densities->values.size()); + for (size_t i = 0; i < filament_densities->values.size(); ++i) { + m_result.filament_densities[i] = static_cast(filament_densities->values[i]); + } + } + + if (m_result.filament_densities.size() < m_result.extruders_count) { + for (size_t i = m_result.filament_densities.size(); i < m_result.extruders_count; ++i) { + m_result.filament_densities.emplace_back(DEFAULT_FILAMENT_DENSITY); + } + } + +#if ENABLE_USED_FILAMENT_POST_PROCESS + const ConfigOptionFloats* filament_cost = config.option("filament_cost"); + if (filament_cost != nullptr) { + m_result.filament_cost.clear(); + m_result.filament_cost.resize(filament_cost->values.size()); + for (size_t i = 0; i < filament_cost->values.size(); ++i) { + m_result.filament_cost[i] = static_cast(filament_cost->values[i]); + } + } + + if (m_result.filament_cost.size() < m_result.extruders_count) { + for (size_t i = m_result.filament_cost.size(); i < m_result.extruders_count; ++i) { + m_result.filament_cost.emplace_back(DEFAULT_FILAMENT_COST); + } + } +#endif // ENABLE_USED_FILAMENT_POST_PROCESS + + const ConfigOptionPoints* extruder_offset = config.option("extruder_offset"); + if (extruder_offset != nullptr) { + m_extruder_offsets.resize(extruder_offset->values.size()); + for (size_t i = 0; i < extruder_offset->values.size(); ++i) { + Vec2f offset = extruder_offset->values[i].cast(); + m_extruder_offsets[i] = { offset(0), offset(1), 0.0f }; + } + } + + if (m_extruder_offsets.size() < m_result.extruders_count) { + for (size_t i = m_extruder_offsets.size(); i < m_result.extruders_count; ++i) { + m_extruder_offsets.emplace_back(DEFAULT_EXTRUDER_OFFSET); + } + } + + const ConfigOptionStrings* extruder_colour = config.option("extruder_colour"); + if (extruder_colour != nullptr) { + // takes colors from config + m_result.extruder_colors = extruder_colour->values; + // try to replace missing values with filament colors + const ConfigOptionStrings* filament_colour = config.option("filament_colour"); + if (filament_colour != nullptr && filament_colour->values.size() == m_result.extruder_colors.size()) { + for (size_t i = 0; i < m_result.extruder_colors.size(); ++i) { + if (m_result.extruder_colors[i].empty()) + m_result.extruder_colors[i] = filament_colour->values[i]; + } + } + } + + if (m_result.extruder_colors.size() < m_result.extruders_count) { + for (size_t i = m_result.extruder_colors.size(); i < m_result.extruders_count; ++i) { + m_result.extruder_colors.emplace_back(std::string()); + } + } + + // replace missing values with default + for (size_t i = 0; i < m_result.extruder_colors.size(); ++i) { + if (m_result.extruder_colors[i].empty()) + m_result.extruder_colors[i] = "#FF8000"; + } + + m_extruder_colors.resize(m_result.extruder_colors.size()); + for (size_t i = 0; i < m_result.extruder_colors.size(); ++i) { + m_extruder_colors[i] = static_cast(i); + } + + m_extruder_temps.resize(m_result.extruders_count); + + const ConfigOptionFloats* filament_load_time = config.option("filament_load_time"); + if (filament_load_time != nullptr) { + m_time_processor.filament_load_times.resize(filament_load_time->values.size()); + for (size_t i = 0; i < filament_load_time->values.size(); ++i) { + m_time_processor.filament_load_times[i] = static_cast(filament_load_time->values[i]); + } + } + + const ConfigOptionFloats* filament_unload_time = config.option("filament_unload_time"); + if (filament_unload_time != nullptr) { + m_time_processor.filament_unload_times.resize(filament_unload_time->values.size()); + for (size_t i = 0; i < filament_unload_time->values.size(); ++i) { + m_time_processor.filament_unload_times[i] = static_cast(filament_unload_time->values[i]); + } + } + + bool use_machine_limits = false; + const ConfigOptionEnum* machine_limits_usage = config.option>("machine_limits_usage"); + if (machine_limits_usage != nullptr) + use_machine_limits = machine_limits_usage->value != MachineLimitsUsage::Ignore; + + if (use_machine_limits && (m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware || m_flavor == gcfRepRapFirmware)) { + const ConfigOptionFloats* machine_max_acceleration_x = config.option("machine_max_acceleration_x"); + if (machine_max_acceleration_x != nullptr) + m_time_processor.machine_limits.machine_max_acceleration_x.values = machine_max_acceleration_x->values; + + const ConfigOptionFloats* machine_max_acceleration_y = config.option("machine_max_acceleration_y"); + if (machine_max_acceleration_y != nullptr) + m_time_processor.machine_limits.machine_max_acceleration_y.values = machine_max_acceleration_y->values; + + const ConfigOptionFloats* machine_max_acceleration_z = config.option("machine_max_acceleration_z"); + if (machine_max_acceleration_z != nullptr) + m_time_processor.machine_limits.machine_max_acceleration_z.values = machine_max_acceleration_z->values; + + const ConfigOptionFloats* machine_max_acceleration_e = config.option("machine_max_acceleration_e"); + if (machine_max_acceleration_e != nullptr) + m_time_processor.machine_limits.machine_max_acceleration_e.values = machine_max_acceleration_e->values; + + const ConfigOptionFloats* machine_max_feedrate_x = config.option("machine_max_feedrate_x"); + if (machine_max_feedrate_x != nullptr) + m_time_processor.machine_limits.machine_max_feedrate_x.values = machine_max_feedrate_x->values; + + const ConfigOptionFloats* machine_max_feedrate_y = config.option("machine_max_feedrate_y"); + if (machine_max_feedrate_y != nullptr) + m_time_processor.machine_limits.machine_max_feedrate_y.values = machine_max_feedrate_y->values; + + const ConfigOptionFloats* machine_max_feedrate_z = config.option("machine_max_feedrate_z"); + if (machine_max_feedrate_z != nullptr) + m_time_processor.machine_limits.machine_max_feedrate_z.values = machine_max_feedrate_z->values; + + const ConfigOptionFloats* machine_max_feedrate_e = config.option("machine_max_feedrate_e"); + if (machine_max_feedrate_e != nullptr) + m_time_processor.machine_limits.machine_max_feedrate_e.values = machine_max_feedrate_e->values; + + const ConfigOptionFloats* machine_max_jerk_x = config.option("machine_max_jerk_x"); + if (machine_max_jerk_x != nullptr) + m_time_processor.machine_limits.machine_max_jerk_x.values = machine_max_jerk_x->values; + + const ConfigOptionFloats* machine_max_jerk_y = config.option("machine_max_jerk_y"); + if (machine_max_jerk_y != nullptr) + m_time_processor.machine_limits.machine_max_jerk_y.values = machine_max_jerk_y->values; + + const ConfigOptionFloats* machine_max_jerk_z = config.option("machine_max_jerkz"); + if (machine_max_jerk_z != nullptr) + m_time_processor.machine_limits.machine_max_jerk_z.values = machine_max_jerk_z->values; + + const ConfigOptionFloats* machine_max_jerk_e = config.option("machine_max_jerk_e"); + if (machine_max_jerk_e != nullptr) + m_time_processor.machine_limits.machine_max_jerk_e.values = machine_max_jerk_e->values; + + const ConfigOptionFloats* machine_max_acceleration_extruding = config.option("machine_max_acceleration_extruding"); + if (machine_max_acceleration_extruding != nullptr) + m_time_processor.machine_limits.machine_max_acceleration_extruding.values = machine_max_acceleration_extruding->values; + + const ConfigOptionFloats* machine_max_acceleration_retracting = config.option("machine_max_acceleration_retracting"); + if (machine_max_acceleration_retracting != nullptr) + m_time_processor.machine_limits.machine_max_acceleration_retracting.values = machine_max_acceleration_retracting->values; + + + // Legacy Marlin does not have separate travel acceleration, it uses the 'extruding' value instead. + const ConfigOptionFloats* machine_max_acceleration_travel = config.option(m_flavor == gcfMarlinLegacy + ? "machine_max_acceleration_extruding" + : "machine_max_acceleration_travel"); + if (machine_max_acceleration_travel != nullptr) + m_time_processor.machine_limits.machine_max_acceleration_travel.values = machine_max_acceleration_travel->values; + + + const ConfigOptionFloats* machine_min_extruding_rate = config.option("machine_min_extruding_rate"); + if (machine_min_extruding_rate != nullptr) { + m_time_processor.machine_limits.machine_min_extruding_rate.values = machine_min_extruding_rate->values; + if (m_flavor == gcfRepRapFirmware) { + // RRF does not support setting min feedrates. Set zero. + m_time_processor.machine_limits.machine_min_extruding_rate.values.assign(m_time_processor.machine_limits.machine_min_extruding_rate.size(), 0.); + } + } + + const ConfigOptionFloats* machine_min_travel_rate = config.option("machine_min_travel_rate"); + if (machine_min_travel_rate != nullptr) { + m_time_processor.machine_limits.machine_min_travel_rate.values = machine_min_travel_rate->values; + if (m_flavor == gcfRepRapFirmware) { + // RRF does not support setting min feedrates. Set zero. + m_time_processor.machine_limits.machine_min_travel_rate.values.assign(m_time_processor.machine_limits.machine_min_travel_rate.size(), 0.); + } + } + } + + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + float max_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_extruding, i); + m_time_processor.machines[i].max_acceleration = max_acceleration; + m_time_processor.machines[i].acceleration = (max_acceleration > 0.0f) ? max_acceleration : DEFAULT_ACCELERATION; + float max_retract_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_retracting, i); + m_time_processor.machines[i].max_retract_acceleration = max_retract_acceleration; + m_time_processor.machines[i].retract_acceleration = (max_retract_acceleration > 0.0f) ? max_retract_acceleration : DEFAULT_RETRACT_ACCELERATION; + float max_travel_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_travel, i); + m_time_processor.machines[i].max_travel_acceleration = max_travel_acceleration; + m_time_processor.machines[i].travel_acceleration = (max_travel_acceleration > 0.0f) ? max_travel_acceleration : DEFAULT_TRAVEL_ACCELERATION; + } + + if (m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware) { + const ConfigOptionBool* silent_mode = config.option("silent_mode"); + if (silent_mode != nullptr) { + if (silent_mode->value && m_time_processor.machine_limits.machine_max_acceleration_x.values.size() > 1) + enable_stealth_time_estimator(true); + } + } + + const ConfigOptionBool* use_volumetric_e = config.option("use_volumetric_e"); + if (use_volumetric_e != nullptr) + m_use_volumetric_e = use_volumetric_e->value; + + const ConfigOptionFloatOrPercent* first_layer_height = config.option("first_layer_height"); + if (first_layer_height != nullptr) + m_first_layer_height = std::abs(first_layer_height->value); + + const ConfigOptionFloat* max_print_height = config.option("max_print_height"); + if (max_print_height != nullptr) + m_result.max_print_height = max_print_height->value; + + const ConfigOptionBool* spiral_vase = config.option("spiral_vase"); + if (spiral_vase != nullptr) + m_spiral_vase_active = spiral_vase->value; + + const ConfigOptionFloat* z_offset = config.option("z_offset"); + if (z_offset != nullptr) + m_z_offset = z_offset->value; +} + +void GCodeProcessor::enable_stealth_time_estimator(bool enabled) +{ + m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].enabled = enabled; +} + +void GCodeProcessor::reset() +{ + m_units = EUnits::Millimeters; + m_global_positioning_type = EPositioningType::Absolute; + m_e_local_positioning_type = EPositioningType::Absolute; + m_extruder_offsets = std::vector(MIN_EXTRUDERS_COUNT, Vec3f::Zero()); + m_flavor = gcfRepRapSprinter; + + m_start_position = { 0.0f, 0.0f, 0.0f, 0.0f }; + m_end_position = { 0.0f, 0.0f, 0.0f, 0.0f }; + m_saved_position = { 0.0f, 0.0f, 0.0f, 0.0f }; + m_origin = { 0.0f, 0.0f, 0.0f, 0.0f }; + m_cached_position.reset(); + m_wiping = false; + + m_line_id = 0; + m_last_line_id = 0; + m_feedrate = 0.0f; + m_feed_multiply.reset(); + m_width = 0.0f; + m_height = 0.0f; + m_forced_width = 0.0f; + m_forced_height = 0.0f; + m_mm3_per_mm = 0.0f; + m_fan_speed = 0.0f; + m_z_offset = 0.0f; + + m_extrusion_role = erNone; + m_extruder_id = 0; + m_extruder_colors.resize(MIN_EXTRUDERS_COUNT); + for (size_t i = 0; i < MIN_EXTRUDERS_COUNT; ++i) { + m_extruder_colors[i] = static_cast(i); + } + m_extruder_temps.resize(MIN_EXTRUDERS_COUNT); + for (size_t i = 0; i < MIN_EXTRUDERS_COUNT; ++i) { + m_extruder_temps[i] = 0.0f; + } + + m_extruded_last_z = 0.0f; + m_first_layer_height = 0.0f; + m_g1_line_id = 0; + m_layer_id = 0; + m_cp_color.reset(); + + m_producer = EProducer::Unknown; + + m_time_processor.reset(); + m_used_filaments.reset(); + + m_result.reset(); + m_result.id = ++s_result_id; + + m_use_volumetric_e = false; + m_last_default_color_id = 0; + + m_options_z_corrector.reset(); + + m_spiral_vase_active = false; + +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + m_mm3_per_mm_compare.reset(); + m_height_compare.reset(); + m_width_compare.reset(); +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING +} + +static inline const char* skip_whitespaces(const char *begin, const char *end) { + for (; begin != end && (*begin == ' ' || *begin == '\t'); ++ begin); + return begin; +} + +static inline const char* remove_eols(const char *begin, const char *end) { + for (; begin != end && (*(end - 1) == '\r' || *(end - 1) == '\n'); -- end); + return end; +} + +// Load a G-code into a stand-alone G-code viewer. +// throws CanceledException through print->throw_if_canceled() (sent by the caller as callback). +void GCodeProcessor::process_file(const std::string& filename, std::function cancel_callback) +{ + CNumericLocalesSetter locales_setter; + +#if ENABLE_GCODE_VIEWER_STATISTICS + m_start_time = std::chrono::high_resolution_clock::now(); +#endif // ENABLE_GCODE_VIEWER_STATISTICS + + // pre-processing + // parse the gcode file to detect its producer + { + m_parser.parse_file_raw(filename, [this](GCodeReader& reader, const char *begin, const char *end) { + begin = skip_whitespaces(begin, end); + if (begin != end && *begin == ';') { + // Comment. + begin = skip_whitespaces(++ begin, end); + end = remove_eols(begin, end); + if (begin != end && detect_producer(std::string_view(begin, end - begin))) + m_parser.quit_parsing(); + } + }); + m_parser.reset(); + + // if the gcode was produced by PrusaSlicer, + // extract the config from it + if (m_producer == EProducer::PrusaSlicer || m_producer == EProducer::Slic3rPE || m_producer == EProducer::Slic3r) { + DynamicPrintConfig config; + config.apply(FullPrintConfig::defaults()); + // Silently substitute unknown values by new ones for loading configurations from PrusaSlicer's own G-code. + // Showing substitution log or errors may make sense, but we are not really reading many values from the G-code config, + // thus a probability of incorrect substitution is low and the G-code viewer is a consumer-only anyways. + config.load_from_gcode_file(filename, ForwardCompatibilitySubstitutionRule::EnableSilent); + apply_config(config); + } + else if (m_producer == EProducer::Simplify3D) + apply_config_simplify3d(filename); + else if (m_producer == EProducer::SuperSlicer) + apply_config_superslicer(filename); + } + + // process gcode + m_result.filename = filename; + m_result.id = ++s_result_id; + // 1st move must be a dummy move + m_result.moves.emplace_back(GCodeProcessorResult::MoveVertex()); + size_t parse_line_callback_cntr = 10000; + m_parser.parse_file(filename, [this, cancel_callback, &parse_line_callback_cntr](GCodeReader& reader, const GCodeReader::GCodeLine& line) { + if (-- parse_line_callback_cntr == 0) { + // Don't call the cancel_callback() too often, do it every at every 10000'th line. + parse_line_callback_cntr = 10000; + if (cancel_callback) + cancel_callback(); + } + this->process_gcode_line(line, true); + }, m_result.lines_ends); + + // Don't post-process the G-code to update time stamps. + this->finalize(false); +} + +void GCodeProcessor::initialize(const std::string& filename) +{ + assert(is_decimal_separator_point()); + +#if ENABLE_GCODE_VIEWER_STATISTICS + m_start_time = std::chrono::high_resolution_clock::now(); +#endif // ENABLE_GCODE_VIEWER_STATISTICS + + // process gcode + m_result.filename = filename; + m_result.id = ++s_result_id; + // 1st move must be a dummy move + m_result.moves.emplace_back(GCodeProcessorResult::MoveVertex()); +} + +void GCodeProcessor::process_buffer(const std::string &buffer) +{ + //FIXME maybe cache GCodeLine gline to be over multiple parse_buffer() invocations. + m_parser.parse_buffer(buffer, [this](GCodeReader&, const GCodeReader::GCodeLine& line) { + this->process_gcode_line(line, false); + }); +} + +void GCodeProcessor::finalize(bool perform_post_process) +{ + // update width/height of wipe moves + for (GCodeProcessorResult::MoveVertex& move : m_result.moves) { + if (move.type == EMoveType::Wipe) { + move.width = Wipe_Width; + move.height = Wipe_Height; + } + } + + // process the time blocks + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + TimeMachine& machine = m_time_processor.machines[i]; + TimeMachine::CustomGCodeTime& gcode_time = machine.gcode_time; + machine.calculate_time(); + if (gcode_time.needed && gcode_time.cache != 0.0f) + gcode_time.times.push_back({ CustomGCode::ColorChange, gcode_time.cache }); + } + + m_used_filaments.process_caches(this); + + update_estimated_times_stats(); + +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + std::cout << "\n"; + m_mm3_per_mm_compare.output(); + m_height_compare.output(); + m_width_compare.output(); +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING + + if (perform_post_process) +#if ENABLE_USED_FILAMENT_POST_PROCESS + post_process(); +#else + m_time_processor.post_process(m_result.filename, m_result.moves, m_result.lines_ends); +#endif // ENABLE_USED_FILAMENT_POST_PROCESS +#if ENABLE_GCODE_VIEWER_STATISTICS + m_result.time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - m_start_time).count(); +#endif // ENABLE_GCODE_VIEWER_STATISTICS +} + +float GCodeProcessor::get_time(PrintEstimatedStatistics::ETimeMode mode) const +{ + return (mode < PrintEstimatedStatistics::ETimeMode::Count) ? m_time_processor.machines[static_cast(mode)].time : 0.0f; +} + +std::string GCodeProcessor::get_time_dhm(PrintEstimatedStatistics::ETimeMode mode) const +{ + return (mode < PrintEstimatedStatistics::ETimeMode::Count) ? short_time(get_time_dhms(m_time_processor.machines[static_cast(mode)].time)) : std::string("N/A"); +} + +#if ENABLE_TRAVEL_TIME +float GCodeProcessor::get_travel_time(PrintEstimatedStatistics::ETimeMode mode) const +{ + return (mode < PrintEstimatedStatistics::ETimeMode::Count) ? m_time_processor.machines[static_cast(mode)].travel_time : 0.0f; +} + +std::string GCodeProcessor::get_travel_time_dhm(PrintEstimatedStatistics::ETimeMode mode) const +{ + return (mode < PrintEstimatedStatistics::ETimeMode::Count) ? short_time(get_time_dhms(m_time_processor.machines[static_cast(mode)].travel_time)) : std::string("N/A"); +} +#endif // ENABLE_TRAVEL_TIME + +std::vector>> GCodeProcessor::get_custom_gcode_times(PrintEstimatedStatistics::ETimeMode mode, bool include_remaining) const +{ + std::vector>> ret; + if (mode < PrintEstimatedStatistics::ETimeMode::Count) { + const TimeMachine& machine = m_time_processor.machines[static_cast(mode)]; + float total_time = 0.0f; + for (const auto& [type, time] : machine.gcode_time.times) { + float remaining = include_remaining ? machine.time - total_time : 0.0f; + ret.push_back({ type, { time, remaining } }); + total_time += time; + } + } + return ret; +} + +std::vector> GCodeProcessor::get_moves_time(PrintEstimatedStatistics::ETimeMode mode) const +{ + std::vector> ret; + if (mode < PrintEstimatedStatistics::ETimeMode::Count) { + for (size_t i = 0; i < m_time_processor.machines[static_cast(mode)].moves_time.size(); ++i) { + float time = m_time_processor.machines[static_cast(mode)].moves_time[i]; + if (time > 0.0f) + ret.push_back({ static_cast(i), time }); + } + } + return ret; +} + +std::vector> GCodeProcessor::get_roles_time(PrintEstimatedStatistics::ETimeMode mode) const +{ + std::vector> ret; + if (mode < PrintEstimatedStatistics::ETimeMode::Count) { + for (size_t i = 0; i < m_time_processor.machines[static_cast(mode)].roles_time.size(); ++i) { + float time = m_time_processor.machines[static_cast(mode)].roles_time[i]; + if (time > 0.0f) + ret.push_back({ static_cast(i), time }); + } + } + return ret; +} + +ConfigSubstitutions load_from_superslicer_gcode_file(const std::string& filename, DynamicPrintConfig& config, ForwardCompatibilitySubstitutionRule compatibility_rule) +{ + // for reference, see: ConfigBase::load_from_gcode_file() + + boost::nowide::ifstream ifs(filename); + + auto header_end_pos = ifs.tellg(); + ConfigSubstitutionContext substitutions_ctxt(compatibility_rule); + size_t key_value_pairs = 0; + + ifs.seekg(0, ifs.end); + auto file_length = ifs.tellg(); + auto data_length = std::min(65535, file_length - header_end_pos); + ifs.seekg(file_length - data_length, ifs.beg); + std::vector data(size_t(data_length) + 1, 0); + ifs.read(data.data(), data_length); + ifs.close(); + key_value_pairs = ConfigBase::load_from_gcode_string_legacy(config, data.data(), substitutions_ctxt); + + if (key_value_pairs < 80) + throw Slic3r::RuntimeError(format("Suspiciously low number of configuration values extracted from %1%: %2%", filename, key_value_pairs)); + + return std::move(substitutions_ctxt.substitutions); +} + +void GCodeProcessor::apply_config_superslicer(const std::string& filename) +{ + DynamicPrintConfig config; + config.apply(FullPrintConfig::defaults()); + load_from_superslicer_gcode_file(filename, config, ForwardCompatibilitySubstitutionRule::EnableSilent); + apply_config(config); +} + +std::vector GCodeProcessor::get_layers_time(PrintEstimatedStatistics::ETimeMode mode) const +{ + return (mode < PrintEstimatedStatistics::ETimeMode::Count) ? + m_time_processor.machines[static_cast(mode)].layers_time : + std::vector(); +} + +void GCodeProcessor::apply_config_simplify3d(const std::string& filename) +{ + struct BedSize + { + double x{ 0.0 }; + double y{ 0.0 }; + + bool is_defined() const { return x > 0.0 && y > 0.0; } + }; + + BedSize bed_size; + bool producer_detected = false; + + m_parser.parse_file_raw(filename, [this, &bed_size, &producer_detected](GCodeReader& reader, const char* begin, const char* end) { + + auto extract_double = [](const std::string_view cmt, const std::string& key, double& out) { + size_t pos = cmt.find(key); + if (pos != cmt.npos) { + pos = cmt.find(',', pos); + if (pos != cmt.npos) { + out = string_to_double_decimal_point(cmt.substr(pos+1)); + return true; + } + } + return false; + }; + + auto extract_floats = [](const std::string_view cmt, const std::string& key, std::vector& out) { + size_t pos = cmt.find(key); + if (pos != cmt.npos) { + pos = cmt.find(',', pos); + if (pos != cmt.npos) { + const std::string_view data_str = cmt.substr(pos + 1); + std::vector values_str; + boost::split(values_str, data_str, boost::is_any_of("|,"), boost::token_compress_on); + for (const std::string& s : values_str) { + out.emplace_back(static_cast(string_to_double_decimal_point(s))); + } + return true; + } + } + return false; + }; + + begin = skip_whitespaces(begin, end); + end = remove_eols(begin, end); + if (begin != end) { + if (*begin == ';') { + // Comment. + begin = skip_whitespaces(++ begin, end); + if (begin != end) { + std::string_view comment(begin, end - begin); + if (producer_detected) { + if (bed_size.x == 0.0 && comment.find("strokeXoverride") != comment.npos) + extract_double(comment, "strokeXoverride", bed_size.x); + else if (bed_size.y == 0.0 && comment.find("strokeYoverride") != comment.npos) + extract_double(comment, "strokeYoverride", bed_size.y); + else if (comment.find("filamentDiameters") != comment.npos) { + m_result.filament_diameters.clear(); + extract_floats(comment, "filamentDiameters", m_result.filament_diameters); + } else if (comment.find("filamentDensities") != comment.npos) { + m_result.filament_densities.clear(); + extract_floats(comment, "filamentDensities", m_result.filament_densities); +#if ENABLE_USED_FILAMENT_POST_PROCESS + } + else if (comment.find("filamentPricesPerKg") != comment.npos) { + m_result.filament_cost.clear(); + extract_floats(comment, "filamentPricesPerKg", m_result.filament_cost); +#endif // ENABLE_USED_FILAMENT_POST_PROCESS + } else if (comment.find("extruderDiameter") != comment.npos) { + std::vector extruder_diameters; + extract_floats(comment, "extruderDiameter", extruder_diameters); + m_result.extruders_count = extruder_diameters.size(); + } + } else if (boost::starts_with(comment, "G-Code generated by Simplify3D(R)")) + producer_detected = true; + } + } else { + // Some non-empty G-code line detected, stop parsing config comments. + reader.quit_parsing(); + } + } + }); + +#if ENABLE_USED_FILAMENT_POST_PROCESS + if (m_result.extruders_count == 0) + m_result.extruders_count = std::max(1, std::min(m_result.filament_diameters.size(), + std::min(m_result.filament_densities.size(), m_result.filament_cost.size()))); +#else + if (m_result.extruders_count == 0) + m_result.extruders_count = std::max(1, std::min(m_result.filament_diameters.size(), m_result.filament_densities.size())); +#endif // ENABLE_USED_FILAMENT_POST_PROCESS + + if (bed_size.is_defined()) { + m_result.bed_shape = { + { 0.0, 0.0 }, + { bed_size.x, 0.0 }, + { bed_size.x, bed_size.y }, + { 0.0, bed_size.y } + }; + } +} + +void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line, bool producers_enabled) +{ +/* std::cout << line.raw() << std::endl; */ + + ++m_line_id; + + // update start position + m_start_position = m_end_position; + + const std::string_view cmd = line.cmd(); + if (cmd.length() > 1) { + // process command lines + switch (cmd[0]) + { + case 'g': + case 'G': + switch (cmd.size()) { + case 2: + switch (cmd[1]) { + case '0': { process_G0(line); break; } // Move + case '1': { process_G1(line); break; } // Move +#if ENABLE_PROCESS_G2_G3_LINES + case '2': { process_G2_G3(line, true); break; } // CW Arc Move + case '3': { process_G2_G3(line, false); break; } // CCW Arc Move +#endif // ENABLE_PROCESS_G2_G3_LINES + default: break; + } + break; + case 3: + switch (cmd[1]) { + case '1': + switch (cmd[2]) { + case '0': { process_G10(line); break; } // Retract + case '1': { process_G11(line); break; } // Unretract + default: break; + } + break; + case '2': + switch (cmd[2]) { + case '0': { process_G20(line); break; } // Set Units to Inches + case '1': { process_G21(line); break; } // Set Units to Millimeters + case '2': { process_G22(line); break; } // Firmware controlled retract + case '3': { process_G23(line); break; } // Firmware controlled unretract + case '8': { process_G28(line); break; } // Move to origin + default: break; + } + break; + case '6': + switch (cmd[2]) { + case '0': { process_G60(line); break; } // Save Current Position + case '1': { process_G61(line); break; } // Return to Saved Position + default: break; + } + break; + case '9': + switch (cmd[2]) { + case '0': { process_G90(line); break; } // Set to Absolute Positioning + case '1': { process_G91(line); break; } // Set to Relative Positioning + case '2': { process_G92(line); break; } // Set Position + default: break; + } + break; + } + break; + default: + break; + } + break; + case 'm': + case 'M': + switch (cmd.size()) { + case 2: + switch (cmd[1]) { + case '1': { process_M1(line); break; } // Sleep or Conditional stop + default: break; + } + break; + case 3: + switch (cmd[1]) { + case '8': + switch (cmd[2]) { + case '2': { process_M82(line); break; } // Set extruder to absolute mode + case '3': { process_M83(line); break; } // Set extruder to relative mode + default: break; + } + break; + default: + break; + } + break; + case 4: + switch (cmd[1]) { + case '1': + switch (cmd[2]) { + case '0': + switch (cmd[3]) { + case '4': { process_M104(line); break; } // Set extruder temperature + case '6': { process_M106(line); break; } // Set fan speed + case '7': { process_M107(line); break; } // Disable fan + case '8': { process_M108(line); break; } // Set tool (Sailfish) + case '9': { process_M109(line); break; } // Set extruder temperature and wait + default: break; + } + break; + case '3': + switch (cmd[3]) { + case '2': { process_M132(line); break; } // Recall stored home offsets + case '5': { process_M135(line); break; } // Set tool (MakerWare) + default: break; + } + break; + default: + break; + } + break; + case '2': + switch (cmd[2]) { + case '0': + switch (cmd[3]) { + case '1': { process_M201(line); break; } // Set max printing acceleration + case '3': { process_M203(line); break; } // Set maximum feedrate + case '4': { process_M204(line); break; } // Set default acceleration + case '5': { process_M205(line); break; } // Advanced settings + default: break; + } + break; + case '2': + switch (cmd[3]) { + case '0': { process_M220(line); break; } // Set Feedrate Percentage + case '1': { process_M221(line); break; } // Set extrude factor override percentage + default: break; + } + break; + default: + break; + } + break; + case '4': + switch (cmd[2]) { + case '0': + switch (cmd[3]) { + case '1': { process_M401(line); break; } // Repetier: Store x, y and z position + case '2': { process_M402(line); break; } // Repetier: Go to stored position + default: break; + } + break; + default: + break; + } + break; + case '5': + switch (cmd[2]) { + case '6': + switch (cmd[3]) { + case '6': { process_M566(line); break; } // Set allowable instantaneous speed change + default: break; + } + break; + default: + break; + } + break; + case '7': + switch (cmd[2]) { + case '0': + switch (cmd[3]) { + case '2': { process_M702(line); break; } // Unload the current filament into the MK3 MMU2 unit at the end of print. + default: break; + } + break; + default: + break; + } + break; + default: + break; + } + break; + default: + break; + } + break; + case 't': + case 'T': + process_T(line); // Select Tool + break; + default: + break; + } + } + else { + const std::string &comment = line.raw(); + if (comment.length() > 2 && comment.front() == ';') + // Process tags embedded into comments. Tag comments always start at the start of a line + // with a comment and continue with a tag without any whitespace separator. + process_tags(comment.substr(1), producers_enabled); + } +} + +#if __has_include() + template + struct is_from_chars_convertible : std::false_type {}; + template + struct is_from_chars_convertible(), std::declval(), std::declval()))>> : std::true_type {}; +#endif + +// Returns true if the number was parsed correctly into out and the number spanned the whole input string. +template +[[nodiscard]] static inline bool parse_number(const std::string_view sv, T &out) +{ + // https://www.bfilipek.com/2019/07/detect-overload-from-chars.html#example-stdfromchars +#if __has_include() + // Visual Studio 19 supports from_chars all right. + // OSX compiler that we use only implements std::from_chars just for ints. + // GCC that we compile on does not provide at all. + if constexpr (is_from_chars_convertible::value) { + auto str_end = sv.data() + sv.size(); + auto [end_ptr, error_code] = std::from_chars(sv.data(), str_end, out); + return error_code == std::errc() && end_ptr == str_end; + } + else +#endif + { + // Legacy conversion, which is costly due to having to make a copy of the string before conversion. + try { + assert(sv.size() < 1024); + assert(sv.data() != nullptr); + std::string str { sv }; + size_t read = 0; + if constexpr (std::is_same_v) + out = std::stoi(str, &read); + else if constexpr (std::is_same_v) + out = std::stol(str, &read); + else if constexpr (std::is_same_v) + out = string_to_double_decimal_point(str, &read); + else if constexpr (std::is_same_v) + out = string_to_double_decimal_point(str, &read); + return str.size() == read; + } catch (...) { + return false; + } + } +} + +void GCodeProcessor::process_tags(const std::string_view comment, bool producers_enabled) +{ + // producers tags + if (producers_enabled && process_producers_tags(comment)) + return; + + // extrusion role tag + if (boost::starts_with(comment, reserved_tag(ETags::Role))) { + set_extrusion_role(ExtrusionEntity::string_to_role(comment.substr(reserved_tag(ETags::Role).length()))); + if (m_extrusion_role == erExternalPerimeter) + m_seams_detector.activate(true); + return; + } + + // wipe start tag + if (boost::starts_with(comment, reserved_tag(ETags::Wipe_Start))) { + m_wiping = true; + return; + } + + // wipe end tag + if (boost::starts_with(comment, reserved_tag(ETags::Wipe_End))) { + m_wiping = false; + return; + } + + if (!producers_enabled || m_producer == EProducer::PrusaSlicer) { + // height tag + if (boost::starts_with(comment, reserved_tag(ETags::Height))) { + if (!parse_number(comment.substr(reserved_tag(ETags::Height).size()), m_forced_height)) + BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ")."; + return; + } + // width tag + if (boost::starts_with(comment, reserved_tag(ETags::Width))) { + if (!parse_number(comment.substr(reserved_tag(ETags::Width).size()), m_forced_width)) + BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Width (" << comment << ")."; + return; + } + } + + // color change tag + if (boost::starts_with(comment, reserved_tag(ETags::Color_Change))) { + unsigned char extruder_id = 0; + static std::vector Default_Colors = { + "#0B2C7A", // { 0.043f, 0.173f, 0.478f }, // bluish + "#1C8891", // { 0.110f, 0.533f, 0.569f }, + "#AAF200", // { 0.667f, 0.949f, 0.000f }, + "#F5CE0A", // { 0.961f, 0.808f, 0.039f }, + "#D16830", // { 0.820f, 0.408f, 0.188f }, + "#942616", // { 0.581f, 0.149f, 0.087f } // reddish + }; + + std::string color = Default_Colors[0]; + auto is_valid_color = [](const std::string& color) { + auto is_hex_digit = [](char c) { + return ((c >= '0' && c <= '9') || + (c >= 'A' && c <= 'F') || + (c >= 'a' && c <= 'f')); + }; + + if (color[0] != '#' || color.length() != 7) + return false; + for (int i = 1; i <= 6; ++i) { + if (!is_hex_digit(color[i])) + return false; + } + return true; + }; + + std::vector tokens; + boost::split(tokens, comment, boost::is_any_of(","), boost::token_compress_on); + if (tokens.size() > 1) { + if (tokens[1][0] == 'T') { + int eid; + if (!parse_number(tokens[1].substr(1), eid) || eid < 0 || eid > 255) { + BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Color_Change (" << comment << ")."; + return; + } + extruder_id = static_cast(eid); + } + } + if (tokens.size() > 2) { + if (is_valid_color(tokens[2])) + color = tokens[2]; + } + else { + color = Default_Colors[m_last_default_color_id]; + ++m_last_default_color_id; + if (m_last_default_color_id == Default_Colors.size()) + m_last_default_color_id = 0; + } + + if (extruder_id < m_extruder_colors.size()) + m_extruder_colors[extruder_id] = static_cast(m_extruder_offsets.size()) + m_cp_color.counter; // color_change position in list of color for preview + ++m_cp_color.counter; + if (m_cp_color.counter == UCHAR_MAX) + m_cp_color.counter = 0; + + if (m_extruder_id == extruder_id) { + m_cp_color.current = m_extruder_colors[extruder_id]; + store_move_vertex(EMoveType::Color_change); + CustomGCode::Item item = { static_cast(m_end_position[2]), CustomGCode::ColorChange, extruder_id + 1, color, "" }; + m_result.custom_gcode_per_print_z.emplace_back(item); + m_options_z_corrector.set(); + process_custom_gcode_time(CustomGCode::ColorChange); + process_filaments(CustomGCode::ColorChange); + } + + return; + } + + // pause print tag + if (comment == reserved_tag(ETags::Pause_Print)) { + store_move_vertex(EMoveType::Pause_Print); + CustomGCode::Item item = { static_cast(m_end_position[2]), CustomGCode::PausePrint, m_extruder_id + 1, "", "" }; + m_result.custom_gcode_per_print_z.emplace_back(item); + m_options_z_corrector.set(); + process_custom_gcode_time(CustomGCode::PausePrint); + return; + } + + // custom code tag + if (comment == reserved_tag(ETags::Custom_Code)) { + store_move_vertex(EMoveType::Custom_GCode); + CustomGCode::Item item = { static_cast(m_end_position[2]), CustomGCode::Custom, m_extruder_id + 1, "", "" }; + m_result.custom_gcode_per_print_z.emplace_back(item); + m_options_z_corrector.set(); + return; + } + + // layer change tag + if (comment == reserved_tag(ETags::Layer_Change)) { + ++m_layer_id; + if (m_spiral_vase_active) { + if (m_result.moves.empty()) + m_result.spiral_vase_layers.push_back({ m_first_layer_height, { 0, 0 } }); + else { + const size_t move_id = m_result.moves.size() - 1; + if (!m_result.spiral_vase_layers.empty() && m_end_position[Z] == m_result.spiral_vase_layers.back().first) + m_result.spiral_vase_layers.back().second.second = move_id; + else + m_result.spiral_vase_layers.push_back({ static_cast(m_end_position[Z]), { move_id, move_id } }); + } + } + return; + } + +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + // mm3_per_mm print tag + if (boost::starts_with(comment, Mm3_Per_Mm_Tag)) { + if (! parse_number(comment.substr(Mm3_Per_Mm_Tag.size()), m_mm3_per_mm_compare.last_tag_value)) + BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Mm3_Per_Mm (" << comment << ")."; + return; + } +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING +} + +bool GCodeProcessor::process_producers_tags(const std::string_view comment) +{ + switch (m_producer) + { + case EProducer::Slic3rPE: + case EProducer::Slic3r: + case EProducer::SuperSlicer: + case EProducer::PrusaSlicer: { return process_prusaslicer_tags(comment); } + case EProducer::Cura: { return process_cura_tags(comment); } + case EProducer::Simplify3D: { return process_simplify3d_tags(comment); } + case EProducer::CraftWare: { return process_craftware_tags(comment); } + case EProducer::ideaMaker: { return process_ideamaker_tags(comment); } + case EProducer::KissSlicer: { return process_kissslicer_tags(comment); } + default: { return false; } + } +} + +bool GCodeProcessor::process_prusaslicer_tags(const std::string_view comment) +{ + return false; +} + +bool GCodeProcessor::process_cura_tags(const std::string_view comment) +{ + // TYPE -> extrusion role + std::string tag = "TYPE:"; + size_t pos = comment.find(tag); + if (pos != comment.npos) { + const std::string_view type = comment.substr(pos + tag.length()); + if (type == "SKIRT") + set_extrusion_role(erSkirt); + else if (type == "WALL-OUTER") + set_extrusion_role(erExternalPerimeter); + else if (type == "WALL-INNER") + set_extrusion_role(erPerimeter); + else if (type == "SKIN") + set_extrusion_role(erSolidInfill); + else if (type == "FILL") + set_extrusion_role(erInternalInfill); + else if (type == "SUPPORT") + set_extrusion_role(erSupportMaterial); + else if (type == "SUPPORT-INTERFACE") + set_extrusion_role(erSupportMaterialInterface); + else if (type == "PRIME-TOWER") + set_extrusion_role(erWipeTower); + else { + set_extrusion_role(erNone); + BOOST_LOG_TRIVIAL(warning) << "GCodeProcessor found unknown extrusion role: " << type; + } + + if (m_extrusion_role == erExternalPerimeter) + m_seams_detector.activate(true); + + return true; + } + + // flavor + tag = "FLAVOR:"; + pos = comment.find(tag); + if (pos != comment.npos) { + const std::string_view flavor = comment.substr(pos + tag.length()); + if (flavor == "BFB") + m_flavor = gcfMarlinLegacy; // is this correct ? + else if (flavor == "Mach3") + m_flavor = gcfMach3; + else if (flavor == "Makerbot") + m_flavor = gcfMakerWare; + else if (flavor == "UltiGCode") + m_flavor = gcfMarlinLegacy; // is this correct ? + else if (flavor == "Marlin(Volumetric)") + m_flavor = gcfMarlinLegacy; // is this correct ? + else if (flavor == "Griffin") + m_flavor = gcfMarlinLegacy; // is this correct ? + else if (flavor == "Repetier") + m_flavor = gcfRepetier; + else if (flavor == "RepRap") + m_flavor = gcfRepRapFirmware; + else if (flavor == "Marlin") + m_flavor = gcfMarlinLegacy; + else + BOOST_LOG_TRIVIAL(warning) << "GCodeProcessor found unknown flavor: " << flavor; + + return true; + } + + // layer + tag = "LAYER:"; + pos = comment.find(tag); + if (pos != comment.npos) { + ++m_layer_id; + return true; + } + + return false; +} + +bool GCodeProcessor::process_simplify3d_tags(const std::string_view comment) +{ + // extrusion roles + + // in older versions the comments did not contain the key 'feature' + std::string_view cmt = comment; + size_t pos = cmt.find(" feature"); + if (pos == 0) + cmt.remove_prefix(8); + + // ; skirt + pos = cmt.find(" skirt"); + if (pos == 0) { + set_extrusion_role(erSkirt); + return true; + } + + // ; outer perimeter + pos = cmt.find(" outer perimeter"); + if (pos == 0) { + set_extrusion_role(erExternalPerimeter); + m_seams_detector.activate(true); + return true; + } + + // ; inner perimeter + pos = cmt.find(" inner perimeter"); + if (pos == 0) { + set_extrusion_role(erPerimeter); + return true; + } + + // ; gap fill + pos = cmt.find(" gap fill"); + if (pos == 0) { + set_extrusion_role(erGapFill); + return true; + } + + // ; infill + pos = cmt.find(" infill"); + if (pos == 0) { + set_extrusion_role(erInternalInfill); + return true; + } + + // ; solid layer + pos = cmt.find(" solid layer"); + if (pos == 0) { + set_extrusion_role(erSolidInfill); + return true; + } + + // ; bridge + pos = cmt.find(" bridge"); + if (pos == 0) { + set_extrusion_role(erBridgeInfill); + return true; + } + + // ; support + pos = cmt.find(" support"); + if (pos == 0) { + set_extrusion_role(erSupportMaterial); + return true; + } + + // ; dense support + pos = cmt.find(" dense support"); + if (pos == 0) { + set_extrusion_role(erSupportMaterialInterface); + return true; + } + + // ; prime pillar + pos = cmt.find(" prime pillar"); + if (pos == 0) { + set_extrusion_role(erWipeTower); + return true; + } + + // ; ooze shield + pos = cmt.find(" ooze shield"); + if (pos == 0) { + set_extrusion_role(erNone); // Missing mapping + return true; + } + + // ; raft + pos = cmt.find(" raft"); + if (pos == 0) { + set_extrusion_role(erSupportMaterial); + return true; + } + + // ; internal single extrusion + pos = cmt.find(" internal single extrusion"); + if (pos == 0) { + set_extrusion_role(erNone); // Missing mapping + return true; + } + + // geometry + // ; tool + std::string tag = " tool"; + pos = cmt.find(tag); + if (pos == 0) { + const std::string_view data = cmt.substr(pos + tag.length()); + std::string h_tag = "H"; + size_t h_start = data.find(h_tag); + size_t h_end = data.find_first_of(' ', h_start); + std::string w_tag = "W"; + size_t w_start = data.find(w_tag); + size_t w_end = data.find_first_of(' ', w_start); + if (h_start != data.npos) { + if (!parse_number(data.substr(h_start + 1, (h_end != data.npos) ? h_end - h_start - 1 : h_end), m_forced_height)) + BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ")."; + } + if (w_start != data.npos) { + if (!parse_number(data.substr(w_start + 1, (w_end != data.npos) ? w_end - w_start - 1 : w_end), m_forced_width)) + BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Width (" << comment << ")."; + } + + return true; + } + + // ; layer + tag = " layer"; + pos = cmt.find(tag); + if (pos == 0) { + // skip lines "; layer end" + const std::string_view data = cmt.substr(pos + tag.length()); + size_t end_start = data.find("end"); + if (end_start == data.npos) + ++m_layer_id; + + return true; + } + + return false; +} + +bool GCodeProcessor::process_craftware_tags(const std::string_view comment) +{ + // segType -> extrusion role + std::string tag = "segType:"; + size_t pos = comment.find(tag); + if (pos != comment.npos) { + const std::string_view type = comment.substr(pos + tag.length()); + if (type == "Skirt") + set_extrusion_role(erSkirt); + else if (type == "Perimeter") + set_extrusion_role(erExternalPerimeter); + else if (type == "HShell") + set_extrusion_role(erNone); // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + else if (type == "InnerHair") + set_extrusion_role(erNone); // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + else if (type == "Loop") + set_extrusion_role(erNone); // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + else if (type == "Infill") + set_extrusion_role(erInternalInfill); + else if (type == "Raft") + set_extrusion_role(erSkirt); + else if (type == "Support") + set_extrusion_role(erSupportMaterial); + else if (type == "SupportTouch") + set_extrusion_role(erSupportMaterial); + else if (type == "SoftSupport") + set_extrusion_role(erSupportMaterialInterface); + else if (type == "Pillar") + set_extrusion_role(erWipeTower); + else { + set_extrusion_role(erNone); + BOOST_LOG_TRIVIAL(warning) << "GCodeProcessor found unknown extrusion role: " << type; + } + + if (m_extrusion_role == erExternalPerimeter) + m_seams_detector.activate(true); + + return true; + } + + // layer + pos = comment.find(" Layer #"); + if (pos == 0) { + ++m_layer_id; + return true; + } + + return false; +} + +bool GCodeProcessor::process_ideamaker_tags(const std::string_view comment) +{ + // TYPE -> extrusion role + std::string tag = "TYPE:"; + size_t pos = comment.find(tag); + if (pos != comment.npos) { + const std::string_view type = comment.substr(pos + tag.length()); + if (type == "RAFT") + set_extrusion_role(erSkirt); + else if (type == "WALL-OUTER") + set_extrusion_role(erExternalPerimeter); + else if (type == "WALL-INNER") + set_extrusion_role(erPerimeter); + else if (type == "SOLID-FILL") + set_extrusion_role(erSolidInfill); + else if (type == "FILL") + set_extrusion_role(erInternalInfill); + else if (type == "BRIDGE") + set_extrusion_role(erBridgeInfill); + else if (type == "SUPPORT") + set_extrusion_role(erSupportMaterial); + else { + set_extrusion_role(erNone); + BOOST_LOG_TRIVIAL(warning) << "GCodeProcessor found unknown extrusion role: " << type; + } + + if (m_extrusion_role == erExternalPerimeter) + m_seams_detector.activate(true); + + return true; + } + + // geometry + // width + tag = "WIDTH:"; + pos = comment.find(tag); + if (pos != comment.npos) { + if (!parse_number(comment.substr(pos + tag.length()), m_forced_width)) + BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Width (" << comment << ")."; + return true; + } + + // height + tag = "HEIGHT:"; + pos = comment.find(tag); + if (pos != comment.npos) { + if (!parse_number(comment.substr(pos + tag.length()), m_forced_height)) + BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ")."; + return true; + } + + // layer + pos = comment.find("LAYER:"); + if (pos == 0) { + ++m_layer_id; + return true; + } + + return false; +} + +bool GCodeProcessor::process_kissslicer_tags(const std::string_view comment) +{ + // extrusion roles + + // ; 'Raft Path' + size_t pos = comment.find(" 'Raft Path'"); + if (pos == 0) { + set_extrusion_role(erSkirt); + return true; + } + + // ; 'Support Interface Path' + pos = comment.find(" 'Support Interface Path'"); + if (pos == 0) { + set_extrusion_role(erSupportMaterialInterface); + return true; + } + + // ; 'Travel/Ironing Path' + pos = comment.find(" 'Travel/Ironing Path'"); + if (pos == 0) { + set_extrusion_role(erIroning); + return true; + } + + // ; 'Support (may Stack) Path' + pos = comment.find(" 'Support (may Stack) Path'"); + if (pos == 0) { + set_extrusion_role(erSupportMaterial); + return true; + } + + // ; 'Perimeter Path' + pos = comment.find(" 'Perimeter Path'"); + if (pos == 0) { + set_extrusion_role(erExternalPerimeter); + m_seams_detector.activate(true); + return true; + } + + // ; 'Pillar Path' + pos = comment.find(" 'Pillar Path'"); + if (pos == 0) { + set_extrusion_role(erNone); // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + return true; + } + + // ; 'Destring/Wipe/Jump Path' + pos = comment.find(" 'Destring/Wipe/Jump Path'"); + if (pos == 0) { + set_extrusion_role(erNone); // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + return true; + } + + // ; 'Prime Pillar Path' + pos = comment.find(" 'Prime Pillar Path'"); + if (pos == 0) { + set_extrusion_role(erNone); // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + return true; + } + + // ; 'Loop Path' + pos = comment.find(" 'Loop Path'"); + if (pos == 0) { + set_extrusion_role(erNone); // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + return true; + } + + // ; 'Crown Path' + pos = comment.find(" 'Crown Path'"); + if (pos == 0) { + set_extrusion_role(erNone); // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + return true; + } + + // ; 'Solid Path' + pos = comment.find(" 'Solid Path'"); + if (pos == 0) { + set_extrusion_role(erNone); + return true; + } + + // ; 'Stacked Sparse Infill Path' + pos = comment.find(" 'Stacked Sparse Infill Path'"); + if (pos == 0) { + set_extrusion_role(erInternalInfill); + return true; + } + + // ; 'Sparse Infill Path' + pos = comment.find(" 'Sparse Infill Path'"); + if (pos == 0) { + set_extrusion_role(erSolidInfill); + return true; + } + + // geometry + + // layer + pos = comment.find(" BEGIN_LAYER_"); + if (pos == 0) { + ++m_layer_id; + return true; + } + + return false; +} + +bool GCodeProcessor::detect_producer(const std::string_view comment) +{ + for (const auto& [id, search_string] : Producers) { + size_t pos = comment.find(search_string); + if (pos != comment.npos) { + m_producer = id; + BOOST_LOG_TRIVIAL(info) << "Detected gcode producer: " << search_string; + return true; + } + } + return false; +} + +void GCodeProcessor::process_G0(const GCodeReader::GCodeLine& line) +{ + process_G1(line); +} + +void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) +{ + const float filament_diameter = (static_cast(m_extruder_id) < m_result.filament_diameters.size()) ? m_result.filament_diameters[m_extruder_id] : m_result.filament_diameters.back(); + const float filament_radius = 0.5f * filament_diameter; + const float area_filament_cross_section = static_cast(M_PI) * sqr(filament_radius); +#if !ENABLE_PROCESS_G2_G3_LINES + auto absolute_position = [this, area_filament_cross_section](Axis axis, const GCodeReader::GCodeLine& lineG1) { + bool is_relative = (m_global_positioning_type == EPositioningType::Relative); + if (axis == E) + is_relative |= (m_e_local_positioning_type == EPositioningType::Relative); + + if (lineG1.has(Slic3r::Axis(axis))) { + float lengthsScaleFactor = (m_units == EUnits::Inches) ? INCHES_TO_MM : 1.0f; + float ret = lineG1.value(Slic3r::Axis(axis)) * lengthsScaleFactor; + if (axis == E && m_use_volumetric_e) + ret /= area_filament_cross_section; + return is_relative ? m_start_position[axis] + ret : m_origin[axis] + ret; + } + else + return m_start_position[axis]; + }; +#endif // !ENABLE_PROCESS_G2_G3_LINES + + auto move_type = [this](const AxisCoords& delta_pos) { + EMoveType type = EMoveType::Noop; + + if (m_wiping) + type = EMoveType::Wipe; + else if (delta_pos[E] < 0.0f) + type = (delta_pos[X] != 0.0f || delta_pos[Y] != 0.0f || delta_pos[Z] != 0.0f) ? EMoveType::Travel : EMoveType::Retract; + else if (delta_pos[E] > 0.0f) { + if (delta_pos[X] == 0.0f && delta_pos[Y] == 0.0f) + type = (delta_pos[Z] == 0.0f) ? EMoveType::Unretract : EMoveType::Travel; + else if (delta_pos[X] != 0.0f || delta_pos[Y] != 0.0f) + type = EMoveType::Extrude; + } + else if (delta_pos[X] != 0.0f || delta_pos[Y] != 0.0f || delta_pos[Z] != 0.0f) + type = EMoveType::Travel; + + return type; + }; + + ++m_g1_line_id; + + // enable processing of lines M201/M203/M204/M205 + m_time_processor.machine_envelope_processing_enabled = true; + + // updates axes positions from line + for (unsigned char a = X; a <= E; ++a) { +#if ENABLE_PROCESS_G2_G3_LINES + m_end_position[a] = extract_absolute_position_on_axis((Axis)a, line, double(area_filament_cross_section)); +#else + m_end_position[a] = absolute_position((Axis)a, line); +#endif // ENABLE_PROCESS_G2_G3_LINES + } + + // updates feedrate from line, if present + if (line.has_f()) + m_feedrate = m_feed_multiply.current * line.f() * MMMIN_TO_MMSEC; + + // calculates movement deltas + float max_abs_delta = 0.0f; + AxisCoords delta_pos; + for (unsigned char a = X; a <= E; ++a) { + delta_pos[a] = m_end_position[a] - m_start_position[a]; + max_abs_delta = std::max(max_abs_delta, std::abs(delta_pos[a])); + } + + // no displacement, return + if (max_abs_delta == 0.0f) + return; + + const EMoveType type = move_type(delta_pos); + if (type == EMoveType::Extrude) { + const float delta_xyz = std::sqrt(sqr(delta_pos[X]) + sqr(delta_pos[Y]) + sqr(delta_pos[Z])); + const float volume_extruded_filament = area_filament_cross_section * delta_pos[E]; + const float area_toolpath_cross_section = volume_extruded_filament / delta_xyz; + + // save extruded volume to the cache + m_used_filaments.increase_caches(volume_extruded_filament); + + // volume extruded filament / tool displacement = area toolpath cross section + m_mm3_per_mm = area_toolpath_cross_section; +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + m_mm3_per_mm_compare.update(area_toolpath_cross_section, m_extrusion_role); +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING + +#if ENABLE_PROCESS_G2_G3_LINES + if (m_forced_height > 0.0f) + m_height = m_forced_height; + else if (m_layer_id == 0) + m_height = (m_end_position[Z] <= double(m_first_layer_height)) ? m_end_position[Z] : m_first_layer_height; + else if (line.comment() != INTERNAL_G2G3_TAG){ + if (m_end_position[Z] > m_extruded_last_z + EPSILON && delta_pos[Z] == 0.0) + m_height = m_end_position[Z] - m_extruded_last_z; + } +#else + if (m_forced_height > 0.0f) + m_height = m_forced_height; + else if (m_layer_id == 0) + m_height = (m_end_position[Z] <= double(m_first_layer_height)) ? m_end_position[Z] : m_first_layer_height; + else { + if (m_end_position[Z] > m_extruded_last_z + EPSILON) + m_height = m_end_position[Z] - m_extruded_last_z; + } +#endif // ENABLE_PROCESS_G2_G3_LINES + + if (m_height == 0.0f) + m_height = DEFAULT_TOOLPATH_HEIGHT; + + if (m_end_position[Z] == 0.0f) + m_end_position[Z] = m_height; + +#if ENABLE_PROCESS_G2_G3_LINES + if (line.comment() != INTERNAL_G2G3_TAG) +#endif // ENABLE_PROCESS_G2_G3_LINES + m_extruded_last_z = m_end_position[Z]; + m_options_z_corrector.update(m_height); + +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + m_height_compare.update(m_height, m_extrusion_role); +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING + + if (m_forced_width > 0.0f) + m_width = m_forced_width; + else if (m_extrusion_role == erExternalPerimeter) + // cross section: rectangle + m_width = delta_pos[E] * static_cast(M_PI * sqr(1.05f * filament_radius)) / (delta_xyz * m_height); + else if (m_extrusion_role == erBridgeInfill || m_extrusion_role == erNone) + // cross section: circle + m_width = static_cast(m_result.filament_diameters[m_extruder_id]) * std::sqrt(delta_pos[E] / delta_xyz); + else + // cross section: rectangle + 2 semicircles + m_width = delta_pos[E] * static_cast(M_PI * sqr(filament_radius)) / (delta_xyz * m_height) + static_cast(1.0 - 0.25 * M_PI) * m_height; + + if (m_width == 0.0f) + m_width = DEFAULT_TOOLPATH_WIDTH; + + // clamp width to avoid artifacts which may arise from wrong values of m_height + m_width = std::min(m_width, std::max(2.0f, 4.0f * m_height)); + +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + m_width_compare.update(m_width, m_extrusion_role); +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING + } + + // time estimate section + auto move_length = [](const AxisCoords& delta_pos) { + float sq_xyz_length = sqr(delta_pos[X]) + sqr(delta_pos[Y]) + sqr(delta_pos[Z]); + return (sq_xyz_length > 0.0f) ? std::sqrt(sq_xyz_length) : std::abs(delta_pos[E]); + }; + + auto is_extrusion_only_move = [](const AxisCoords& delta_pos) { + return delta_pos[X] == 0.0f && delta_pos[Y] == 0.0f && delta_pos[Z] == 0.0f && delta_pos[E] != 0.0f; + }; + + const float distance = move_length(delta_pos); + assert(distance != 0.0f); + const float inv_distance = 1.0f / distance; + + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + TimeMachine& machine = m_time_processor.machines[i]; + if (!machine.enabled) + continue; + + TimeMachine::State& curr = machine.curr; + TimeMachine::State& prev = machine.prev; + std::vector& blocks = machine.blocks; + + curr.feedrate = (delta_pos[E] == 0.0f) ? + minimum_travel_feedrate(static_cast(i), m_feedrate) : + minimum_feedrate(static_cast(i), m_feedrate); + + TimeBlock block; + block.move_type = type; + block.role = m_extrusion_role; + block.distance = distance; + block.g1_line_id = m_g1_line_id; + block.layer_id = std::max(1, m_layer_id); + + // calculates block cruise feedrate + float min_feedrate_factor = 1.0f; + for (unsigned char a = X; a <= E; ++a) { + curr.axis_feedrate[a] = curr.feedrate * delta_pos[a] * inv_distance; + if (a == E) + curr.axis_feedrate[a] *= machine.extrude_factor_override_percentage; + + curr.abs_axis_feedrate[a] = std::abs(curr.axis_feedrate[a]); + if (curr.abs_axis_feedrate[a] != 0.0f) { + const float axis_max_feedrate = get_axis_max_feedrate(static_cast(i), static_cast(a)); + if (axis_max_feedrate != 0.0f) + min_feedrate_factor = std::min(min_feedrate_factor, axis_max_feedrate / curr.abs_axis_feedrate[a]); + } + } + + block.feedrate_profile.cruise = min_feedrate_factor * curr.feedrate; + + if (min_feedrate_factor < 1.0f) { + for (unsigned char a = X; a <= E; ++a) { + curr.axis_feedrate[a] *= min_feedrate_factor; + curr.abs_axis_feedrate[a] *= min_feedrate_factor; + } + } + + // calculates block acceleration + float acceleration = + (type == EMoveType::Travel) ? get_travel_acceleration(static_cast(i)) : + (is_extrusion_only_move(delta_pos) ? + get_retract_acceleration(static_cast(i)) : + get_acceleration(static_cast(i))); + + for (unsigned char a = X; a <= E; ++a) { + const float axis_max_acceleration = get_axis_max_acceleration(static_cast(i), static_cast(a)); + if (acceleration * std::abs(delta_pos[a]) * inv_distance > axis_max_acceleration) + acceleration = axis_max_acceleration; + } + + block.acceleration = acceleration; + + // calculates block exit feedrate + curr.safe_feedrate = block.feedrate_profile.cruise; + + for (unsigned char a = X; a <= E; ++a) { + const float axis_max_jerk = get_axis_max_jerk(static_cast(i), static_cast(a)); + if (curr.abs_axis_feedrate[a] > axis_max_jerk) + curr.safe_feedrate = std::min(curr.safe_feedrate, axis_max_jerk); + } + + block.feedrate_profile.exit = curr.safe_feedrate; + + static const float PREVIOUS_FEEDRATE_THRESHOLD = 0.0001f; + + // 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_profile.cruise; + float smaller_speed_factor = prev_speed_larger ? (block.feedrate_profile.cruise / prev.feedrate) : (prev.feedrate / block.feedrate_profile.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_profile.cruise : prev.feedrate; + + float v_factor = 1.0f; + bool limited = false; + + for (unsigned char a = X; a <= E; ++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. + const 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)); + + const float axis_max_jerk = get_axis_max_jerk(static_cast(i), static_cast(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. + const 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; + } + + const float v_allowable = max_allowable_speed(-acceleration, curr.safe_feedrate, block.distance); + block.feedrate_profile.entry = std::min(vmax_junction, v_allowable); + + block.max_entry_speed = vmax_junction; + block.flags.nominal_length = (block.feedrate_profile.cruise <= v_allowable); + block.flags.recalculate = true; + block.safe_feedrate = curr.safe_feedrate; + + // calculates block trapezoid + block.calculate_trapezoid(); + + // updates previous + prev = curr; + + blocks.push_back(block); + + if (blocks.size() > TimeProcessor::Planner::refresh_threshold) + machine.calculate_time(TimeProcessor::Planner::queue_size); + } + + if (m_seams_detector.is_active()) { + // check for seam starting vertex + if (type == EMoveType::Extrude && m_extrusion_role == erExternalPerimeter && !m_seams_detector.has_first_vertex()) + m_seams_detector.set_first_vertex(m_result.moves.back().position - m_extruder_offsets[m_extruder_id]); + // check for seam ending vertex and store the resulting move + else if ((type != EMoveType::Extrude || (m_extrusion_role != erExternalPerimeter && m_extrusion_role != erOverhangPerimeter)) && m_seams_detector.has_first_vertex()) { + auto set_end_position = [this](const Vec3f& pos) { + m_end_position[X] = pos.x(); m_end_position[Y] = pos.y(); m_end_position[Z] = pos.z(); + }; + + const Vec3f curr_pos(m_end_position[X], m_end_position[Y], m_end_position[Z]); + const Vec3f new_pos = m_result.moves.back().position - m_extruder_offsets[m_extruder_id]; + const std::optional first_vertex = m_seams_detector.get_first_vertex(); + // the threshold value = 0.0625f == 0.25 * 0.25 is arbitrary, we may find some smarter condition later + + if ((new_pos - *first_vertex).squaredNorm() < 0.0625f) { + set_end_position(0.5f * (new_pos + *first_vertex) + m_z_offset * Vec3f::UnitZ()); + store_move_vertex(EMoveType::Seam); + set_end_position(curr_pos); + } + + m_seams_detector.activate(false); + } + } + else if (type == EMoveType::Extrude && m_extrusion_role == erExternalPerimeter) { + m_seams_detector.activate(true); + m_seams_detector.set_first_vertex(m_result.moves.back().position - m_extruder_offsets[m_extruder_id]); + } + + if (m_spiral_vase_active && !m_result.spiral_vase_layers.empty() && !m_result.moves.empty()) + m_result.spiral_vase_layers.back().second.second = m_result.moves.size() - 1; + + // store move +#if ENABLE_PROCESS_G2_G3_LINES + store_move_vertex(type, line.comment() == INTERNAL_G2G3_TAG); +#else + store_move_vertex(type); +#endif // ENABLE_PROCESS_G2_G3_LINES +} + +#if ENABLE_PROCESS_G2_G3_LINES +void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line, bool clockwise) +{ + if (!line.has('X') || !line.has('Y') || !line.has('I') || !line.has('J')) + return; + + // relative center + Vec3f rel_center = Vec3f::Zero(); + if (!line.has_value('I', rel_center.x()) || !line.has_value('J', rel_center.y())) + return; + + // scale center, if needed + if (m_units == EUnits::Inches) + rel_center *= INCHES_TO_MM; + + struct Arc + { + Vec3d start{ Vec3d::Zero() }; + Vec3d end{ Vec3d::Zero() }; + Vec3d center{ Vec3d::Zero() }; + + double angle{ 0.0 }; + double delta_x() const { return end.x() - start.x(); } + double delta_y() const { return end.y() - start.y(); } + double delta_z() const { return end.z() - start.z(); } + + double length() const { return angle * start_radius(); } + double travel_length() const { return std::sqrt(sqr(length() + sqr(delta_z()))); } + double start_radius() const { return (start - center).norm(); } + double end_radius() const { return (end - center).norm(); } + + Vec3d relative_start() const { return start - center; } + Vec3d relative_end() const { return end - center; } + + bool closed() const { return end.isApprox(start); } + }; + + Arc arc; + + // arc start endpoint + arc.start = Vec3d(m_start_position[X], m_start_position[Y], m_start_position[Z]); + + // arc center + arc.center = arc.start + rel_center.cast(); + + const float filament_diameter = (static_cast(m_extruder_id) < m_result.filament_diameters.size()) ? m_result.filament_diameters[m_extruder_id] : m_result.filament_diameters.back(); + const float filament_radius = 0.5f * filament_diameter; + const float area_filament_cross_section = static_cast(M_PI) * sqr(filament_radius); + + AxisCoords end_position = m_start_position; + for (unsigned char a = X; a <= E; ++a) { + end_position[a] = extract_absolute_position_on_axis((Axis)a, line, double(area_filament_cross_section)); + } + + // arc end endpoint + arc.end = Vec3d(end_position[X], end_position[Y], end_position[Z]); + + // radii + if (std::abs(arc.end_radius() - arc.start_radius()) > EPSILON) { + // what to do ??? + } + + // updates feedrate from line + std::optional feedrate; + if (line.has_f()) + feedrate = m_feed_multiply.current * line.f() * MMMIN_TO_MMSEC; + + // updates extrusion from line + std::optional extrusion; + if (line.has_e()) + extrusion = end_position[E] - m_start_position[E]; + + // relative arc endpoints + const Vec3d rel_arc_start = arc.relative_start(); + const Vec3d rel_arc_end = arc.relative_end(); + + // arc angle + if (arc.closed()) + arc.angle = 2.0 * PI; + else { + arc.angle = std::atan2(rel_arc_start.x() * rel_arc_end.y() - rel_arc_start.y() * rel_arc_end.x(), + rel_arc_start.x() * rel_arc_end.x() + rel_arc_start.y() * rel_arc_end.y()); + if (arc.angle < 0.0) + arc.angle += 2.0 * PI; + if (clockwise) + arc.angle -= 2.0 * PI; + } + + const double travel_length = arc.travel_length(); + if (travel_length < 0.001) + return; + + auto adjust_target = [this, area_filament_cross_section](const AxisCoords& target, const AxisCoords& prev_position) { + AxisCoords ret = target; + if (m_global_positioning_type == EPositioningType::Relative) { + for (unsigned char a = X; a <= E; ++a) { + ret[a] -= prev_position[a]; + } + } + else if (m_e_local_positioning_type == EPositioningType::Relative) + ret[E] -= prev_position[E]; + + if (m_use_volumetric_e) + ret[E] *= area_filament_cross_section; + + const double lengthsScaleFactor = (m_units == EUnits::Inches) ? double(INCHES_TO_MM) : 1.0; + for (unsigned char a = X; a <= E; ++a) { + ret[a] /= lengthsScaleFactor; + } + return ret; + }; + + auto internal_only_g1_line = [](const AxisCoords& target, bool has_z, const std::optional& feedrate, const std::optional& extrusion) { + std::string ret = (boost::format("G1 X%1% Y%2%") % target[X] % target[Y]).str(); + if (has_z) + ret += (boost::format(" Z%1%") % target[Z]).str(); + if (feedrate.has_value()) + ret += (boost::format(" F%1%") % *feedrate).str(); + if (extrusion.has_value()) + ret += (boost::format(" E%1%") % target[E]).str(); + + ret += (boost::format(" ;%1%\n") % INTERNAL_G2G3_TAG).str(); + + return ret; + }; + + // calculate arc segments + // reference: + // Prusa-Firmware\Firmware\motion_control.cpp - mc_arc() + + // segments count + static const double MM_PER_ARC_SEGMENT = 1.0; + const size_t segments = std::max(std::floor(travel_length / MM_PER_ARC_SEGMENT), 1); + + const double theta_per_segment = arc.angle / double(segments); + const double z_per_segment = arc.delta_z() / double(segments); + const double extruder_per_segment = (extrusion.has_value()) ? *extrusion / double(segments) : 0.0; + + double cos_T = 1.0 - 0.5 * sqr(theta_per_segment); // Small angle approximation + double sin_T = theta_per_segment; + + AxisCoords prev_target = m_start_position; + AxisCoords arc_target; + double sin_Ti; + double cos_Ti; + double r_axisi; + size_t count = 0; + + // Initialize the linear axis + arc_target[Z] = m_start_position[Z]; + + // Initialize the extruder axis + arc_target[E] = m_start_position[E]; + + static const size_t N_ARC_CORRECTION = 25; + + Vec3d curr_rel_arc_start = arc.relative_start(); + + std::string gcode; + + for (size_t i = 1; i < segments; ++i) { // Increment (segments-1) + if (count < N_ARC_CORRECTION) { + // Apply vector rotation matrix + r_axisi = curr_rel_arc_start.x() * sin_T + curr_rel_arc_start.y() * cos_T; + curr_rel_arc_start.x() = curr_rel_arc_start.x() * cos_T - curr_rel_arc_start.y() * sin_T; + curr_rel_arc_start.y() = r_axisi; + count++; + } + else { + // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments. + // Compute exact location by applying transformation matrix from initial radius vector(=-offset). + cos_Ti = ::cos(double(i) * theta_per_segment); + sin_Ti = ::sin(double(i) * theta_per_segment); + curr_rel_arc_start.x() = -double(rel_center.x()) * cos_Ti + double(rel_center.y()) * sin_Ti; + curr_rel_arc_start.y() = -double(rel_center.x()) * sin_Ti - double(rel_center.y()) * cos_Ti; + count = 0; + } + + // Update arc_target location + arc_target[X] = arc.center.x() + curr_rel_arc_start.x(); + arc_target[Y] = arc.center.y() + curr_rel_arc_start.y(); + arc_target[Z] += z_per_segment; + arc_target[E] += extruder_per_segment; + + gcode += internal_only_g1_line(adjust_target(arc_target, prev_target), z_per_segment != 0.0, feedrate, extrusion); + prev_target = arc_target; + + // feedrate is constant, we do not need to repeat it + feedrate.reset(); + } + + // Ensure last segment arrives at target location. + gcode += internal_only_g1_line(adjust_target(end_position, prev_target), arc.delta_z() != 0.0, feedrate, extrusion); + + // process fake gcode lines + GCodeReader parser; + parser.parse_buffer(gcode, [this](GCodeReader&, const GCodeReader::GCodeLine& line) { + // force all lines to share the same id + --m_line_id; + process_gcode_line(line, false); + }); +} +#endif // ENABLE_PROCESS_G2_G3_LINES + +void GCodeProcessor::process_G10(const GCodeReader::GCodeLine& line) +{ + // stores retract move + store_move_vertex(EMoveType::Retract); +} + +void GCodeProcessor::process_G11(const GCodeReader::GCodeLine& line) +{ + // stores unretract move + store_move_vertex(EMoveType::Unretract); +} + +void GCodeProcessor::process_G20(const GCodeReader::GCodeLine& line) +{ + m_units = EUnits::Inches; +} + +void GCodeProcessor::process_G21(const GCodeReader::GCodeLine& line) +{ + m_units = EUnits::Millimeters; +} + +void GCodeProcessor::process_G22(const GCodeReader::GCodeLine& line) +{ + // stores retract move + store_move_vertex(EMoveType::Retract); +} + +void GCodeProcessor::process_G23(const GCodeReader::GCodeLine& line) +{ + // stores unretract move + store_move_vertex(EMoveType::Unretract); +} + +void GCodeProcessor::process_G28(const GCodeReader::GCodeLine& line) +{ + std::string_view cmd = line.cmd(); + std::string new_line_raw = { cmd.data(), cmd.size() }; + bool found = false; + if (line.has('X')) { + new_line_raw += " X0"; + found = true; + } + if (line.has('Y')) { + new_line_raw += " Y0"; + found = true; + } + if (line.has('Z')) { + new_line_raw += " Z0"; + found = true; + } + if (!found) + new_line_raw += " X0 Y0 Z0"; + + GCodeReader::GCodeLine new_gline; + GCodeReader reader; + reader.parse_line(new_line_raw, [&](GCodeReader& reader, const GCodeReader::GCodeLine& gline) { new_gline = gline; }); + process_G1(new_gline); +} + +void GCodeProcessor::process_G60(const GCodeReader::GCodeLine& line) +{ + if (m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware) + m_saved_position = m_end_position; +} + +void GCodeProcessor::process_G61(const GCodeReader::GCodeLine& line) +{ + if (m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware) { + bool modified = false; + if (line.has_x()) { + m_end_position[X] = m_saved_position[X]; + modified = true; + } + if (line.has_y()) { + m_end_position[Y] = m_saved_position[Y]; + modified = true; + } + if (line.has_z()) { + m_end_position[Z] = m_saved_position[Z]; + modified = true; + } + if (line.has_e()) { + m_end_position[E] = m_saved_position[E]; + modified = true; + } + if (line.has_f()) + m_feedrate = m_feed_multiply.current * line.f(); + + if (!modified) + m_end_position = m_saved_position; + + + store_move_vertex(EMoveType::Travel); + } +} + +void GCodeProcessor::process_G90(const GCodeReader::GCodeLine& line) +{ + m_global_positioning_type = EPositioningType::Absolute; +} + +void GCodeProcessor::process_G91(const GCodeReader::GCodeLine& line) +{ + m_global_positioning_type = EPositioningType::Relative; +} + +void GCodeProcessor::process_G92(const GCodeReader::GCodeLine& line) +{ + float lengths_scale_factor = (m_units == EUnits::Inches) ? INCHES_TO_MM : 1.0f; + bool any_found = false; + + if (line.has_x()) { + m_origin[X] = m_end_position[X] - line.x() * lengths_scale_factor; + any_found = true; + } + + if (line.has_y()) { + m_origin[Y] = m_end_position[Y] - line.y() * lengths_scale_factor; + any_found = true; + } + + if (line.has_z()) { + m_origin[Z] = m_end_position[Z] - line.z() * lengths_scale_factor; + any_found = true; + } + + if (line.has_e()) { + // extruder coordinate can grow to the point where its float representation does not allow for proper addition with small increments, + // we set the value taken from the G92 line as the new current position for it + m_end_position[E] = line.e() * lengths_scale_factor; + any_found = true; + } + else + simulate_st_synchronize(); + + if (!any_found && !line.has_unknown_axis()) { + // The G92 may be called for axes that PrusaSlicer does not recognize, for example see GH issue #3510, + // where G92 A0 B0 is called although the extruder axis is till E. + for (unsigned char a = X; a <= E; ++a) { + m_origin[a] = m_end_position[a]; + } + } +} + +void GCodeProcessor::process_M1(const GCodeReader::GCodeLine& line) +{ + simulate_st_synchronize(); +} + +void GCodeProcessor::process_M82(const GCodeReader::GCodeLine& line) +{ + m_e_local_positioning_type = EPositioningType::Absolute; +} + +void GCodeProcessor::process_M83(const GCodeReader::GCodeLine& line) +{ + m_e_local_positioning_type = EPositioningType::Relative; +} + +void GCodeProcessor::process_M104(const GCodeReader::GCodeLine& line) +{ + float new_temp; + if (line.has_value('S', new_temp)) + m_extruder_temps[m_extruder_id] = new_temp; +} + +void GCodeProcessor::process_M106(const GCodeReader::GCodeLine& line) +{ + if (!line.has('P')) { + // The absence of P means the print cooling fan, so ignore anything else. + float new_fan_speed; + if (line.has_value('S', new_fan_speed)) + m_fan_speed = (100.0f / 255.0f) * new_fan_speed; + else + m_fan_speed = 100.0f; + } +} + +void GCodeProcessor::process_M107(const GCodeReader::GCodeLine& line) +{ + m_fan_speed = 0.0f; +} + +void GCodeProcessor::process_M108(const GCodeReader::GCodeLine& line) +{ + // These M-codes are used by Sailfish to change active tool. + // They have to be processed otherwise toolchanges will be unrecognised + // by the analyzer - see https://github.com/prusa3d/PrusaSlicer/issues/2566 + + if (m_flavor != gcfSailfish) + return; + + std::string cmd = line.raw(); + size_t pos = cmd.find("T"); + if (pos != std::string::npos) + process_T(cmd.substr(pos)); +} + +void GCodeProcessor::process_M109(const GCodeReader::GCodeLine& line) +{ + float new_temp; + if (line.has_value('R', new_temp)) { + float val; + if (line.has_value('T', val)) { + size_t eid = static_cast(val); + if (eid < m_extruder_temps.size()) + m_extruder_temps[eid] = new_temp; + } + else + m_extruder_temps[m_extruder_id] = new_temp; + } + else if (line.has_value('S', new_temp)) + m_extruder_temps[m_extruder_id] = new_temp; +} + +void GCodeProcessor::process_M132(const GCodeReader::GCodeLine& line) +{ + // This command is used by Makerbot to load the current home position from EEPROM + // see: https://github.com/makerbot/s3g/blob/master/doc/GCodeProtocol.md + // Using this command to reset the axis origin to zero helps in fixing: https://github.com/prusa3d/PrusaSlicer/issues/3082 + + if (line.has('X')) + m_origin[X] = 0.0f; + + if (line.has('Y')) + m_origin[Y] = 0.0f; + + if (line.has('Z')) + m_origin[Z] = 0.0f; + + if (line.has('E')) + m_origin[E] = 0.0f; +} + +void GCodeProcessor::process_M135(const GCodeReader::GCodeLine& line) +{ + // These M-codes are used by MakerWare to change active tool. + // They have to be processed otherwise toolchanges will be unrecognised + // by the analyzer - see https://github.com/prusa3d/PrusaSlicer/issues/2566 + + if (m_flavor != gcfMakerWare) + return; + + std::string cmd = line.raw(); + size_t pos = cmd.find("T"); + if (pos != std::string::npos) + process_T(cmd.substr(pos)); +} + +void GCodeProcessor::process_M201(const GCodeReader::GCodeLine& line) +{ + // see http://reprap.org/wiki/G-code#M201:_Set_max_printing_acceleration + float factor = ((m_flavor != gcfRepRapSprinter && m_flavor != gcfRepRapFirmware) && m_units == EUnits::Inches) ? INCHES_TO_MM : 1.0f; + + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + if (static_cast(i) == PrintEstimatedStatistics::ETimeMode::Normal || + m_time_processor.machine_envelope_processing_enabled) { + if (line.has_x()) + set_option_value(m_time_processor.machine_limits.machine_max_acceleration_x, i, line.x() * factor); + + if (line.has_y()) + set_option_value(m_time_processor.machine_limits.machine_max_acceleration_y, i, line.y() * factor); + + if (line.has_z()) + set_option_value(m_time_processor.machine_limits.machine_max_acceleration_z, i, line.z() * factor); + + if (line.has_e()) + set_option_value(m_time_processor.machine_limits.machine_max_acceleration_e, i, line.e() * factor); + } + } +} + +void GCodeProcessor::process_M203(const GCodeReader::GCodeLine& line) +{ + // see http://reprap.org/wiki/G-code#M203:_Set_maximum_feedrate + if (m_flavor == gcfRepetier) + return; + + // see http://reprap.org/wiki/G-code#M203:_Set_maximum_feedrate + // http://smoothieware.org/supported-g-codes + float factor = (m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware || m_flavor == gcfSmoothie) ? 1.0f : MMMIN_TO_MMSEC; + + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + if (static_cast(i) == PrintEstimatedStatistics::ETimeMode::Normal || + m_time_processor.machine_envelope_processing_enabled) { + if (line.has_x()) + set_option_value(m_time_processor.machine_limits.machine_max_feedrate_x, i, line.x() * factor); + + if (line.has_y()) + set_option_value(m_time_processor.machine_limits.machine_max_feedrate_y, i, line.y() * factor); + + if (line.has_z()) + set_option_value(m_time_processor.machine_limits.machine_max_feedrate_z, i, line.z() * factor); + + if (line.has_e()) + set_option_value(m_time_processor.machine_limits.machine_max_feedrate_e, i, line.e() * factor); + } + } +} + +void GCodeProcessor::process_M204(const GCodeReader::GCodeLine& line) +{ + float value; + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + if (static_cast(i) == PrintEstimatedStatistics::ETimeMode::Normal || + m_time_processor.machine_envelope_processing_enabled) { + if (line.has_value('S', value)) { + // Legacy acceleration format. This format is used by the legacy Marlin, MK2 or MK3 firmware + // It is also generated by PrusaSlicer to control acceleration per extrusion type + // (perimeters, first layer etc) when 'Marlin (legacy)' flavor is used. + set_acceleration(static_cast(i), value); + set_travel_acceleration(static_cast(i), value); + if (line.has_value('T', value)) + set_retract_acceleration(static_cast(i), value); + } + else { + // New acceleration format, compatible with the upstream Marlin. + if (line.has_value('P', value)) + set_acceleration(static_cast(i), value); + if (line.has_value('R', value)) + set_retract_acceleration(static_cast(i), value); + if (line.has_value('T', value)) + // Interpret the T value as the travel acceleration in the new Marlin format. + set_travel_acceleration(static_cast(i), value); + } + } + } +} + +void GCodeProcessor::process_M205(const GCodeReader::GCodeLine& line) +{ + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + if (static_cast(i) == PrintEstimatedStatistics::ETimeMode::Normal || + m_time_processor.machine_envelope_processing_enabled) { + if (line.has_x()) { + float max_jerk = line.x(); + set_option_value(m_time_processor.machine_limits.machine_max_jerk_x, i, max_jerk); + set_option_value(m_time_processor.machine_limits.machine_max_jerk_y, i, max_jerk); + } + + if (line.has_y()) + set_option_value(m_time_processor.machine_limits.machine_max_jerk_y, i, line.y()); + + if (line.has_z()) + set_option_value(m_time_processor.machine_limits.machine_max_jerk_z, i, line.z()); + + if (line.has_e()) + set_option_value(m_time_processor.machine_limits.machine_max_jerk_e, i, line.e()); + + float value; + if (line.has_value('S', value)) + set_option_value(m_time_processor.machine_limits.machine_min_extruding_rate, i, value); + + if (line.has_value('T', value)) + set_option_value(m_time_processor.machine_limits.machine_min_travel_rate, i, value); + } + } +} + +void GCodeProcessor::process_M220(const GCodeReader::GCodeLine& line) +{ + if (m_flavor != gcfMarlinLegacy && m_flavor != gcfMarlinFirmware) + return; + + if (line.has('B')) + m_feed_multiply.saved = m_feed_multiply.current; + float value; + if (line.has_value('S', value)) + m_feed_multiply.current = value * 0.01f; + if (line.has('R')) + m_feed_multiply.current = m_feed_multiply.saved; +} + +void GCodeProcessor::process_M221(const GCodeReader::GCodeLine& line) +{ + float value_s; + float value_t; + if (line.has_value('S', value_s) && !line.has_value('T', value_t)) { + value_s *= 0.01f; + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + m_time_processor.machines[i].extrude_factor_override_percentage = value_s; + } + } +} + +void GCodeProcessor::process_M401(const GCodeReader::GCodeLine& line) +{ + if (m_flavor != gcfRepetier) + return; + + for (unsigned char a = 0; a <= 3; ++a) { + m_cached_position.position[a] = m_start_position[a]; + } + m_cached_position.feedrate = m_feedrate; +} + +void GCodeProcessor::process_M402(const GCodeReader::GCodeLine& line) +{ + if (m_flavor != gcfRepetier) + return; + + // see for reference: + // https://github.com/repetier/Repetier-Firmware/blob/master/src/ArduinoAVR/Repetier/Printer.cpp + // void Printer::GoToMemoryPosition(bool x, bool y, bool z, bool e, float feed) + + bool has_xyz = !(line.has('X') || line.has('Y') || line.has('Z')); + + float p = FLT_MAX; + for (unsigned char a = X; a <= Z; ++a) { + if (has_xyz || line.has(a)) { + p = m_cached_position.position[a]; + if (p != FLT_MAX) + m_start_position[a] = p; + } + } + + p = m_cached_position.position[E]; + if (p != FLT_MAX) + m_start_position[E] = p; + + p = FLT_MAX; + if (!line.has_value(4, p)) + p = m_cached_position.feedrate; + + if (p != FLT_MAX) + m_feedrate = p; +} + +void GCodeProcessor::process_M566(const GCodeReader::GCodeLine& line) +{ + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + if (line.has_x()) + set_option_value(m_time_processor.machine_limits.machine_max_jerk_x, i, line.x() * MMMIN_TO_MMSEC); + + if (line.has_y()) + set_option_value(m_time_processor.machine_limits.machine_max_jerk_y, i, line.y() * MMMIN_TO_MMSEC); + + if (line.has_z()) + set_option_value(m_time_processor.machine_limits.machine_max_jerk_z, i, line.z() * MMMIN_TO_MMSEC); + + if (line.has_e()) + set_option_value(m_time_processor.machine_limits.machine_max_jerk_e, i, line.e() * MMMIN_TO_MMSEC); + } +} + +void GCodeProcessor::process_M702(const GCodeReader::GCodeLine& line) +{ + 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. + m_time_processor.extruder_unloaded = true; + simulate_st_synchronize(get_filament_unload_time(m_extruder_id)); + } +} + +void GCodeProcessor::process_T(const GCodeReader::GCodeLine& line) +{ + process_T(line.cmd()); +} + +void GCodeProcessor::process_T(const std::string_view command) +{ + if (command.length() > 1) { + int eid = 0; + if (! parse_number(command.substr(1), eid) || eid < 0 || eid > 255) { + // Specific to the MMU2 V2 (see https://www.help.prusa3d.com/en/article/prusa-specific-g-codes_112173): + if ((m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware) && (command == "Tx" || command == "Tc" || command == "T?")) + return; + + // T-1 is a valid gcode line for RepRap Firmwares (used to deselects all tools) see https://github.com/prusa3d/PrusaSlicer/issues/5677 + if ((m_flavor != gcfRepRapFirmware && m_flavor != gcfRepRapSprinter) || eid != -1) + BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid toolchange (" << command << ")."; + } else { + unsigned char id = static_cast(eid); + if (m_extruder_id != id) { + if (id >= m_result.extruders_count) + BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid toolchange, maybe from a custom gcode."; + else { + unsigned char old_extruder_id = m_extruder_id; + process_filaments(CustomGCode::ToolChange); + m_extruder_id = id; + m_cp_color.current = m_extruder_colors[id]; + // Specific to the MK3 MMU2: + // The initial value of extruder_unloaded is set to true indicating + // that the filament is parked in the MMU2 unit and there is nothing to be unloaded yet. + float extra_time = get_filament_unload_time(static_cast(old_extruder_id)); + m_time_processor.extruder_unloaded = false; + extra_time += get_filament_load_time(static_cast(m_extruder_id)); + simulate_st_synchronize(extra_time); + } + + // store tool change move + store_move_vertex(EMoveType::Tool_change); + } + } + } +} + +#if ENABLE_USED_FILAMENT_POST_PROCESS +void GCodeProcessor::post_process() +{ + FilePtr in{ boost::nowide::fopen(m_result.filename.c_str(), "rb") }; + if (in.f == nullptr) + throw Slic3r::RuntimeError(std::string("GCode processor post process export failed.\nCannot open file for reading.\n")); + + // temporary file to contain modified gcode + std::string out_path = m_result.filename + ".postprocess"; + FilePtr out{ boost::nowide::fopen(out_path.c_str(), "wb") }; + if (out.f == nullptr) { + throw Slic3r::RuntimeError(std::string("GCode processor post process export failed.\nCannot open file for writing.\n")); + } + + auto time_in_minutes = [](float time_in_seconds) { + assert(time_in_seconds >= 0.f); + return int((time_in_seconds + 0.5f) / 60.0f); + }; + + auto time_in_last_minute = [](float time_in_seconds) { + assert(time_in_seconds <= 60.0f); + return time_in_seconds / 60.0f; + }; + + auto format_line_M73_main = [](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); + }; + + auto format_line_M73_stop_int = [](const std::string& mask, int time) { + char line_M73[64]; + sprintf(line_M73, mask.c_str(), std::to_string(time).c_str()); + return std::string(line_M73); + }; + + auto format_time_float = [](float time) { + return Slic3r::float_to_string_decimal_point(time, 2); + }; + + auto format_line_M73_stop_float = [format_time_float](const std::string& mask, float time) { + char line_M73[64]; + sprintf(line_M73, mask.c_str(), format_time_float(time).c_str()); + return std::string(line_M73); + }; + + std::string gcode_line; + size_t g1_lines_counter = 0; + // keeps track of last exported pair + std::array, static_cast(PrintEstimatedStatistics::ETimeMode::Count)> last_exported_main; + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + last_exported_main[i] = { 0, time_in_minutes(m_time_processor.machines[i].time) }; + } + + // keeps track of last exported remaining time to next printer stop + std::array(PrintEstimatedStatistics::ETimeMode::Count)> last_exported_stop; + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + last_exported_stop[i] = time_in_minutes(m_time_processor.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 + // gcode_line is in/out parameter, to reduce expensive memory allocation + auto process_placeholders = [&](std::string& gcode_line) { + unsigned int extra_lines_count = 0; + + // remove trailing '\n' + auto line = std::string_view(gcode_line).substr(0, gcode_line.length() - 1); + + std::string ret; + if (line.length() > 1) { + line = line.substr(1); + if (m_time_processor.export_remaining_time_enabled && + (line == reserved_tag(ETags::First_Line_M73_Placeholder) || line == reserved_tag(ETags::Last_Line_M73_Placeholder))) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + const TimeMachine& machine = m_time_processor.machines[i]; + if (machine.enabled) { + // export pair + ret += format_line_M73_main(machine.line_m73_main_mask.c_str(), + (line == reserved_tag(ETags::First_Line_M73_Placeholder)) ? 0 : 100, + (line == reserved_tag(ETags::First_Line_M73_Placeholder)) ? time_in_minutes(machine.time) : 0); + ++extra_lines_count; + + // export remaining time to next printer stop + if (line == reserved_tag(ETags::First_Line_M73_Placeholder) && !machine.stop_times.empty()) { + int to_export_stop = time_in_minutes(machine.stop_times.front().elapsed_time); + ret += format_line_M73_stop_int(machine.line_m73_stop_mask.c_str(), to_export_stop); + last_exported_stop[i] = to_export_stop; + ++extra_lines_count; + } + } + } + } + else if (line == reserved_tag(ETags::Estimated_Printing_Time_Placeholder)) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + const TimeMachine& machine = m_time_processor.machines[i]; + PrintEstimatedStatistics::ETimeMode mode = static_cast(i); + if (mode == PrintEstimatedStatistics::ETimeMode::Normal || machine.enabled) { + char buf[128]; + sprintf(buf, "; estimated printing time (%s mode) = %s\n", + (mode == PrintEstimatedStatistics::ETimeMode::Normal) ? "normal" : "silent", + get_time_dhms(machine.time).c_str()); + ret += buf; + } + } + } + } + + if (!ret.empty()) + // Not moving the move operator on purpose, so that the gcode_line allocation will grow and it will not be reallocated after handful of lines are processed. + gcode_line = ret; + return std::tuple(!ret.empty(), (extra_lines_count == 0) ? extra_lines_count : extra_lines_count - 1); + }; + + struct FilamentData + { + double mm{ 0.0 }; + double cm3{ 0.0 }; + double g{ 0.0 }; + double cost{ 0.0 }; + }; + + FilamentData filament_data; + for (const auto& [role, used] : m_result.print_statistics.used_filaments_per_role) { + filament_data.mm += used.first; + filament_data.g += used.second; + } + for (const auto& [id, volume] : m_result.print_statistics.volumes_per_extruder) { + filament_data.cm3 += volume; + filament_data.cost += volume * double(m_result.filament_densities[id]) * double(m_result.filament_cost[id]) * 0.000001; + } + + auto process_used_filament = [&filament_data](std::string& gcode_line) { + auto process_tag = [](std::string& gcode_line, const std::string& tag, double value) { + if (boost::algorithm::istarts_with(gcode_line, tag)) { + char buf[128]; + sprintf(buf, "%s %.2lf\n", tag.c_str(), value); + gcode_line = buf; + return true; + } + return false; + }; + + bool ret = false; + ret |= process_tag(gcode_line, "; filament used [mm] =", filament_data.mm * 1000.0); + ret |= process_tag(gcode_line, "; filament used [g] =", filament_data.g); + ret |= process_tag(gcode_line, "; total filament used [g] =", filament_data.g); + ret |= process_tag(gcode_line, "; filament used [cm3] =", filament_data.cm3 / 1000.0); + ret |= process_tag(gcode_line, "; filament cost =", filament_data.cost); + ret |= process_tag(gcode_line, "; total filament cost =", filament_data.cost); + return ret; + }; + + // check for temporary lines + auto is_temporary_decoration = [](const std::string_view gcode_line) { + // remove trailing '\n' + assert(!gcode_line.empty()); + assert(gcode_line.back() == '\n'); + + // return true for decorations which are used in processing the gcode but that should not be exported into the final gcode + // i.e.: + // bool ret = gcode_line.substr(0, gcode_line.length() - 1) == ";" + Layer_Change_Tag; + // ... + // return ret; + return false; + }; + + // Iterators for the normal and silent cached time estimate entry recently processed, used by process_line_G1. + auto g1_times_cache_it = Slic3r::reserve_vector::const_iterator>(m_time_processor.machines.size()); + for (const auto& machine : m_time_processor.machines) + g1_times_cache_it.emplace_back(machine.g1_times_cache.begin()); + + // add lines M73 to exported gcode + auto process_line_G1 = [this, + // Lambdas, mostly for string formatting, all with an empty capture block. + time_in_minutes, format_time_float, format_line_M73_main, format_line_M73_stop_int, format_line_M73_stop_float, time_in_last_minute, + // Caches, to be modified + &g1_times_cache_it, &last_exported_main, &last_exported_stop, + // String output + &export_line] + (const size_t g1_lines_counter) { + unsigned int exported_lines_count = 0; + if (m_time_processor.export_remaining_time_enabled) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + const TimeMachine& machine = m_time_processor.machines[i]; + if (machine.enabled) { + // export pair + // Skip all machine.g1_times_cache below g1_lines_counter. + auto& it = g1_times_cache_it[i]; + while (it != machine.g1_times_cache.end() && it->id < g1_lines_counter) + ++it; + if (it != machine.g1_times_cache.end() && it->id == g1_lines_counter) { + std::pair to_export_main = { int(100.0f * it->elapsed_time / machine.time), + time_in_minutes(machine.time - it->elapsed_time) }; + if (last_exported_main[i] != to_export_main) { + export_line += format_line_M73_main(machine.line_m73_main_mask.c_str(), + to_export_main.first, to_export_main.second); + last_exported_main[i] = to_export_main; + ++exported_lines_count; + } + // export remaining time to next printer stop + auto it_stop = std::upper_bound(machine.stop_times.begin(), machine.stop_times.end(), it->elapsed_time, + [](float value, const TimeMachine::StopTime& t) { return value < t.elapsed_time; }); + if (it_stop != machine.stop_times.end()) { + int to_export_stop = time_in_minutes(it_stop->elapsed_time - it->elapsed_time); + if (last_exported_stop[i] != to_export_stop) { + if (to_export_stop > 0) { + if (last_exported_stop[i] != to_export_stop) { + export_line += format_line_M73_stop_int(machine.line_m73_stop_mask.c_str(), to_export_stop); + last_exported_stop[i] = to_export_stop; + ++exported_lines_count; + } + } + else { + bool is_last = false; + auto next_it = it + 1; + is_last |= (next_it == machine.g1_times_cache.end()); + + if (next_it != machine.g1_times_cache.end()) { + auto next_it_stop = std::upper_bound(machine.stop_times.begin(), machine.stop_times.end(), next_it->elapsed_time, + [](float value, const TimeMachine::StopTime& t) { return value < t.elapsed_time; }); + is_last |= (next_it_stop != it_stop); + + std::string time_float_str = format_time_float(time_in_last_minute(it_stop->elapsed_time - it->elapsed_time)); + std::string next_time_float_str = format_time_float(time_in_last_minute(it_stop->elapsed_time - next_it->elapsed_time)); + is_last |= (string_to_double_decimal_point(time_float_str) > 0. && string_to_double_decimal_point(next_time_float_str) == 0.); + } + + if (is_last) { + if (std::distance(machine.stop_times.begin(), it_stop) == static_cast(machine.stop_times.size() - 1)) + export_line += format_line_M73_stop_int(machine.line_m73_stop_mask.c_str(), to_export_stop); + else + export_line += format_line_M73_stop_float(machine.line_m73_stop_mask.c_str(), time_in_last_minute(it_stop->elapsed_time - it->elapsed_time)); + + last_exported_stop[i] = to_export_stop; + ++exported_lines_count; + } + } + } + } + } + } + } + } + return exported_lines_count; + }; + + // helper function to write to disk + size_t out_file_pos = 0; + m_result.lines_ends.clear(); + auto write_string = [this, &export_line, &out, &out_path, &out_file_pos](const std::string& str) { + fwrite((const void*)export_line.c_str(), 1, export_line.length(), out.f); + if (ferror(out.f)) { + out.close(); + boost::nowide::remove(out_path.c_str()); + throw Slic3r::RuntimeError(std::string("GCode processor post process export failed.\nIs the disk full?\n")); + } + for (size_t i = 0; i < export_line.size(); ++i) + if (export_line[i] == '\n') + m_result.lines_ends.emplace_back(out_file_pos + i + 1); + out_file_pos += export_line.size(); + export_line.clear(); + }; + + unsigned int line_id = 0; + std::vector> offsets; + + { + // Read the input stream 64kB at a time, extract lines and process them. + std::vector buffer(65536 * 10, 0); + // Line buffer. + assert(gcode_line.empty()); + for (;;) { + size_t cnt_read = ::fread(buffer.data(), 1, buffer.size(), in.f); + if (::ferror(in.f)) + throw Slic3r::RuntimeError(std::string("GCode processor post process export failed.\nError while reading from file.\n")); + bool eof = cnt_read == 0; + auto it = buffer.begin(); + auto it_bufend = buffer.begin() + cnt_read; + while (it != it_bufend || (eof && !gcode_line.empty())) { + // Find end of line. + bool eol = false; + auto it_end = it; + for (; it_end != it_bufend && !(eol = *it_end == '\r' || *it_end == '\n'); ++it_end); + // End of line is indicated also if end of file was reached. + eol |= eof && it_end == it_bufend; + gcode_line.insert(gcode_line.end(), it, it_end); + if (eol) { + ++line_id; + + gcode_line += "\n"; + // replace placeholder lines + auto [processed, lines_added_count] = process_placeholders(gcode_line); + if (processed && lines_added_count > 0) + offsets.push_back({ line_id, lines_added_count }); + if (!processed) + processed = process_used_filament(gcode_line); + if (!processed && !is_temporary_decoration(gcode_line) && GCodeReader::GCodeLine::cmd_is(gcode_line, "G1")) { + // remove temporary lines, add lines M73 where needed + unsigned int extra_lines_count = process_line_G1(g1_lines_counter++); + if (extra_lines_count > 0) + offsets.push_back({ line_id, extra_lines_count }); + } + + export_line += gcode_line; + if (export_line.length() > 65535) + write_string(export_line); + gcode_line.clear(); + } + // Skip EOL. + it = it_end; + if (it != it_bufend && *it == '\r') + ++it; + if (it != it_bufend && *it == '\n') + ++it; + } + if (eof) + break; + } + } + + if (!export_line.empty()) + write_string(export_line); + + out.close(); + in.close(); + + // updates moves' gcode ids which have been modified by the insertion of the M73 lines + unsigned int curr_offset_id = 0; + unsigned int total_offset = 0; + for (GCodeProcessorResult::MoveVertex& move : m_result.moves) { + while (curr_offset_id < static_cast(offsets.size()) && offsets[curr_offset_id].first <= move.gcode_id) { + total_offset += offsets[curr_offset_id].second; + ++curr_offset_id; + } + move.gcode_id += total_offset; + } + + if (rename_file(out_path, m_result.filename)) + throw Slic3r::RuntimeError(std::string("Failed to rename the output G-code file from ") + out_path + " to " + m_result.filename + '\n' + + "Is " + out_path + " locked?" + '\n'); +} +#endif // ENABLE_USED_FILAMENT_POST_PROCESS + +#if ENABLE_PROCESS_G2_G3_LINES +void GCodeProcessor::store_move_vertex(EMoveType type, bool internal_only) +#else +void GCodeProcessor::store_move_vertex(EMoveType type) +#endif // ENABLE_PROCESS_G2_G3_LINES +{ + m_last_line_id = (type == EMoveType::Color_change || type == EMoveType::Pause_Print || type == EMoveType::Custom_GCode) ? + m_line_id + 1 : + ((type == EMoveType::Seam) ? m_last_line_id : m_line_id); + + m_result.moves.push_back({ + m_last_line_id, + type, + m_extrusion_role, + m_extruder_id, + m_cp_color.current, + Vec3f(m_end_position[X], m_end_position[Y], m_end_position[Z] - m_z_offset) + m_extruder_offsets[m_extruder_id], + static_cast(m_end_position[E] - m_start_position[E]), + m_feedrate, + m_width, + m_height, + m_mm3_per_mm, + m_fan_speed, + m_extruder_temps[m_extruder_id], +#if ENABLE_PROCESS_G2_G3_LINES + static_cast(m_result.moves.size()), + internal_only +#else + static_cast(m_result.moves.size()) +#endif // ENABLE_PROCESS_G2_G3_LINES + }); + + // stores stop time placeholders for later use + if (type == EMoveType::Color_change || type == EMoveType::Pause_Print) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + TimeMachine& machine = m_time_processor.machines[i]; + if (!machine.enabled) + continue; + + machine.stop_times.push_back({ m_g1_line_id, 0.0f }); + } + } +} + +void GCodeProcessor::set_extrusion_role(ExtrusionRole role) +{ + m_used_filaments.process_role_cache(this); + m_extrusion_role = role; +} + +float GCodeProcessor::minimum_feedrate(PrintEstimatedStatistics::ETimeMode mode, float feedrate) const +{ + if (m_time_processor.machine_limits.machine_min_extruding_rate.empty()) + return feedrate; + + return std::max(feedrate, get_option_value(m_time_processor.machine_limits.machine_min_extruding_rate, static_cast(mode))); +} + +float GCodeProcessor::minimum_travel_feedrate(PrintEstimatedStatistics::ETimeMode mode, float feedrate) const +{ + if (m_time_processor.machine_limits.machine_min_travel_rate.empty()) + return feedrate; + + return std::max(feedrate, get_option_value(m_time_processor.machine_limits.machine_min_travel_rate, static_cast(mode))); +} + +float GCodeProcessor::get_axis_max_feedrate(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const +{ + switch (axis) + { + case X: { return get_option_value(m_time_processor.machine_limits.machine_max_feedrate_x, static_cast(mode)); } + case Y: { return get_option_value(m_time_processor.machine_limits.machine_max_feedrate_y, static_cast(mode)); } + case Z: { return get_option_value(m_time_processor.machine_limits.machine_max_feedrate_z, static_cast(mode)); } + case E: { return get_option_value(m_time_processor.machine_limits.machine_max_feedrate_e, static_cast(mode)); } + default: { return 0.0f; } + } +} + +float GCodeProcessor::get_axis_max_acceleration(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const +{ + switch (axis) + { + case X: { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_x, static_cast(mode)); } + case Y: { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_y, static_cast(mode)); } + case Z: { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_z, static_cast(mode)); } + case E: { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_e, static_cast(mode)); } + default: { return 0.0f; } + } +} + +float GCodeProcessor::get_axis_max_jerk(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const +{ + switch (axis) + { + case X: { return get_option_value(m_time_processor.machine_limits.machine_max_jerk_x, static_cast(mode)); } + case Y: { return get_option_value(m_time_processor.machine_limits.machine_max_jerk_y, static_cast(mode)); } + case Z: { return get_option_value(m_time_processor.machine_limits.machine_max_jerk_z, static_cast(mode)); } + case E: { return get_option_value(m_time_processor.machine_limits.machine_max_jerk_e, static_cast(mode)); } + default: { return 0.0f; } + } +} + +float GCodeProcessor::get_retract_acceleration(PrintEstimatedStatistics::ETimeMode mode) const +{ + size_t id = static_cast(mode); + return (id < m_time_processor.machines.size()) ? m_time_processor.machines[id].retract_acceleration : DEFAULT_RETRACT_ACCELERATION; +} + +void GCodeProcessor::set_retract_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value) +{ + size_t id = static_cast(mode); + if (id < m_time_processor.machines.size()) { + m_time_processor.machines[id].retract_acceleration = (m_time_processor.machines[id].max_retract_acceleration == 0.0f) ? value : + // Clamp the acceleration with the maximum. + std::min(value, m_time_processor.machines[id].max_retract_acceleration); + } +} + +float GCodeProcessor::get_acceleration(PrintEstimatedStatistics::ETimeMode mode) const +{ + size_t id = static_cast(mode); + return (id < m_time_processor.machines.size()) ? m_time_processor.machines[id].acceleration : DEFAULT_ACCELERATION; +} + +void GCodeProcessor::set_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value) +{ + size_t id = static_cast(mode); + if (id < m_time_processor.machines.size()) { + m_time_processor.machines[id].acceleration = (m_time_processor.machines[id].max_acceleration == 0.0f) ? value : + // Clamp the acceleration with the maximum. + std::min(value, m_time_processor.machines[id].max_acceleration); + } +} + +float GCodeProcessor::get_travel_acceleration(PrintEstimatedStatistics::ETimeMode mode) const +{ + size_t id = static_cast(mode); + return (id < m_time_processor.machines.size()) ? m_time_processor.machines[id].travel_acceleration : DEFAULT_TRAVEL_ACCELERATION; +} + +void GCodeProcessor::set_travel_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value) +{ + size_t id = static_cast(mode); + if (id < m_time_processor.machines.size()) { + m_time_processor.machines[id].travel_acceleration = (m_time_processor.machines[id].max_travel_acceleration == 0.0f) ? value : + // Clamp the acceleration with the maximum. + std::min(value, m_time_processor.machines[id].max_travel_acceleration); + } +} + +float GCodeProcessor::get_filament_load_time(size_t extruder_id) +{ + return (m_time_processor.filament_load_times.empty() || m_time_processor.extruder_unloaded) ? + 0.0f : + ((extruder_id < m_time_processor.filament_load_times.size()) ? + m_time_processor.filament_load_times[extruder_id] : m_time_processor.filament_load_times.front()); +} + +float GCodeProcessor::get_filament_unload_time(size_t extruder_id) +{ + return (m_time_processor.filament_unload_times.empty() || m_time_processor.extruder_unloaded) ? + 0.0f : + ((extruder_id < m_time_processor.filament_unload_times.size()) ? + m_time_processor.filament_unload_times[extruder_id] : m_time_processor.filament_unload_times.front()); +} + +void GCodeProcessor::process_custom_gcode_time(CustomGCode::Type code) +{ + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + TimeMachine& machine = m_time_processor.machines[i]; + if (!machine.enabled) + continue; + + TimeMachine::CustomGCodeTime& gcode_time = machine.gcode_time; + gcode_time.needed = true; + //FIXME this simulates st_synchronize! is it correct? + // The estimated time may be longer than the real print time. + machine.simulate_st_synchronize(); + if (gcode_time.cache != 0.0f) { + gcode_time.times.push_back({ code, gcode_time.cache }); + gcode_time.cache = 0.0f; + } + } +} + +void GCodeProcessor::process_filaments(CustomGCode::Type code) +{ + if (code == CustomGCode::ColorChange) + m_used_filaments.process_color_change_cache(); + + if (code == CustomGCode::ToolChange) + m_used_filaments.process_extruder_cache(this); +} + +void GCodeProcessor::simulate_st_synchronize(float additional_time) +{ + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + m_time_processor.machines[i].simulate_st_synchronize(additional_time); + } +} + +void GCodeProcessor::update_estimated_times_stats() +{ + auto update_mode = [this](PrintEstimatedStatistics::ETimeMode mode) { + PrintEstimatedStatistics::Mode& data = m_result.print_statistics.modes[static_cast(mode)]; + data.time = get_time(mode); +#if ENABLE_TRAVEL_TIME + data.travel_time = get_travel_time(mode); +#endif // ENABLE_TRAVEL_TIME + data.custom_gcode_times = get_custom_gcode_times(mode, true); + data.moves_times = get_moves_time(mode); + data.roles_times = get_roles_time(mode); + data.layers_times = get_layers_time(mode); + }; + + update_mode(PrintEstimatedStatistics::ETimeMode::Normal); + if (m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].enabled) + update_mode(PrintEstimatedStatistics::ETimeMode::Stealth); + else + m_result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].reset(); + + m_result.print_statistics.volumes_per_color_change = m_used_filaments.volumes_per_color_change; + m_result.print_statistics.volumes_per_extruder = m_used_filaments.volumes_per_extruder; + m_result.print_statistics.used_filaments_per_role = m_used_filaments.filaments_per_role; +} + +#if ENABLE_PROCESS_G2_G3_LINES +double GCodeProcessor::extract_absolute_position_on_axis(Axis axis, const GCodeReader::GCodeLine& line, double area_filament_cross_section) +{ + if (line.has(Slic3r::Axis(axis))) { + bool is_relative = (m_global_positioning_type == EPositioningType::Relative); + if (axis == E) + is_relative |= (m_e_local_positioning_type == EPositioningType::Relative); + + const double lengthsScaleFactor = (m_units == EUnits::Inches) ? double(INCHES_TO_MM) : 1.0; + double ret = line.value(Slic3r::Axis(axis)) * lengthsScaleFactor; + if (axis == E && m_use_volumetric_e) + ret /= area_filament_cross_section; + return is_relative ? m_start_position[axis] + ret : m_origin[axis] + ret; + } + else + return m_start_position[axis]; +} +#endif // ENABLE_PROCESS_G2_G3_LINES + +} /* namespace Slic3r */ + diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 1ef414c3f3..08e70f2f5f 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -1,795 +1,816 @@ -#ifndef slic3r_GCodeProcessor_hpp_ -#define slic3r_GCodeProcessor_hpp_ - -#include "libslic3r/GCodeReader.hpp" -#include "libslic3r/Point.hpp" -#include "libslic3r/ExtrusionEntity.hpp" -#include "libslic3r/PrintConfig.hpp" -#include "libslic3r/CustomGCode.hpp" - -#include -#include -#include -#include -#include -#include - -namespace Slic3r { - - enum class EMoveType : unsigned char - { - Noop, - Retract, - Unretract, - Seam, - Tool_change, - Color_change, - Pause_Print, - Custom_GCode, - Travel, - Wipe, - Extrude, - Count - }; - - struct PrintEstimatedStatistics - { - enum class ETimeMode : unsigned char - { - Normal, - Stealth, - Count - }; - - struct Mode - { - float time; -#if ENABLE_TRAVEL_TIME - float travel_time; -#endif // ENABLE_TRAVEL_TIME - std::vector>> custom_gcode_times; - std::vector> moves_times; - std::vector> roles_times; - std::vector layers_times; - - void reset() { - time = 0.0f; -#if ENABLE_TRAVEL_TIME - travel_time = 0.0f; -#endif // ENABLE_TRAVEL_TIME - custom_gcode_times.clear(); - moves_times.clear(); - roles_times.clear(); - layers_times.clear(); - } - }; - - std::vector volumes_per_color_change; - std::map volumes_per_extruder; - std::map> used_filaments_per_role; - - std::array(ETimeMode::Count)> modes; - - PrintEstimatedStatistics() { reset(); } - - void reset() { - for (auto m : modes) { - m.reset(); - } - volumes_per_color_change.clear(); - volumes_per_extruder.clear(); - used_filaments_per_role.clear(); - } - }; - - struct GCodeProcessorResult - { - struct SettingsIds - { - std::string print; - std::vector filament; - std::string printer; - - void reset() { - print.clear(); - filament.clear(); - printer.clear(); - } - }; - - struct MoveVertex - { - unsigned int gcode_id{ 0 }; - EMoveType type{ EMoveType::Noop }; - ExtrusionRole extrusion_role{ erNone }; - unsigned char extruder_id{ 0 }; - unsigned char cp_color_id{ 0 }; - Vec3f position{ Vec3f::Zero() }; // mm - float delta_extruder{ 0.0f }; // mm - float feedrate{ 0.0f }; // mm/s - float width{ 0.0f }; // mm - float height{ 0.0f }; // mm - float mm3_per_mm{ 0.0f }; - float fan_speed{ 0.0f }; // percentage - float temperature{ 0.0f }; // Celsius degrees - float time{ 0.0f }; // s -#if ENABLE_PROCESS_G2_G3_LINES - bool internal_only{ false }; -#endif // ENABLE_PROCESS_G2_G3_LINES - - float volumetric_rate() const { return feedrate * mm3_per_mm; } - }; - - std::string filename; - unsigned int id; - std::vector moves; - // Positions of ends of lines of the final G-code this->filename after TimeProcessor::post_process() finalizes the G-code. - std::vector lines_ends; - Pointfs bed_shape; - float max_print_height; - SettingsIds settings_ids; - size_t extruders_count; - std::vector extruder_colors; - std::vector filament_diameters; - std::vector filament_densities; - PrintEstimatedStatistics print_statistics; - std::vector custom_gcode_per_print_z; - std::vector>> spiral_vase_layers; - -#if ENABLE_GCODE_VIEWER_STATISTICS - int64_t time{ 0 }; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - void reset(); - }; - - - class GCodeProcessor - { - static const std::vector Reserved_Tags; - - public: - enum class ETags : unsigned char - { - Role, - Wipe_Start, - Wipe_End, - Height, - Width, - Layer_Change, - Color_Change, - Pause_Print, - Custom_Code, - First_Line_M73_Placeholder, - Last_Line_M73_Placeholder, - Estimated_Printing_Time_Placeholder - }; - - static const std::string& reserved_tag(ETags tag) { return Reserved_Tags[static_cast(tag)]; } - // checks the given gcode for reserved tags and returns true when finding the 1st (which is returned into found_tag) - static bool contains_reserved_tag(const std::string& gcode, std::string& found_tag); - // checks the given gcode for reserved tags and returns true when finding any - // (the first max_count found tags are returned into found_tag) - static bool contains_reserved_tags(const std::string& gcode, unsigned int max_count, std::vector& found_tag); - - static const float Wipe_Width; - static const float Wipe_Height; - -#if ENABLE_GCODE_VIEWER_DATA_CHECKING - static const std::string Mm3_Per_Mm_Tag; -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING - - private: - using AxisCoords = std::array; - using ExtruderColors = std::vector; - using ExtruderTemps = std::vector; - - enum class EUnits : unsigned char - { - Millimeters, - Inches - }; - - enum class EPositioningType : unsigned char - { - Absolute, - Relative - }; - - struct CachedPosition - { - AxisCoords position; // mm - float feedrate; // mm/s - - void reset(); - }; - - struct CpColor - { - unsigned char counter; - unsigned char current; - - void reset(); - }; - - public: - struct FeedrateProfile - { - float entry{ 0.0f }; // mm/s - float cruise{ 0.0f }; // mm/s - float exit{ 0.0f }; // mm/s - }; - - struct Trapezoid - { - float accelerate_until{ 0.0f }; // mm - float decelerate_after{ 0.0f }; // mm - float cruise_feedrate{ 0.0f }; // mm/sec - - float acceleration_time(float entry_feedrate, float acceleration) const; - float cruise_time() const; - float deceleration_time(float distance, float acceleration) const; - float cruise_distance() const; - }; - - struct TimeBlock - { - struct Flags - { - bool recalculate{ false }; - bool nominal_length{ false }; - }; - - EMoveType move_type{ EMoveType::Noop }; - ExtrusionRole role{ erNone }; - unsigned int g1_line_id{ 0 }; - unsigned int layer_id{ 0 }; - float distance{ 0.0f }; // mm - float acceleration{ 0.0f }; // mm/s^2 - float max_entry_speed{ 0.0f }; // mm/s - float safe_feedrate{ 0.0f }; // mm/s - Flags flags; - FeedrateProfile feedrate_profile; - Trapezoid trapezoid; - - // Calculates this block's trapezoid - void calculate_trapezoid(); - - float time() const; - }; - - private: - struct TimeMachine - { - struct State - { - float feedrate; // mm/s - float safe_feedrate; // mm/s - AxisCoords axis_feedrate; // mm/s - AxisCoords abs_axis_feedrate; // mm/s - - void reset(); - }; - - struct CustomGCodeTime - { - bool needed; - float cache; - std::vector> times; - - void reset(); - }; - - struct G1LinesCacheItem - { - unsigned int id; - float elapsed_time; - }; - - bool enabled; - float acceleration; // mm/s^2 - // hard limit for the acceleration, to which the firmware will clamp. - float max_acceleration; // mm/s^2 - float retract_acceleration; // mm/s^2 - // hard limit for the acceleration, to which the firmware will clamp. - float max_retract_acceleration; // mm/s^2 - float travel_acceleration; // mm/s^2 - // hard limit for the travel acceleration, to which the firmware will clamp. - float max_travel_acceleration; // mm/s^2 - float extrude_factor_override_percentage; - float time; // s -#if ENABLE_TRAVEL_TIME - float travel_time; // s -#endif // ENABLE_TRAVEL_TIME - struct StopTime - { - unsigned int g1_line_id; - float elapsed_time; - }; - std::vector stop_times; - std::string line_m73_main_mask; - std::string line_m73_stop_mask; - State curr; - State prev; - CustomGCodeTime gcode_time; - std::vector blocks; - std::vector g1_times_cache; - std::array(EMoveType::Count)> moves_time; - std::array(ExtrusionRole::erCount)> roles_time; - std::vector layers_time; - - void reset(); - - // Simulates firmware st_synchronize() call - void simulate_st_synchronize(float additional_time = 0.0f); - void calculate_time(size_t keep_last_n_blocks = 0, float additional_time = 0.0f); - }; - - struct TimeProcessor - { - struct Planner - { - // Size of the firmware planner queue. The old 8-bit Marlins usually just managed 16 trapezoidal blocks. - // Let's be conservative and plan for newer boards with more memory. - static constexpr size_t queue_size = 64; - // The firmware recalculates last planner_queue_size trapezoidal blocks each time a new block is added. - // We are not simulating the firmware exactly, we calculate a sequence of blocks once a reasonable number of blocks accumulate. - static constexpr size_t refresh_threshold = queue_size * 4; - }; - - // extruder_id is currently used to correctly calculate filament load / unload times into the total print time. - // 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; - // whether or not to export post-process the gcode to export lines M73 in it - bool export_remaining_time_enabled; - // allow to skip the lines M201/M203/M204/M205 generated by GCode::print_machine_envelope() for non-Normal time estimate mode - bool machine_envelope_processing_enabled; - MachineEnvelopeConfig machine_limits; - // Additional load / unload times for a filament exchange sequence. - std::vector filament_load_times; - std::vector filament_unload_times; - std::array(PrintEstimatedStatistics::ETimeMode::Count)> machines; - - void reset(); - - // post process the file with the given filename to add remaining time lines M73 - // and updates moves' gcode ids accordingly - void post_process(const std::string& filename, std::vector& moves, std::vector& lines_ends); - }; - - struct UsedFilaments // filaments per ColorChange - { - double color_change_cache; - std::vector volumes_per_color_change; - - double tool_change_cache; - std::map volumes_per_extruder; - - double role_cache; - std::map> filaments_per_role; - - void reset(); - - void increase_caches(double extruded_volume); - - void process_color_change_cache(); - void process_extruder_cache(GCodeProcessor* processor); - void process_role_cache(GCodeProcessor* processor); - void process_caches(GCodeProcessor* processor); - - friend class GCodeProcessor; - }; - - public: - class SeamsDetector - { - bool m_active{ false }; - std::optional m_first_vertex; - - public: - void activate(bool active) { - if (m_active != active) { - m_active = active; - if (m_active) - m_first_vertex.reset(); - } - } - - std::optional get_first_vertex() const { return m_first_vertex; } - void set_first_vertex(const Vec3f& vertex) { m_first_vertex = vertex; } - - bool is_active() const { return m_active; } - bool has_first_vertex() const { return m_first_vertex.has_value(); } - }; - - // Helper class used to fix the z for color change, pause print and - // custom gcode markes - class OptionsZCorrector - { - GCodeProcessorResult& m_result; - std::optional m_move_id; - std::optional m_custom_gcode_per_print_z_id; - - public: - explicit OptionsZCorrector(GCodeProcessorResult& result) : m_result(result) { - } - - void set() { - m_move_id = m_result.moves.size() - 1; - m_custom_gcode_per_print_z_id = m_result.custom_gcode_per_print_z.size() - 1; - } - - void update(float height) { - if (!m_move_id.has_value() || !m_custom_gcode_per_print_z_id.has_value()) - return; - - const Vec3f position = m_result.moves.back().position; - - GCodeProcessorResult::MoveVertex& move = m_result.moves.emplace_back(m_result.moves[*m_move_id]); - move.position = position; - move.height = height; - m_result.moves.erase(m_result.moves.begin() + *m_move_id); - m_result.custom_gcode_per_print_z[*m_custom_gcode_per_print_z_id].print_z = position.z(); - reset(); - } - - void reset() { - m_move_id.reset(); - m_custom_gcode_per_print_z_id.reset(); - } - }; - -#if ENABLE_GCODE_VIEWER_DATA_CHECKING - struct DataChecker - { - struct Error - { - float value; - float tag_value; - ExtrusionRole role; - }; - - std::string type; - float threshold{ 0.01f }; - float last_tag_value{ 0.0f }; - unsigned int count{ 0 }; - std::vector errors; - - DataChecker(const std::string& type, float threshold) - : type(type), threshold(threshold) - {} - - void update(float value, ExtrusionRole role) { - if (role != erCustom) { - ++count; - if (last_tag_value != 0.0f) { - if (std::abs(value - last_tag_value) / last_tag_value > threshold) - errors.push_back({ value, last_tag_value, role }); - } - } - } - - void reset() { last_tag_value = 0.0f; errors.clear(); count = 0; } - - std::pair get_min() const { - float delta_min = FLT_MAX; - float perc_min = 0.0f; - for (const Error& e : errors) { - if (delta_min > e.value - e.tag_value) { - delta_min = e.value - e.tag_value; - perc_min = 100.0f * delta_min / e.tag_value; - } - } - return { delta_min, perc_min }; - } - - std::pair get_max() const { - float delta_max = -FLT_MAX; - float perc_max = 0.0f; - for (const Error& e : errors) { - if (delta_max < e.value - e.tag_value) { - delta_max = e.value - e.tag_value; - perc_max = 100.0f * delta_max / e.tag_value; - } - } - return { delta_max, perc_max }; - } - - void output() const { - if (!errors.empty()) { - std::cout << type << ":\n"; - std::cout << "Errors: " << errors.size() << " (" << 100.0f * float(errors.size()) / float(count) << "%)\n"; - auto [min, perc_min] = get_min(); - auto [max, perc_max] = get_max(); - std::cout << "min: " << min << "(" << perc_min << "%) - max: " << max << "(" << perc_max << "%)\n"; - } - } - }; -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING - - private: - GCodeReader m_parser; - - EUnits m_units; - EPositioningType m_global_positioning_type; - EPositioningType m_e_local_positioning_type; - std::vector m_extruder_offsets; - GCodeFlavor m_flavor; - - AxisCoords m_start_position; // mm - AxisCoords m_end_position; // mm - AxisCoords m_saved_position; // mm - AxisCoords m_origin; // mm - CachedPosition m_cached_position; - bool m_wiping; - - unsigned int m_line_id; - unsigned int m_last_line_id; - float m_feedrate; // mm/s - struct FeedMultiply - { - float current; // percentage - float saved; // percentage - - void reset() { - current = 1.0f; - saved = 1.0f; - } - }; - FeedMultiply m_feed_multiply; - float m_width; // mm - float m_height; // mm - float m_forced_width; // mm - float m_forced_height; // mm - float m_mm3_per_mm; - float m_fan_speed; // percentage - float m_z_offset; // mm - ExtrusionRole m_extrusion_role; - unsigned char m_extruder_id; - ExtruderColors m_extruder_colors; - ExtruderTemps m_extruder_temps; - float m_extruded_last_z; - float m_first_layer_height; // mm - unsigned int m_g1_line_id; - unsigned int m_layer_id; - CpColor m_cp_color; - bool m_use_volumetric_e; - SeamsDetector m_seams_detector; - OptionsZCorrector m_options_z_corrector; - size_t m_last_default_color_id; - bool m_spiral_vase_active; -#if ENABLE_GCODE_VIEWER_STATISTICS - std::chrono::time_point m_start_time; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - enum class EProducer - { - Unknown, - PrusaSlicer, - Slic3rPE, - Slic3r, - SuperSlicer, - Cura, - Simplify3D, - CraftWare, - ideaMaker, - KissSlicer - }; - - static const std::vector> Producers; - EProducer m_producer; - - TimeProcessor m_time_processor; - UsedFilaments m_used_filaments; - - GCodeProcessorResult m_result; - static unsigned int s_result_id; - -#if ENABLE_GCODE_VIEWER_DATA_CHECKING - DataChecker m_mm3_per_mm_compare{ "mm3_per_mm", 0.01f }; - DataChecker m_height_compare{ "height", 0.01f }; - DataChecker m_width_compare{ "width", 0.01f }; -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING - - public: - GCodeProcessor(); - - void apply_config(const PrintConfig& config); - void enable_stealth_time_estimator(bool enabled); - bool is_stealth_time_estimator_enabled() const { - return m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].enabled; - } - void enable_machine_envelope_processing(bool enabled) { m_time_processor.machine_envelope_processing_enabled = enabled; } - void reset(); - - const GCodeProcessorResult& get_result() const { return m_result; } - GCodeProcessorResult&& extract_result() { return std::move(m_result); } - - // Load a G-code into a stand-alone G-code viewer. - // throws CanceledException through print->throw_if_canceled() (sent by the caller as callback). - void process_file(const std::string& filename, std::function cancel_callback = nullptr); - - // Streaming interface, for processing G-codes just generated by PrusaSlicer in a pipelined fashion. - void initialize(const std::string& filename); - void process_buffer(const std::string& buffer); - void finalize(bool post_process); - - float get_time(PrintEstimatedStatistics::ETimeMode mode) const; - std::string get_time_dhm(PrintEstimatedStatistics::ETimeMode mode) const; -#if ENABLE_TRAVEL_TIME - float get_travel_time(PrintEstimatedStatistics::ETimeMode mode) const; - std::string get_travel_time_dhm(PrintEstimatedStatistics::ETimeMode mode) const; -#endif // ENABLE_TRAVEL_TIME - std::vector>> get_custom_gcode_times(PrintEstimatedStatistics::ETimeMode mode, bool include_remaining) const; - - std::vector> get_moves_time(PrintEstimatedStatistics::ETimeMode mode) const; - std::vector> get_roles_time(PrintEstimatedStatistics::ETimeMode mode) const; - std::vector get_layers_time(PrintEstimatedStatistics::ETimeMode mode) const; - - private: - void apply_config(const DynamicPrintConfig& config); - void apply_config_simplify3d(const std::string& filename); - void apply_config_superslicer(const std::string& filename); - void process_gcode_line(const GCodeReader::GCodeLine& line, bool producers_enabled); - - // Process tags embedded into comments - void process_tags(const std::string_view comment, bool producers_enabled); - bool process_producers_tags(const std::string_view comment); - bool process_prusaslicer_tags(const std::string_view comment); - bool process_cura_tags(const std::string_view comment); - bool process_simplify3d_tags(const std::string_view comment); - bool process_craftware_tags(const std::string_view comment); - bool process_ideamaker_tags(const std::string_view comment); - bool process_kissslicer_tags(const std::string_view comment); - - bool detect_producer(const std::string_view comment); - - // Move - void process_G0(const GCodeReader::GCodeLine& line); - void process_G1(const GCodeReader::GCodeLine& line); - -#if ENABLE_PROCESS_G2_G3_LINES - // Arc Move - void process_G2_G3(const GCodeReader::GCodeLine& line, bool clockwise); -#endif // ENABLE_PROCESS_G2_G3_LINES - - // Retract - void process_G10(const GCodeReader::GCodeLine& line); - - // Unretract - void process_G11(const GCodeReader::GCodeLine& line); - - // Set Units to Inches - void process_G20(const GCodeReader::GCodeLine& line); - - // Set Units to Millimeters - void process_G21(const GCodeReader::GCodeLine& line); - - // Firmware controlled Retract - void process_G22(const GCodeReader::GCodeLine& line); - - // Firmware controlled Unretract - void process_G23(const GCodeReader::GCodeLine& line); - - // Move to origin - void process_G28(const GCodeReader::GCodeLine& line); - - // Save Current Position - void process_G60(const GCodeReader::GCodeLine& line); - - // Return to Saved Position - void process_G61(const GCodeReader::GCodeLine& line); - - // Set to Absolute Positioning - void process_G90(const GCodeReader::GCodeLine& line); - - // Set to Relative Positioning - void process_G91(const GCodeReader::GCodeLine& line); - - // Set Position - void process_G92(const GCodeReader::GCodeLine& line); - - // Sleep or Conditional stop - void process_M1(const GCodeReader::GCodeLine& line); - - // Set extruder to absolute mode - void process_M82(const GCodeReader::GCodeLine& line); - - // Set extruder to relative mode - void process_M83(const GCodeReader::GCodeLine& line); - - // Set extruder temperature - void process_M104(const GCodeReader::GCodeLine& line); - - // Set fan speed - void process_M106(const GCodeReader::GCodeLine& line); - - // Disable fan - void process_M107(const GCodeReader::GCodeLine& line); - - // Set tool (Sailfish) - void process_M108(const GCodeReader::GCodeLine& line); - - // Set extruder temperature and wait - void process_M109(const GCodeReader::GCodeLine& line); - - // Recall stored home offsets - void process_M132(const GCodeReader::GCodeLine& line); - - // Set tool (MakerWare) - void process_M135(const GCodeReader::GCodeLine& line); - - // Set max printing acceleration - void process_M201(const GCodeReader::GCodeLine& line); - - // Set maximum feedrate - void process_M203(const GCodeReader::GCodeLine& line); - - // Set default acceleration - void process_M204(const GCodeReader::GCodeLine& line); - - // Advanced settings - void process_M205(const GCodeReader::GCodeLine& line); - - // Set Feedrate Percentage - void process_M220(const GCodeReader::GCodeLine& line); - - // Set extrude factor override percentage - void process_M221(const GCodeReader::GCodeLine& line); - - // Repetier: Store x, y and z position - void process_M401(const GCodeReader::GCodeLine& line); - - // Repetier: Go to stored position - void process_M402(const GCodeReader::GCodeLine& line); - - // Set allowable instantaneous speed change - void process_M566(const GCodeReader::GCodeLine& line); - - // Unload the current filament into the MK3 MMU2 unit at the end of print. - void process_M702(const GCodeReader::GCodeLine& line); - - // Processes T line (Select Tool) - void process_T(const GCodeReader::GCodeLine& line); - void process_T(const std::string_view command); - -#if ENABLE_PROCESS_G2_G3_LINES - void store_move_vertex(EMoveType type, bool internal_only = false); -#else - void store_move_vertex(EMoveType type); -#endif // ENABLE_PROCESS_G2_G3_LINES - - void set_extrusion_role(ExtrusionRole role); - - float minimum_feedrate(PrintEstimatedStatistics::ETimeMode mode, float feedrate) const; - float minimum_travel_feedrate(PrintEstimatedStatistics::ETimeMode mode, float feedrate) const; - float get_axis_max_feedrate(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const; - float get_axis_max_acceleration(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const; - float get_axis_max_jerk(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const; - float get_retract_acceleration(PrintEstimatedStatistics::ETimeMode mode) const; - void set_retract_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value); - float get_acceleration(PrintEstimatedStatistics::ETimeMode mode) const; - void set_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value); - float get_travel_acceleration(PrintEstimatedStatistics::ETimeMode mode) const; - void set_travel_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value); - float get_filament_load_time(size_t extruder_id); - float get_filament_unload_time(size_t extruder_id); - - void process_custom_gcode_time(CustomGCode::Type code); - void process_filaments(CustomGCode::Type code); - - // Simulates firmware st_synchronize() call - void simulate_st_synchronize(float additional_time = 0.0f); - - void update_estimated_times_stats(); - -#if ENABLE_PROCESS_G2_G3_LINES - double extract_absolute_position_on_axis(Axis axis, const GCodeReader::GCodeLine& line, double area_filament_cross_section); -#endif // ENABLE_PROCESS_G2_G3_LINES - }; - -} /* namespace Slic3r */ - -#endif /* slic3r_GCodeProcessor_hpp_ */ - - +#ifndef slic3r_GCodeProcessor_hpp_ +#define slic3r_GCodeProcessor_hpp_ + +#include "libslic3r/GCodeReader.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/ExtrusionEntity.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/CustomGCode.hpp" + +#include +#include +#include +#include +#include +#include + +namespace Slic3r { + + enum class EMoveType : unsigned char + { + Noop, + Retract, + Unretract, + Seam, + Tool_change, + Color_change, + Pause_Print, + Custom_GCode, + Travel, + Wipe, + Extrude, + Count + }; + + struct PrintEstimatedStatistics + { + enum class ETimeMode : unsigned char + { + Normal, + Stealth, + Count + }; + + struct Mode + { + float time; +#if ENABLE_TRAVEL_TIME + float travel_time; +#endif // ENABLE_TRAVEL_TIME + std::vector>> custom_gcode_times; + std::vector> moves_times; + std::vector> roles_times; + std::vector layers_times; + + void reset() { + time = 0.0f; +#if ENABLE_TRAVEL_TIME + travel_time = 0.0f; +#endif // ENABLE_TRAVEL_TIME + custom_gcode_times.clear(); + moves_times.clear(); + roles_times.clear(); + layers_times.clear(); + } + }; + + std::vector volumes_per_color_change; + std::map volumes_per_extruder; + std::map> used_filaments_per_role; +#if ENABLE_USED_FILAMENT_POST_PROCESS + std::map cost_per_extruder; +#endif // ENABLE_USED_FILAMENT_POST_PROCESS + + std::array(ETimeMode::Count)> modes; + + PrintEstimatedStatistics() { reset(); } + + void reset() { + for (auto m : modes) { + m.reset(); + } + volumes_per_color_change.clear(); + volumes_per_extruder.clear(); + used_filaments_per_role.clear(); +#if ENABLE_USED_FILAMENT_POST_PROCESS + cost_per_extruder.clear(); +#endif // ENABLE_USED_FILAMENT_POST_PROCESS + } + }; + + struct GCodeProcessorResult + { + struct SettingsIds + { + std::string print; + std::vector filament; + std::string printer; + + void reset() { + print.clear(); + filament.clear(); + printer.clear(); + } + }; + + struct MoveVertex + { + unsigned int gcode_id{ 0 }; + EMoveType type{ EMoveType::Noop }; + ExtrusionRole extrusion_role{ erNone }; + unsigned char extruder_id{ 0 }; + unsigned char cp_color_id{ 0 }; + Vec3f position{ Vec3f::Zero() }; // mm + float delta_extruder{ 0.0f }; // mm + float feedrate{ 0.0f }; // mm/s + float width{ 0.0f }; // mm + float height{ 0.0f }; // mm + float mm3_per_mm{ 0.0f }; + float fan_speed{ 0.0f }; // percentage + float temperature{ 0.0f }; // Celsius degrees + float time{ 0.0f }; // s +#if ENABLE_PROCESS_G2_G3_LINES + bool internal_only{ false }; +#endif // ENABLE_PROCESS_G2_G3_LINES + + float volumetric_rate() const { return feedrate * mm3_per_mm; } + }; + + std::string filename; + unsigned int id; + std::vector moves; + // Positions of ends of lines of the final G-code this->filename after TimeProcessor::post_process() finalizes the G-code. + std::vector lines_ends; + Pointfs bed_shape; + float max_print_height; + SettingsIds settings_ids; + size_t extruders_count; + std::vector extruder_colors; + std::vector filament_diameters; + std::vector filament_densities; +#if ENABLE_USED_FILAMENT_POST_PROCESS + std::vector filament_cost; +#endif // ENABLE_USED_FILAMENT_POST_PROCESS + + PrintEstimatedStatistics print_statistics; + std::vector custom_gcode_per_print_z; + std::vector>> spiral_vase_layers; + +#if ENABLE_GCODE_VIEWER_STATISTICS + int64_t time{ 0 }; +#endif // ENABLE_GCODE_VIEWER_STATISTICS + void reset(); + }; + + + class GCodeProcessor + { + static const std::vector Reserved_Tags; + + public: + enum class ETags : unsigned char + { + Role, + Wipe_Start, + Wipe_End, + Height, + Width, + Layer_Change, + Color_Change, + Pause_Print, + Custom_Code, + First_Line_M73_Placeholder, + Last_Line_M73_Placeholder, + Estimated_Printing_Time_Placeholder + }; + + static const std::string& reserved_tag(ETags tag) { return Reserved_Tags[static_cast(tag)]; } + // checks the given gcode for reserved tags and returns true when finding the 1st (which is returned into found_tag) + static bool contains_reserved_tag(const std::string& gcode, std::string& found_tag); + // checks the given gcode for reserved tags and returns true when finding any + // (the first max_count found tags are returned into found_tag) + static bool contains_reserved_tags(const std::string& gcode, unsigned int max_count, std::vector& found_tag); + + static const float Wipe_Width; + static const float Wipe_Height; + +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + static const std::string Mm3_Per_Mm_Tag; +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING + + private: + using AxisCoords = std::array; + using ExtruderColors = std::vector; + using ExtruderTemps = std::vector; + + enum class EUnits : unsigned char + { + Millimeters, + Inches + }; + + enum class EPositioningType : unsigned char + { + Absolute, + Relative + }; + + struct CachedPosition + { + AxisCoords position; // mm + float feedrate; // mm/s + + void reset(); + }; + + struct CpColor + { + unsigned char counter; + unsigned char current; + + void reset(); + }; + + public: + struct FeedrateProfile + { + float entry{ 0.0f }; // mm/s + float cruise{ 0.0f }; // mm/s + float exit{ 0.0f }; // mm/s + }; + + struct Trapezoid + { + float accelerate_until{ 0.0f }; // mm + float decelerate_after{ 0.0f }; // mm + float cruise_feedrate{ 0.0f }; // mm/sec + + float acceleration_time(float entry_feedrate, float acceleration) const; + float cruise_time() const; + float deceleration_time(float distance, float acceleration) const; + float cruise_distance() const; + }; + + struct TimeBlock + { + struct Flags + { + bool recalculate{ false }; + bool nominal_length{ false }; + }; + + EMoveType move_type{ EMoveType::Noop }; + ExtrusionRole role{ erNone }; + unsigned int g1_line_id{ 0 }; + unsigned int layer_id{ 0 }; + float distance{ 0.0f }; // mm + float acceleration{ 0.0f }; // mm/s^2 + float max_entry_speed{ 0.0f }; // mm/s + float safe_feedrate{ 0.0f }; // mm/s + Flags flags; + FeedrateProfile feedrate_profile; + Trapezoid trapezoid; + + // Calculates this block's trapezoid + void calculate_trapezoid(); + + float time() const; + }; + + private: + struct TimeMachine + { + struct State + { + float feedrate; // mm/s + float safe_feedrate; // mm/s + AxisCoords axis_feedrate; // mm/s + AxisCoords abs_axis_feedrate; // mm/s + + void reset(); + }; + + struct CustomGCodeTime + { + bool needed; + float cache; + std::vector> times; + + void reset(); + }; + + struct G1LinesCacheItem + { + unsigned int id; + float elapsed_time; + }; + + bool enabled; + float acceleration; // mm/s^2 + // hard limit for the acceleration, to which the firmware will clamp. + float max_acceleration; // mm/s^2 + float retract_acceleration; // mm/s^2 + // hard limit for the acceleration, to which the firmware will clamp. + float max_retract_acceleration; // mm/s^2 + float travel_acceleration; // mm/s^2 + // hard limit for the travel acceleration, to which the firmware will clamp. + float max_travel_acceleration; // mm/s^2 + float extrude_factor_override_percentage; + float time; // s +#if ENABLE_TRAVEL_TIME + float travel_time; // s +#endif // ENABLE_TRAVEL_TIME + struct StopTime + { + unsigned int g1_line_id; + float elapsed_time; + }; + std::vector stop_times; + std::string line_m73_main_mask; + std::string line_m73_stop_mask; + State curr; + State prev; + CustomGCodeTime gcode_time; + std::vector blocks; + std::vector g1_times_cache; + std::array(EMoveType::Count)> moves_time; + std::array(ExtrusionRole::erCount)> roles_time; + std::vector layers_time; + + void reset(); + + // Simulates firmware st_synchronize() call + void simulate_st_synchronize(float additional_time = 0.0f); + void calculate_time(size_t keep_last_n_blocks = 0, float additional_time = 0.0f); + }; + + struct TimeProcessor + { + struct Planner + { + // Size of the firmware planner queue. The old 8-bit Marlins usually just managed 16 trapezoidal blocks. + // Let's be conservative and plan for newer boards with more memory. + static constexpr size_t queue_size = 64; + // The firmware recalculates last planner_queue_size trapezoidal blocks each time a new block is added. + // We are not simulating the firmware exactly, we calculate a sequence of blocks once a reasonable number of blocks accumulate. + static constexpr size_t refresh_threshold = queue_size * 4; + }; + + // extruder_id is currently used to correctly calculate filament load / unload times into the total print time. + // 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; + // whether or not to export post-process the gcode to export lines M73 in it + bool export_remaining_time_enabled; + // allow to skip the lines M201/M203/M204/M205 generated by GCode::print_machine_envelope() for non-Normal time estimate mode + bool machine_envelope_processing_enabled; + MachineEnvelopeConfig machine_limits; + // Additional load / unload times for a filament exchange sequence. + std::vector filament_load_times; + std::vector filament_unload_times; + std::array(PrintEstimatedStatistics::ETimeMode::Count)> machines; + + void reset(); + +#if ENABLE_USED_FILAMENT_POST_PROCESS + friend class GCodeProcessor; +#else + // post process the file with the given filename to add remaining time lines M73 + // and updates moves' gcode ids accordingly + void post_process(const std::string& filename, std::vector& moves, std::vector& lines_ends); +#endif // !ENABLE_USED_FILAMENT_POST_PROCESS + }; + + struct UsedFilaments // filaments per ColorChange + { + double color_change_cache; + std::vector volumes_per_color_change; + + double tool_change_cache; + std::map volumes_per_extruder; + + double role_cache; + std::map> filaments_per_role; + + void reset(); + + void increase_caches(double extruded_volume); + + void process_color_change_cache(); + void process_extruder_cache(GCodeProcessor* processor); + void process_role_cache(GCodeProcessor* processor); + void process_caches(GCodeProcessor* processor); + + friend class GCodeProcessor; + }; + + public: + class SeamsDetector + { + bool m_active{ false }; + std::optional m_first_vertex; + + public: + void activate(bool active) { + if (m_active != active) { + m_active = active; + if (m_active) + m_first_vertex.reset(); + } + } + + std::optional get_first_vertex() const { return m_first_vertex; } + void set_first_vertex(const Vec3f& vertex) { m_first_vertex = vertex; } + + bool is_active() const { return m_active; } + bool has_first_vertex() const { return m_first_vertex.has_value(); } + }; + + // Helper class used to fix the z for color change, pause print and + // custom gcode markes + class OptionsZCorrector + { + GCodeProcessorResult& m_result; + std::optional m_move_id; + std::optional m_custom_gcode_per_print_z_id; + + public: + explicit OptionsZCorrector(GCodeProcessorResult& result) : m_result(result) { + } + + void set() { + m_move_id = m_result.moves.size() - 1; + m_custom_gcode_per_print_z_id = m_result.custom_gcode_per_print_z.size() - 1; + } + + void update(float height) { + if (!m_move_id.has_value() || !m_custom_gcode_per_print_z_id.has_value()) + return; + + const Vec3f position = m_result.moves.back().position; + + GCodeProcessorResult::MoveVertex& move = m_result.moves.emplace_back(m_result.moves[*m_move_id]); + move.position = position; + move.height = height; + m_result.moves.erase(m_result.moves.begin() + *m_move_id); + m_result.custom_gcode_per_print_z[*m_custom_gcode_per_print_z_id].print_z = position.z(); + reset(); + } + + void reset() { + m_move_id.reset(); + m_custom_gcode_per_print_z_id.reset(); + } + }; + +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + struct DataChecker + { + struct Error + { + float value; + float tag_value; + ExtrusionRole role; + }; + + std::string type; + float threshold{ 0.01f }; + float last_tag_value{ 0.0f }; + unsigned int count{ 0 }; + std::vector errors; + + DataChecker(const std::string& type, float threshold) + : type(type), threshold(threshold) + {} + + void update(float value, ExtrusionRole role) { + if (role != erCustom) { + ++count; + if (last_tag_value != 0.0f) { + if (std::abs(value - last_tag_value) / last_tag_value > threshold) + errors.push_back({ value, last_tag_value, role }); + } + } + } + + void reset() { last_tag_value = 0.0f; errors.clear(); count = 0; } + + std::pair get_min() const { + float delta_min = FLT_MAX; + float perc_min = 0.0f; + for (const Error& e : errors) { + if (delta_min > e.value - e.tag_value) { + delta_min = e.value - e.tag_value; + perc_min = 100.0f * delta_min / e.tag_value; + } + } + return { delta_min, perc_min }; + } + + std::pair get_max() const { + float delta_max = -FLT_MAX; + float perc_max = 0.0f; + for (const Error& e : errors) { + if (delta_max < e.value - e.tag_value) { + delta_max = e.value - e.tag_value; + perc_max = 100.0f * delta_max / e.tag_value; + } + } + return { delta_max, perc_max }; + } + + void output() const { + if (!errors.empty()) { + std::cout << type << ":\n"; + std::cout << "Errors: " << errors.size() << " (" << 100.0f * float(errors.size()) / float(count) << "%)\n"; + auto [min, perc_min] = get_min(); + auto [max, perc_max] = get_max(); + std::cout << "min: " << min << "(" << perc_min << "%) - max: " << max << "(" << perc_max << "%)\n"; + } + } + }; +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING + + private: + GCodeReader m_parser; + + EUnits m_units; + EPositioningType m_global_positioning_type; + EPositioningType m_e_local_positioning_type; + std::vector m_extruder_offsets; + GCodeFlavor m_flavor; + + AxisCoords m_start_position; // mm + AxisCoords m_end_position; // mm + AxisCoords m_saved_position; // mm + AxisCoords m_origin; // mm + CachedPosition m_cached_position; + bool m_wiping; + + unsigned int m_line_id; + unsigned int m_last_line_id; + float m_feedrate; // mm/s + struct FeedMultiply + { + float current; // percentage + float saved; // percentage + + void reset() { + current = 1.0f; + saved = 1.0f; + } + }; + FeedMultiply m_feed_multiply; + float m_width; // mm + float m_height; // mm + float m_forced_width; // mm + float m_forced_height; // mm + float m_mm3_per_mm; + float m_fan_speed; // percentage + float m_z_offset; // mm + ExtrusionRole m_extrusion_role; + unsigned char m_extruder_id; + ExtruderColors m_extruder_colors; + ExtruderTemps m_extruder_temps; + float m_extruded_last_z; + float m_first_layer_height; // mm + unsigned int m_g1_line_id; + unsigned int m_layer_id; + CpColor m_cp_color; + bool m_use_volumetric_e; + SeamsDetector m_seams_detector; + OptionsZCorrector m_options_z_corrector; + size_t m_last_default_color_id; + bool m_spiral_vase_active; +#if ENABLE_GCODE_VIEWER_STATISTICS + std::chrono::time_point m_start_time; +#endif // ENABLE_GCODE_VIEWER_STATISTICS + + enum class EProducer + { + Unknown, + PrusaSlicer, + Slic3rPE, + Slic3r, + SuperSlicer, + Cura, + Simplify3D, + CraftWare, + ideaMaker, + KissSlicer + }; + + static const std::vector> Producers; + EProducer m_producer; + + TimeProcessor m_time_processor; + UsedFilaments m_used_filaments; + + GCodeProcessorResult m_result; + static unsigned int s_result_id; + +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + DataChecker m_mm3_per_mm_compare{ "mm3_per_mm", 0.01f }; + DataChecker m_height_compare{ "height", 0.01f }; + DataChecker m_width_compare{ "width", 0.01f }; +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING + + public: + GCodeProcessor(); + + void apply_config(const PrintConfig& config); + void enable_stealth_time_estimator(bool enabled); + bool is_stealth_time_estimator_enabled() const { + return m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].enabled; + } + void enable_machine_envelope_processing(bool enabled) { m_time_processor.machine_envelope_processing_enabled = enabled; } + void reset(); + + const GCodeProcessorResult& get_result() const { return m_result; } + GCodeProcessorResult&& extract_result() { return std::move(m_result); } + + // Load a G-code into a stand-alone G-code viewer. + // throws CanceledException through print->throw_if_canceled() (sent by the caller as callback). + void process_file(const std::string& filename, std::function cancel_callback = nullptr); + + // Streaming interface, for processing G-codes just generated by PrusaSlicer in a pipelined fashion. + void initialize(const std::string& filename); + void process_buffer(const std::string& buffer); + void finalize(bool post_process); + + float get_time(PrintEstimatedStatistics::ETimeMode mode) const; + std::string get_time_dhm(PrintEstimatedStatistics::ETimeMode mode) const; +#if ENABLE_TRAVEL_TIME + float get_travel_time(PrintEstimatedStatistics::ETimeMode mode) const; + std::string get_travel_time_dhm(PrintEstimatedStatistics::ETimeMode mode) const; +#endif // ENABLE_TRAVEL_TIME + std::vector>> get_custom_gcode_times(PrintEstimatedStatistics::ETimeMode mode, bool include_remaining) const; + + std::vector> get_moves_time(PrintEstimatedStatistics::ETimeMode mode) const; + std::vector> get_roles_time(PrintEstimatedStatistics::ETimeMode mode) const; + std::vector get_layers_time(PrintEstimatedStatistics::ETimeMode mode) const; + + private: + void apply_config(const DynamicPrintConfig& config); + void apply_config_simplify3d(const std::string& filename); + void apply_config_superslicer(const std::string& filename); + void process_gcode_line(const GCodeReader::GCodeLine& line, bool producers_enabled); + + // Process tags embedded into comments + void process_tags(const std::string_view comment, bool producers_enabled); + bool process_producers_tags(const std::string_view comment); + bool process_prusaslicer_tags(const std::string_view comment); + bool process_cura_tags(const std::string_view comment); + bool process_simplify3d_tags(const std::string_view comment); + bool process_craftware_tags(const std::string_view comment); + bool process_ideamaker_tags(const std::string_view comment); + bool process_kissslicer_tags(const std::string_view comment); + + bool detect_producer(const std::string_view comment); + + // Move + void process_G0(const GCodeReader::GCodeLine& line); + void process_G1(const GCodeReader::GCodeLine& line); + +#if ENABLE_PROCESS_G2_G3_LINES + // Arc Move + void process_G2_G3(const GCodeReader::GCodeLine& line, bool clockwise); +#endif // ENABLE_PROCESS_G2_G3_LINES + + // Retract + void process_G10(const GCodeReader::GCodeLine& line); + + // Unretract + void process_G11(const GCodeReader::GCodeLine& line); + + // Set Units to Inches + void process_G20(const GCodeReader::GCodeLine& line); + + // Set Units to Millimeters + void process_G21(const GCodeReader::GCodeLine& line); + + // Firmware controlled Retract + void process_G22(const GCodeReader::GCodeLine& line); + + // Firmware controlled Unretract + void process_G23(const GCodeReader::GCodeLine& line); + + // Move to origin + void process_G28(const GCodeReader::GCodeLine& line); + + // Save Current Position + void process_G60(const GCodeReader::GCodeLine& line); + + // Return to Saved Position + void process_G61(const GCodeReader::GCodeLine& line); + + // Set to Absolute Positioning + void process_G90(const GCodeReader::GCodeLine& line); + + // Set to Relative Positioning + void process_G91(const GCodeReader::GCodeLine& line); + + // Set Position + void process_G92(const GCodeReader::GCodeLine& line); + + // Sleep or Conditional stop + void process_M1(const GCodeReader::GCodeLine& line); + + // Set extruder to absolute mode + void process_M82(const GCodeReader::GCodeLine& line); + + // Set extruder to relative mode + void process_M83(const GCodeReader::GCodeLine& line); + + // Set extruder temperature + void process_M104(const GCodeReader::GCodeLine& line); + + // Set fan speed + void process_M106(const GCodeReader::GCodeLine& line); + + // Disable fan + void process_M107(const GCodeReader::GCodeLine& line); + + // Set tool (Sailfish) + void process_M108(const GCodeReader::GCodeLine& line); + + // Set extruder temperature and wait + void process_M109(const GCodeReader::GCodeLine& line); + + // Recall stored home offsets + void process_M132(const GCodeReader::GCodeLine& line); + + // Set tool (MakerWare) + void process_M135(const GCodeReader::GCodeLine& line); + + // Set max printing acceleration + void process_M201(const GCodeReader::GCodeLine& line); + + // Set maximum feedrate + void process_M203(const GCodeReader::GCodeLine& line); + + // Set default acceleration + void process_M204(const GCodeReader::GCodeLine& line); + + // Advanced settings + void process_M205(const GCodeReader::GCodeLine& line); + + // Set Feedrate Percentage + void process_M220(const GCodeReader::GCodeLine& line); + + // Set extrude factor override percentage + void process_M221(const GCodeReader::GCodeLine& line); + + // Repetier: Store x, y and z position + void process_M401(const GCodeReader::GCodeLine& line); + + // Repetier: Go to stored position + void process_M402(const GCodeReader::GCodeLine& line); + + // Set allowable instantaneous speed change + void process_M566(const GCodeReader::GCodeLine& line); + + // Unload the current filament into the MK3 MMU2 unit at the end of print. + void process_M702(const GCodeReader::GCodeLine& line); + + // Processes T line (Select Tool) + void process_T(const GCodeReader::GCodeLine& line); + void process_T(const std::string_view command); + +#if ENABLE_USED_FILAMENT_POST_PROCESS + // post process the file with the given filename to: + // 1) add remaining time lines M73 and update moves' gcode ids accordingly + // 2) update used filament data + void post_process(); +#endif // ENABLE_USED_FILAMENT_POST_PROCESS + +#if ENABLE_PROCESS_G2_G3_LINES + void store_move_vertex(EMoveType type, bool internal_only = false); +#else + void store_move_vertex(EMoveType type); +#endif // ENABLE_PROCESS_G2_G3_LINES + + void set_extrusion_role(ExtrusionRole role); + + float minimum_feedrate(PrintEstimatedStatistics::ETimeMode mode, float feedrate) const; + float minimum_travel_feedrate(PrintEstimatedStatistics::ETimeMode mode, float feedrate) const; + float get_axis_max_feedrate(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const; + float get_axis_max_acceleration(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const; + float get_axis_max_jerk(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const; + float get_retract_acceleration(PrintEstimatedStatistics::ETimeMode mode) const; + void set_retract_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value); + float get_acceleration(PrintEstimatedStatistics::ETimeMode mode) const; + void set_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value); + float get_travel_acceleration(PrintEstimatedStatistics::ETimeMode mode) const; + void set_travel_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value); + float get_filament_load_time(size_t extruder_id); + float get_filament_unload_time(size_t extruder_id); + + void process_custom_gcode_time(CustomGCode::Type code); + void process_filaments(CustomGCode::Type code); + + // Simulates firmware st_synchronize() call + void simulate_st_synchronize(float additional_time = 0.0f); + + void update_estimated_times_stats(); + +#if ENABLE_PROCESS_G2_G3_LINES + double extract_absolute_position_on_axis(Axis axis, const GCodeReader::GCodeLine& line, double area_filament_cross_section); +#endif // ENABLE_PROCESS_G2_G3_LINES + }; + +} /* namespace Slic3r */ + +#endif /* slic3r_GCodeProcessor_hpp_ */ + + diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 14d063155b..0c95d98c09 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -818,7 +818,7 @@ private: } // Providing a new mesh, therefore this volume will get a new unique ID assigned. ModelVolume(ModelObject *object, const ModelVolume &other, TriangleMesh &&mesh) : - name(other.name), source(other.source), m_mesh(new TriangleMesh(std::move(mesh))), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation) + name(other.name), source(other.source), config(other.config), object(object), m_mesh(new TriangleMesh(std::move(mesh))), m_type(other.m_type), m_transformation(other.m_transformation) { assert(this->id().valid()); assert(this->config.id().valid()); @@ -833,7 +833,7 @@ private: assert(this->config.id() == other.config.id()); this->set_material_id(other.material_id()); this->config.set_new_unique_id(); - if (mesh.facets_count() > 1) + if (m_mesh->facets_count() > 1) calculate_convex_hull(); assert(this->config.id().valid()); assert(this->config.id() != other.config.id()); diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 94830a70a2..462d4696c4 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -1,82 +1,86 @@ -#ifndef _prusaslicer_technologies_h_ -#define _prusaslicer_technologies_h_ - -//============= -// debug techs -//============= -// Shows camera target in the 3D scene -#define ENABLE_SHOW_CAMERA_TARGET 0 -// Log debug messages to console when changing selection -#define ENABLE_SELECTION_DEBUG_OUTPUT 0 -// Renders a small sphere in the center of the bounding box of the current selection when no gizmo is active -#define ENABLE_RENDER_SELECTION_CENTER 0 -// Shows an imgui dialog with camera related data -#define ENABLE_CAMERA_STATISTICS 0 -// Render the picking pass instead of the main scene (use [T] key to toggle between regular rendering and picking pass only rendering) -#define ENABLE_RENDER_PICKING_PASS 0 -// Enable extracting thumbnails from selected gcode and save them as png files -#define ENABLE_THUMBNAIL_GENERATOR_DEBUG 0 -// Disable synchronization of unselected instances -#define DISABLE_INSTANCES_SYNCH 0 -// Use wxDataViewRender instead of wxDataViewCustomRenderer -#define ENABLE_NONCUSTOM_DATA_VIEW_RENDERING 0 -// Enable G-Code viewer statistics imgui dialog -#define ENABLE_GCODE_VIEWER_STATISTICS 0 -// Enable G-Code viewer comparison between toolpaths height and width detected from gcode and calculated at gcode generation -#define ENABLE_GCODE_VIEWER_DATA_CHECKING 0 -// Enable project dirty state manager debug window -#define ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW 0 -// Disable using instanced models to render options in gcode preview -#define DISABLE_GCODEVIEWER_INSTANCED_MODELS 1 - - -// Enable rendering of objects using environment map -#define ENABLE_ENVIRONMENT_MAP 0 -// Enable smoothing of objects normals -#define ENABLE_SMOOTH_NORMALS 0 - - -//==================== -// 2.5.0.alpha1 techs -//==================== -#define ENABLE_2_5_0_ALPHA1 1 - -// Enable changes in preview layout -#define ENABLE_PREVIEW_LAYOUT (1 && ENABLE_2_5_0_ALPHA1) -// Enable drawing the items in legend toolbar using icons -#define ENABLE_LEGEND_TOOLBAR_ICONS (1 && ENABLE_PREVIEW_LAYOUT) -// Enable coloring of toolpaths in preview by layer time -#define ENABLE_PREVIEW_LAYER_TIME (1 && ENABLE_2_5_0_ALPHA1) -// Enable showing time estimate for travel moves in legend -#define ENABLE_TRAVEL_TIME (1 && ENABLE_2_5_0_ALPHA1) -// Enable not killing focus in object manipulator fields when hovering over 3D scene -#define ENABLE_OBJECT_MANIPULATOR_FOCUS (1 && ENABLE_2_5_0_ALPHA1) -// Enable removal of wipe tower magic object_id equal to 1000 -#define ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL (1 && ENABLE_2_5_0_ALPHA1) -// Enable removal of legacy OpenGL calls -#define ENABLE_LEGACY_OPENGL_REMOVAL (1 && ENABLE_2_5_0_ALPHA1) -// Enable OpenGL ES -#define ENABLE_OPENGL_ES (1 && ENABLE_LEGACY_OPENGL_REMOVAL) -// Enable OpenGL core profile context (tested against Mesa 20.1.8 on Windows) -#define ENABLE_GL_CORE_PROFILE (1 && ENABLE_LEGACY_OPENGL_REMOVAL && !ENABLE_OPENGL_ES) -// Shows an imgui dialog with GLModel statistics data -#define ENABLE_GLMODEL_STATISTICS (0 && ENABLE_LEGACY_OPENGL_REMOVAL) -// Enable show non-manifold edges -#define ENABLE_SHOW_NON_MANIFOLD_EDGES (1 && ENABLE_2_5_0_ALPHA1) -// Enable rework of Reload from disk command -#define ENABLE_RELOAD_FROM_DISK_REWORK (1 && ENABLE_2_5_0_ALPHA1) -// Enable showing toolpaths center of gravity -#define ENABLE_SHOW_TOOLPATHS_COG (1 && ENABLE_2_5_0_ALPHA1) -// Enable recalculating toolpaths when switching to/from volumetric rate visualization -#define ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC (1 && ENABLE_2_5_0_ALPHA1) -// Enable modified camera control using mouse -#define ENABLE_NEW_CAMERA_MOVEMENTS (1 && ENABLE_2_5_0_ALPHA1) -// Enable modified rectangle selection -#define ENABLE_NEW_RECTANGLE_SELECTION (1 && ENABLE_2_5_0_ALPHA1) -// Enable alternative version of file_wildcards() -#define ENABLE_ALTERNATIVE_FILE_WILDCARDS_GENERATOR (1 && ENABLE_2_5_0_ALPHA1) -// Enable processing of gcode G2 and G3 lines -#define ENABLE_PROCESS_G2_G3_LINES (1 && ENABLE_2_5_0_ALPHA1) - - -#endif // _prusaslicer_technologies_h_ +#ifndef _prusaslicer_technologies_h_ +#define _prusaslicer_technologies_h_ + +//============= +// debug techs +//============= +// Shows camera target in the 3D scene +#define ENABLE_SHOW_CAMERA_TARGET 0 +// Log debug messages to console when changing selection +#define ENABLE_SELECTION_DEBUG_OUTPUT 0 +// Renders a small sphere in the center of the bounding box of the current selection when no gizmo is active +#define ENABLE_RENDER_SELECTION_CENTER 0 +// Shows an imgui dialog with camera related data +#define ENABLE_CAMERA_STATISTICS 0 +// Render the picking pass instead of the main scene (use [T] key to toggle between regular rendering and picking pass only rendering) +#define ENABLE_RENDER_PICKING_PASS 0 +// Enable extracting thumbnails from selected gcode and save them as png files +#define ENABLE_THUMBNAIL_GENERATOR_DEBUG 0 +// Disable synchronization of unselected instances +#define DISABLE_INSTANCES_SYNCH 0 +// Use wxDataViewRender instead of wxDataViewCustomRenderer +#define ENABLE_NONCUSTOM_DATA_VIEW_RENDERING 0 +// Enable G-Code viewer statistics imgui dialog +#define ENABLE_GCODE_VIEWER_STATISTICS 0 +// Enable G-Code viewer comparison between toolpaths height and width detected from gcode and calculated at gcode generation +#define ENABLE_GCODE_VIEWER_DATA_CHECKING 0 +// Enable project dirty state manager debug window +#define ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW 0 +// Disable using instanced models to render options in gcode preview +#define DISABLE_GCODEVIEWER_INSTANCED_MODELS 1 + + +// Enable rendering of objects using environment map +#define ENABLE_ENVIRONMENT_MAP 0 +// Enable smoothing of objects normals +#define ENABLE_SMOOTH_NORMALS 0 + + +//==================== +// 2.5.0.alpha1 techs +//==================== +#define ENABLE_2_5_0_ALPHA1 1 + +// Enable changes in preview layout +#define ENABLE_PREVIEW_LAYOUT (1 && ENABLE_2_5_0_ALPHA1) +// Enable drawing the items in legend toolbar using icons +#define ENABLE_LEGEND_TOOLBAR_ICONS (1 && ENABLE_PREVIEW_LAYOUT) +// Enable coloring of toolpaths in preview by layer time +#define ENABLE_PREVIEW_LAYER_TIME (1 && ENABLE_2_5_0_ALPHA1) +// Enable showing time estimate for travel moves in legend +#define ENABLE_TRAVEL_TIME (1 && ENABLE_2_5_0_ALPHA1) +// Enable not killing focus in object manipulator fields when hovering over 3D scene +#define ENABLE_OBJECT_MANIPULATOR_FOCUS (1 && ENABLE_2_5_0_ALPHA1) +// Enable removal of wipe tower magic object_id equal to 1000 +#define ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL (1 && ENABLE_2_5_0_ALPHA1) +// Enable removal of legacy OpenGL calls +#define ENABLE_LEGACY_OPENGL_REMOVAL (1 && ENABLE_2_5_0_ALPHA1) +// Enable OpenGL ES +#define ENABLE_OPENGL_ES (1 && ENABLE_LEGACY_OPENGL_REMOVAL) +// Enable OpenGL core profile context (tested against Mesa 20.1.8 on Windows) +#define ENABLE_GL_CORE_PROFILE (1 && ENABLE_LEGACY_OPENGL_REMOVAL && !ENABLE_OPENGL_ES) +// Shows an imgui dialog with GLModel statistics data +#define ENABLE_GLMODEL_STATISTICS (0 && ENABLE_LEGACY_OPENGL_REMOVAL) +// Enable show non-manifold edges +#define ENABLE_SHOW_NON_MANIFOLD_EDGES (1 && ENABLE_2_5_0_ALPHA1) +// Enable rework of Reload from disk command +#define ENABLE_RELOAD_FROM_DISK_REWORK (1 && ENABLE_2_5_0_ALPHA1) +// Enable showing toolpaths center of gravity +#define ENABLE_SHOW_TOOLPATHS_COG (1 && ENABLE_2_5_0_ALPHA1) +// Enable recalculating toolpaths when switching to/from volumetric rate visualization +#define ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC (1 && ENABLE_2_5_0_ALPHA1) +// Enable modified camera control using mouse +#define ENABLE_NEW_CAMERA_MOVEMENTS (1 && ENABLE_2_5_0_ALPHA1) +// Enable modified rectangle selection +#define ENABLE_NEW_RECTANGLE_SELECTION (1 && ENABLE_2_5_0_ALPHA1) +// Enable alternative version of file_wildcards() +#define ENABLE_ALTERNATIVE_FILE_WILDCARDS_GENERATOR (1 && ENABLE_2_5_0_ALPHA1) +// Enable processing of gcode G2 and G3 lines +#define ENABLE_PROCESS_G2_G3_LINES (1 && ENABLE_2_5_0_ALPHA1) +// Enable fix of used filament data exported to gcode file +#define ENABLE_USED_FILAMENT_POST_PROCESS (1 && ENABLE_2_5_0_ALPHA1) +// Enable gizmo grabbers to share common models +#define ENABLE_GIZMO_GRABBER_REFACTOR (1 && ENABLE_2_5_0_ALPHA1) + + +#endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp index ca2504fbfe..c441cac067 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp @@ -18,10 +18,19 @@ const float GLGizmoBase::Grabber::SizeFactor = 0.05f; const float GLGizmoBase::Grabber::MinHalfSize = 1.5f; const float GLGizmoBase::Grabber::DraggingScaleFactor = 1.25f; -void GLGizmoBase::Grabber::render(bool hover, float size) -{ - render(size, hover ? complementary(color) : color, false); -} +#if ENABLE_GIZMO_GRABBER_REFACTOR +GLModel GLGizmoBase::Grabber::s_cube; +GLModel GLGizmoBase::Grabber::s_cone; + +GLGizmoBase::Grabber::~Grabber() +{ + if (s_cube.is_initialized()) + s_cube.reset(); + + if (s_cone.is_initialized()) + s_cone.reset(); +} +#endif // ENABLE_GIZMO_GRABBER_REFACTOR float GLGizmoBase::Grabber::get_half_size(float size) const { @@ -41,39 +50,151 @@ void GLGizmoBase::Grabber::render(float size, const ColorRGBA& render_color, boo return; #endif // ENABLE_LEGACY_OPENGL_REMOVAL - if (!m_cube.is_initialized()) { +#if ENABLE_GIZMO_GRABBER_REFACTOR + if (!s_cube.is_initialized()) { +#else + if (!m_cube.is_initialized()) { +#endif // ENABLE_GIZMO_GRABBER_REFACTOR // This cannot be done in constructor, OpenGL is not yet // initialized at that point (on Linux at least). - indexed_triangle_set its = its_make_cube(1., 1., 1.); + indexed_triangle_set its = its_make_cube(1.0, 1.0, 1.0); its_translate(its, -0.5f * Vec3f::Ones()); #if ENABLE_LEGACY_OPENGL_REMOVAL - m_cube.init_from(its); +#if ENABLE_GIZMO_GRABBER_REFACTOR + s_cube.init_from(its); +#else + m_cube.init_from(its); +#endif // ENABLE_GIZMO_GRABBER_REFACTOR #else - m_cube.init_from(its, BoundingBoxf3{ { -0.5, -0.5, -0.5 }, { 0.5, 0.5, 0.5 } }); +#if ENABLE_GIZMO_GRABBER_REFACTOR + s_cube.init_from(its, BoundingBoxf3{ { -0.5, -0.5, -0.5 }, { 0.5, 0.5, 0.5 } }); +#else + m_cube.init_from(its, BoundingBoxf3{ { -0.5, -0.5, -0.5 }, { 0.5, 0.5, 0.5 } }); +#endif // ENABLE_GIZMO_GRABBER_REFACTOR #endif // ENABLE_LEGACY_OPENGL_REMOVAL } - const float fullsize = 2.0f * (dragging ? get_dragging_half_size(size) : get_half_size(size)); +#if ENABLE_GIZMO_GRABBER_REFACTOR + if (!s_cone.is_initialized()) + s_cone.init_from(its_make_cone(0.375, 1.5, double(PI) / 18.0)); +#endif // ENABLE_GIZMO_GRABBER_REFACTOR + +#if ENABLE_GIZMO_GRABBER_REFACTOR + const float half_size = dragging ? get_dragging_half_size(size) : get_half_size(size); +#else + const float fullsize = 2.0f * (dragging ? get_dragging_half_size(size) : get_half_size(size)); +#endif // ENABLE_GIZMO_GRABBER_REFACTOR #if ENABLE_LEGACY_OPENGL_REMOVAL - m_cube.set_color(render_color); +#if ENABLE_GIZMO_GRABBER_REFACTOR + s_cube.set_color(render_color); + s_cone.set_color(render_color); +#else + m_cube.set_color(render_color); +#endif // ENABLE_GIZMO_GRABBER_REFACTOR const Camera& camera = wxGetApp().plater()->get_camera(); - const Transform3d view_model_matrix = camera.get_view_matrix() * matrix * Geometry::assemble_transform(center, angles, fullsize * Vec3d::Ones()); +#if ENABLE_GIZMO_GRABBER_REFACTOR + const Transform3d view_model_matrix = camera.get_view_matrix() * matrix * Geometry::assemble_transform(center, angles, 2.0 * half_size * Vec3d::Ones()); +#else + const Transform3d view_model_matrix = camera.get_view_matrix() * matrix * Geometry::assemble_transform(center, angles, fullsize * Vec3d::Ones()); +#endif // ENABLE_GIZMO_GRABBER_REFACTOR const Transform3d& projection_matrix = camera.get_projection_matrix(); shader->set_uniform("view_model_matrix", view_model_matrix); shader->set_uniform("projection_matrix", projection_matrix); shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); #else - m_cube.set_color(-1, render_color); +#if ENABLE_GIZMO_GRABBER_REFACTOR + s_cube.set_color(-1, render_color); + s_cone.set_color(-1, render_color); +#else + m_cube.set_color(-1, render_color); +#endif // ENABLE_GIZMO_GRABBER_REFACTOR glsafe(::glPushMatrix()); glsafe(::glTranslated(center.x(), center.y(), center.z())); glsafe(::glRotated(Geometry::rad2deg(angles.z()), 0.0, 0.0, 1.0)); glsafe(::glRotated(Geometry::rad2deg(angles.y()), 0.0, 1.0, 0.0)); glsafe(::glRotated(Geometry::rad2deg(angles.x()), 1.0, 0.0, 0.0)); - glsafe(::glScaled(fullsize, fullsize, fullsize)); +#if ENABLE_GIZMO_GRABBER_REFACTOR + glsafe(::glScaled(2.0 * half_size, 2.0 * half_size, 2.0 * half_size)); +#else + glsafe(::glScaled(fullsize, fullsize, fullsize)); +#endif // ENABLE_GIZMO_GRABBER_REFACTOR #endif // ENABLE_LEGACY_OPENGL_REMOVAL - m_cube.render(); +#if ENABLE_GIZMO_GRABBER_REFACTOR + s_cube.render(); + +#if ENABLE_LEGACY_OPENGL_REMOVAL + if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::PosX)) != 0) { + shader->set_uniform("view_model_matrix", view_model_matrix * Geometry::assemble_transform(Vec3d::UnitX(), Vec3d(0.0, 0.5 * double(PI), 0.0))); + s_cone.render(); + } + if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::NegX)) != 0) { + shader->set_uniform("view_model_matrix", view_model_matrix * Geometry::assemble_transform(-Vec3d::UnitX(), Vec3d(0.0, -0.5 * double(PI), 0.0))); + s_cone.render(); + } + if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::PosY)) != 0) { + shader->set_uniform("view_model_matrix", view_model_matrix * Geometry::assemble_transform(Vec3d::UnitY(), Vec3d(-0.5 * double(PI), 0.0, 0.0))); + s_cone.render(); + } + if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::NegY)) != 0) { + shader->set_uniform("view_model_matrix", view_model_matrix * Geometry::assemble_transform(-Vec3d::UnitY(), Vec3d(0.5 * double(PI), 0.0, 0.0))); + s_cone.render(); + } + if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::PosZ)) != 0) { + shader->set_uniform("view_model_matrix", view_model_matrix * Geometry::assemble_transform(Vec3d::UnitZ())); + s_cone.render(); + } + if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::NegZ)) != 0) { + shader->set_uniform("view_model_matrix", view_model_matrix * Geometry::assemble_transform(-Vec3d::UnitZ(), Vec3d(double(PI), 0.0, 0.0))); + s_cone.render(); + } +#else + if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::PosX)) != 0) { + glsafe(::glPushMatrix()); + glsafe(::glTranslated(1.0, 0.0, 0.0)); + glsafe(::glRotated(0.5 * Geometry::rad2deg(double(PI)), 0.0, 1.0, 0.0)); + s_cone.render(); + glsafe(::glPopMatrix()); + } + if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::NegX)) != 0) { + glsafe(::glPushMatrix()); + glsafe(::glTranslated(-1.0, 0.0, 0.0)); + glsafe(::glRotated(-0.5 * Geometry::rad2deg(double(PI)), 0.0, 1.0, 0.0)); + s_cone.render(); + glsafe(::glPopMatrix()); + } + if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::PosY)) != 0) { + glsafe(::glPushMatrix()); + glsafe(::glTranslated(0.0, 1.0, 0.0)); + glsafe(::glRotated(-0.5 * Geometry::rad2deg(double(PI)), 1.0, 0.0, 0.0)); + s_cone.render(); + glsafe(::glPopMatrix()); + } + if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::NegY)) != 0) { + glsafe(::glPushMatrix()); + glsafe(::glTranslated(0.0, -1.0, 0.0)); + glsafe(::glRotated(0.5 * Geometry::rad2deg(double(PI)), 1.0, 0.0, 0.0)); + s_cone.render(); + glsafe(::glPopMatrix()); + } + if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::PosZ)) != 0) { + glsafe(::glPushMatrix()); + glsafe(::glTranslated(0.0, 0.0, 1.0)); + s_cone.render(); + glsafe(::glPopMatrix()); + } + if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::NegZ)) != 0) { + glsafe(::glPushMatrix()); + glsafe(::glTranslated(0.0, 0.0, -1.0)); + glsafe(::glRotated(Geometry::rad2deg(double(PI)), 1.0, 0.0, 0.0)); + s_cone.render(); + glsafe(::glPopMatrix()); + } +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +#else + m_cube.render(); +#endif // ENABLE_GIZMO_GRABBER_REFACTOR #if !ENABLE_LEGACY_OPENGL_REMOVAL glsafe(::glPopMatrix()); #endif // !ENABLE_LEGACY_OPENGL_REMOVAL diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp index 23e542a728..97c0437a51 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp @@ -38,6 +38,19 @@ public: // (254 is choosen to leave some space for forward compatibility) static const unsigned int BASE_ID = 255 * 255 * 254; +#if ENABLE_GIZMO_GRABBER_REFACTOR + enum class EGrabberExtension + { + None = 0, + PosX = 1 << 0, + NegX = 1 << 1, + PosY = 1 << 2, + NegY = 1 << 3, + PosZ = 1 << 4, + NegZ = 1 << 5, + }; +#endif // ENABLE_GIZMO_GRABBER_REFACTOR + protected: struct Grabber { @@ -53,10 +66,16 @@ protected: Transform3d matrix{ Transform3d::Identity() }; #endif // ENABLE_LEGACY_OPENGL_REMOVAL ColorRGBA color{ ColorRGBA::WHITE() }; +#if ENABLE_GIZMO_GRABBER_REFACTOR + EGrabberExtension extensions{ EGrabberExtension::None }; +#endif // ENABLE_GIZMO_GRABBER_REFACTOR Grabber() = default; +#if ENABLE_GIZMO_GRABBER_REFACTOR + ~Grabber(); +#endif // ENABLE_GIZMO_GRABBER_REFACTOR - void render(bool hover, float size); + void render(bool hover, float size) { render(size, hover ? complementary(color) : color, false); } void render_for_picking(float size) { render(size, color, true); } float get_half_size(float size) const; @@ -65,7 +84,12 @@ protected: private: void render(float size, const ColorRGBA& render_color, bool picking); - GLModel m_cube; +#if ENABLE_GIZMO_GRABBER_REFACTOR + static GLModel s_cube; + static GLModel s_cone; +#else + GLModel m_cube; +#endif // ENABLE_GIZMO_GRABBER_REFACTOR }; public: diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index 75e6cf9def..fe56fb3a13 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -49,8 +49,16 @@ bool GLGizmoMove3D::on_init() { for (int i = 0; i < 3; ++i) { m_grabbers.push_back(Grabber()); +#if ENABLE_GIZMO_GRABBER_REFACTOR + m_grabbers.back().extensions = GLGizmoBase::EGrabberExtension::PosZ; +#endif // ENABLE_GIZMO_GRABBER_REFACTOR } +#if ENABLE_GIZMO_GRABBER_REFACTOR + m_grabbers[0].angles = { 0.0, 0.5 * double(PI), 0.0 }; + m_grabbers[1].angles = { -0.5 * double(PI), 0.0, 0.0 }; +#endif // ENABLE_GIZMO_GRABBER_REFACTOR + m_shortcut_key = WXK_CONTROL_M; return true; @@ -99,8 +107,10 @@ void GLGizmoMove3D::on_dragging(const UpdateData& data) void GLGizmoMove3D::on_render() { +#if !ENABLE_GIZMO_GRABBER_REFACTOR if (!m_cone.is_initialized()) m_cone.init_from(its_make_cone(1.0, 1.0, double(PI) / 18.0)); +#endif // !ENABLE_GIZMO_GRABBER_REFACTOR const Selection& selection = m_parent.get_selection(); @@ -196,10 +206,12 @@ void GLGizmoMove3D::on_render() // draw grabbers render_grabbers(box); +#if !ENABLE_GIZMO_GRABBER_REFACTOR for (unsigned int i = 0; i < 3; ++i) { if (m_grabbers[i].enabled) render_grabber_extension((Axis)i, box, false); } +#endif // !ENABLE_GIZMO_GRABBER_REFACTOR } else { // draw axis @@ -244,7 +256,9 @@ void GLGizmoMove3D::on_render() m_grabbers[m_hover_id].render(true, mean_size); shader->stop_using(); } +#if !ENABLE_GIZMO_GRABBER_REFACTOR render_grabber_extension((Axis)m_hover_id, box, false); +#endif // !ENABLE_GIZMO_GRABBER_REFACTOR } } @@ -254,9 +268,11 @@ void GLGizmoMove3D::on_render_for_picking() const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box(); render_grabbers_for_picking(box); +#if !ENABLE_GIZMO_GRABBER_REFACTOR render_grabber_extension(X, box, true); render_grabber_extension(Y, box, true); render_grabber_extension(Z, box, true); +#endif // !ENABLE_GIZMO_GRABBER_REFACTOR } double GLGizmoMove3D::calc_projection(const UpdateData& data) const @@ -285,6 +301,7 @@ double GLGizmoMove3D::calc_projection(const UpdateData& data) const return projection; } +#if !ENABLE_GIZMO_GRABBER_REFACTOR void GLGizmoMove3D::render_grabber_extension(Axis axis, const BoundingBoxf3& box, bool picking) { const float mean_size = float((box.size().x() + box.size().y() + box.size().z()) / 3.0); @@ -339,6 +356,7 @@ void GLGizmoMove3D::render_grabber_extension(Axis axis, const BoundingBoxf3& box #endif // !ENABLE_LEGACY_OPENGL_REMOVAL shader->stop_using(); } +#endif // !ENABLE_GIZMO_GRABBER_REFACTOR } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp index 92729c1990..6a618c3e4d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp @@ -1,70 +1,73 @@ -#ifndef slic3r_GLGizmoMove_hpp_ -#define slic3r_GLGizmoMove_hpp_ - -#include "GLGizmoBase.hpp" - - -namespace Slic3r { -namespace GUI { - -class GLGizmoMove3D : public GLGizmoBase -{ - static const double Offset; - - Vec3d m_displacement{ Vec3d::Zero() }; - double m_snap_step{ 1.0 }; - Vec3d m_starting_drag_position{ Vec3d::Zero() }; - Vec3d m_starting_box_center{ Vec3d::Zero() }; - Vec3d m_starting_box_bottom_center{ Vec3d::Zero() }; - - GLModel m_cone; -#if ENABLE_LEGACY_OPENGL_REMOVAL - struct GrabberConnection - { - GLModel model; - Vec3d old_center{ Vec3d::Zero() }; - }; - std::array m_grabber_connections; -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - -public: - GLGizmoMove3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); - virtual ~GLGizmoMove3D() = default; - - double get_snap_step(double step) const { return m_snap_step; } - void set_snap_step(double step) { m_snap_step = step; } - - std::string get_tooltip() const override; - - /// - /// Postpone to Grabber for move - /// - /// Keep information about mouse click - /// Return True when use the information otherwise False. - bool on_mouse(const wxMouseEvent &mouse_event) override; - - /// - /// Detect reduction of move for wipetover on selection change - /// - void data_changed() override; -protected: - bool on_init() override; - std::string on_get_name() const override; - bool on_is_activable() const override; - void on_start_dragging() override; - void on_stop_dragging() override; - void on_dragging(const UpdateData& data) override; - void on_render() override; - void on_render_for_picking() override; - -private: - double calc_projection(const UpdateData& data) const; - void render_grabber_extension(Axis axis, const BoundingBoxf3& box, bool picking); -}; - - - -} // namespace GUI -} // namespace Slic3r - -#endif // slic3r_GLGizmoMove_hpp_ +#ifndef slic3r_GLGizmoMove_hpp_ +#define slic3r_GLGizmoMove_hpp_ + +#include "GLGizmoBase.hpp" + + +namespace Slic3r { +namespace GUI { + +class GLGizmoMove3D : public GLGizmoBase +{ + static const double Offset; + + Vec3d m_displacement{ Vec3d::Zero() }; + double m_snap_step{ 1.0 }; + Vec3d m_starting_drag_position{ Vec3d::Zero() }; + Vec3d m_starting_box_center{ Vec3d::Zero() }; + Vec3d m_starting_box_bottom_center{ Vec3d::Zero() }; + +#if !ENABLE_GIZMO_GRABBER_REFACTOR + GLModel m_cone; +#endif // !ENABLE_GIZMO_GRABBER_REFACTOR +#if ENABLE_LEGACY_OPENGL_REMOVAL + struct GrabberConnection + { + GLModel model; + Vec3d old_center{ Vec3d::Zero() }; + }; + std::array m_grabber_connections; +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + +public: + GLGizmoMove3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); + virtual ~GLGizmoMove3D() = default; + + double get_snap_step(double step) const { return m_snap_step; } + void set_snap_step(double step) { m_snap_step = step; } + + std::string get_tooltip() const override; + + /// + /// Postpone to Grabber for move + /// + /// Keep information about mouse click + /// Return True when use the information otherwise False. + bool on_mouse(const wxMouseEvent &mouse_event) override; + + /// + /// Detect reduction of move for wipetover on selection change + /// + void data_changed() override; + +protected: + bool on_init() override; + std::string on_get_name() const override; + bool on_is_activable() const override; + void on_start_dragging() override; + void on_stop_dragging() override; + void on_dragging(const UpdateData& data) override; + void on_render() override; + void on_render_for_picking() override; + +private: + double calc_projection(const UpdateData& data) const; +#if !ENABLE_GIZMO_GRABBER_REFACTOR + void render_grabber_extension(Axis axis, const BoundingBoxf3& box, bool picking); +#endif // !ENABLE_GIZMO_GRABBER_REFACTOR +}; + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_GLGizmoMove_hpp_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index cb61f4c9dd..d2d35b6af3 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -91,6 +91,9 @@ void GLGizmoRotate::disable_grabber() { m_grabbers[0].enabled = false; } bool GLGizmoRotate::on_init() { m_grabbers.push_back(Grabber()); +#if ENABLE_GIZMO_GRABBER_REFACTOR + m_grabbers.back().extensions = (GLGizmoBase::EGrabberExtension)(int(GLGizmoBase::EGrabberExtension::PosY) | int(GLGizmoBase::EGrabberExtension::NegY)); +#endif // ENABLE_GIZMO_GRABBER_REFACTOR return true; } @@ -142,8 +145,10 @@ void GLGizmoRotate::on_render() if (!m_grabbers.front().enabled) return; +#if !ENABLE_GIZMO_GRABBER_REFACTOR if (!m_cone.is_initialized()) m_cone.init_from(its_make_cone(1.0, 1.0, double(PI) / 12.0)); +#endif // !ENABLE_GIZMO_GRABBER_REFACTOR const Selection& selection = m_parent.get_selection(); const BoundingBoxf3& box = selection.get_bounding_box(); @@ -229,7 +234,9 @@ void GLGizmoRotate::on_render() #endif // ENABLE_LEGACY_OPENGL_REMOVAL render_grabber(box); +#if !ENABLE_GIZMO_GRABBER_REFACTOR render_grabber_extension(box, false); +#endif // !ENABLE_GIZMO_GRABBER_REFACTOR #if !ENABLE_LEGACY_OPENGL_REMOVAL glsafe(::glPopMatrix()); @@ -251,7 +258,9 @@ void GLGizmoRotate::on_render_for_picking() const BoundingBoxf3& box = selection.get_bounding_box(); render_grabbers_for_picking(box); +#if !ENABLE_GIZMO_GRABBER_REFACTOR render_grabber_extension(box, true); +#endif // !ENABLE_GIZMO_GRABBER_REFACTOR #if !ENABLE_LEGACY_OPENGL_REMOVAL glsafe(::glPopMatrix()); @@ -576,7 +585,8 @@ void GLGizmoRotate::render_grabber(const BoundingBoxf3& box) render_grabbers(box); } -void GLGizmoRotate::render_grabber_extension(const BoundingBoxf3& box, bool picking) +#if !ENABLE_GIZMO_GRABBER_REFACTOR +\void GLGizmoRotate::render_grabber_extension(const BoundingBoxf3& box, bool picking) { const float mean_size = float((box.size().x() + box.size().y() + box.size().z()) / 3.0); const double size = m_dragging ? double(m_grabbers.front().get_dragging_half_size(mean_size)) : double(m_grabbers.front().get_half_size(mean_size)); @@ -648,6 +658,7 @@ void GLGizmoRotate::render_grabber_extension(const BoundingBoxf3& box, bool pick #endif // !ENABLE_LEGACY_OPENGL_REMOVAL shader->stop_using(); } +#endif // !ENABLE_GIZMO_GRABBER_REFACTOR #if ENABLE_LEGACY_OPENGL_REMOVAL Transform3d GLGizmoRotate::local_transform(const Selection& selection) const diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp index 545fc9eb7f..0f8137602c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp @@ -35,7 +35,9 @@ private: float m_snap_fine_in_radius{ 0.0f }; float m_snap_fine_out_radius{ 0.0f }; +#if !ENABLE_GIZMO_GRABBER_REFACTOR GLModel m_cone; +#endif // !ENABLE_GIZMO_GRABBER_REFACTOR #if ENABLE_LEGACY_OPENGL_REMOVAL GLModel m_circle; GLModel m_scale; @@ -55,6 +57,7 @@ private: ColorRGBA m_drag_color; ColorRGBA m_highlight_color; + public: GLGizmoRotate(GLCanvas3D& parent, Axis axis); virtual ~GLGizmoRotate() = default; @@ -104,7 +107,9 @@ private: void render_angle() const; #endif // ENABLE_LEGACY_OPENGL_REMOVAL void render_grabber(const BoundingBoxf3& box); +#if !ENABLE_GIZMO_GRABBER_REFACTOR void render_grabber_extension(const BoundingBoxf3& box, bool picking); +#endif // !ENABLE_GIZMO_GRABBER_REFACTOR #if ENABLE_LEGACY_OPENGL_REMOVAL Transform3d local_transform(const Selection& selection) const; diff --git a/src/slic3r/GUI/Jobs/SLAImportDialog.hpp b/src/slic3r/GUI/Jobs/SLAImportDialog.hpp index 7dbecff2ae..15cab9ed1b 100644 --- a/src/slic3r/GUI/Jobs/SLAImportDialog.hpp +++ b/src/slic3r/GUI/Jobs/SLAImportDialog.hpp @@ -54,6 +54,7 @@ public: inp_choices.size(), inp_choices.data(), wxCB_READONLY | wxCB_DROPDOWN); szchoices->Add(m_import_dropdown); + szchoices->AddStretchSpacer(1); szchoices->Add(new wxStaticText(this, wxID_ANY, _L("Quality") + ": "), 0, wxALIGN_CENTER | wxALL, 5); static const std::vector qual_choices = { @@ -65,7 +66,7 @@ public: m_quality_dropdown = new wxComboBox( this, wxID_ANY, qual_choices[0], wxDefaultPosition, wxDefaultSize, qual_choices.size(), qual_choices.data(), wxCB_READONLY | wxCB_DROPDOWN); - szchoices->Add(m_quality_dropdown); + szchoices->Add(m_quality_dropdown, 1); m_import_dropdown->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) { if (get_selection() == Sel::profileOnly) @@ -73,14 +74,20 @@ public: else m_quality_dropdown->Enable(); }); - szvert->Add(szchoices, 0, wxALL, 5); - szvert->AddStretchSpacer(1); + szvert->Add(szchoices, 1, wxEXPAND | wxALL, 5); auto szbtn = new wxBoxSizer(wxHORIZONTAL); - szbtn->Add(new wxButton{this, wxID_CANCEL}); + szbtn->Add(new wxButton{this, wxID_CANCEL}, 0, wxRIGHT, 5); szbtn->Add(new wxButton{this, wxID_OK}); szvert->Add(szbtn, 0, wxALIGN_RIGHT | wxALL, 5); SetSizerAndFit(szvert); + wxGetApp().UpdateDlgDarkUI(this); + } + + int ShowModal() override + { + CenterOnParent(); + return wxDialog::ShowModal(); } Sel get_selection() const override diff --git a/src/slic3r/GUI/Jobs/SLAImportJob.cpp b/src/slic3r/GUI/Jobs/SLAImportJob.cpp index 96702d158f..b7dc64d525 100644 --- a/src/slic3r/GUI/Jobs/SLAImportJob.cpp +++ b/src/slic3r/GUI/Jobs/SLAImportJob.cpp @@ -139,6 +139,7 @@ void SLAImportJob::finalize(bool canceled, std::exception_ptr &eptr) config += std::move(p->profile); wxGetApp().preset_bundle->load_config_model(name, std::move(config)); + p->plater->check_selected_presets_visibility(ptSLA); wxGetApp().load_current_presets(); } diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index ec4d012b24..af285e46e2 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -1821,6 +1821,8 @@ bool MainFrame::load_config_file(const std::string &path) show_error(this, ex.what()); return false; } + + m_plater->check_selected_presets_visibility(ptFFF); wxGetApp().load_current_presets(); return true; } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 4218c93933..457b434d0a 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2336,6 +2336,52 @@ std::string Plater::priv::get_config(const std::string &key) const return wxGetApp().app_config->get(key); } +// After loading of the presets from project, check if they are visible. +// Set them to visible if they are not. +void Plater::check_selected_presets_visibility(PrinterTechnology loaded_printer_technology) +{ + auto update_selected_preset_visibility = [](PresetCollection& presets, std::vector& names) { + if (!presets.get_selected_preset().is_visible) { + assert(presets.get_selected_preset().name == presets.get_edited_preset().name); + presets.get_selected_preset().is_visible = true; + presets.get_edited_preset().is_visible = true; + names.emplace_back(presets.get_selected_preset().name); + } + }; + + std::vector names; + PresetBundle* preset_bundle = wxGetApp().preset_bundle; + if (loaded_printer_technology == ptFFF) { + update_selected_preset_visibility(preset_bundle->prints, names); + for (const std::string& filament : preset_bundle->filament_presets) { + Preset* preset = preset_bundle->filaments.find_preset(filament); + if (preset && !preset->is_visible) { + preset->is_visible = true; + names.emplace_back(preset->name); + if (preset->name == preset_bundle->filaments.get_edited_preset().name) + preset_bundle->filaments.get_selected_preset().is_visible = true; + } + } + } + else { + update_selected_preset_visibility(preset_bundle->sla_prints, names); + update_selected_preset_visibility(preset_bundle->sla_materials, names); + } + update_selected_preset_visibility(preset_bundle->printers, names); + + preset_bundle->update_compatible(PresetSelectCompatibleType::Never); + + // show notification about temporarily installed presets + if (!names.empty()) { + std::string notif_text = into_u8(_L_PLURAL("The preset below was temporarily installed on the active instance of PrusaSlicer", + "The presets below were temporarily installed on the active instance of PrusaSlicer", names.size())) + ":"; + for (std::string& name : names) + notif_text += "\n - " + name; + get_notification_manager()->push_notification(NotificationType::CustomNotification, + NotificationManager::NotificationLevel::PrintInfoNotificationLevel, notif_text); + } +} + std::vector Plater::priv::load_files(const std::vector& input_files, bool load_model, bool load_config, bool imperial_units/* = false*/) { if (input_files.empty()) { return std::vector(); } @@ -2435,50 +2481,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ Preset::normalize(config); PresetBundle* preset_bundle = wxGetApp().preset_bundle; preset_bundle->load_config_model(filename.string(), std::move(config)); - { - // After loading of the presets from project, check if they are visible. - // Set them to visible if they are not. - - auto update_selected_preset_visibility = [](PresetCollection& presets, std::vector& names) { - if (!presets.get_selected_preset().is_visible) { - assert(presets.get_selected_preset().name == presets.get_edited_preset().name); - presets.get_selected_preset().is_visible = true; - presets.get_edited_preset().is_visible = true; - names.emplace_back(presets.get_selected_preset().name); - } - }; - - std::vector names; - if (loaded_printer_technology == ptFFF) { - update_selected_preset_visibility(preset_bundle->prints, names); - for (const std::string& filament : preset_bundle->filament_presets) { - Preset* preset = preset_bundle->filaments.find_preset(filament); - if (preset && !preset->is_visible) { - preset->is_visible = true; - names.emplace_back(preset->name); - if (preset->name == preset_bundle->filaments.get_edited_preset().name) - preset_bundle->filaments.get_selected_preset().is_visible = true; - } - } - } - else { - update_selected_preset_visibility(preset_bundle->sla_prints, names); - update_selected_preset_visibility(preset_bundle->sla_materials, names); - } - update_selected_preset_visibility(preset_bundle->printers, names); - - preset_bundle->update_compatible(PresetSelectCompatibleType::Never); - - // show notification about temporarily installed presets - if (!names.empty()) { - std::string notif_text = into_u8(_L_PLURAL("The preset below was temporarily installed on the active instance of PrusaSlicer", - "The presets below were temporarily installed on the active instance of PrusaSlicer", names.size())) + ":"; - for (std::string& name : names) - notif_text += "\n - " + name; - notification_manager->push_notification(NotificationType::CustomNotification, - NotificationManager::NotificationLevel::PrintInfoNotificationLevel, notif_text); - } - } + q->check_selected_presets_visibility(loaded_printer_technology); if (loaded_printer_technology == ptFFF) CustomGCode::update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z, &preset_bundle->project_config); diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 124725018e..ff27834ce8 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -175,6 +175,7 @@ public: std::vector load_files(const std::vector& input_files, bool load_model = true, bool load_config = true, bool imperial_units = false); // to be called on drag and drop bool load_files(const wxArrayString& filenames); + void check_selected_presets_visibility(PrinterTechnology loaded_printer_technology); const wxString& get_last_loaded_gcode() const { return m_last_loaded_gcode; }