From 3ce2d3a700ef215b37faef273f54be5619b9d642 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 13 Apr 2022 15:27:46 +0200 Subject: [PATCH] #8176 - Tech ENABLE_USED_FILAMENT_POST_PROCESS - Fixes used filament data exported to gcode file not taking in account custom gcode --- src/libslic3r/GCode/GCodeProcessor.cpp | 7882 ++++++++++---------- src/libslic3r/GCode/GCodeProcessor.hpp | 1611 ++-- src/libslic3r/Technologies.hpp | 166 +- src/slic3r/GUI/GCodeViewer.cpp | 9344 ++++++++++++------------ 4 files changed, 9712 insertions(+), 9291 deletions(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index a46103d6c..f4cf12064 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 1ef414c3f..08e70f2f5 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/Technologies.hpp b/src/libslic3r/Technologies.hpp index a9e38e016..80f2a5b81 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -1,82 +1,84 @@ -#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 using vertex attributes and matrices in shaders -#define ENABLE_GL_SHADERS_ATTRIBUTES (1 && ENABLE_LEGACY_OPENGL_REMOVAL) -// Enable rendering imgui using shaders -#define ENABLE_GL_IMGUI_SHADERS (1 && ENABLE_GL_SHADERS_ATTRIBUTES) -// 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 using vertex attributes and matrices in shaders +#define ENABLE_GL_SHADERS_ATTRIBUTES (1 && ENABLE_LEGACY_OPENGL_REMOVAL) +// Enable rendering imgui using shaders +#define ENABLE_GL_IMGUI_SHADERS (1 && ENABLE_GL_SHADERS_ATTRIBUTES) +// 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) + + +#endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 6aa4bb718..56ea90163 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -1,4672 +1,4672 @@ -#include "libslic3r/libslic3r.h" -#include "GCodeViewer.hpp" - -#include "libslic3r/BuildVolume.hpp" -#include "libslic3r/Print.hpp" -#include "libslic3r/Geometry.hpp" -#include "libslic3r/Model.hpp" -#include "libslic3r/Utils.hpp" -#include "libslic3r/LocalesUtils.hpp" -#include "libslic3r/PresetBundle.hpp" - -#include "GUI_App.hpp" -#include "MainFrame.hpp" -#include "Plater.hpp" -#include "Camera.hpp" -#include "I18N.hpp" -#include "GUI_Utils.hpp" -#include "GUI.hpp" -#include "DoubleSlider.hpp" -#include "GLCanvas3D.hpp" -#include "GLToolbar.hpp" -#include "GUI_Preview.hpp" -#include "GUI_ObjectManipulation.hpp" - -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -namespace Slic3r { -namespace GUI { - -static unsigned char buffer_id(EMoveType type) { - return static_cast(type) - static_cast(EMoveType::Retract); -} - -static EMoveType buffer_type(unsigned char id) { - return static_cast(static_cast(EMoveType::Retract) + id); -} - -// Round to a bin with minimum two digits resolution. -// Equivalent to conversion to string with sprintf(buf, "%.2g", value) and conversion back to float, but faster. -static float round_to_bin(const float value) -{ -// assert(value > 0); - constexpr float const scale [5] = { 100.f, 1000.f, 10000.f, 100000.f, 1000000.f }; - constexpr float const invscale [5] = { 0.01f, 0.001f, 0.0001f, 0.00001f, 0.000001f }; - constexpr float const threshold[5] = { 0.095f, 0.0095f, 0.00095f, 0.000095f, 0.0000095f }; - // Scaling factor, pointer to the tables above. - int i = 0; - // While the scaling factor is not yet large enough to get two integer digits after scaling and rounding: - for (; value < threshold[i] && i < 4; ++ i) ; - return std::round(value * scale[i]) * invscale[i]; -} - -void GCodeViewer::VBuffer::reset() -{ - // release gpu memory - if (!vbos.empty()) { - glsafe(::glDeleteBuffers(static_cast(vbos.size()), static_cast(vbos.data()))); - vbos.clear(); - } - sizes.clear(); - count = 0; -} - -void GCodeViewer::InstanceVBuffer::Ranges::reset() -{ - for (Range& range : ranges) { - // release gpu memory - if (range.vbo > 0) - glsafe(::glDeleteBuffers(1, &range.vbo)); - } - - ranges.clear(); -} - -void GCodeViewer::InstanceVBuffer::reset() -{ - s_ids.clear(); - buffer.clear(); - render_ranges.reset(); -} - -void GCodeViewer::IBuffer::reset() -{ - // release gpu memory - if (ibo > 0) { - glsafe(::glDeleteBuffers(1, &ibo)); - ibo = 0; - } - - vbo = 0; - count = 0; -} - -#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC -bool GCodeViewer::Path::matches(const GCodeProcessorResult::MoveVertex& move, bool account_for_volumetric_rate) const -#else -bool GCodeViewer::Path::matches(const GCodeProcessorResult::MoveVertex& move) const -#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC -{ - auto matches_percent = [](float value1, float value2, float max_percent) { - return std::abs(value2 - value1) / value1 <= max_percent; - }; - - switch (move.type) - { - case EMoveType::Tool_change: - case EMoveType::Color_change: - case EMoveType::Pause_Print: - case EMoveType::Custom_GCode: - case EMoveType::Retract: - case EMoveType::Unretract: - case EMoveType::Seam: - case EMoveType::Extrude: { - // use rounding to reduce the number of generated paths -#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC - if (account_for_volumetric_rate) - return type == move.type && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id && role == move.extrusion_role && - move.position.z() <= sub_paths.front().first.position.z() && feedrate == move.feedrate && fan_speed == move.fan_speed && - height == round_to_bin(move.height) && width == round_to_bin(move.width) && - matches_percent(volumetric_rate, move.volumetric_rate(), 0.001f); - else - return type == move.type && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id && role == move.extrusion_role && - move.position.z() <= sub_paths.front().first.position.z() && feedrate == move.feedrate && fan_speed == move.fan_speed && - height == round_to_bin(move.height) && width == round_to_bin(move.width); -#else - return type == move.type && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id && role == move.extrusion_role && - move.position.z() <= sub_paths.front().first.position.z() && feedrate == move.feedrate && fan_speed == move.fan_speed && - height == round_to_bin(move.height) && width == round_to_bin(move.width) && - matches_percent(volumetric_rate, move.volumetric_rate(), 0.05f); -#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC - } - case EMoveType::Travel: { - return type == move.type && feedrate == move.feedrate && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id; - } - default: { return false; } - } -} - -void GCodeViewer::TBuffer::Model::reset() -{ - instances.reset(); -} - -void GCodeViewer::TBuffer::reset() -{ - vertices.reset(); - for (IBuffer& buffer : indices) { - buffer.reset(); - } - - indices.clear(); - paths.clear(); - render_paths.clear(); - model.reset(); -} - -void GCodeViewer::TBuffer::add_path(const GCodeProcessorResult::MoveVertex& move, unsigned int b_id, size_t i_id, size_t s_id) -{ - Path::Endpoint endpoint = { b_id, i_id, s_id, move.position }; - // use rounding to reduce the number of generated paths - paths.push_back({ move.type, move.extrusion_role, move.delta_extruder, - round_to_bin(move.height), round_to_bin(move.width), - move.feedrate, move.fan_speed, move.temperature, - move.volumetric_rate(), move.extruder_id, move.cp_color_id, { { endpoint, endpoint } } }); -} - -#if ENABLE_SHOW_TOOLPATHS_COG -void GCodeViewer::COG::render() -{ - if (!m_visible) - return; - - init(); - - GLShaderProgram* shader = wxGetApp().get_shader("toolpaths_cog"); - if (shader == nullptr) - return; - - shader->start_using(); - - glsafe(::glDisable(GL_DEPTH_TEST)); - -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Camera& camera = wxGetApp().plater()->get_camera(); - Transform3d matrix = camera.get_view_matrix() * Geometry::assemble_transform(cog()); - if (m_fixed_size) { - const double inv_zoom = wxGetApp().plater()->get_camera().get_inv_zoom(); - matrix = matrix * Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), inv_zoom * Vec3d::Ones()); - } - shader->set_uniform("view_model_matrix", matrix); - shader->set_uniform("projection_matrix", camera.get_projection_matrix()); - shader->set_uniform("normal_matrix", (Matrix3d)matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); - m_model.render(); -#else - glsafe(::glPushMatrix()); - const Vec3d position = cog(); - glsafe(::glTranslated(position.x(), position.y(), position.z())); - if (m_fixed_size) { - const double inv_zoom = wxGetApp().plater()->get_camera().get_inv_zoom(); - glsafe(::glScaled(inv_zoom, inv_zoom, inv_zoom)); - } - m_model.render(); - glsafe(::glPopMatrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - shader->stop_using(); - - ////Show ImGui window - //static float last_window_width = 0.0f; - //static size_t last_text_length = 0; - - //ImGuiWrapper& imgui = *wxGetApp().imgui(); - //const Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size(); - //imgui.set_next_window_pos(0.5f * static_cast(cnv_size.get_width()), 0.0f, ImGuiCond_Always, 0.5f, 0.0f); - //ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); - //ImGui::SetNextWindowBgAlpha(0.25f); - //imgui.begin(std::string("COG"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); - //imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _u8L("Center of mass") + ":"); - //ImGui::SameLine(); - //char buf[1024]; - //const Vec3d position = cog(); - //sprintf(buf, "X: %.3f, Y: %.3f, Z: %.3f", position.x(), position.y(), position.z()); - //imgui.text(std::string(buf)); - - //// force extra frame to automatically update window size - //const float width = ImGui::GetWindowWidth(); - //const size_t length = strlen(buf); - //if (width != last_window_width || length != last_text_length) { - // last_window_width = width; - // last_text_length = length; - // imgui.set_requires_extra_frame(); - //} - - //imgui.end(); - //ImGui::PopStyleVar(); -} -#endif // ENABLE_SHOW_TOOLPATHS_COG - -#if ENABLE_PREVIEW_LAYER_TIME -float GCodeViewer::Extrusions::Range::step_size(EType type) const -{ - switch (type) - { - default: - case EType::Linear: { return (max > min) ? (max - min) / (static_cast(Range_Colors.size()) - 1.0f) : 0.0f; } - case EType::Logarithmic: { return (max > min && min > 0.0f) ? ::log(max / min) / (static_cast(Range_Colors.size()) - 1.0f) : 0.0f; } - } -} - -ColorRGBA GCodeViewer::Extrusions::Range::get_color_at(float value, EType type) const -#else -ColorRGBA GCodeViewer::Extrusions::Range::get_color_at(float value) const -#endif // ENABLE_PREVIEW_LAYER_TIME -{ - // Input value scaled to the colors range -#if ENABLE_PREVIEW_LAYER_TIME - float global_t = 0.0f; - const float step = step_size(type); - if (step > 0.0f) { - switch (type) - { - default: - case EType::Linear: { global_t = (value > min) ? (value - min) / step : 0.0f; break; } - case EType::Logarithmic: { global_t = (value > min && min > 0.0f) ? ::log(value / min) / step : 0.0f; break; } - } - } -#else - const float step = step_size(); - const float global_t = (step != 0.0f) ? std::max(0.0f, value - min) / step : 0.0f; // lower limit of 0.0f -#endif // ENABLE_PREVIEW_LAYER_TIME - - const size_t color_max_idx = Range_Colors.size() - 1; - - // Compute the two colors just below (low) and above (high) the input value - const size_t color_low_idx = std::clamp(static_cast(global_t), 0, color_max_idx); - const size_t color_high_idx = std::clamp(color_low_idx + 1, 0, color_max_idx); - - // Interpolate between the low and high colors to find exactly which color the input value should get - return lerp(Range_Colors[color_low_idx], Range_Colors[color_high_idx], global_t - static_cast(color_low_idx)); -} - -GCodeViewer::SequentialRangeCap::~SequentialRangeCap() { - if (ibo > 0) - glsafe(::glDeleteBuffers(1, &ibo)); -} - -void GCodeViewer::SequentialRangeCap::reset() { - if (ibo > 0) - glsafe(::glDeleteBuffers(1, &ibo)); - - buffer = nullptr; - ibo = 0; - vbo = 0; - color = { 0.0f, 0.0f, 0.0f, 1.0f }; -} - -void GCodeViewer::SequentialView::Marker::init() -{ - m_model.init_from(stilized_arrow(16, 2.0f, 4.0f, 1.0f, 8.0f)); -#if ENABLE_LEGACY_OPENGL_REMOVAL - m_model.set_color({ 1.0f, 1.0f, 1.0f, 0.5f }); -#else - m_model.set_color(-1, { 1.0f, 1.0f, 1.0f, 0.5f }); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -} - -void GCodeViewer::SequentialView::Marker::set_world_position(const Vec3f& position) -{ - m_world_position = position; - m_world_transform = (Geometry::assemble_transform((position + m_z_offset * Vec3f::UnitZ()).cast()) * Geometry::assemble_transform(m_model.get_bounding_box().size().z() * Vec3d::UnitZ(), { M_PI, 0.0, 0.0 })).cast(); -} - -void GCodeViewer::SequentialView::Marker::render() -{ - if (!m_visible) - return; - - GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); - if (shader == nullptr) - return; - - glsafe(::glEnable(GL_BLEND)); - glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - - shader->start_using(); - shader->set_uniform("emission_factor", 0.0f); -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Camera& camera = wxGetApp().plater()->get_camera(); - const Transform3d matrix = camera.get_view_matrix() * m_world_transform.cast(); - shader->set_uniform("view_model_matrix", matrix); - shader->set_uniform("projection_matrix", camera.get_projection_matrix()); - shader->set_uniform("normal_matrix", (Matrix3d)matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); -#else - glsafe(::glPushMatrix()); - glsafe(::glMultMatrixf(m_world_transform.data())); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - m_model.render(); - -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPopMatrix()); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - - shader->stop_using(); - - glsafe(::glDisable(GL_BLEND)); - - static float last_window_width = 0.0f; - static size_t last_text_length = 0; - - ImGuiWrapper& imgui = *wxGetApp().imgui(); - const Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size(); - imgui.set_next_window_pos(0.5f * static_cast(cnv_size.get_width()), static_cast(cnv_size.get_height()), ImGuiCond_Always, 0.5f, 1.0f); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); - ImGui::SetNextWindowBgAlpha(0.25f); - imgui.begin(std::string("ToolPosition"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _u8L("Tool position") + ":"); - ImGui::SameLine(); - char buf[1024]; - const Vec3f position = m_world_position + m_world_offset; - sprintf(buf, "X: %.3f, Y: %.3f, Z: %.3f", position.x(), position.y(), position.z()); - imgui.text(std::string(buf)); - - // force extra frame to automatically update window size - const float width = ImGui::GetWindowWidth(); - const size_t length = strlen(buf); - if (width != last_window_width || length != last_text_length) { - last_window_width = width; - last_text_length = length; - imgui.set_requires_extra_frame(); - } - - imgui.end(); - ImGui::PopStyleVar(); -} - -void GCodeViewer::SequentialView::GCodeWindow::load_gcode(const std::string& filename, std::vector &&lines_ends) -{ - assert(! m_file.is_open()); - if (m_file.is_open()) - return; - - m_filename = filename; - m_lines_ends = std::move(lines_ends); - - m_selected_line_id = 0; - m_last_lines_size = 0; - - try - { - m_file.open(boost::filesystem::path(m_filename)); - } - catch (...) - { - BOOST_LOG_TRIVIAL(error) << "Unable to map file " << m_filename << ". Cannot show G-code window."; - reset(); - } -} - -void GCodeViewer::SequentialView::GCodeWindow::render(float top, float bottom, uint64_t curr_line_id) const -{ - auto update_lines = [this](uint64_t start_id, uint64_t end_id) { - std::vector ret; - ret.reserve(end_id - start_id + 1); - for (uint64_t id = start_id; id <= end_id; ++id) { - // read line from file - const size_t start = id == 1 ? 0 : m_lines_ends[id - 2]; - const size_t len = m_lines_ends[id - 1] - start; - std::string gline(m_file.data() + start, len); - - std::string command; - std::string parameters; - std::string comment; - - // extract comment - std::vector tokens; - boost::split(tokens, gline, boost::is_any_of(";"), boost::token_compress_on); - command = tokens.front(); - if (tokens.size() > 1) - comment = ";" + tokens.back(); - - // extract gcode command and parameters - if (!command.empty()) { - boost::split(tokens, command, boost::is_any_of(" "), boost::token_compress_on); - command = tokens.front(); - if (tokens.size() > 1) { - for (size_t i = 1; i < tokens.size(); ++i) { - parameters += " " + tokens[i]; - } - } - } - ret.push_back({ command, parameters, comment }); - } - return ret; - }; - - static const ImVec4 LINE_NUMBER_COLOR = ImGuiWrapper::COL_ORANGE_LIGHT; - static const ImVec4 SELECTION_RECT_COLOR = ImGuiWrapper::COL_ORANGE_DARK; - static const ImVec4 COMMAND_COLOR = { 0.8f, 0.8f, 0.0f, 1.0f }; - static const ImVec4 PARAMETERS_COLOR = { 1.0f, 1.0f, 1.0f, 1.0f }; - static const ImVec4 COMMENT_COLOR = { 0.7f, 0.7f, 0.7f, 1.0f }; - static const ImVec4 ELLIPSIS_COLOR = { 0.0f, 0.7f, 0.0f, 1.0f }; - - if (!m_visible || m_filename.empty() || m_lines_ends.empty() || curr_line_id == 0) - return; - - // window height - const float wnd_height = bottom - top; - - // number of visible lines - const float text_height = ImGui::CalcTextSize("0").y; - const ImGuiStyle& style = ImGui::GetStyle(); - const uint64_t lines_count = static_cast((wnd_height - 2.0f * style.WindowPadding.y + style.ItemSpacing.y) / (text_height + style.ItemSpacing.y)); - - if (lines_count == 0) - return; - - // visible range - const uint64_t half_lines_count = lines_count / 2; - uint64_t start_id = (curr_line_id >= half_lines_count) ? curr_line_id - half_lines_count : 0; - uint64_t end_id = start_id + lines_count - 1; - if (end_id >= static_cast(m_lines_ends.size())) { - end_id = static_cast(m_lines_ends.size()) - 1; - start_id = end_id - lines_count + 1; - } - - // updates list of lines to show, if needed - if (m_selected_line_id != curr_line_id || m_last_lines_size != end_id - start_id + 1) { - try - { - *const_cast*>(&m_lines) = update_lines(start_id, end_id); - } - catch (...) - { - BOOST_LOG_TRIVIAL(error) << "Error while loading from file " << m_filename << ". Cannot show G-code window."; - return; - } - *const_cast(&m_selected_line_id) = curr_line_id; - *const_cast(&m_last_lines_size) = m_lines.size(); - } - - // line number's column width - const float id_width = ImGui::CalcTextSize(std::to_string(end_id).c_str()).x; - - ImGuiWrapper& imgui = *wxGetApp().imgui(); - - auto add_item_to_line = [&imgui](const std::string& txt, const ImVec4& color, float spacing, size_t& current_length) { - static const size_t LENGTH_THRESHOLD = 60; - - if (txt.empty()) - return false; - - std::string out_text = txt; - bool reduced = false; - if (current_length + out_text.length() > LENGTH_THRESHOLD) { - out_text = out_text.substr(0, LENGTH_THRESHOLD - current_length); - reduced = true; - } - - current_length += out_text.length(); - - ImGui::SameLine(0.0f, spacing); - ImGui::PushStyleColor(ImGuiCol_Text, color); - imgui.text(out_text); - ImGui::PopStyleColor(); - if (reduced) { - ImGui::SameLine(0.0f, 0.0f); - ImGui::PushStyleColor(ImGuiCol_Text, ELLIPSIS_COLOR); - imgui.text("..."); - ImGui::PopStyleColor(); - } - - return reduced; - }; - - imgui.set_next_window_pos(0.0f, top, ImGuiCond_Always, 0.0f, 0.0f); - imgui.set_next_window_size(0.0f, wnd_height, ImGuiCond_Always); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); - ImGui::SetNextWindowBgAlpha(0.6f); - imgui.begin(std::string("G-code"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); - - // center the text in the window by pushing down the first line - const float f_lines_count = static_cast(lines_count); - ImGui::SetCursorPosY(0.5f * (wnd_height - f_lines_count * text_height - (f_lines_count - 1.0f) * style.ItemSpacing.y)); - - // render text lines - for (uint64_t id = start_id; id <= end_id; ++id) { - const Line& line = m_lines[id - start_id]; - - // rect around the current selected line - if (id == curr_line_id) { - const float pos_y = ImGui::GetCursorScreenPos().y; - const float half_ItemSpacing_y = 0.5f * style.ItemSpacing.y; - const float half_padding_x = 0.5f * style.WindowPadding.x; - ImGui::GetWindowDrawList()->AddRect({ half_padding_x, pos_y - half_ItemSpacing_y }, - { ImGui::GetCurrentWindow()->Size.x - half_padding_x, pos_y + text_height + half_ItemSpacing_y }, - ImGui::GetColorU32(SELECTION_RECT_COLOR)); - } - - const std::string id_str = std::to_string(id); - // spacer to right align text - ImGui::Dummy({ id_width - ImGui::CalcTextSize(id_str.c_str()).x, text_height }); - - size_t line_length = 0; - // render line number - bool stop_adding = add_item_to_line(id_str, LINE_NUMBER_COLOR, 0.0f, line_length); - if (!stop_adding && !line.command.empty()) - // render command - stop_adding = add_item_to_line(line.command, COMMAND_COLOR, -1.0f, line_length); - if (!stop_adding && !line.parameters.empty()) - // render parameters - stop_adding = add_item_to_line(line.parameters, PARAMETERS_COLOR, 0.0f, line_length); - if (!stop_adding && !line.comment.empty()) - // render comment - stop_adding = add_item_to_line(line.comment, COMMENT_COLOR, line.command.empty() ? -1.0f : 0.0f, line_length); - } - - imgui.end(); - ImGui::PopStyleVar(); -} - -void GCodeViewer::SequentialView::GCodeWindow::stop_mapping_file() -{ - if (m_file.is_open()) - m_file.close(); -} - -void GCodeViewer::SequentialView::render(float legend_height) -{ - marker.render(); - float bottom = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size().get_height(); - if (wxGetApp().is_editor()) - bottom -= wxGetApp().plater()->get_view_toolbar().get_height(); - gcode_window.render(legend_height, bottom, static_cast(gcode_ids[current.last])); -} - -const std::vector GCodeViewer::Extrusion_Role_Colors{ { - { 0.90f, 0.70f, 0.70f, 1.0f }, // erNone - { 1.00f, 0.90f, 0.30f, 1.0f }, // erPerimeter - { 1.00f, 0.49f, 0.22f, 1.0f }, // erExternalPerimeter - { 0.12f, 0.12f, 1.00f, 1.0f }, // erOverhangPerimeter - { 0.69f, 0.19f, 0.16f, 1.0f }, // erInternalInfill - { 0.59f, 0.33f, 0.80f, 1.0f }, // erSolidInfill - { 0.94f, 0.25f, 0.25f, 1.0f }, // erTopSolidInfill - { 1.00f, 0.55f, 0.41f, 1.0f }, // erIroning - { 0.30f, 0.50f, 0.73f, 1.0f }, // erBridgeInfill - { 1.00f, 1.00f, 1.00f, 1.0f }, // erGapFill - { 0.00f, 0.53f, 0.43f, 1.0f }, // erSkirt - { 0.00f, 1.00f, 0.00f, 1.0f }, // erSupportMaterial - { 0.00f, 0.50f, 0.00f, 1.0f }, // erSupportMaterialInterface - { 0.70f, 0.89f, 0.67f, 1.0f }, // erWipeTower - { 0.37f, 0.82f, 0.58f, 1.0f }, // erCustom - { 0.00f, 0.00f, 0.00f, 1.0f } // erMixed -}}; - -const std::vector GCodeViewer::Options_Colors{ { - { 0.803f, 0.135f, 0.839f, 1.0f }, // Retractions - { 0.287f, 0.679f, 0.810f, 1.0f }, // Unretractions - { 0.900f, 0.900f, 0.900f, 1.0f }, // Seams - { 0.758f, 0.744f, 0.389f, 1.0f }, // ToolChanges - { 0.856f, 0.582f, 0.546f, 1.0f }, // ColorChanges - { 0.322f, 0.942f, 0.512f, 1.0f }, // PausePrints - { 0.886f, 0.825f, 0.262f, 1.0f } // CustomGCodes -}}; - -const std::vector GCodeViewer::Travel_Colors{ { - { 0.219f, 0.282f, 0.609f, 1.0f }, // Move - { 0.112f, 0.422f, 0.103f, 1.0f }, // Extrude - { 0.505f, 0.064f, 0.028f, 1.0f } // Retract -}}; - -#if 1 -// Normal ranges -const std::vector GCodeViewer::Range_Colors{ { - { 0.043f, 0.173f, 0.478f, 1.0f }, // bluish - { 0.075f, 0.349f, 0.522f, 1.0f }, - { 0.110f, 0.533f, 0.569f, 1.0f }, - { 0.016f, 0.839f, 0.059f, 1.0f }, - { 0.667f, 0.949f, 0.000f, 1.0f }, - { 0.988f, 0.975f, 0.012f, 1.0f }, - { 0.961f, 0.808f, 0.039f, 1.0f }, - { 0.890f, 0.533f, 0.125f, 1.0f }, - { 0.820f, 0.408f, 0.188f, 1.0f }, - { 0.761f, 0.322f, 0.235f, 1.0f }, - { 0.581f, 0.149f, 0.087f, 1.0f } // reddish -}}; -#else -// Detailed ranges -const std::vector GCodeViewer::Range_Colors{ { - { 0.043f, 0.173f, 0.478f, 1.0f }, // bluish - { 0.5f * (0.043f + 0.075f), 0.5f * (0.173f + 0.349f), 0.5f * (0.478f + 0.522f), 1.0f }, - { 0.075f, 0.349f, 0.522f, 1.0f }, - { 0.5f * (0.075f + 0.110f), 0.5f * (0.349f + 0.533f), 0.5f * (0.522f + 0.569f), 1.0f }, - { 0.110f, 0.533f, 0.569f, 1.0f }, - { 0.5f * (0.110f + 0.016f), 0.5f * (0.533f + 0.839f), 0.5f * (0.569f + 0.059f), 1.0f }, - { 0.016f, 0.839f, 0.059f, 1.0f }, - { 0.5f * (0.016f + 0.667f), 0.5f * (0.839f + 0.949f), 0.5f * (0.059f + 0.000f), 1.0f }, - { 0.667f, 0.949f, 0.000f, 1.0f }, - { 0.5f * (0.667f + 0.988f), 0.5f * (0.949f + 0.975f), 0.5f * (0.000f + 0.012f), 1.0f }, - { 0.988f, 0.975f, 0.012f, 1.0f }, - { 0.5f * (0.988f + 0.961f), 0.5f * (0.975f + 0.808f), 0.5f * (0.012f + 0.039f), 1.0f }, - { 0.961f, 0.808f, 0.039f, 1.0f }, - { 0.5f * (0.961f + 0.890f), 0.5f * (0.808f + 0.533f), 0.5f * (0.039f + 0.125f), 1.0f }, - { 0.890f, 0.533f, 0.125f, 1.0f }, - { 0.5f * (0.890f + 0.820f), 0.5f * (0.533f + 0.408f), 0.5f * (0.125f + 0.188f), 1.0f }, - { 0.820f, 0.408f, 0.188f, 1.0f }, - { 0.5f * (0.820f + 0.761f), 0.5f * (0.408f + 0.322f), 0.5f * (0.188f + 0.235f), 1.0f }, - { 0.761f, 0.322f, 0.235f, 1.0f }, - { 0.5f * (0.761f + 0.581f), 0.5f * (0.322f + 0.149f), 0.5f * (0.235f + 0.087f), 1.0f }, - { 0.581f, 0.149f, 0.087f, 1.0f } // reddishgit -} }; -#endif - -const ColorRGBA GCodeViewer::Wipe_Color = ColorRGBA::YELLOW(); -const ColorRGBA GCodeViewer::Neutral_Color = ColorRGBA::DARK_GRAY(); - -GCodeViewer::GCodeViewer() -{ - m_extrusions.reset_role_visibility_flags(); - -// m_sequential_view.skip_invisible_moves = true; -} - -void GCodeViewer::init() -{ - if (m_gl_data_initialized) - return; - - // initializes opengl data of TBuffers - for (size_t i = 0; i < m_buffers.size(); ++i) { - TBuffer& buffer = m_buffers[i]; - EMoveType type = buffer_type(i); - switch (type) - { - default: { break; } - case EMoveType::Tool_change: - case EMoveType::Color_change: - case EMoveType::Pause_Print: - case EMoveType::Custom_GCode: - case EMoveType::Retract: - case EMoveType::Unretract: - case EMoveType::Seam: { -#if !DISABLE_GCODEVIEWER_INSTANCED_MODELS - if (wxGetApp().is_gl_version_greater_or_equal_to(3, 3)) { - buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::InstancedModel; - buffer.shader = "gouraud_light_instanced"; - buffer.model.model.init_from(diamond(16)); - buffer.model.color = option_color(type); - buffer.model.instances.format = InstanceVBuffer::EFormat::InstancedModel; - } - else { -#endif // !DISABLE_GCODEVIEWER_INSTANCED_MODELS - buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::BatchedModel; - buffer.vertices.format = VBuffer::EFormat::PositionNormal3; - buffer.shader = "gouraud_light"; - buffer.model.data = diamond(16); - buffer.model.color = option_color(type); - buffer.model.instances.format = InstanceVBuffer::EFormat::BatchedModel; -#if !DISABLE_GCODEVIEWER_INSTANCED_MODELS - } -#endif // !DISABLE_GCODEVIEWER_INSTANCED_MODELS - break; - } - case EMoveType::Wipe: - case EMoveType::Extrude: { - buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Triangle; - buffer.vertices.format = VBuffer::EFormat::PositionNormal3; - buffer.shader = "gouraud_light"; - break; - } - case EMoveType::Travel: { - buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Line; -#if ENABLE_GL_SHADERS_ATTRIBUTES - buffer.vertices.format = VBuffer::EFormat::Position; - buffer.shader = "flat"; -#else - buffer.vertices.format = VBuffer::EFormat::PositionNormal3; - buffer.shader = "toolpaths_lines"; -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - break; - } - } - - set_toolpath_move_type_visible(EMoveType::Extrude, true); - } - - // initializes tool marker - m_sequential_view.marker.init(); - - // initializes point sizes - std::array point_sizes; - ::glGetIntegerv(GL_ALIASED_POINT_SIZE_RANGE, point_sizes.data()); - m_detected_point_sizes = { static_cast(point_sizes[0]), static_cast(point_sizes[1]) }; - - m_gl_data_initialized = true; -} - -#if ENABLE_LEGACY_OPENGL_REMOVAL -void GCodeViewer::load(const GCodeProcessorResult& gcode_result, const Print& print) -#else -void GCodeViewer::load(const GCodeProcessorResult& gcode_result, const Print& print, bool initialized) -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -{ - // avoid processing if called with the same gcode_result -#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC - if (m_last_result_id == gcode_result.id && - (m_last_view_type == m_view_type || (m_last_view_type != EViewType::VolumetricRate && m_view_type != EViewType::VolumetricRate))) - return; -#else - if (m_last_result_id == gcode_result.id) - return; -#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC - - m_last_result_id = gcode_result.id; -#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC - m_last_view_type = m_view_type; -#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC - - // release gpu memory, if used - reset(); - - m_sequential_view.gcode_window.load_gcode(gcode_result.filename, - // Stealing out lines_ends should be safe because this gcode_result is processed only once (see the 1st if in this function). - std::move(const_cast&>(gcode_result.lines_ends))); - - if (wxGetApp().is_gcode_viewer()) - m_custom_gcode_per_print_z = gcode_result.custom_gcode_per_print_z; - - m_max_print_height = gcode_result.max_print_height; - - load_toolpaths(gcode_result); - - if (m_layers.empty()) - return; - - m_settings_ids = gcode_result.settings_ids; - m_filament_diameters = gcode_result.filament_diameters; - m_filament_densities = gcode_result.filament_densities; - - if (wxGetApp().is_editor()) -#if ENABLE_LEGACY_OPENGL_REMOVAL - load_shells(print); -#else - load_shells(print, initialized); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - else { - Pointfs bed_shape; - std::string texture; - std::string model; - - if (!gcode_result.bed_shape.empty()) { - // bed shape detected in the gcode - bed_shape = gcode_result.bed_shape; - const auto bundle = wxGetApp().preset_bundle; - if (bundle != nullptr && !m_settings_ids.printer.empty()) { - const Preset* preset = bundle->printers.find_preset(m_settings_ids.printer); - if (preset != nullptr) { - model = PresetUtils::system_printer_bed_model(*preset); - texture = PresetUtils::system_printer_bed_texture(*preset); - } - } - } - else { - // adjust printbed size in dependence of toolpaths bbox - const double margin = 10.0; - const Vec2d min(m_paths_bounding_box.min.x() - margin, m_paths_bounding_box.min.y() - margin); - const Vec2d max(m_paths_bounding_box.max.x() + margin, m_paths_bounding_box.max.y() + margin); - - const Vec2d size = max - min; - bed_shape = { - { min.x(), min.y() }, - { max.x(), min.y() }, - { max.x(), min.y() + 0.442265 * size.y()}, - { max.x() - 10.0, min.y() + 0.4711325 * size.y()}, - { max.x() + 10.0, min.y() + 0.5288675 * size.y()}, - { max.x(), min.y() + 0.557735 * size.y()}, - { max.x(), max.y() }, - { min.x() + 0.557735 * size.x(), max.y()}, - { min.x() + 0.5288675 * size.x(), max.y() - 10.0}, - { min.x() + 0.4711325 * size.x(), max.y() + 10.0}, - { min.x() + 0.442265 * size.x(), max.y()}, - { min.x(), max.y() } }; - } - - wxGetApp().plater()->set_bed_shape(bed_shape, gcode_result.max_print_height, texture, model, gcode_result.bed_shape.empty()); - } - - m_print_statistics = gcode_result.print_statistics; - - if (m_time_estimate_mode != PrintEstimatedStatistics::ETimeMode::Normal) { - const float time = m_print_statistics.modes[static_cast(m_time_estimate_mode)].time; - if (time == 0.0f || - short_time(get_time_dhms(time)) == short_time(get_time_dhms(m_print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].time))) - m_time_estimate_mode = PrintEstimatedStatistics::ETimeMode::Normal; - } -} - -void GCodeViewer::refresh(const GCodeProcessorResult& gcode_result, const std::vector& str_tool_colors) -{ -#if ENABLE_GCODE_VIEWER_STATISTICS - auto start_time = std::chrono::high_resolution_clock::now(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - if (m_moves_count == 0) - return; - - wxBusyCursor busy; - - if (m_view_type == EViewType::Tool && !gcode_result.extruder_colors.empty()) - // update tool colors from config stored in the gcode - decode_colors(gcode_result.extruder_colors, m_tool_colors); - else - // update tool colors - decode_colors(str_tool_colors, m_tool_colors); - - ColorRGBA default_color; - decode_color("#FF8000", default_color); - - // ensure there are enough colors defined - while (m_tool_colors.size() < std::max(size_t(1), gcode_result.extruders_count)) - m_tool_colors.push_back(default_color); - - // update ranges for coloring / legend - m_extrusions.reset_ranges(); - for (size_t i = 0; i < m_moves_count; ++i) { - // skip first vertex - if (i == 0) - continue; - - const GCodeProcessorResult::MoveVertex& curr = gcode_result.moves[i]; - - switch (curr.type) - { - case EMoveType::Extrude: - { - m_extrusions.ranges.height.update_from(round_to_bin(curr.height)); - m_extrusions.ranges.width.update_from(round_to_bin(curr.width)); - m_extrusions.ranges.fan_speed.update_from(curr.fan_speed); - m_extrusions.ranges.temperature.update_from(curr.temperature); - if (curr.extrusion_role != erCustom || is_visible(erCustom)) - m_extrusions.ranges.volumetric_rate.update_from(round_to_bin(curr.volumetric_rate())); - [[fallthrough]]; - } - case EMoveType::Travel: - { - if (m_buffers[buffer_id(curr.type)].visible) - m_extrusions.ranges.feedrate.update_from(curr.feedrate); - - break; - } - default: { break; } - } - } - -#if ENABLE_PREVIEW_LAYER_TIME - for (size_t i = 0; i < gcode_result.print_statistics.modes.size(); ++i) { - m_layers_times[i] = gcode_result.print_statistics.modes[i].layers_times; - } - - for (size_t i = 0; i < m_layers_times.size(); ++i) { - for (float time : m_layers_times[i]) { - m_extrusions.ranges.layer_time[i].update_from(time); - } - } -#endif // ENABLE_PREVIEW_LAYER_TIME - -#if ENABLE_GCODE_VIEWER_STATISTICS - m_statistics.refresh_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - // update buffers' render paths -#if ENABLE_PREVIEW_LAYOUT - refresh_render_paths(false, false); -#else - refresh_render_paths(); -#endif // ENABLE_PREVIEW_LAYOUT - log_memory_used("Refreshed G-code extrusion paths, "); -} - -#if !ENABLE_PREVIEW_LAYOUT -void GCodeViewer::refresh_render_paths() -{ - refresh_render_paths(false, false); -} -#endif // !ENABLE_PREVIEW_LAYOUT - -void GCodeViewer::update_shells_color_by_extruder(const DynamicPrintConfig* config) -{ - if (config != nullptr) - m_shells.volumes.update_colors_by_extruder(config); -} - -void GCodeViewer::reset() -{ - m_moves_count = 0; - for (TBuffer& buffer : m_buffers) { - buffer.reset(); - } - - m_paths_bounding_box = BoundingBoxf3(); - m_max_bounding_box = BoundingBoxf3(); - m_max_print_height = 0.0f; - m_tool_colors = std::vector(); - m_extruders_count = 0; - m_extruder_ids = std::vector(); - m_filament_diameters = std::vector(); - m_filament_densities = std::vector(); - m_extrusions.reset_ranges(); - m_shells.volumes.clear(); - m_layers.reset(); - m_layers_z_range = { 0, 0 }; - m_roles = std::vector(); - m_print_statistics.reset(); -#if ENABLE_PREVIEW_LAYER_TIME - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - m_layers_times[i] = std::vector(); - } -#endif // ENABLE_PREVIEW_LAYER_TIME - m_custom_gcode_per_print_z = std::vector(); - m_sequential_view.gcode_window.reset(); -#if ENABLE_GCODE_VIEWER_STATISTICS - m_statistics.reset_all(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - m_contained_in_bed = true; -#if ENABLE_PREVIEW_LAYOUT - m_legend_resizer.reset(); -#endif // ENABLE_PREVIEW_LAYOUT -} - -void GCodeViewer::render() -{ -#if ENABLE_GCODE_VIEWER_STATISTICS - m_statistics.reset_opengl(); - m_statistics.total_instances_gpu_size = 0; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - if (m_roles.empty()) - return; - - glsafe(::glEnable(GL_DEPTH_TEST)); - render_toolpaths(); - render_shells(); - float legend_height = 0.0f; - render_legend(legend_height); - if (m_sequential_view.current.last != m_sequential_view.endpoints.last) { - m_sequential_view.marker.set_world_position(m_sequential_view.current_position); - m_sequential_view.marker.set_world_offset(m_sequential_view.current_offset); - m_sequential_view.render(legend_height); - } -#if ENABLE_GCODE_VIEWER_STATISTICS - render_statistics(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS -} - -bool GCodeViewer::can_export_toolpaths() const -{ - return has_data() && m_buffers[buffer_id(EMoveType::Extrude)].render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle; -} - -void GCodeViewer::update_sequential_view_current(unsigned int first, unsigned int last) -{ - auto is_visible = [this](unsigned int id) { - for (const TBuffer& buffer : m_buffers) { - if (buffer.visible) { - for (const Path& path : buffer.paths) { - if (path.sub_paths.front().first.s_id <= id && id <= path.sub_paths.back().last.s_id) - return true; - } - } - } - return false; - }; - - const int first_diff = static_cast(first) - static_cast(m_sequential_view.last_current.first); - const int last_diff = static_cast(last) - static_cast(m_sequential_view.last_current.last); - - unsigned int new_first = first; - unsigned int new_last = last; - - if (m_sequential_view.skip_invisible_moves) { - while (!is_visible(new_first)) { - if (first_diff > 0) - ++new_first; - else - --new_first; - } - - while (!is_visible(new_last)) { - if (last_diff > 0) - ++new_last; - else - --new_last; - } - } - - m_sequential_view.current.first = new_first; - m_sequential_view.current.last = new_last; - m_sequential_view.last_current = m_sequential_view.current; - - refresh_render_paths(true, true); - - if (new_first != first || new_last != last) - wxGetApp().plater()->update_preview_moves_slider(); -} - -bool GCodeViewer::is_toolpath_move_type_visible(EMoveType type) const -{ - size_t id = static_cast(buffer_id(type)); - return (id < m_buffers.size()) ? m_buffers[id].visible : false; -} - -void GCodeViewer::set_toolpath_move_type_visible(EMoveType type, bool visible) -{ - size_t id = static_cast(buffer_id(type)); - if (id < m_buffers.size()) - m_buffers[id].visible = visible; -} - -unsigned int GCodeViewer::get_options_visibility_flags() const -{ - auto set_flag = [](unsigned int flags, unsigned int flag, bool active) { - return active ? (flags | (1 << flag)) : flags; - }; - - unsigned int flags = 0; - flags = set_flag(flags, static_cast(Preview::OptionType::Travel), is_toolpath_move_type_visible(EMoveType::Travel)); - flags = set_flag(flags, static_cast(Preview::OptionType::Wipe), is_toolpath_move_type_visible(EMoveType::Wipe)); - flags = set_flag(flags, static_cast(Preview::OptionType::Retractions), is_toolpath_move_type_visible(EMoveType::Retract)); - flags = set_flag(flags, static_cast(Preview::OptionType::Unretractions), is_toolpath_move_type_visible(EMoveType::Unretract)); - flags = set_flag(flags, static_cast(Preview::OptionType::Seams), is_toolpath_move_type_visible(EMoveType::Seam)); - flags = set_flag(flags, static_cast(Preview::OptionType::ToolChanges), is_toolpath_move_type_visible(EMoveType::Tool_change)); - flags = set_flag(flags, static_cast(Preview::OptionType::ColorChanges), is_toolpath_move_type_visible(EMoveType::Color_change)); - flags = set_flag(flags, static_cast(Preview::OptionType::PausePrints), is_toolpath_move_type_visible(EMoveType::Pause_Print)); - flags = set_flag(flags, static_cast(Preview::OptionType::CustomGCodes), is_toolpath_move_type_visible(EMoveType::Custom_GCode)); -#if ENABLE_SHOW_TOOLPATHS_COG - flags = set_flag(flags, static_cast(Preview::OptionType::CenterOfGravity), m_cog.is_visible()); -#endif // ENABLE_SHOW_TOOLPATHS_COG - flags = set_flag(flags, static_cast(Preview::OptionType::Shells), m_shells.visible); - flags = set_flag(flags, static_cast(Preview::OptionType::ToolMarker), m_sequential_view.marker.is_visible()); -#if !ENABLE_PREVIEW_LAYOUT - flags = set_flag(flags, static_cast(Preview::OptionType::Legend), is_legend_enabled()); -#endif // !ENABLE_PREVIEW_LAYOUT - return flags; -} - -void GCodeViewer::set_options_visibility_from_flags(unsigned int flags) -{ - auto is_flag_set = [flags](unsigned int flag) { - return (flags & (1 << flag)) != 0; - }; - - set_toolpath_move_type_visible(EMoveType::Travel, is_flag_set(static_cast(Preview::OptionType::Travel))); - set_toolpath_move_type_visible(EMoveType::Wipe, is_flag_set(static_cast(Preview::OptionType::Wipe))); - set_toolpath_move_type_visible(EMoveType::Retract, is_flag_set(static_cast(Preview::OptionType::Retractions))); - set_toolpath_move_type_visible(EMoveType::Unretract, is_flag_set(static_cast(Preview::OptionType::Unretractions))); - set_toolpath_move_type_visible(EMoveType::Seam, is_flag_set(static_cast(Preview::OptionType::Seams))); - set_toolpath_move_type_visible(EMoveType::Tool_change, is_flag_set(static_cast(Preview::OptionType::ToolChanges))); - set_toolpath_move_type_visible(EMoveType::Color_change, is_flag_set(static_cast(Preview::OptionType::ColorChanges))); - set_toolpath_move_type_visible(EMoveType::Pause_Print, is_flag_set(static_cast(Preview::OptionType::PausePrints))); - set_toolpath_move_type_visible(EMoveType::Custom_GCode, is_flag_set(static_cast(Preview::OptionType::CustomGCodes))); -#if ENABLE_SHOW_TOOLPATHS_COG - m_cog.set_visible(is_flag_set(static_cast(Preview::OptionType::CenterOfGravity))); -#endif // ENABLE_SHOW_TOOLPATHS_COG - m_shells.visible = is_flag_set(static_cast(Preview::OptionType::Shells)); - m_sequential_view.marker.set_visible(is_flag_set(static_cast(Preview::OptionType::ToolMarker))); -#if !ENABLE_PREVIEW_LAYOUT - enable_legend(is_flag_set(static_cast(Preview::OptionType::Legend))); -#endif // !ENABLE_PREVIEW_LAYOUT -} - -void GCodeViewer::set_layers_z_range(const std::array& layers_z_range) -{ - bool keep_sequential_current_first = layers_z_range[0] >= m_layers_z_range[0]; - bool keep_sequential_current_last = layers_z_range[1] <= m_layers_z_range[1]; - m_layers_z_range = layers_z_range; - refresh_render_paths(keep_sequential_current_first, keep_sequential_current_last); - wxGetApp().plater()->update_preview_moves_slider(); -} - -void GCodeViewer::export_toolpaths_to_obj(const char* filename) const -{ - if (filename == nullptr) - return; - - if (!has_data()) - return; - - wxBusyCursor busy; - - // the data needed is contained into the Extrude TBuffer - const TBuffer& t_buffer = m_buffers[buffer_id(EMoveType::Extrude)]; - if (!t_buffer.has_data()) - return; - - if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Triangle) - return; - - // collect color information to generate materials - std::vector colors; - for (const RenderPath& path : t_buffer.render_paths) { - colors.push_back(path.color); - } - sort_remove_duplicates(colors); - - // save materials file - boost::filesystem::path mat_filename(filename); - mat_filename.replace_extension("mtl"); - - CNumericLocalesSetter locales_setter; - - FILE* fp = boost::nowide::fopen(mat_filename.string().c_str(), "w"); - if (fp == nullptr) { - BOOST_LOG_TRIVIAL(error) << "GCodeViewer::export_toolpaths_to_obj: Couldn't open " << mat_filename.string().c_str() << " for writing"; - return; - } - - fprintf(fp, "# G-Code Toolpaths Materials\n"); - fprintf(fp, "# Generated by %s-%s based on Slic3r\n", SLIC3R_APP_NAME, SLIC3R_VERSION); - - unsigned int colors_count = 1; - for (const ColorRGBA& color : colors) { - fprintf(fp, "\nnewmtl material_%d\n", colors_count++); - fprintf(fp, "Ka 1 1 1\n"); - fprintf(fp, "Kd %g %g %g\n", color.r(), color.g(), color.b()); - fprintf(fp, "Ks 0 0 0\n"); - } - - fclose(fp); - - // save geometry file - fp = boost::nowide::fopen(filename, "w"); - if (fp == nullptr) { - BOOST_LOG_TRIVIAL(error) << "GCodeViewer::export_toolpaths_to_obj: Couldn't open " << filename << " for writing"; - return; - } - - fprintf(fp, "# G-Code Toolpaths\n"); - fprintf(fp, "# Generated by %s-%s based on Slic3r\n", SLIC3R_APP_NAME, SLIC3R_VERSION); - fprintf(fp, "\nmtllib ./%s\n", mat_filename.filename().string().c_str()); - - const size_t floats_per_vertex = t_buffer.vertices.vertex_size_floats(); - - std::vector out_vertices; - std::vector out_normals; - - struct VerticesOffset - { - unsigned int vbo; - size_t offset; - }; - std::vector vertices_offsets; - vertices_offsets.push_back({ t_buffer.vertices.vbos.front(), 0 }); - - // get vertices/normals data from vertex buffers on gpu - for (size_t i = 0; i < t_buffer.vertices.vbos.size(); ++i) { - const size_t floats_count = t_buffer.vertices.sizes[i] / sizeof(float); - VertexBuffer vertices(floats_count); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, t_buffer.vertices.vbos[i])); - glsafe(::glGetBufferSubData(GL_ARRAY_BUFFER, 0, static_cast(t_buffer.vertices.sizes[i]), static_cast(vertices.data()))); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - const size_t vertices_count = floats_count / floats_per_vertex; - for (size_t j = 0; j < vertices_count; ++j) { - const size_t base = j * floats_per_vertex; - out_vertices.push_back({ vertices[base + 0], vertices[base + 1], vertices[base + 2] }); - out_normals.push_back({ vertices[base + 3], vertices[base + 4], vertices[base + 5] }); - } - - if (i < t_buffer.vertices.vbos.size() - 1) - vertices_offsets.push_back({ t_buffer.vertices.vbos[i + 1], vertices_offsets.back().offset + vertices_count }); - } - - // save vertices to file - fprintf(fp, "\n# vertices\n"); - for (const Vec3f& v : out_vertices) { - fprintf(fp, "v %g %g %g\n", v.x(), v.y(), v.z()); - } - - // save normals to file - fprintf(fp, "\n# normals\n"); - for (const Vec3f& n : out_normals) { - fprintf(fp, "vn %g %g %g\n", n.x(), n.y(), n.z()); - } - - size_t i = 0; - for (const ColorRGBA& color : colors) { - // save material triangles to file - fprintf(fp, "\nusemtl material_%zu\n", i + 1); - fprintf(fp, "# triangles material %zu\n", i + 1); - - for (const RenderPath& render_path : t_buffer.render_paths) { - if (render_path.color != color) - continue; - - const IBuffer& ibuffer = t_buffer.indices[render_path.ibuffer_id]; - size_t vertices_offset = 0; - for (size_t j = 0; j < vertices_offsets.size(); ++j) { - const VerticesOffset& offset = vertices_offsets[j]; - if (offset.vbo == ibuffer.vbo) { - vertices_offset = offset.offset; - break; - } - } - - // get indices data from index buffer on gpu - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibuffer.ibo)); - for (size_t j = 0; j < render_path.sizes.size(); ++j) { - IndexBuffer indices(render_path.sizes[j]); - glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast(render_path.offsets[j]), - static_cast(render_path.sizes[j] * sizeof(IBufferType)), static_cast(indices.data()))); - - const size_t triangles_count = render_path.sizes[j] / 3; - for (size_t k = 0; k < triangles_count; ++k) { - const size_t base = k * 3; - const size_t v1 = 1 + static_cast(indices[base + 0]) + vertices_offset; - const size_t v2 = 1 + static_cast(indices[base + 1]) + vertices_offset; - const size_t v3 = 1 + static_cast(indices[base + 2]) + vertices_offset; - if (v1 != v2) - // do not export dummy triangles - fprintf(fp, "f %zu//%zu %zu//%zu %zu//%zu\n", v1, v1, v2, v2, v3, v3); - } - } - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - } - ++i; - } - - fclose(fp); -} - -void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) -{ - // max index buffer size, in bytes - static const size_t IBUFFER_THRESHOLD_BYTES = 64 * 1024 * 1024; - - auto log_memory_usage = [this](const std::string& label, const std::vector& vertices, const std::vector& indices) { - int64_t vertices_size = 0; - for (const MultiVertexBuffer& buffers : vertices) { - for (const VertexBuffer& buffer : buffers) { - vertices_size += SLIC3R_STDVEC_MEMSIZE(buffer, float); - } - } - int64_t indices_size = 0; - for (const MultiIndexBuffer& buffers : indices) { - for (const IndexBuffer& buffer : buffers) { - indices_size += SLIC3R_STDVEC_MEMSIZE(buffer, IBufferType); - } - } - log_memory_used(label, vertices_size + indices_size); - }; - - // format data into the buffers to be rendered as lines - auto add_vertices_as_line = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, VertexBuffer& vertices) { -#if !ENABLE_GL_SHADERS_ATTRIBUTES - // x component of the normal to the current segment (the normal is parallel to the XY plane) - const Vec3f dir = (curr.position - prev.position).normalized(); - Vec3f normal(dir.y(), -dir.x(), 0.0); - normal.normalize(); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - -#if ENABLE_GL_SHADERS_ATTRIBUTES - auto add_vertex = [&vertices](const GCodeProcessorResult::MoveVertex& vertex) { - // add position - vertices.push_back(vertex.position.x()); - vertices.push_back(vertex.position.y()); - vertices.push_back(vertex.position.z()); - }; -#else - auto add_vertex = [&vertices, &normal](const GCodeProcessorResult::MoveVertex& vertex) { - // add position - vertices.push_back(vertex.position.x()); - vertices.push_back(vertex.position.y()); - vertices.push_back(vertex.position.z()); - // add normal - vertices.push_back(normal.x()); - vertices.push_back(normal.y()); - vertices.push_back(normal.z()); - }; -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - // add previous vertex - add_vertex(prev); - // add current vertex - add_vertex(curr); - }; -#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC - auto add_indices_as_line = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, TBuffer& buffer, - unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id, bool account_for_volumetric_rate) { - if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr, account_for_volumetric_rate)) { -#else - auto add_indices_as_line = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, TBuffer& buffer, - unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id) { - if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr)) { -#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC - // add starting index - indices.push_back(static_cast(indices.size())); - buffer.add_path(curr, ibuffer_id, indices.size() - 1, move_id - 1); - buffer.paths.back().sub_paths.front().first.position = prev.position; - } - - Path& last_path = buffer.paths.back(); - if (last_path.sub_paths.front().first.i_id != last_path.sub_paths.back().last.i_id) { - // add previous index - indices.push_back(static_cast(indices.size())); - } - - // add current index - indices.push_back(static_cast(indices.size())); - last_path.sub_paths.back().last = { ibuffer_id, indices.size() - 1, move_id, curr.position }; - }; - - // format data into the buffers to be rendered as solid -#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC - auto add_vertices_as_solid = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, TBuffer& buffer, - unsigned int vbuffer_id, VertexBuffer& vertices, size_t move_id, bool account_for_volumetric_rate) { -#else - auto add_vertices_as_solid = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, TBuffer& buffer, - unsigned int vbuffer_id, VertexBuffer& vertices, size_t move_id) { -#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC - auto store_vertex = [](VertexBuffer& vertices, const Vec3f& position, const Vec3f& normal) { - // append position - vertices.push_back(position.x()); - vertices.push_back(position.y()); - vertices.push_back(position.z()); - // append normal - vertices.push_back(normal.x()); - vertices.push_back(normal.y()); - vertices.push_back(normal.z()); - }; - -#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC - if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr, account_for_volumetric_rate)) { -#else - if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr)) { -#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC - buffer.add_path(curr, vbuffer_id, vertices.size(), move_id - 1); - buffer.paths.back().sub_paths.back().first.position = prev.position; - } - - Path& last_path = buffer.paths.back(); - - const Vec3f dir = (curr.position - prev.position).normalized(); - const Vec3f right = Vec3f(dir.y(), -dir.x(), 0.0f).normalized(); - const Vec3f left = -right; - const Vec3f up = right.cross(dir); - const Vec3f down = -up; - const float half_width = 0.5f * last_path.width; - const float half_height = 0.5f * last_path.height; - const Vec3f prev_pos = prev.position - half_height * up; - const Vec3f curr_pos = curr.position - half_height * up; - const Vec3f d_up = half_height * up; - const Vec3f d_down = -half_height * up; - const Vec3f d_right = half_width * right; - const Vec3f d_left = -half_width * right; - - // vertices 1st endpoint - if (last_path.vertices_count() == 1 || vertices.empty()) { - // 1st segment or restart into a new vertex buffer - // =============================================== - store_vertex(vertices, prev_pos + d_up, up); - store_vertex(vertices, prev_pos + d_right, right); - store_vertex(vertices, prev_pos + d_down, down); - store_vertex(vertices, prev_pos + d_left, left); - } - else { - // any other segment - // ================= - store_vertex(vertices, prev_pos + d_right, right); - store_vertex(vertices, prev_pos + d_left, left); - } - - // vertices 2nd endpoint - store_vertex(vertices, curr_pos + d_up, up); - store_vertex(vertices, curr_pos + d_right, right); - store_vertex(vertices, curr_pos + d_down, down); - store_vertex(vertices, curr_pos + d_left, left); - - last_path.sub_paths.back().last = { vbuffer_id, vertices.size(), move_id, curr.position }; - }; -#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC - auto add_indices_as_solid = [&](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, - const GCodeProcessorResult::MoveVertex* next, TBuffer& buffer, size_t& vbuffer_size, unsigned int ibuffer_id, - IndexBuffer& indices, size_t move_id, bool account_for_volumetric_rate) { -#else - auto add_indices_as_solid = [&](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, - const GCodeProcessorResult::MoveVertex* next, TBuffer& buffer, size_t& vbuffer_size, unsigned int ibuffer_id, - IndexBuffer& indices, size_t move_id) { -#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC - static Vec3f prev_dir; - static Vec3f prev_up; - static float sq_prev_length; - auto store_triangle = [](IndexBuffer& indices, IBufferType i1, IBufferType i2, IBufferType i3) { - indices.push_back(i1); - indices.push_back(i2); - indices.push_back(i3); - }; - auto append_dummy_cap = [store_triangle](IndexBuffer& indices, IBufferType id) { - store_triangle(indices, id, id, id); - store_triangle(indices, id, id, id); - }; - auto convert_vertices_offset = [](size_t vbuffer_size, const std::array& v_offsets) { - std::array ret = { - static_cast(static_cast(vbuffer_size) + v_offsets[0]), - static_cast(static_cast(vbuffer_size) + v_offsets[1]), - static_cast(static_cast(vbuffer_size) + v_offsets[2]), - static_cast(static_cast(vbuffer_size) + v_offsets[3]), - static_cast(static_cast(vbuffer_size) + v_offsets[4]), - static_cast(static_cast(vbuffer_size) + v_offsets[5]), - static_cast(static_cast(vbuffer_size) + v_offsets[6]), - static_cast(static_cast(vbuffer_size) + v_offsets[7]) - }; - return ret; - }; - auto append_starting_cap_triangles = [&](IndexBuffer& indices, const std::array& v_offsets) { - store_triangle(indices, v_offsets[0], v_offsets[2], v_offsets[1]); - store_triangle(indices, v_offsets[0], v_offsets[3], v_offsets[2]); - }; - auto append_stem_triangles = [&](IndexBuffer& indices, const std::array& v_offsets) { - store_triangle(indices, v_offsets[0], v_offsets[1], v_offsets[4]); - store_triangle(indices, v_offsets[1], v_offsets[5], v_offsets[4]); - store_triangle(indices, v_offsets[1], v_offsets[2], v_offsets[5]); - store_triangle(indices, v_offsets[2], v_offsets[6], v_offsets[5]); - store_triangle(indices, v_offsets[2], v_offsets[3], v_offsets[6]); - store_triangle(indices, v_offsets[3], v_offsets[7], v_offsets[6]); - store_triangle(indices, v_offsets[3], v_offsets[0], v_offsets[7]); - store_triangle(indices, v_offsets[0], v_offsets[4], v_offsets[7]); - }; - auto append_ending_cap_triangles = [&](IndexBuffer& indices, const std::array& v_offsets) { - store_triangle(indices, v_offsets[4], v_offsets[6], v_offsets[7]); - store_triangle(indices, v_offsets[4], v_offsets[5], v_offsets[6]); - }; - -#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC - if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr, account_for_volumetric_rate)) { -#else - if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr)) { -#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC - buffer.add_path(curr, ibuffer_id, indices.size(), move_id - 1); - buffer.paths.back().sub_paths.back().first.position = prev.position; - } - - Path& last_path = buffer.paths.back(); - - const Vec3f dir = (curr.position - prev.position).normalized(); - const Vec3f right = Vec3f(dir.y(), -dir.x(), 0.0f).normalized(); - const Vec3f up = right.cross(dir); - const float sq_length = (curr.position - prev.position).squaredNorm(); - - const std::array first_seg_v_offsets = convert_vertices_offset(vbuffer_size, { 0, 1, 2, 3, 4, 5, 6, 7 }); - const std::array non_first_seg_v_offsets = convert_vertices_offset(vbuffer_size, { -4, 0, -2, 1, 2, 3, 4, 5 }); - const bool is_first_segment = (last_path.vertices_count() == 1); - if (is_first_segment || vbuffer_size == 0) { - // 1st segment or restart into a new vertex buffer - // =============================================== - if (is_first_segment) - // starting cap triangles - append_starting_cap_triangles(indices, first_seg_v_offsets); - // dummy triangles outer corner cap - append_dummy_cap(indices, vbuffer_size); - - // stem triangles - append_stem_triangles(indices, first_seg_v_offsets); - - vbuffer_size += 8; - } - else { - // any other segment - // ================= - float displacement = 0.0f; - const float cos_dir = prev_dir.dot(dir); - if (cos_dir > -0.9998477f) { - // if the angle between adjacent segments is smaller than 179 degrees - const Vec3f med_dir = (prev_dir + dir).normalized(); - const float half_width = 0.5f * last_path.width; - displacement = half_width * ::tan(::acos(std::clamp(dir.dot(med_dir), -1.0f, 1.0f))); - } - - const float sq_displacement = sqr(displacement); - const bool can_displace = displacement > 0.0f && sq_displacement < sq_prev_length && sq_displacement < sq_length; - - const bool is_right_turn = prev_up.dot(prev_dir.cross(dir)) <= 0.0f; - // whether the angle between adjacent segments is greater than 45 degrees - const bool is_sharp = cos_dir < 0.7071068f; - - bool right_displaced = false; - bool left_displaced = false; - - if (!is_sharp && can_displace) { - if (is_right_turn) - left_displaced = true; - else - right_displaced = true; - } - - // triangles outer corner cap - if (is_right_turn) { - if (left_displaced) - // dummy triangles - append_dummy_cap(indices, vbuffer_size); - else { - store_triangle(indices, vbuffer_size - 4, vbuffer_size + 1, vbuffer_size - 1); - store_triangle(indices, vbuffer_size + 1, vbuffer_size - 2, vbuffer_size - 1); - } - } - else { - if (right_displaced) - // dummy triangles - append_dummy_cap(indices, vbuffer_size); - else { - store_triangle(indices, vbuffer_size - 4, vbuffer_size - 3, vbuffer_size + 0); - store_triangle(indices, vbuffer_size - 3, vbuffer_size - 2, vbuffer_size + 0); - } - } - - // stem triangles - append_stem_triangles(indices, non_first_seg_v_offsets); - - vbuffer_size += 6; - } - -#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC - if (next != nullptr && (curr.type != next->type || !last_path.matches(*next, account_for_volumetric_rate))) -#else - if (next != nullptr && (curr.type != next->type || !last_path.matches(*next))) -#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC - // ending cap triangles - append_ending_cap_triangles(indices, is_first_segment ? first_seg_v_offsets : non_first_seg_v_offsets); - - last_path.sub_paths.back().last = { ibuffer_id, indices.size() - 1, move_id, curr.position }; - prev_dir = dir; - prev_up = up; - sq_prev_length = sq_length; - }; - - // format data into the buffers to be rendered as instanced model - auto add_model_instance = [](const GCodeProcessorResult::MoveVertex& curr, InstanceBuffer& instances, InstanceIdBuffer& instances_ids, size_t move_id) { - // append position - instances.push_back(curr.position.x()); - instances.push_back(curr.position.y()); - instances.push_back(curr.position.z()); - // append width - instances.push_back(curr.width); - // append height - instances.push_back(curr.height); - - // append id - instances_ids.push_back(move_id); - }; - - // format data into the buffers to be rendered as batched model - auto add_vertices_as_model_batch = [](const GCodeProcessorResult::MoveVertex& curr, const GLModel::Geometry& data, VertexBuffer& vertices, InstanceBuffer& instances, InstanceIdBuffer& instances_ids, size_t move_id) { - const double width = static_cast(1.5f * curr.width); - const double height = static_cast(1.5f * curr.height); - - const Transform3d trafo = Geometry::assemble_transform((curr.position - 0.5f * curr.height * Vec3f::UnitZ()).cast(), Vec3d::Zero(), { width, width, height }); - const Eigen::Matrix normal_matrix = trafo.matrix().template block<3, 3>(0, 0).inverse().transpose(); - -#if ENABLE_LEGACY_OPENGL_REMOVAL - // append vertices - const size_t vertices_count = data.vertices_count(); - for (size_t i = 0; i < vertices_count; ++i) { - // append position - const Vec3d position = trafo * data.extract_position_3(i).cast(); - vertices.push_back(float(position.x())); - vertices.push_back(float(position.y())); - vertices.push_back(float(position.z())); - - // append normal - const Vec3d normal = normal_matrix * data.extract_normal_3(i).cast(); - vertices.push_back(float(normal.x())); - vertices.push_back(float(normal.y())); - vertices.push_back(float(normal.z())); - } -#else - for (const auto& entity : data.entities) { - // append vertices - for (size_t i = 0; i < entity.positions.size(); ++i) { - // append position - const Vec3d position = trafo * entity.positions[i].cast(); - vertices.push_back(static_cast(position.x())); - vertices.push_back(static_cast(position.y())); - vertices.push_back(static_cast(position.z())); - - // append normal - const Vec3d normal = normal_matrix * entity.normals[i].cast(); - vertices.push_back(static_cast(normal.x())); - vertices.push_back(static_cast(normal.y())); - vertices.push_back(static_cast(normal.z())); - } - } -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - // append instance position - instances.push_back(curr.position.x()); - instances.push_back(curr.position.y()); - instances.push_back(curr.position.z()); - // append instance id - instances_ids.push_back(move_id); - }; - - auto add_indices_as_model_batch = [](const GLModel::Geometry& data, IndexBuffer& indices, IBufferType base_index) { -#if ENABLE_LEGACY_OPENGL_REMOVAL - const size_t indices_count = data.indices_count(); - for (size_t i = 0; i < indices_count; ++i) { - indices.push_back(static_cast(data.extract_index(i) + base_index)); - } -#else - for (const auto& entity : data.entities) { - for (size_t i = 0; i < entity.indices.size(); ++i) { - indices.push_back(static_cast(entity.indices[i] + base_index)); - } - } -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - }; - -#if ENABLE_GCODE_VIEWER_STATISTICS - auto start_time = std::chrono::high_resolution_clock::now(); - m_statistics.results_size = SLIC3R_STDVEC_MEMSIZE(gcode_result.moves, GCodeProcessorResult::MoveVertex); - m_statistics.results_time = gcode_result.time; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - m_moves_count = gcode_result.moves.size(); - if (m_moves_count == 0) - return; - - m_extruders_count = gcode_result.extruders_count; - - unsigned int progress_count = 0; - static const unsigned int progress_threshold = 1000; - wxProgressDialog* progress_dialog = wxGetApp().is_gcode_viewer() ? - new wxProgressDialog(_L("Generating toolpaths"), "...", - 100, wxGetApp().mainframe, wxPD_AUTO_HIDE | wxPD_APP_MODAL) : nullptr; - - wxBusyCursor busy; - - // extract approximate paths bounding box from result - for (const GCodeProcessorResult::MoveVertex& move : gcode_result.moves) { - if (wxGetApp().is_gcode_viewer()) - // for the gcode viewer we need to take in account all moves to correctly size the printbed - m_paths_bounding_box.merge(move.position.cast()); - else { - if (move.type == EMoveType::Extrude && move.extrusion_role != erCustom && move.width != 0.0f && move.height != 0.0f) - m_paths_bounding_box.merge(move.position.cast()); - } - } - - // set approximate max bounding box (take in account also the tool marker) - m_max_bounding_box = m_paths_bounding_box; - m_max_bounding_box.merge(m_paths_bounding_box.max + m_sequential_view.marker.get_bounding_box().size().z() * Vec3d::UnitZ()); - - if (wxGetApp().is_editor()) - m_contained_in_bed = wxGetApp().plater()->build_volume().all_paths_inside(gcode_result, m_paths_bounding_box); - -#if ENABLE_SHOW_TOOLPATHS_COG - m_cog.reset(); -#endif // ENABLE_SHOW_TOOLPATHS_COG - - m_sequential_view.gcode_ids.clear(); - for (size_t i = 0; i < gcode_result.moves.size(); ++i) { - const GCodeProcessorResult::MoveVertex& move = gcode_result.moves[i]; - if (move.type != EMoveType::Seam) - m_sequential_view.gcode_ids.push_back(move.gcode_id); - } - -#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC - bool account_for_volumetric_rate = m_view_type == EViewType::VolumetricRate; -#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC - - std::vector vertices(m_buffers.size()); - std::vector indices(m_buffers.size()); - std::vector instances(m_buffers.size()); - std::vector instances_ids(m_buffers.size()); - std::vector instances_offsets(m_buffers.size()); - std::vector options_zs; - - std::vector biased_seams_ids; - - // toolpaths data -> extract vertices from result - for (size_t i = 0; i < m_moves_count; ++i) { - const GCodeProcessorResult::MoveVertex& curr = gcode_result.moves[i]; - if (curr.type == EMoveType::Seam) - biased_seams_ids.push_back(i - biased_seams_ids.size() - 1); - - const size_t move_id = i - biased_seams_ids.size(); - - // skip first vertex - if (i == 0) - continue; - - const GCodeProcessorResult::MoveVertex& prev = gcode_result.moves[i - 1]; - -#if ENABLE_SHOW_TOOLPATHS_COG - if (curr.type == EMoveType::Extrude && - curr.extrusion_role != erSkirt && - curr.extrusion_role != erSupportMaterial && - curr.extrusion_role != erSupportMaterialInterface && - curr.extrusion_role != erWipeTower && - curr.extrusion_role != erCustom && - curr.extrusion_role != erMixed) { - const Vec3d curr_pos = curr.position.cast(); - const Vec3d prev_pos = prev.position.cast(); - m_cog.add_segment(curr_pos, prev_pos, curr.mm3_per_mm * (curr_pos - prev_pos).norm()); - } -#endif // ENABLE_SHOW_TOOLPATHS_COG - - // update progress dialog - ++progress_count; - if (progress_dialog != nullptr && progress_count % progress_threshold == 0) { - progress_dialog->Update(int(100.0f * float(i) / (2.0f * float(m_moves_count))), - _L("Generating vertex buffer") + ": " + wxNumberFormatter::ToString(100.0 * double(i) / double(m_moves_count), 0, wxNumberFormatter::Style_None) + "%"); - progress_dialog->Fit(); - progress_count = 0; - } - - const unsigned char id = buffer_id(curr.type); - TBuffer& t_buffer = m_buffers[id]; - MultiVertexBuffer& v_multibuffer = vertices[id]; - InstanceBuffer& inst_buffer = instances[id]; - InstanceIdBuffer& inst_id_buffer = instances_ids[id]; - InstancesOffsets& inst_offsets = instances_offsets[id]; - - // ensure there is at least one vertex buffer - if (v_multibuffer.empty()) - v_multibuffer.push_back(VertexBuffer()); - - // if adding the vertices for the current segment exceeds the threshold size of the current vertex buffer - // add another vertex buffer - size_t vertices_size_to_add = (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) ? t_buffer.model.data.vertices_size_bytes() : t_buffer.max_vertices_per_segment_size_bytes(); - if (v_multibuffer.back().size() * sizeof(float) > t_buffer.vertices.max_size_bytes() - vertices_size_to_add) { - v_multibuffer.push_back(VertexBuffer()); - if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) { - Path& last_path = t_buffer.paths.back(); -#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC - if (prev.type == curr.type && last_path.matches(curr, account_for_volumetric_rate)) -#else - if (prev.type == curr.type && last_path.matches(curr)) -#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC - last_path.add_sub_path(prev, static_cast(v_multibuffer.size()) - 1, 0, move_id - 1); - } - } - - VertexBuffer& v_buffer = v_multibuffer.back(); - - switch (t_buffer.render_primitive_type) - { - case TBuffer::ERenderPrimitiveType::Line: { add_vertices_as_line(prev, curr, v_buffer); break; } -#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC - case TBuffer::ERenderPrimitiveType::Triangle: { add_vertices_as_solid(prev, curr, t_buffer, static_cast(v_multibuffer.size()) - 1, v_buffer, move_id, account_for_volumetric_rate); break; } -#else - case TBuffer::ERenderPrimitiveType::Triangle: { add_vertices_as_solid(prev, curr, t_buffer, static_cast(v_multibuffer.size()) - 1, v_buffer, move_id); break; } -#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC - case TBuffer::ERenderPrimitiveType::InstancedModel: - { - add_model_instance(curr, inst_buffer, inst_id_buffer, move_id); - inst_offsets.push_back(prev.position - curr.position); -#if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.instances_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - break; - } - case TBuffer::ERenderPrimitiveType::BatchedModel: - { - add_vertices_as_model_batch(curr, t_buffer.model.data, v_buffer, inst_buffer, inst_id_buffer, move_id); - inst_offsets.push_back(prev.position - curr.position); -#if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.batched_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - break; - } - } - - // collect options zs for later use - if (curr.type == EMoveType::Pause_Print || curr.type == EMoveType::Custom_GCode) { - const float* const last_z = options_zs.empty() ? nullptr : &options_zs.back(); - if (last_z == nullptr || curr.position[2] < *last_z - EPSILON || *last_z + EPSILON < curr.position[2]) - options_zs.emplace_back(curr.position[2]); - } - } - - // smooth toolpaths corners for the given TBuffer using triangles - auto smooth_triangle_toolpaths_corners = [&gcode_result, &biased_seams_ids](const TBuffer& t_buffer, MultiVertexBuffer& v_multibuffer) { - auto extract_position_at = [](const VertexBuffer& vertices, size_t offset) { - return Vec3f(vertices[offset + 0], vertices[offset + 1], vertices[offset + 2]); - }; - auto update_position_at = [](VertexBuffer& vertices, size_t offset, const Vec3f& position) { - vertices[offset + 0] = position.x(); - vertices[offset + 1] = position.y(); - vertices[offset + 2] = position.z(); - }; - auto match_right_vertices = [&](const Path::Sub_Path& prev_sub_path, const Path::Sub_Path& next_sub_path, - size_t curr_s_id, size_t vertex_size_floats, const Vec3f& displacement_vec) { - if (&prev_sub_path == &next_sub_path) { // previous and next segment are both contained into to the same vertex buffer - VertexBuffer& vbuffer = v_multibuffer[prev_sub_path.first.b_id]; - // offset into the vertex buffer of the next segment 1st vertex - const size_t next_1st_offset = (prev_sub_path.last.s_id - curr_s_id) * 6 * vertex_size_floats; - // offset into the vertex buffer of the right vertex of the previous segment - const size_t prev_right_offset = prev_sub_path.last.i_id - next_1st_offset - 3 * vertex_size_floats; - // new position of the right vertices - const Vec3f shared_vertex = extract_position_at(vbuffer, prev_right_offset) + displacement_vec; - // update previous segment - update_position_at(vbuffer, prev_right_offset, shared_vertex); - // offset into the vertex buffer of the right vertex of the next segment - const size_t next_right_offset = next_sub_path.last.i_id - next_1st_offset; - // update next segment - update_position_at(vbuffer, next_right_offset, shared_vertex); - } - else { // previous and next segment are contained into different vertex buffers - VertexBuffer& prev_vbuffer = v_multibuffer[prev_sub_path.first.b_id]; - VertexBuffer& next_vbuffer = v_multibuffer[next_sub_path.first.b_id]; - // offset into the previous vertex buffer of the right vertex of the previous segment - const size_t prev_right_offset = prev_sub_path.last.i_id - 3 * vertex_size_floats; - // new position of the right vertices - const Vec3f shared_vertex = extract_position_at(prev_vbuffer, prev_right_offset) + displacement_vec; - // update previous segment - update_position_at(prev_vbuffer, prev_right_offset, shared_vertex); - // offset into the next vertex buffer of the right vertex of the next segment - const size_t next_right_offset = next_sub_path.first.i_id + 1 * vertex_size_floats; - // update next segment - update_position_at(next_vbuffer, next_right_offset, shared_vertex); - } - }; - auto match_left_vertices = [&](const Path::Sub_Path& prev_sub_path, const Path::Sub_Path& next_sub_path, - size_t curr_s_id, size_t vertex_size_floats, const Vec3f& displacement_vec) { - if (&prev_sub_path == &next_sub_path) { // previous and next segment are both contained into to the same vertex buffer - VertexBuffer& vbuffer = v_multibuffer[prev_sub_path.first.b_id]; - // offset into the vertex buffer of the next segment 1st vertex - const size_t next_1st_offset = (prev_sub_path.last.s_id - curr_s_id) * 6 * vertex_size_floats; - // offset into the vertex buffer of the left vertex of the previous segment - const size_t prev_left_offset = prev_sub_path.last.i_id - next_1st_offset - 1 * vertex_size_floats; - // new position of the left vertices - const Vec3f shared_vertex = extract_position_at(vbuffer, prev_left_offset) + displacement_vec; - // update previous segment - update_position_at(vbuffer, prev_left_offset, shared_vertex); - // offset into the vertex buffer of the left vertex of the next segment - const size_t next_left_offset = next_sub_path.last.i_id - next_1st_offset + 1 * vertex_size_floats; - // update next segment - update_position_at(vbuffer, next_left_offset, shared_vertex); - } - else { // previous and next segment are contained into different vertex buffers - VertexBuffer& prev_vbuffer = v_multibuffer[prev_sub_path.first.b_id]; - VertexBuffer& next_vbuffer = v_multibuffer[next_sub_path.first.b_id]; - // offset into the previous vertex buffer of the left vertex of the previous segment - const size_t prev_left_offset = prev_sub_path.last.i_id - 1 * vertex_size_floats; - // new position of the left vertices - const Vec3f shared_vertex = extract_position_at(prev_vbuffer, prev_left_offset) + displacement_vec; - // update previous segment - update_position_at(prev_vbuffer, prev_left_offset, shared_vertex); - // offset into the next vertex buffer of the left vertex of the next segment - const size_t next_left_offset = next_sub_path.first.i_id + 3 * vertex_size_floats; - // update next segment - update_position_at(next_vbuffer, next_left_offset, shared_vertex); - } - }; - - auto extract_move_id = [&biased_seams_ids](size_t id) { - size_t new_id = size_t(-1); - auto it = std::lower_bound(biased_seams_ids.begin(), biased_seams_ids.end(), id); - if (it == biased_seams_ids.end()) - new_id = id + biased_seams_ids.size(); - else { - if (it == biased_seams_ids.begin() && *it < id) - new_id = id; - else if (it != biased_seams_ids.begin()) - new_id = id + std::distance(biased_seams_ids.begin(), it); - } - return (new_id == size_t(-1)) ? id : new_id; - }; - - const size_t vertex_size_floats = t_buffer.vertices.vertex_size_floats(); - for (const Path& path : t_buffer.paths) { - // the two segments of the path sharing the current vertex may belong - // to two different vertex buffers - size_t prev_sub_path_id = 0; - size_t next_sub_path_id = 0; - const size_t path_vertices_count = path.vertices_count(); - const float half_width = 0.5f * path.width; - for (size_t j = 1; j < path_vertices_count - 1; ++j) { - const size_t curr_s_id = path.sub_paths.front().first.s_id + j; - const size_t move_id = extract_move_id(curr_s_id); - const Vec3f& prev = gcode_result.moves[move_id - 1].position; - const Vec3f& curr = gcode_result.moves[move_id].position; - const Vec3f& next = gcode_result.moves[move_id + 1].position; - - // select the subpaths which contains the previous/next segments - if (!path.sub_paths[prev_sub_path_id].contains(curr_s_id)) - ++prev_sub_path_id; - if (!path.sub_paths[next_sub_path_id].contains(curr_s_id + 1)) - ++next_sub_path_id; - const Path::Sub_Path& prev_sub_path = path.sub_paths[prev_sub_path_id]; - const Path::Sub_Path& next_sub_path = path.sub_paths[next_sub_path_id]; - - const Vec3f prev_dir = (curr - prev).normalized(); - const Vec3f prev_right = Vec3f(prev_dir.y(), -prev_dir.x(), 0.0f).normalized(); - const Vec3f prev_up = prev_right.cross(prev_dir); - - const Vec3f next_dir = (next - curr).normalized(); - - const bool is_right_turn = prev_up.dot(prev_dir.cross(next_dir)) <= 0.0f; - const float cos_dir = prev_dir.dot(next_dir); - // whether the angle between adjacent segments is greater than 45 degrees - const bool is_sharp = cos_dir < 0.7071068f; - - float displacement = 0.0f; - if (cos_dir > -0.9998477f) { - // if the angle between adjacent segments is smaller than 179 degrees - const Vec3f med_dir = (prev_dir + next_dir).normalized(); - displacement = half_width * ::tan(::acos(std::clamp(next_dir.dot(med_dir), -1.0f, 1.0f))); - } - - const float sq_prev_length = (curr - prev).squaredNorm(); - const float sq_next_length = (next - curr).squaredNorm(); - const float sq_displacement = sqr(displacement); - const bool can_displace = displacement > 0.0f && sq_displacement < sq_prev_length && sq_displacement < sq_next_length; - - if (can_displace) { - // displacement to apply to the vertices to match - const Vec3f displacement_vec = displacement * prev_dir; - // matches inner corner vertices - if (is_right_turn) - match_right_vertices(prev_sub_path, next_sub_path, curr_s_id, vertex_size_floats, -displacement_vec); - else - match_left_vertices(prev_sub_path, next_sub_path, curr_s_id, vertex_size_floats, -displacement_vec); - - if (!is_sharp) { - // matches outer corner vertices - if (is_right_turn) - match_left_vertices(prev_sub_path, next_sub_path, curr_s_id, vertex_size_floats, displacement_vec); - else - match_right_vertices(prev_sub_path, next_sub_path, curr_s_id, vertex_size_floats, displacement_vec); - } - } - } - } - }; - -#if ENABLE_GCODE_VIEWER_STATISTICS - auto load_vertices_time = std::chrono::high_resolution_clock::now(); - m_statistics.load_vertices = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - // smooth toolpaths corners for TBuffers using triangles - for (size_t i = 0; i < m_buffers.size(); ++i) { - const TBuffer& t_buffer = m_buffers[i]; - if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) - smooth_triangle_toolpaths_corners(t_buffer, vertices[i]); - } - - // dismiss, no more needed - std::vector().swap(biased_seams_ids); - - for (MultiVertexBuffer& v_multibuffer : vertices) { - for (VertexBuffer& v_buffer : v_multibuffer) { - v_buffer.shrink_to_fit(); - } - } - - // move the wipe toolpaths half height up to render them on proper position - MultiVertexBuffer& wipe_vertices = vertices[buffer_id(EMoveType::Wipe)]; - for (VertexBuffer& v_buffer : wipe_vertices) { - for (size_t i = 2; i < v_buffer.size(); i += 3) { - v_buffer[i] += 0.5f * GCodeProcessor::Wipe_Height; - } - } - - // send vertices data to gpu, where needed - for (size_t i = 0; i < m_buffers.size(); ++i) { - TBuffer& t_buffer = m_buffers[i]; - if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::InstancedModel) { - const InstanceBuffer& inst_buffer = instances[i]; - if (!inst_buffer.empty()) { - t_buffer.model.instances.buffer = inst_buffer; - t_buffer.model.instances.s_ids = instances_ids[i]; - t_buffer.model.instances.offsets = instances_offsets[i]; - } - } - else { - if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) { - const InstanceBuffer& inst_buffer = instances[i]; - if (!inst_buffer.empty()) { - t_buffer.model.instances.buffer = inst_buffer; - t_buffer.model.instances.s_ids = instances_ids[i]; - t_buffer.model.instances.offsets = instances_offsets[i]; - } - } - const MultiVertexBuffer& v_multibuffer = vertices[i]; - for (const VertexBuffer& v_buffer : v_multibuffer) { - const size_t size_elements = v_buffer.size(); - const size_t size_bytes = size_elements * sizeof(float); - const size_t vertices_count = size_elements / t_buffer.vertices.vertex_size_floats(); - t_buffer.vertices.count += vertices_count; - -#if ENABLE_GCODE_VIEWER_STATISTICS - m_statistics.total_vertices_gpu_size += static_cast(size_bytes); - m_statistics.max_vbuffer_gpu_size = std::max(m_statistics.max_vbuffer_gpu_size, static_cast(size_bytes)); - ++m_statistics.vbuffers_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - GLuint id = 0; - glsafe(::glGenBuffers(1, &id)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, id)); - glsafe(::glBufferData(GL_ARRAY_BUFFER, size_bytes, v_buffer.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - - t_buffer.vertices.vbos.push_back(static_cast(id)); - t_buffer.vertices.sizes.push_back(size_bytes); - } - } - } - -#if ENABLE_GCODE_VIEWER_STATISTICS - auto smooth_vertices_time = std::chrono::high_resolution_clock::now(); - m_statistics.smooth_vertices = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - load_vertices_time).count(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - log_memory_usage("Loaded G-code generated vertex buffers ", vertices, indices); - - // dismiss vertices data, no more needed - std::vector().swap(vertices); - std::vector().swap(instances); - std::vector().swap(instances_ids); - - // toolpaths data -> extract indices from result - // paths may have been filled while extracting vertices, - // so reset them, they will be filled again while extracting indices - for (TBuffer& buffer : m_buffers) { - buffer.paths.clear(); - } - - // variable used to keep track of the current vertex buffers index and size - using CurrVertexBuffer = std::pair; - std::vector curr_vertex_buffers(m_buffers.size(), { 0, 0 }); - - // variable used to keep track of the vertex buffers ids - using VboIndexList = std::vector; - std::vector vbo_indices(m_buffers.size()); - - size_t seams_count = 0; - - for (size_t i = 0; i < m_moves_count; ++i) { - const GCodeProcessorResult::MoveVertex& curr = gcode_result.moves[i]; - if (curr.type == EMoveType::Seam) - ++seams_count; - - const size_t move_id = i - seams_count; - - // skip first vertex - if (i == 0) - continue; - - const GCodeProcessorResult::MoveVertex& prev = gcode_result.moves[i - 1]; - const GCodeProcessorResult::MoveVertex* next = nullptr; - if (i < m_moves_count - 1) - next = &gcode_result.moves[i + 1]; - - ++progress_count; - if (progress_dialog != nullptr && progress_count % progress_threshold == 0) { - progress_dialog->Update(int(100.0f * float(m_moves_count + i) / (2.0f * float(m_moves_count))), - _L("Generating index buffers") + ": " + wxNumberFormatter::ToString(100.0 * double(i) / double(m_moves_count), 0, wxNumberFormatter::Style_None) + "%"); - progress_dialog->Fit(); - progress_count = 0; - } - - const unsigned char id = buffer_id(curr.type); - TBuffer& t_buffer = m_buffers[id]; - MultiIndexBuffer& i_multibuffer = indices[id]; - CurrVertexBuffer& curr_vertex_buffer = curr_vertex_buffers[id]; - VboIndexList& vbo_index_list = vbo_indices[id]; - - // ensure there is at least one index buffer - if (i_multibuffer.empty()) { - i_multibuffer.push_back(IndexBuffer()); - if (!t_buffer.vertices.vbos.empty()) - vbo_index_list.push_back(t_buffer.vertices.vbos[curr_vertex_buffer.first]); - } - - // if adding the indices for the current segment exceeds the threshold size of the current index buffer - // create another index buffer - size_t indiced_size_to_add = (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) ? t_buffer.model.data.indices_size_bytes() : t_buffer.max_indices_per_segment_size_bytes(); - if (i_multibuffer.back().size() * sizeof(IBufferType) >= IBUFFER_THRESHOLD_BYTES - indiced_size_to_add) { - i_multibuffer.push_back(IndexBuffer()); - vbo_index_list.push_back(t_buffer.vertices.vbos[curr_vertex_buffer.first]); - if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::BatchedModel) { - Path& last_path = t_buffer.paths.back(); - last_path.add_sub_path(prev, static_cast(i_multibuffer.size()) - 1, 0, move_id - 1); - } - } - - // if adding the vertices for the current segment exceeds the threshold size of the current vertex buffer - // create another index buffer - size_t vertices_size_to_add = (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) ? t_buffer.model.data.vertices_size_bytes() : t_buffer.max_vertices_per_segment_size_bytes(); - if (curr_vertex_buffer.second * t_buffer.vertices.vertex_size_bytes() > t_buffer.vertices.max_size_bytes() - vertices_size_to_add) { - i_multibuffer.push_back(IndexBuffer()); - - ++curr_vertex_buffer.first; - curr_vertex_buffer.second = 0; - vbo_index_list.push_back(t_buffer.vertices.vbos[curr_vertex_buffer.first]); - - if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::BatchedModel) { - Path& last_path = t_buffer.paths.back(); - last_path.add_sub_path(prev, static_cast(i_multibuffer.size()) - 1, 0, move_id - 1); - } - } - - IndexBuffer& i_buffer = i_multibuffer.back(); - - switch (t_buffer.render_primitive_type) - { - case TBuffer::ERenderPrimitiveType::Line: { -#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC - add_indices_as_line(prev, curr, t_buffer, static_cast(i_multibuffer.size()) - 1, i_buffer, move_id, account_for_volumetric_rate); -#else - add_indices_as_line(prev, curr, t_buffer, static_cast(i_multibuffer.size()) - 1, i_buffer, move_id); -#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC - curr_vertex_buffer.second += t_buffer.max_vertices_per_segment(); - break; - } - case TBuffer::ERenderPrimitiveType::Triangle: { -#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC - add_indices_as_solid(prev, curr, next, t_buffer, curr_vertex_buffer.second, static_cast(i_multibuffer.size()) - 1, i_buffer, move_id, account_for_volumetric_rate); -#else - add_indices_as_solid(prev, curr, next, t_buffer, curr_vertex_buffer.second, static_cast(i_multibuffer.size()) - 1, i_buffer, move_id); -#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC - break; - } - case TBuffer::ERenderPrimitiveType::BatchedModel: { - add_indices_as_model_batch(t_buffer.model.data, i_buffer, curr_vertex_buffer.second); - curr_vertex_buffer.second += t_buffer.model.data.vertices_count(); - break; - } - default: { break; } - } - } - - for (MultiIndexBuffer& i_multibuffer : indices) { - for (IndexBuffer& i_buffer : i_multibuffer) { - i_buffer.shrink_to_fit(); - } - } - - // toolpaths data -> send indices data to gpu - for (size_t i = 0; i < m_buffers.size(); ++i) { - TBuffer& t_buffer = m_buffers[i]; - if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::InstancedModel) { - const MultiIndexBuffer& i_multibuffer = indices[i]; - for (const IndexBuffer& i_buffer : i_multibuffer) { - const size_t size_elements = i_buffer.size(); - const size_t size_bytes = size_elements * sizeof(IBufferType); - - // stores index buffer informations into TBuffer - t_buffer.indices.push_back(IBuffer()); - IBuffer& ibuf = t_buffer.indices.back(); - ibuf.count = size_elements; - ibuf.vbo = vbo_indices[i][t_buffer.indices.size() - 1]; - -#if ENABLE_GCODE_VIEWER_STATISTICS - m_statistics.total_indices_gpu_size += static_cast(size_bytes); - m_statistics.max_ibuffer_gpu_size = std::max(m_statistics.max_ibuffer_gpu_size, static_cast(size_bytes)); - ++m_statistics.ibuffers_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - glsafe(::glGenBuffers(1, &ibuf.ibo)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibuf.ibo)); - glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, size_bytes, i_buffer.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - } - } - } - - if (progress_dialog != nullptr) { - progress_dialog->Update(100, ""); - progress_dialog->Fit(); - } - -#if ENABLE_GCODE_VIEWER_STATISTICS - for (const TBuffer& buffer : m_buffers) { - m_statistics.paths_size += SLIC3R_STDVEC_MEMSIZE(buffer.paths, Path); - } - - auto update_segments_count = [&](EMoveType type, int64_t& count) { - unsigned int id = buffer_id(type); - const MultiIndexBuffer& buffers = indices[id]; - int64_t indices_count = 0; - for (const IndexBuffer& buffer : buffers) { - indices_count += buffer.size(); - } - const TBuffer& t_buffer = m_buffers[id]; - if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) - indices_count -= static_cast(12 * t_buffer.paths.size()); // remove the starting + ending caps = 4 triangles - - count += indices_count / t_buffer.indices_per_segment(); - }; - - update_segments_count(EMoveType::Travel, m_statistics.travel_segments_count); - update_segments_count(EMoveType::Wipe, m_statistics.wipe_segments_count); - update_segments_count(EMoveType::Extrude, m_statistics.extrude_segments_count); - - m_statistics.load_indices = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - smooth_vertices_time).count(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - log_memory_usage("Loaded G-code generated indices buffers ", vertices, indices); - - // dismiss indices data, no more needed - std::vector().swap(indices); - - // layers zs / roles / extruder ids -> extract from result - size_t last_travel_s_id = 0; - seams_count = 0; - for (size_t i = 0; i < m_moves_count; ++i) { - const GCodeProcessorResult::MoveVertex& move = gcode_result.moves[i]; - if (move.type == EMoveType::Seam) - ++seams_count; - - size_t move_id = i - seams_count; - - if (move.type == EMoveType::Extrude) { -#if ENABLE_PROCESS_G2_G3_LINES - if (move.extrusion_role != erNone && !move.internal_only) { -#endif // ENABLE_PROCESS_G2_G3_LINES - // layers zs - const double* const last_z = m_layers.empty() ? nullptr : &m_layers.get_zs().back(); - const double z = static_cast(move.position.z()); - if (last_z == nullptr || z < *last_z - EPSILON || *last_z + EPSILON < z) - m_layers.append(z, { last_travel_s_id, move_id }); - else - m_layers.get_ranges().back().last = move_id; -#if ENABLE_PROCESS_G2_G3_LINES - } -#endif // ENABLE_PROCESS_G2_G3_LINES - // extruder ids - m_extruder_ids.emplace_back(move.extruder_id); - // roles - if (i > 0) - m_roles.emplace_back(move.extrusion_role); - } - else if (move.type == EMoveType::Travel) { - if (move_id - last_travel_s_id > 1 && !m_layers.empty()) - m_layers.get_ranges().back().last = move_id; - - last_travel_s_id = move_id; - } - } - - // roles -> remove duplicates - sort_remove_duplicates(m_roles); - m_roles.shrink_to_fit(); - - // extruder ids -> remove duplicates - sort_remove_duplicates(m_extruder_ids); - m_extruder_ids.shrink_to_fit(); - - // replace layers for spiral vase mode - if (!gcode_result.spiral_vase_layers.empty()) { - m_layers.reset(); - for (const auto& layer : gcode_result.spiral_vase_layers) { - m_layers.append(layer.first, { layer.second.first, layer.second.second }); - } - } - - // set layers z range - if (!m_layers.empty()) - m_layers_z_range = { 0, static_cast(m_layers.size() - 1) }; - - // change color of paths whose layer contains option points - if (!options_zs.empty()) { - TBuffer& extrude_buffer = m_buffers[buffer_id(EMoveType::Extrude)]; - for (Path& path : extrude_buffer.paths) { - const float z = path.sub_paths.front().first.position.z(); - if (std::find_if(options_zs.begin(), options_zs.end(), [z](float f) { return f - EPSILON <= z && z <= f + EPSILON; }) != options_zs.end()) - path.cp_color_id = 255 - path.cp_color_id; - } - } - -#if ENABLE_GCODE_VIEWER_STATISTICS - m_statistics.load_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - if (progress_dialog != nullptr) - progress_dialog->Destroy(); -} - -#if ENABLE_LEGACY_OPENGL_REMOVAL -void GCodeViewer::load_shells(const Print& print) -#else -void GCodeViewer::load_shells(const Print& print, bool initialized) -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -{ - if (print.objects().empty()) - // no shells, return - return; - - // adds objects' volumes - int object_id = 0; - for (const PrintObject* obj : print.objects()) { - const ModelObject* model_obj = obj->model_object(); - - std::vector instance_ids(model_obj->instances.size()); - for (int i = 0; i < (int)model_obj->instances.size(); ++i) { - instance_ids[i] = i; - } - - size_t current_volumes_count = m_shells.volumes.volumes.size(); -#if ENABLE_LEGACY_OPENGL_REMOVAL - m_shells.volumes.load_object(model_obj, object_id, instance_ids); -#else - m_shells.volumes.load_object(model_obj, object_id, instance_ids, initialized); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - // adjust shells' z if raft is present - const SlicingParameters& slicing_parameters = obj->slicing_parameters(); - if (slicing_parameters.object_print_z_min != 0.0) { - const Vec3d z_offset = slicing_parameters.object_print_z_min * Vec3d::UnitZ(); - for (size_t i = current_volumes_count; i < m_shells.volumes.volumes.size(); ++i) { - GLVolume* v = m_shells.volumes.volumes[i]; - v->set_volume_offset(v->get_volume_offset() + z_offset); - } - } - - ++object_id; - } - - if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF) { - // adds wipe tower's volume - const double max_z = print.objects()[0]->model_object()->get_model()->bounding_box().max(2); - const PrintConfig& config = print.config(); - const size_t extruders_count = config.nozzle_diameter.size(); - if (extruders_count > 1 && config.wipe_tower && !config.complete_objects) { - const float depth = print.wipe_tower_data(extruders_count).depth; - const float brim_width = print.wipe_tower_data(extruders_count).brim_width; - -#if ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - m_shells.volumes.load_wipe_tower_preview(config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle, - !print.is_step_done(psWipeTower), brim_width); -#else - m_shells.volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle, - !print.is_step_done(psWipeTower), brim_width); -#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL -#else -#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - m_shells.volumes.load_wipe_tower_preview(config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle, - !print.is_step_done(psWipeTower), brim_width, initialized); -#else - m_shells.volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle, - !print.is_step_done(psWipeTower), brim_width, initialized); -#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - } - - // remove modifiers - while (true) { - GLVolumePtrs::iterator it = std::find_if(m_shells.volumes.volumes.begin(), m_shells.volumes.volumes.end(), [](GLVolume* volume) { return volume->is_modifier; }); - if (it != m_shells.volumes.volumes.end()) { - delete (*it); - m_shells.volumes.volumes.erase(it); - } - else - break; - } - - for (GLVolume* volume : m_shells.volumes.volumes) { - volume->zoom_to_volumes = false; - volume->color.a(0.25f); - volume->force_native_color = true; - volume->set_render_color(true); - } -} - -void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const -{ -#if ENABLE_GCODE_VIEWER_STATISTICS - auto start_time = std::chrono::high_resolution_clock::now(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - auto extrusion_color = [this](const Path& path) { - ColorRGBA color; - switch (m_view_type) - { - case EViewType::FeatureType: { color = Extrusion_Role_Colors[static_cast(path.role)]; break; } - case EViewType::Height: { color = m_extrusions.ranges.height.get_color_at(path.height); break; } - case EViewType::Width: { color = m_extrusions.ranges.width.get_color_at(path.width); break; } - case EViewType::Feedrate: { color = m_extrusions.ranges.feedrate.get_color_at(path.feedrate); break; } - case EViewType::FanSpeed: { color = m_extrusions.ranges.fan_speed.get_color_at(path.fan_speed); break; } - case EViewType::Temperature: { color = m_extrusions.ranges.temperature.get_color_at(path.temperature); break; } -#if ENABLE_PREVIEW_LAYER_TIME - case EViewType::LayerTimeLinear: - case EViewType::LayerTimeLogarithmic: { - const Path::Sub_Path& sub_path = path.sub_paths.front(); - double z = static_cast(sub_path.first.position.z()); - const std::vector& zs = m_layers.get_zs(); - const std::vector& ranges = m_layers.get_ranges(); - size_t time_mode_id = static_cast(m_time_estimate_mode); - for (size_t i = 0; i < zs.size(); ++i) { - if (std::abs(zs[i] - z) < EPSILON) { - if (ranges[i].contains(sub_path.first.s_id)) { - color = m_extrusions.ranges.layer_time[time_mode_id].get_color_at(m_layers_times[time_mode_id][i], - (m_view_type == EViewType::LayerTimeLinear) ? Extrusions::Range::EType::Linear : Extrusions::Range::EType::Logarithmic); - break; - } - } - } - break; - } -#endif // ENABLE_PREVIEW_LAYER_TIME - case EViewType::VolumetricRate: { color = m_extrusions.ranges.volumetric_rate.get_color_at(path.volumetric_rate); break; } - case EViewType::Tool: { color = m_tool_colors[path.extruder_id]; break; } - case EViewType::ColorPrint: { - if (path.cp_color_id >= static_cast(m_tool_colors.size())) - color = ColorRGBA::GRAY(); - else - color = m_tool_colors[path.cp_color_id]; - - break; - } - default: { color = ColorRGBA::WHITE(); break; } - } - - return color; - }; - - auto travel_color = [](const Path& path) { - return (path.delta_extruder < 0.0f) ? Travel_Colors[2] /* Retract */ : - ((path.delta_extruder > 0.0f) ? Travel_Colors[1] /* Extrude */ : - Travel_Colors[0] /* Move */); - }; - - auto is_in_layers_range = [this](const Path& path, size_t min_id, size_t max_id) { - auto in_layers_range = [this, min_id, max_id](size_t id) { - return m_layers.get_range_at(min_id).first <= id && id <= m_layers.get_range_at(max_id).last; - }; - - return in_layers_range(path.sub_paths.front().first.s_id) && in_layers_range(path.sub_paths.back().last.s_id); - }; - - auto is_travel_in_layers_range = [this](size_t path_id, size_t min_id, size_t max_id) { - const TBuffer& buffer = m_buffers[buffer_id(EMoveType::Travel)]; - if (path_id >= buffer.paths.size()) - return false; - - Path path = buffer.paths[path_id]; - size_t first = path_id; - size_t last = path_id; - - // check adjacent paths - while (first > 0 && path.sub_paths.front().first.position.isApprox(buffer.paths[first - 1].sub_paths.back().last.position)) { - --first; - path.sub_paths.front().first = buffer.paths[first].sub_paths.front().first; - } - while (last < buffer.paths.size() - 1 && path.sub_paths.back().last.position.isApprox(buffer.paths[last + 1].sub_paths.front().first.position)) { - ++last; - path.sub_paths.back().last = buffer.paths[last].sub_paths.back().last; - } - - const size_t min_s_id = m_layers.get_range_at(min_id).first; - const size_t max_s_id = m_layers.get_range_at(max_id).last; - - return (min_s_id <= path.sub_paths.front().first.s_id && path.sub_paths.front().first.s_id <= max_s_id) || - (min_s_id <= path.sub_paths.back().last.s_id && path.sub_paths.back().last.s_id <= max_s_id); - }; - -#if ENABLE_GCODE_VIEWER_STATISTICS - Statistics* statistics = const_cast(&m_statistics); - statistics->render_paths_size = 0; - statistics->models_instances_size = 0; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - const bool top_layer_only = get_app_config()->get("seq_top_layer_only") == "1"; - - SequentialView::Endpoints global_endpoints = { m_moves_count , 0 }; - SequentialView::Endpoints top_layer_endpoints = global_endpoints; - SequentialView* sequential_view = const_cast(&m_sequential_view); - if (top_layer_only || !keep_sequential_current_first) sequential_view->current.first = 0; - if (!keep_sequential_current_last) sequential_view->current.last = m_moves_count; - - // first pass: collect visible paths and update sequential view data - std::vector> paths; - for (size_t b = 0; b < m_buffers.size(); ++b) { - TBuffer& buffer = const_cast(m_buffers[b]); - // reset render paths - buffer.render_paths.clear(); - - if (!buffer.visible) - continue; - - if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::InstancedModel || - buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) { - for (size_t id : buffer.model.instances.s_ids) { - if (id < m_layers.get_range_at(m_layers_z_range[0]).first || m_layers.get_range_at(m_layers_z_range[1]).last < id) - continue; - - global_endpoints.first = std::min(global_endpoints.first, id); - global_endpoints.last = std::max(global_endpoints.last, id); - - if (top_layer_only) { - if (id < m_layers.get_range_at(m_layers_z_range[1]).first || m_layers.get_range_at(m_layers_z_range[1]).last < id) - continue; - - top_layer_endpoints.first = std::min(top_layer_endpoints.first, id); - top_layer_endpoints.last = std::max(top_layer_endpoints.last, id); - } - } - } - else { - for (size_t i = 0; i < buffer.paths.size(); ++i) { - const Path& path = buffer.paths[i]; - if (path.type == EMoveType::Travel) { - if (!is_travel_in_layers_range(i, m_layers_z_range[0], m_layers_z_range[1])) - continue; - } - else if (!is_in_layers_range(path, m_layers_z_range[0], m_layers_z_range[1])) - continue; - - if (path.type == EMoveType::Extrude && !is_visible(path)) - continue; - - // store valid path - for (size_t j = 0; j < path.sub_paths.size(); ++j) { - paths.push_back({ static_cast(b), path.sub_paths[j].first.b_id, static_cast(i), static_cast(j) }); - } - - global_endpoints.first = std::min(global_endpoints.first, path.sub_paths.front().first.s_id); - global_endpoints.last = std::max(global_endpoints.last, path.sub_paths.back().last.s_id); - - if (top_layer_only) { - if (path.type == EMoveType::Travel) { - if (is_travel_in_layers_range(i, m_layers_z_range[1], m_layers_z_range[1])) { - top_layer_endpoints.first = std::min(top_layer_endpoints.first, path.sub_paths.front().first.s_id); - top_layer_endpoints.last = std::max(top_layer_endpoints.last, path.sub_paths.back().last.s_id); - } - } - else if (is_in_layers_range(path, m_layers_z_range[1], m_layers_z_range[1])) { - top_layer_endpoints.first = std::min(top_layer_endpoints.first, path.sub_paths.front().first.s_id); - top_layer_endpoints.last = std::max(top_layer_endpoints.last, path.sub_paths.back().last.s_id); - } - } - } - } - } - - // update current sequential position - sequential_view->current.first = !top_layer_only && keep_sequential_current_first ? std::clamp(sequential_view->current.first, global_endpoints.first, global_endpoints.last) : global_endpoints.first; - sequential_view->current.last = keep_sequential_current_last ? std::clamp(sequential_view->current.last, global_endpoints.first, global_endpoints.last) : global_endpoints.last; - - // get the world position from the vertex buffer - bool found = false; - for (const TBuffer& buffer : m_buffers) { - if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::InstancedModel || - buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) { - for (size_t i = 0; i < buffer.model.instances.s_ids.size(); ++i) { - if (buffer.model.instances.s_ids[i] == m_sequential_view.current.last) { - size_t offset = i * buffer.model.instances.instance_size_floats(); - sequential_view->current_position.x() = buffer.model.instances.buffer[offset + 0]; - sequential_view->current_position.y() = buffer.model.instances.buffer[offset + 1]; - sequential_view->current_position.z() = buffer.model.instances.buffer[offset + 2]; - sequential_view->current_offset = buffer.model.instances.offsets[i]; - found = true; - break; - } - } - } - else { - // searches the path containing the current position - for (const Path& path : buffer.paths) { - if (path.contains(m_sequential_view.current.last)) { - const int sub_path_id = path.get_id_of_sub_path_containing(m_sequential_view.current.last); - if (sub_path_id != -1) { - const Path::Sub_Path& sub_path = path.sub_paths[sub_path_id]; - unsigned int offset = static_cast(m_sequential_view.current.last - sub_path.first.s_id); - if (offset > 0) { - if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Line) - offset = 2 * offset - 1; - else if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) { - unsigned int indices_count = buffer.indices_per_segment(); - offset = indices_count * (offset - 1) + (indices_count - 2); - if (sub_path_id == 0) - offset += 6; // add 2 triangles for starting cap - } - } - offset += static_cast(sub_path.first.i_id); - - // gets the vertex index from the index buffer on gpu - const IBuffer& i_buffer = buffer.indices[sub_path.first.b_id]; - IBufferType index = 0; - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo)); - glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast(offset * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&index))); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - - // gets the position from the vertices buffer on gpu - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, i_buffer.vbo)); - glsafe(::glGetBufferSubData(GL_ARRAY_BUFFER, static_cast(index * buffer.vertices.vertex_size_bytes()), static_cast(3 * sizeof(float)), static_cast(sequential_view->current_position.data()))); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - - sequential_view->current_offset = Vec3f::Zero(); - found = true; - break; - } - } - } - } - - if (found) - break; - } - - // second pass: filter paths by sequential data and collect them by color - RenderPath* render_path = nullptr; - for (const auto& [tbuffer_id, ibuffer_id, path_id, sub_path_id] : paths) { - TBuffer& buffer = const_cast(m_buffers[tbuffer_id]); - const Path& path = buffer.paths[path_id]; - const Path::Sub_Path& sub_path = path.sub_paths[sub_path_id]; - if (m_sequential_view.current.last < sub_path.first.s_id || sub_path.last.s_id < m_sequential_view.current.first) - continue; - - ColorRGBA color; - switch (path.type) - { - case EMoveType::Tool_change: - case EMoveType::Color_change: - case EMoveType::Pause_Print: - case EMoveType::Custom_GCode: - case EMoveType::Retract: - case EMoveType::Unretract: - case EMoveType::Seam: { color = option_color(path.type); break; } - case EMoveType::Extrude: { - if (!top_layer_only || - m_sequential_view.current.last == global_endpoints.last || - is_in_layers_range(path, m_layers_z_range[1], m_layers_z_range[1])) - color = extrusion_color(path); - else - color = Neutral_Color; - - break; - } - case EMoveType::Travel: { - if (!top_layer_only || m_sequential_view.current.last == global_endpoints.last || is_travel_in_layers_range(path_id, m_layers_z_range[1], m_layers_z_range[1])) - color = (m_view_type == EViewType::Feedrate || m_view_type == EViewType::Tool || m_view_type == EViewType::ColorPrint) ? extrusion_color(path) : travel_color(path); - else - color = Neutral_Color; - - break; - } - case EMoveType::Wipe: { color = Wipe_Color; break; } - default: { color = { 0.0f, 0.0f, 0.0f, 1.0f }; break; } - } - - RenderPath key{ tbuffer_id, color, static_cast(ibuffer_id), path_id }; - if (render_path == nullptr || !RenderPathPropertyEqual()(*render_path, key)) { - buffer.render_paths.emplace_back(key); - render_path = const_cast(&buffer.render_paths.back()); - } - - unsigned int delta_1st = 0; - if (sub_path.first.s_id < m_sequential_view.current.first && m_sequential_view.current.first <= sub_path.last.s_id) - delta_1st = static_cast(m_sequential_view.current.first - sub_path.first.s_id); - - unsigned int size_in_indices = 0; - switch (buffer.render_primitive_type) - { - case TBuffer::ERenderPrimitiveType::Line: - case TBuffer::ERenderPrimitiveType::Triangle: { - unsigned int segments_count = std::min(m_sequential_view.current.last, sub_path.last.s_id) - std::max(m_sequential_view.current.first, sub_path.first.s_id); - size_in_indices = buffer.indices_per_segment() * segments_count; - break; - } - default: { break; } - } - - if (size_in_indices == 0) - continue; - - if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) { - if (sub_path_id == 0 && delta_1st == 0) - size_in_indices += 6; // add 2 triangles for starting cap - if (sub_path_id == path.sub_paths.size() - 1 && path.sub_paths.back().last.s_id <= m_sequential_view.current.last) - size_in_indices += 6; // add 2 triangles for ending cap - if (delta_1st > 0) - size_in_indices -= 6; // remove 2 triangles for corner cap - } - - render_path->sizes.push_back(size_in_indices); - - if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) { - delta_1st *= buffer.indices_per_segment(); - if (delta_1st > 0) { - delta_1st += 6; // skip 2 triangles for corner cap - if (sub_path_id == 0) - delta_1st += 6; // skip 2 triangles for starting cap - } - } - - render_path->offsets.push_back(static_cast((sub_path.first.i_id + delta_1st) * sizeof(IBufferType))); - -#if 0 - // check sizes and offsets against index buffer size on gpu - GLint buffer_size; - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer->indices[render_path->ibuffer_id].ibo)); - glsafe(::glGetBufferParameteriv(GL_ELEMENT_ARRAY_BUFFER, GL_BUFFER_SIZE, &buffer_size)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - if (render_path->offsets.back() + render_path->sizes.back() * sizeof(IBufferType) > buffer_size) - BOOST_LOG_TRIVIAL(error) << "GCodeViewer::refresh_render_paths: Invalid render path data"; -#endif - } - - // Removes empty render paths and sort. - for (size_t b = 0; b < m_buffers.size(); ++b) { - TBuffer* buffer = const_cast(&m_buffers[b]); - buffer->render_paths.erase(std::remove_if(buffer->render_paths.begin(), buffer->render_paths.end(), - [](const auto &path){ return path.sizes.empty() || path.offsets.empty(); }), - buffer->render_paths.end()); - } - - // second pass: for buffers using instanced and batched models, update the instances render ranges - for (size_t b = 0; b < m_buffers.size(); ++b) { - TBuffer& buffer = const_cast(m_buffers[b]); - if (buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::InstancedModel && - buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::BatchedModel) - continue; - - buffer.model.instances.render_ranges.reset(); - - if (!buffer.visible || buffer.model.instances.s_ids.empty()) - continue; - - buffer.model.instances.render_ranges.ranges.push_back({ 0, 0, 0, buffer.model.color }); - bool has_second_range = top_layer_only && m_sequential_view.current.last != m_sequential_view.global.last; - if (has_second_range) - buffer.model.instances.render_ranges.ranges.push_back({ 0, 0, 0, Neutral_Color }); - - if (m_sequential_view.current.first <= buffer.model.instances.s_ids.back() && buffer.model.instances.s_ids.front() <= m_sequential_view.current.last) { - for (size_t id : buffer.model.instances.s_ids) { - if (has_second_range) { - if (id < m_sequential_view.endpoints.first) { - ++buffer.model.instances.render_ranges.ranges.front().offset; - if (id <= m_sequential_view.current.first) - ++buffer.model.instances.render_ranges.ranges.back().offset; - else - ++buffer.model.instances.render_ranges.ranges.back().count; - } - else if (id <= m_sequential_view.current.last) - ++buffer.model.instances.render_ranges.ranges.front().count; - else - break; - } - else { - if (id <= m_sequential_view.current.first) - ++buffer.model.instances.render_ranges.ranges.front().offset; - else if (id <= m_sequential_view.current.last) - ++buffer.model.instances.render_ranges.ranges.front().count; - else - break; - } - } - } - } - - // set sequential data to their final value - sequential_view->endpoints = top_layer_only ? top_layer_endpoints : global_endpoints; - sequential_view->current.first = !top_layer_only && keep_sequential_current_first ? std::clamp(sequential_view->current.first, sequential_view->endpoints.first, sequential_view->endpoints.last) : sequential_view->endpoints.first; - sequential_view->global = global_endpoints; - - // updates sequential range caps - std::array* sequential_range_caps = const_cast*>(&m_sequential_range_caps); - (*sequential_range_caps)[0].reset(); - (*sequential_range_caps)[1].reset(); - - if (m_sequential_view.current.first != m_sequential_view.current.last) { - for (const auto& [tbuffer_id, ibuffer_id, path_id, sub_path_id] : paths) { - TBuffer& buffer = const_cast(m_buffers[tbuffer_id]); - if (buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Triangle) - continue; - - const Path& path = buffer.paths[path_id]; - const Path::Sub_Path& sub_path = path.sub_paths[sub_path_id]; - if (m_sequential_view.current.last <= sub_path.first.s_id || sub_path.last.s_id <= m_sequential_view.current.first) - continue; - - // update cap for first endpoint of current range - if (m_sequential_view.current.first > sub_path.first.s_id) { - SequentialRangeCap& cap = (*sequential_range_caps)[0]; - const IBuffer& i_buffer = buffer.indices[ibuffer_id]; - cap.buffer = &buffer; - cap.vbo = i_buffer.vbo; - - // calculate offset into the index buffer - unsigned int offset = sub_path.first.i_id; - offset += 6; // add 2 triangles for corner cap - offset += static_cast(m_sequential_view.current.first - sub_path.first.s_id) * buffer.indices_per_segment(); - if (sub_path_id == 0) - offset += 6; // add 2 triangles for starting cap - - // extract indices from index buffer - std::array indices{ 0, 0, 0, 0, 0, 0 }; - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo)); - glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 0) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[0]))); - glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 7) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[1]))); - glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 1) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[2]))); - glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 13) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[4]))); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - indices[3] = indices[0]; - indices[5] = indices[1]; - - // send indices to gpu - glsafe(::glGenBuffers(1, &cap.ibo)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cap.ibo)); - glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(IBufferType), indices.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - - // extract color from render path - size_t offset_bytes = offset * sizeof(IBufferType); - for (const RenderPath& render_path : buffer.render_paths) { - if (render_path.ibuffer_id == ibuffer_id) { - for (size_t j = 0; j < render_path.offsets.size(); ++j) { - if (render_path.contains(offset_bytes)) { - cap.color = render_path.color; - break; - } - } - } - } - } - - // update cap for last endpoint of current range - if (m_sequential_view.current.last < sub_path.last.s_id) { - SequentialRangeCap& cap = (*sequential_range_caps)[1]; - const IBuffer& i_buffer = buffer.indices[ibuffer_id]; - cap.buffer = &buffer; - cap.vbo = i_buffer.vbo; - - // calculate offset into the index buffer - unsigned int offset = sub_path.first.i_id; - offset += 6; // add 2 triangles for corner cap - offset += static_cast(m_sequential_view.current.last - 1 - sub_path.first.s_id) * buffer.indices_per_segment(); - if (sub_path_id == 0) - offset += 6; // add 2 triangles for starting cap - - // extract indices from index buffer - std::array indices{ 0, 0, 0, 0, 0, 0 }; - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo)); - glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 2) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[0]))); - glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 4) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[1]))); - glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 10) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[2]))); - glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 16) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[5]))); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - indices[3] = indices[0]; - indices[4] = indices[2]; - - // send indices to gpu - glsafe(::glGenBuffers(1, &cap.ibo)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cap.ibo)); - glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * sizeof(IBufferType), indices.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - - // extract color from render path - size_t offset_bytes = offset * sizeof(IBufferType); - for (const RenderPath& render_path : buffer.render_paths) { - if (render_path.ibuffer_id == ibuffer_id) { - for (size_t j = 0; j < render_path.offsets.size(); ++j) { - if (render_path.contains(offset_bytes)) { - cap.color = render_path.color; - break; - } - } - } - } - } - - if ((*sequential_range_caps)[0].is_renderable() && (*sequential_range_caps)[1].is_renderable()) - break; - } - } - - wxGetApp().plater()->enable_preview_moves_slider(!paths.empty()); - -#if ENABLE_GCODE_VIEWER_STATISTICS - for (const TBuffer& buffer : m_buffers) { - statistics->render_paths_size += SLIC3R_STDUNORDEREDSET_MEMSIZE(buffer.render_paths, RenderPath); - for (const RenderPath& path : buffer.render_paths) { - statistics->render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.sizes, unsigned int); - statistics->render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.offsets, size_t); - } - statistics->models_instances_size += SLIC3R_STDVEC_MEMSIZE(buffer.model.instances.buffer, float); - statistics->models_instances_size += SLIC3R_STDVEC_MEMSIZE(buffer.model.instances.s_ids, size_t); - statistics->models_instances_size += SLIC3R_STDVEC_MEMSIZE(buffer.model.instances.render_ranges.ranges, InstanceVBuffer::Ranges::Range); - } - statistics->refresh_paths_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS -} - -void GCodeViewer::render_toolpaths() -{ -#if !ENABLE_GL_SHADERS_ATTRIBUTES - const std::array light_intensity = { 0.25f, 0.70f, 0.75f, 0.75f }; -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - const Camera& camera = wxGetApp().plater()->get_camera(); - const double zoom = camera.get_zoom(); - -#if !ENABLE_GL_SHADERS_ATTRIBUTES - auto shader_init_as_lines = [light_intensity](GLShaderProgram &shader) { - shader.set_uniform("light_intensity", light_intensity); - }; -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - auto render_as_lines = [ -#if ENABLE_GCODE_VIEWER_STATISTICS - this -#endif // ENABLE_GCODE_VIEWER_STATISTICS - ](std::vector::iterator it_path, std::vector::iterator it_end, GLShaderProgram& shader, int uniform_color) { - for (auto it = it_path; it != it_end && it_path->ibuffer_id == it->ibuffer_id; ++it) { - const RenderPath& path = *it; - // Some OpenGL drivers crash on empty glMultiDrawElements, see GH #7415. - assert(! path.sizes.empty()); - assert(! path.offsets.empty()); - shader.set_uniform(uniform_color, path.color); - glsafe(::glMultiDrawElements(GL_LINES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_SHORT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); -#if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_multi_lines_calls_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - } - }; - - auto render_as_triangles = [ -#if ENABLE_GCODE_VIEWER_STATISTICS - this -#endif // ENABLE_GCODE_VIEWER_STATISTICS - ](std::vector::iterator it_path, std::vector::iterator it_end, GLShaderProgram& shader, int uniform_color) { - for (auto it = it_path; it != it_end && it_path->ibuffer_id == it->ibuffer_id; ++it) { - const RenderPath& path = *it; - // Some OpenGL drivers crash on empty glMultiDrawElements, see GH #7415. - assert(! path.sizes.empty()); - assert(! path.offsets.empty()); - shader.set_uniform(uniform_color, path.color); - glsafe(::glMultiDrawElements(GL_TRIANGLES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_SHORT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); -#if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_multi_triangles_calls_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - } - }; - - auto render_as_instanced_model = [ -#if ENABLE_GCODE_VIEWER_STATISTICS - this -#endif // ENABLE_GCODE_VIEWER_STATISTICS - ](TBuffer& buffer, GLShaderProgram & shader) { - for (auto& range : buffer.model.instances.render_ranges.ranges) { - if (range.vbo == 0 && range.count > 0) { - glsafe(::glGenBuffers(1, &range.vbo)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, range.vbo)); - glsafe(::glBufferData(GL_ARRAY_BUFFER, range.count * buffer.model.instances.instance_size_bytes(), (const void*)&buffer.model.instances.buffer[range.offset * buffer.model.instances.instance_size_floats()], GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - } - - if (range.vbo > 0) { -#if ENABLE_LEGACY_OPENGL_REMOVAL - buffer.model.model.set_color(range.color); -#else - buffer.model.model.set_color(-1, range.color); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - buffer.model.model.render_instanced(range.vbo, range.count); -#if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_instanced_models_calls_count; - m_statistics.total_instances_gpu_size += static_cast(range.count * buffer.model.instances.instance_size_bytes()); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - } - } - }; - -#if ENABLE_GL_SHADERS_ATTRIBUTES -#if ENABLE_GCODE_VIEWER_STATISTICS - auto render_as_batched_model = [this](TBuffer& buffer, GLShaderProgram& shader, int position_id, int normal_id) { -#else - auto render_as_batched_model = [](TBuffer& buffer, GLShaderProgram& shader, int position_id, int normal_id) { -#endif // ENABLE_GCODE_VIEWER_STATISTICS -#else -#if ENABLE_GCODE_VIEWER_STATISTICS - auto render_as_batched_model = [this](TBuffer& buffer, GLShaderProgram& shader) { -#else - auto render_as_batched_model = [](TBuffer& buffer, GLShaderProgram& shader) { -#endif // ENABLE_GCODE_VIEWER_STATISTICS -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - struct Range - { - unsigned int first; - unsigned int last; - bool intersects(const Range& other) const { return (other.last < first || other.first > last) ? false : true; } - }; - Range buffer_range = { 0, 0 }; - const size_t indices_per_instance = buffer.model.data.indices_count(); - - for (size_t j = 0; j < buffer.indices.size(); ++j) { - const IBuffer& i_buffer = buffer.indices[j]; - buffer_range.last = buffer_range.first + i_buffer.count / indices_per_instance; - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, i_buffer.vbo)); -#if ENABLE_GL_SHADERS_ATTRIBUTES - if (position_id != -1) { - glsafe(::glVertexAttribPointer(position_id, buffer.vertices.position_size_floats(), GL_FLOAT, GL_FALSE, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_bytes())); - glsafe(::glEnableVertexAttribArray(position_id)); - } -#else - glsafe(::glVertexPointer(buffer.vertices.position_size_floats(), GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_bytes())); - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - const bool has_normals = buffer.vertices.normal_size_floats() > 0; - if (has_normals) { -#if ENABLE_GL_SHADERS_ATTRIBUTES - if (normal_id != -1) { - glsafe(::glVertexAttribPointer(normal_id, buffer.vertices.normal_size_floats(), GL_FLOAT, GL_FALSE, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_bytes())); - glsafe(::glEnableVertexAttribArray(normal_id)); - } -#else - glsafe(::glNormalPointer(GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_bytes())); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - } - - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo)); - - for (auto& range : buffer.model.instances.render_ranges.ranges) { - const Range range_range = { range.offset, range.offset + range.count }; - if (range_range.intersects(buffer_range)) { - shader.set_uniform("uniform_color", range.color); - const unsigned int offset = (range_range.first > buffer_range.first) ? range_range.first - buffer_range.first : 0; - const size_t offset_bytes = static_cast(offset) * indices_per_instance * sizeof(IBufferType); - const Range render_range = { std::max(range_range.first, buffer_range.first), std::min(range_range.last, buffer_range.last) }; - const size_t count = static_cast(render_range.last - render_range.first) * indices_per_instance; - if (count > 0) { - glsafe(::glDrawElements(GL_TRIANGLES, (GLsizei)count, GL_UNSIGNED_SHORT, (const void*)offset_bytes)); -#if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_batched_models_calls_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - } - } - } - - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - -#if ENABLE_GL_SHADERS_ATTRIBUTES - if (normal_id != -1) - glsafe(::glDisableVertexAttribArray(normal_id)); - if (position_id != -1) - glsafe(::glDisableVertexAttribArray(position_id)); -#else - if (has_normals) - glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); - - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - - buffer_range.first = buffer_range.last; - } - }; - - auto line_width = [](double zoom) { - return (zoom < 5.0) ? 1.0 : (1.0 + 5.0 * (zoom - 5.0) / (100.0 - 5.0)); - }; - - const unsigned char begin_id = buffer_id(EMoveType::Retract); - const unsigned char end_id = buffer_id(EMoveType::Count); - - for (unsigned char i = begin_id; i < end_id; ++i) { - TBuffer& buffer = m_buffers[i]; - if (!buffer.visible || !buffer.has_data()) - continue; - - GLShaderProgram* shader = wxGetApp().get_shader(buffer.shader.c_str()); - if (shader == nullptr) - continue; - - shader->start_using(); - -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Transform3d& view_matrix = camera.get_view_matrix(); - shader->set_uniform("view_model_matrix", view_matrix); - shader->set_uniform("projection_matrix", camera.get_projection_matrix()); - shader->set_uniform("normal_matrix", (Matrix3d)view_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::InstancedModel) { - shader->set_uniform("emission_factor", 0.25f); - render_as_instanced_model(buffer, *shader); - shader->set_uniform("emission_factor", 0.0f); - } - else if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) { - shader->set_uniform("emission_factor", 0.25f); -#if ENABLE_GL_SHADERS_ATTRIBUTES - const int position_id = shader->get_attrib_location("v_position"); - const int normal_id = shader->get_attrib_location("v_normal"); - render_as_batched_model(buffer, *shader, position_id, normal_id); -#else - render_as_batched_model(buffer, *shader); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - shader->set_uniform("emission_factor", 0.0f); - } - else { -#if ENABLE_GL_SHADERS_ATTRIBUTES - const int position_id = shader->get_attrib_location("v_position"); - const int normal_id = shader->get_attrib_location("v_normal"); -#else - if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Line) - shader_init_as_lines(*shader); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - const int uniform_color = shader->get_uniform_location("uniform_color"); - - auto it_path = buffer.render_paths.begin(); - for (unsigned int ibuffer_id = 0; ibuffer_id < static_cast(buffer.indices.size()); ++ibuffer_id) { - const IBuffer& i_buffer = buffer.indices[ibuffer_id]; - // Skip all paths with ibuffer_id < ibuffer_id. - for (; it_path != buffer.render_paths.end() && it_path->ibuffer_id < ibuffer_id; ++it_path); - if (it_path == buffer.render_paths.end() || it_path->ibuffer_id > ibuffer_id) - // Not found. This shall not happen. - continue; - - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, i_buffer.vbo)); -#if ENABLE_GL_SHADERS_ATTRIBUTES - if (position_id != -1) { - glsafe(::glVertexAttribPointer(position_id, buffer.vertices.position_size_floats(), GL_FLOAT, GL_FALSE, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_bytes())); - glsafe(::glEnableVertexAttribArray(position_id)); - } -#else - glsafe(::glVertexPointer(buffer.vertices.position_size_floats(), GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_bytes())); - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - const bool has_normals = buffer.vertices.normal_size_floats() > 0; - if (has_normals) { -#if ENABLE_GL_SHADERS_ATTRIBUTES - if (normal_id != -1) { - glsafe(::glVertexAttribPointer(normal_id, buffer.vertices.normal_size_floats(), GL_FLOAT, GL_FALSE, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_bytes())); - glsafe(::glEnableVertexAttribArray(normal_id)); - } -#else - glsafe(::glNormalPointer(GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_bytes())); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - } - - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo)); - - // Render all elements with it_path->ibuffer_id == ibuffer_id, possible with varying colors. - switch (buffer.render_primitive_type) - { - case TBuffer::ERenderPrimitiveType::Line: { - glsafe(::glLineWidth(static_cast(line_width(zoom)))); - render_as_lines(it_path, buffer.render_paths.end(), *shader, uniform_color); - break; - } - case TBuffer::ERenderPrimitiveType::Triangle: { - render_as_triangles(it_path, buffer.render_paths.end(), *shader, uniform_color); - break; - } - default: { break; } - } - - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - -#if ENABLE_GL_SHADERS_ATTRIBUTES - if (normal_id != -1) - glsafe(::glDisableVertexAttribArray(normal_id)); - if (position_id != -1) - glsafe(::glDisableVertexAttribArray(position_id)); -#else - if (has_normals) - glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); - - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - } - } - - shader->stop_using(); - } - -#if ENABLE_GCODE_VIEWER_STATISTICS - auto render_sequential_range_cap = [this, &camera] -#else - auto render_sequential_range_cap = [&camera] -#endif // ENABLE_GCODE_VIEWER_STATISTICS - (const SequentialRangeCap& cap) { - const TBuffer* buffer = cap.buffer; - GLShaderProgram* shader = wxGetApp().get_shader(buffer->shader.c_str()); - if (shader == nullptr) - return; - - shader->start_using(); - -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Transform3d& view_matrix = camera.get_view_matrix(); - shader->set_uniform("view_model_matrix", view_matrix); - shader->set_uniform("projection_matrix", camera.get_projection_matrix()); - shader->set_uniform("normal_matrix", (Matrix3d)view_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); - - const int position_id = shader->get_attrib_location("v_position"); - const int normal_id = shader->get_attrib_location("v_normal"); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, cap.vbo)); -#if ENABLE_GL_SHADERS_ATTRIBUTES - if (position_id != -1) { - glsafe(::glVertexAttribPointer(position_id, buffer->vertices.position_size_floats(), GL_FLOAT, GL_FALSE, buffer->vertices.vertex_size_bytes(), (const void*)buffer->vertices.position_offset_bytes())); - glsafe(::glEnableVertexAttribArray(position_id)); - } -#else - glsafe(::glVertexPointer(buffer->vertices.position_size_floats(), GL_FLOAT, buffer->vertices.vertex_size_bytes(), (const void*)buffer->vertices.position_offset_bytes())); - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - const bool has_normals = buffer->vertices.normal_size_floats() > 0; - if (has_normals) { -#if ENABLE_GL_SHADERS_ATTRIBUTES - if (normal_id != -1) { - glsafe(::glVertexAttribPointer(normal_id, buffer->vertices.normal_size_floats(), GL_FLOAT, GL_FALSE, buffer->vertices.vertex_size_bytes(), (const void*)buffer->vertices.normal_offset_bytes())); - glsafe(::glEnableVertexAttribArray(normal_id)); - } -#else - glsafe(::glNormalPointer(GL_FLOAT, buffer->vertices.vertex_size_bytes(), (const void*)buffer->vertices.normal_offset_bytes())); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - } - - shader->set_uniform("uniform_color", cap.color); - - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cap.ibo)); - glsafe(::glDrawElements(GL_TRIANGLES, (GLsizei)cap.indices_count(), GL_UNSIGNED_SHORT, nullptr)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - -#if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_triangles_calls_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - -#if ENABLE_GL_SHADERS_ATTRIBUTES - if (normal_id != -1) - glsafe(::glDisableVertexAttribArray(normal_id)); - if (position_id != -1) - glsafe(::glDisableVertexAttribArray(position_id)); -#else - if (has_normals) - glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); - - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - - shader->stop_using(); - }; - - for (unsigned int i = 0; i < 2; ++i) { - if (m_sequential_range_caps[i].is_renderable()) - render_sequential_range_cap(m_sequential_range_caps[i]); - } -} - -void GCodeViewer::render_shells() -{ - if (!m_shells.visible || m_shells.volumes.empty()) - return; - - GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); - if (shader == nullptr) - return; - -#if !ENABLE_LEGACY_OPENGL_REMOVAL - // when the background processing is enabled, it may happen that the shells data have been loaded - // before opengl has been initialized for the preview canvas. - // when this happens, the volumes' data have not been sent to gpu yet. - for (GLVolume* v : m_shells.volumes.volumes) { - if (!v->indexed_vertex_array.has_VBOs()) - v->finalize_geometry(true); - } -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - -// glsafe(::glDepthMask(GL_FALSE)); - - shader->start_using(); -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Camera& camera = wxGetApp().plater()->get_camera(); - m_shells.volumes.render(GLVolumeCollection::ERenderType::Transparent, true, camera.get_view_matrix(), camera.get_projection_matrix()); -#else - m_shells.volumes.render(GLVolumeCollection::ERenderType::Transparent, true, wxGetApp().plater()->get_camera().get_view_matrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - shader->stop_using(); - -// glsafe(::glDepthMask(GL_TRUE)); -} - -void GCodeViewer::render_legend(float& legend_height) -{ - if (!m_legend_enabled) - return; - - const Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size(); - - ImGuiWrapper& imgui = *wxGetApp().imgui(); - - imgui.set_next_window_pos(0.0f, 0.0f, ImGuiCond_Always); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); - ImGui::SetNextWindowBgAlpha(0.6f); - const float max_height = 0.75f * static_cast(cnv_size.get_height()); - const float child_height = 0.3333f * max_height; - ImGui::SetNextWindowSizeConstraints({ 0.0f, 0.0f }, { -1.0f, max_height }); -#if ENABLE_PREVIEW_LAYOUT - imgui.begin(std::string("Legend"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove); -#else - imgui.begin(std::string("Legend"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove); -#endif // ENABLE_PREVIEW_LAYOUT - - enum class EItemType : unsigned char - { - Rect, - Circle, - Hexagon, - Line - }; - - const PrintEstimatedStatistics::Mode& time_mode = m_print_statistics.modes[static_cast(m_time_estimate_mode)]; -#if ENABLE_PREVIEW_LAYER_TIME - bool show_estimated_time = time_mode.time > 0.0f && (m_view_type == EViewType::FeatureType || - m_view_type == EViewType::LayerTimeLinear || m_view_type == EViewType::LayerTimeLogarithmic || - (m_view_type == EViewType::ColorPrint && !time_mode.custom_gcode_times.empty())); -#else - bool show_estimated_time = time_mode.time > 0.0f && (m_view_type == EViewType::FeatureType || - (m_view_type == EViewType::ColorPrint && !time_mode.custom_gcode_times.empty())); -#endif // ENABLE_PREVIEW_LAYER_TIME - - const float icon_size = ImGui::GetTextLineHeight(); - const float percent_bar_size = 2.0f * ImGui::GetTextLineHeight(); - - bool imperial_units = wxGetApp().app_config->get("use_inches") == "1"; - - auto append_item = [icon_size, percent_bar_size, &imgui, imperial_units](EItemType type, const ColorRGBA& color, const std::string& label, - bool visible = true, const std::string& time = "", float percent = 0.0f, float max_percent = 0.0f, const std::array& offsets = { 0.0f, 0.0f, 0.0f, 0.0f }, - double used_filament_m = 0.0, double used_filament_g = 0.0, - std::function callback = nullptr) { - if (!visible) - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); - - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - ImVec2 pos = ImGui::GetCursorScreenPos(); - switch (type) { - default: - case EItemType::Rect: { - draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, - ImGuiWrapper::to_ImU32(color)); - break; - } - case EItemType::Circle: { - ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); - draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGuiWrapper::to_ImU32(color), 16); - break; - } - case EItemType::Hexagon: { - ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); - draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGuiWrapper::to_ImU32(color), 6); - break; - } - case EItemType::Line: { - draw_list->AddLine({ pos.x + 1, pos.y + icon_size - 1 }, { pos.x + icon_size - 1, pos.y + 1 }, ImGuiWrapper::to_ImU32(color), 3.0f); - break; - } - } - - // draw text - ImGui::Dummy({ icon_size, icon_size }); - ImGui::SameLine(); - if (callback != nullptr) { - if (ImGui::MenuItem(label.c_str())) - callback(); - else { - // show tooltip - if (ImGui::IsItemHovered()) { - if (!visible) - ImGui::PopStyleVar(); - ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND); - ImGui::BeginTooltip(); - imgui.text(visible ? _u8L("Click to hide") : _u8L("Click to show")); - ImGui::EndTooltip(); - ImGui::PopStyleColor(); - if (!visible) - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); - - // to avoid the tooltip to change size when moving the mouse - imgui.set_requires_extra_frame(); - } - } - - if (!time.empty()) { - ImGui::SameLine(offsets[0]); - imgui.text(time); - ImGui::SameLine(offsets[1]); - pos = ImGui::GetCursorScreenPos(); - const float width = std::max(1.0f, percent_bar_size * percent / max_percent); - draw_list->AddRectFilled({ pos.x, pos.y + 2.0f }, { pos.x + width, pos.y + icon_size - 2.0f }, - ImGui::GetColorU32(ImGuiWrapper::COL_ORANGE_LIGHT)); - ImGui::Dummy({ percent_bar_size, icon_size }); - ImGui::SameLine(); - char buf[64]; - ::sprintf(buf, "%.1f%%", 100.0f * percent); - ImGui::TextUnformatted((percent > 0.0f) ? buf : ""); - ImGui::SameLine(offsets[2]); - ::sprintf(buf, imperial_units ? "%.2f in" : "%.2f m", used_filament_m); - imgui.text(buf); - ImGui::SameLine(offsets[3]); - ::sprintf(buf, "%.2f g", used_filament_g); - imgui.text(buf); - } - } - else { - imgui.text(label); -#if ENABLE_TRAVEL_TIME - if (!time.empty()) { - ImGui::SameLine(offsets[0]); - imgui.text(time); - ImGui::SameLine(offsets[1]); - pos = ImGui::GetCursorScreenPos(); - const float width = std::max(1.0f, percent_bar_size * percent / max_percent); - draw_list->AddRectFilled({ pos.x, pos.y + 2.0f }, { pos.x + width, pos.y + icon_size - 2.0f }, - ImGui::GetColorU32(ImGuiWrapper::COL_ORANGE_LIGHT)); - ImGui::Dummy({ percent_bar_size, icon_size }); - ImGui::SameLine(); - char buf[64]; - ::sprintf(buf, "%.1f%%", 100.0f * percent); - ImGui::TextUnformatted((percent > 0.0f) ? buf : ""); - } - else if (used_filament_m > 0.0) { -#else - if (used_filament_m > 0.0) { -#endif // ENABLE_TRAVEL_TIME - char buf[64]; - ImGui::SameLine(offsets[0]); - ::sprintf(buf, imperial_units ? "%.2f in" : "%.2f m", used_filament_m); - imgui.text(buf); - ImGui::SameLine(offsets[1]); - ::sprintf(buf, "%.2f g", used_filament_g); - imgui.text(buf); - } - } - - if (!visible) - ImGui::PopStyleVar(); - }; - - auto append_range = [append_item](const Extrusions::Range& range, unsigned int decimals) { - auto append_range_item = [append_item](int i, float value, unsigned int decimals) { - char buf[1024]; - ::sprintf(buf, "%.*f", decimals, value); - append_item(EItemType::Rect, Range_Colors[i], buf); - }; - - if (range.count == 1) - // single item use case - append_range_item(0, range.min, decimals); - else if (range.count == 2) { - // two items use case - append_range_item(static_cast(Range_Colors.size()) - 1, range.max, decimals); - append_range_item(0, range.min, decimals); - } - else { - const float step_size = range.step_size(); - for (int i = static_cast(Range_Colors.size()) - 1; i >= 0; --i) { - append_range_item(i, range.min + static_cast(i) * step_size, decimals); - } - } - }; - -#if ENABLE_PREVIEW_LAYER_TIME - auto append_time_range = [append_item](const Extrusions::Range& range, Extrusions::Range::EType type) { - auto append_range_item = [append_item](int i, float value) { - std::string str_value = get_time_dhms(value); - if (str_value == "0s") - str_value = "< 1s"; - append_item(EItemType::Rect, Range_Colors[i], str_value); - }; - - if (range.count == 1) - // single item use case - append_range_item(0, range.min); - else if (range.count == 2) { - // two items use case - append_range_item(static_cast(Range_Colors.size()) - 1, range.max); - append_range_item(0, range.min); - } - else { - float step_size = range.step_size(type); - for (int i = static_cast(Range_Colors.size()) - 1; i >= 0; --i) { - float value = 0.0f; - switch (type) - { - default: - case Extrusions::Range::EType::Linear: { value = range.min + static_cast(i) * step_size; break; } - case Extrusions::Range::EType::Logarithmic: { value = ::exp(::log(range.min) + static_cast(i) * step_size); break; } - } - append_range_item(i, value); - } - } - }; -#endif // ENABLE_PREVIEW_LAYER_TIME - - auto append_headers = [&imgui](const std::array& texts, const std::array& offsets) { - size_t i = 0; - for (; i < offsets.size(); i++) { - imgui.text(texts[i]); - ImGui::SameLine(offsets[i]); - } - imgui.text(texts[i]); - ImGui::Separator(); - }; - - auto max_width = [](const std::vector& items, const std::string& title, float extra_size = 0.0f) { - float ret = ImGui::CalcTextSize(title.c_str()).x; - for (const std::string& item : items) { - ret = std::max(ret, extra_size + ImGui::CalcTextSize(item.c_str()).x); - } - return ret; - }; - - auto calculate_offsets = [max_width](const std::vector& labels, const std::vector& times, - const std::array& titles, float extra_size = 0.0f) { - const ImGuiStyle& style = ImGui::GetStyle(); - std::array ret = { 0.0f, 0.0f, 0.0f, 0.0f }; - ret[0] = max_width(labels, titles[0], extra_size) + 3.0f * style.ItemSpacing.x; - for (size_t i = 1; i < titles.size(); i++) - ret[i] = ret[i-1] + max_width(times, titles[i]) + style.ItemSpacing.x; - return ret; - }; - - auto color_print_ranges = [this](unsigned char extruder_id, const std::vector& custom_gcode_per_print_z) { - std::vector>> ret; - ret.reserve(custom_gcode_per_print_z.size()); - - for (const auto& item : custom_gcode_per_print_z) { - if (extruder_id + 1 != static_cast(item.extruder)) - continue; - - if (item.type != ColorChange) - continue; - - const std::vector zs = m_layers.get_zs(); - auto lower_b = std::lower_bound(zs.begin(), zs.end(), item.print_z - Slic3r::DoubleSlider::epsilon()); - if (lower_b == zs.end()) - continue; - - const double current_z = *lower_b; - const double previous_z = (lower_b == zs.begin()) ? 0.0 : *(--lower_b); - - // to avoid duplicate values, check adding values - if (ret.empty() || !(ret.back().second.first == previous_z && ret.back().second.second == current_z)) { - ColorRGBA color; - decode_color(item.color, color); - ret.push_back({ color, { previous_z, current_z } }); - } - } - - return ret; - }; - - auto upto_label = [](double z) { - char buf[64]; - ::sprintf(buf, "%.2f", z); - return _u8L("up to") + " " + std::string(buf) + " " + _u8L("mm"); - }; - - auto above_label = [](double z) { - char buf[64]; - ::sprintf(buf, "%.2f", z); - return _u8L("above") + " " + std::string(buf) + " " + _u8L("mm"); - }; - - auto fromto_label = [](double z1, double z2) { - char buf1[64]; - ::sprintf(buf1, "%.2f", z1); - char buf2[64]; - ::sprintf(buf2, "%.2f", z2); - return _u8L("from") + " " + std::string(buf1) + " " + _u8L("to") + " " + std::string(buf2) + " " + _u8L("mm"); - }; - - auto role_time_and_percent = [time_mode](ExtrusionRole role) { - auto it = std::find_if(time_mode.roles_times.begin(), time_mode.roles_times.end(), [role](const std::pair& item) { return role == item.first; }); - return (it != time_mode.roles_times.end()) ? std::make_pair(it->second, it->second / time_mode.time) : std::make_pair(0.0f, 0.0f); - }; - - auto used_filament_per_role = [this, imperial_units](ExtrusionRole role) { - auto it = m_print_statistics.used_filaments_per_role.find(role); - if (it == m_print_statistics.used_filaments_per_role.end()) - return std::make_pair(0.0, 0.0); - - double koef = imperial_units ? 1000.0 / ObjectManipulation::in_to_mm : 1.0; - return std::make_pair(it->second.first * koef, it->second.second); - }; - - // data used to properly align items in columns when showing time - std::array offsets = { 0.0f, 0.0f, 0.0f, 0.0f }; - std::vector labels; - std::vector times; - std::vector percents; - std::vector used_filaments_m; - std::vector used_filaments_g; - float max_time_percent = 0.0f; - - if (m_view_type == EViewType::FeatureType) { - // calculate offsets to align time/percentage data - for (size_t i = 0; i < m_roles.size(); ++i) { - ExtrusionRole role = m_roles[i]; - if (role < erCount) { - labels.push_back(_u8L(ExtrusionEntity::role_to_string(role))); - auto [time, percent] = role_time_and_percent(role); - times.push_back((time > 0.0f) ? short_time(get_time_dhms(time)) : ""); - percents.push_back(percent); - max_time_percent = std::max(max_time_percent, percent); - auto [used_filament_m, used_filament_g] = used_filament_per_role(role); - used_filaments_m.push_back(used_filament_m); - used_filaments_g.push_back(used_filament_g); - } - } - - std::string longest_percentage_string; - for (double item : percents) { - char buffer[64]; - ::sprintf(buffer, "%.2f %%", item); - if (::strlen(buffer) > longest_percentage_string.length()) - longest_percentage_string = buffer; - } - longest_percentage_string += " "; - if (_u8L("Percentage").length() > longest_percentage_string.length()) - longest_percentage_string = _u8L("Percentage"); - - std::string longest_used_filament_string; - for (double item : used_filaments_m) { - char buffer[64]; - ::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", item); - if (::strlen(buffer) > longest_used_filament_string.length()) - longest_used_filament_string = buffer; - } - - offsets = calculate_offsets(labels, times, { _u8L("Feature type"), _u8L("Time"), longest_percentage_string, longest_used_filament_string }, icon_size); - } - - // get used filament (meters and grams) from used volume in respect to the active extruder - auto get_used_filament_from_volume = [this, imperial_units](double volume, int extruder_id) { - double koef = imperial_units ? 1.0 / ObjectManipulation::in_to_mm : 0.001; - std::pair ret = { koef * volume / (PI * sqr(0.5 * m_filament_diameters[extruder_id])), - volume * m_filament_densities[extruder_id] * 0.001 }; - return ret; - }; - - if (m_view_type == EViewType::Tool) { - // calculate used filaments data - for (size_t extruder_id : m_extruder_ids) { - if (m_print_statistics.volumes_per_extruder.find(extruder_id) == m_print_statistics.volumes_per_extruder.end()) - continue; - double volume = m_print_statistics.volumes_per_extruder.at(extruder_id); - - auto [used_filament_m, used_filament_g] = get_used_filament_from_volume(volume, extruder_id); - used_filaments_m.push_back(used_filament_m); - used_filaments_g.push_back(used_filament_g); - } - - std::string longest_used_filament_string; - for (double item : used_filaments_m) { - char buffer[64]; - ::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", item); - if (::strlen(buffer) > longest_used_filament_string.length()) - longest_used_filament_string = buffer; - } - - offsets = calculate_offsets(labels, times, { "Extruder NNN", longest_used_filament_string }, icon_size); - } - -#if ENABLE_PREVIEW_LAYOUT - // selection section - bool view_type_changed = false; - int old_view_type = static_cast(get_view_type()); - int view_type = old_view_type; - - if (!m_legend_resizer.dirty) - ImGui::SetNextItemWidth(-1.0f); - - ImGui::PushStyleColor(ImGuiCol_FrameBg, { 0.1f, 0.1f, 0.1f, 0.8f }); - ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, { 0.2f, 0.2f, 0.2f, 0.8f }); - imgui.combo("", { _u8L("Feature type"), - _u8L("Height (mm)"), - _u8L("Width (mm)"), - _u8L("Speed (mm/s)"), - _u8L("Fan speed (%)"), - _u8L("Temperature (°C)"), - _u8L("Volumetric flow rate (mm³/s)"), -#if ENABLE_PREVIEW_LAYER_TIME - _u8L("Layer time (linear)"), - _u8L("Layer time (logarithmic)"), -#endif // ENABLE_PREVIEW_LAYER_TIME - _u8L("Tool"), - _u8L("Color Print") }, view_type, ImGuiComboFlags_HeightLargest); - ImGui::PopStyleColor(2); - - if (old_view_type != view_type) { - set_view_type(static_cast(view_type)); - wxGetApp().plater()->set_keep_current_preview_type(true); - wxGetApp().plater()->refresh_print(); - view_type_changed = true; - } - - // extrusion paths section -> title - if (m_view_type == EViewType::FeatureType) - append_headers({ _u8L(""), _u8L("Time"), _u8L("Percentage"), _u8L("Used filament") }, offsets); - else if (m_view_type == EViewType::Tool) - append_headers({ _u8L(""), _u8L("Used filament"), _u8L(""), _u8L("") }, offsets); - else - ImGui::Separator(); -#else - // extrusion paths section -> title - switch (m_view_type) - { - case EViewType::FeatureType: - { - append_headers({ _u8L("Feature type"), _u8L("Time"), _u8L("Percentage"), _u8L("Used filament") }, offsets); - break; - } - case EViewType::Height: { imgui.title(_u8L("Height (mm)")); break; } - case EViewType::Width: { imgui.title(_u8L("Width (mm)")); break; } - case EViewType::Feedrate: { imgui.title(_u8L("Speed (mm/s)")); break; } - case EViewType::FanSpeed: { imgui.title(_u8L("Fan Speed (%)")); break; } - case EViewType::Temperature: { imgui.title(_u8L("Temperature (°C)")); break; } - case EViewType::VolumetricRate: { imgui.title(_u8L("Volumetric flow rate (mm³/s)")); break; } -#if ENABLE_PREVIEW_LAYER_TIME - case EViewType::LayerTimeLinear: { imgui.title(_u8L("Layer time (linear)")); break; } - case EViewType::LayerTimeLogarithmic: { imgui.title(_u8L("Layer time (logarithmic)")); break; } -#endif // ENABLE_PREVIEW_LAYER_TIME - case EViewType::Tool: { - append_headers({ _u8L("Tool"), _u8L("Used filament") }, offsets); - break; - } - case EViewType::ColorPrint: { imgui.title(_u8L("Color Print")); break; } - default: { break; } - } -#endif // ENABLE_PREVIEW_LAYOUT - -#if ENABLE_PREVIEW_LAYOUT - if (!view_type_changed) { -#endif // ENABLE_PREVIEW_LAYOUT - // extrusion paths section -> items - switch (m_view_type) - { - case EViewType::FeatureType: - { -#if ENABLE_TRAVEL_TIME - max_time_percent = std::max(max_time_percent, time_mode.travel_time / time_mode.time); -#endif // ENABLE_TRAVEL_TIME - - for (size_t i = 0; i < m_roles.size(); ++i) { - ExtrusionRole role = m_roles[i]; - if (role >= erCount) - continue; - const bool visible = is_visible(role); - append_item(EItemType::Rect, Extrusion_Role_Colors[static_cast(role)], labels[i], - visible, times[i], percents[i], max_time_percent, offsets, used_filaments_m[i], used_filaments_g[i], [this, role, visible]() { - m_extrusions.role_visibility_flags = visible ? m_extrusions.role_visibility_flags & ~(1 << role) : m_extrusions.role_visibility_flags | (1 << role); - // update buffers' render paths - refresh_render_paths(false, false); - wxGetApp().plater()->update_preview_moves_slider(); - wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); -#if !ENABLE_PREVIEW_LAYOUT - wxGetApp().plater()->update_preview_bottom_toolbar(); -#endif // !ENABLE_PREVIEW_LAYOUT - } - ); - } - -#if ENABLE_TRAVEL_TIME - if (m_buffers[buffer_id(EMoveType::Travel)].visible) - append_item(EItemType::Line, Travel_Colors[0], _u8L("Travel"), true, short_time(get_time_dhms(time_mode.travel_time)), - time_mode.travel_time / time_mode.time, max_time_percent, offsets, 0.0f, 0.0f); -#endif // ENABLE_TRAVEL_TIME - - break; - } - case EViewType::Height: { append_range(m_extrusions.ranges.height, 3); break; } - case EViewType::Width: { append_range(m_extrusions.ranges.width, 3); break; } - case EViewType::Feedrate: { append_range(m_extrusions.ranges.feedrate, 1); break; } - case EViewType::FanSpeed: { append_range(m_extrusions.ranges.fan_speed, 0); break; } - case EViewType::Temperature: { append_range(m_extrusions.ranges.temperature, 0); break; } - case EViewType::VolumetricRate: { append_range(m_extrusions.ranges.volumetric_rate, 3); break; } -#if ENABLE_PREVIEW_LAYER_TIME - case EViewType::LayerTimeLinear: { append_time_range(m_extrusions.ranges.layer_time[static_cast(m_time_estimate_mode)], Extrusions::Range::EType::Linear); break; } - case EViewType::LayerTimeLogarithmic: { append_time_range(m_extrusions.ranges.layer_time[static_cast(m_time_estimate_mode)], Extrusions::Range::EType::Logarithmic); break; } -#endif // ENABLE_PREVIEW_LAYER_TIME - case EViewType::Tool: { - // shows only extruders actually used - size_t i = 0; - for (unsigned char extruder_id : m_extruder_ids) { - append_item(EItemType::Rect, m_tool_colors[extruder_id], _u8L("Extruder") + " " + std::to_string(extruder_id + 1), - true, "", 0.0f, 0.0f, offsets, used_filaments_m[i], used_filaments_g[i]); - ++i; - } - break; - } - case EViewType::ColorPrint: - { - const std::vector& custom_gcode_per_print_z = wxGetApp().is_editor() ? wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes : m_custom_gcode_per_print_z; - size_t total_items = 1; - for (unsigned char i : m_extruder_ids) { - total_items += color_print_ranges(i, custom_gcode_per_print_z).size(); - } - - const bool need_scrollable = static_cast(total_items) * icon_size + (static_cast(total_items) - 1.0f) * ImGui::GetStyle().ItemSpacing.y > child_height; - - // add scrollable region, if needed - if (need_scrollable) - ImGui::BeginChild("color_prints", { -1.0f, child_height }, false); - if (m_extruders_count == 1) { // single extruder use case - const std::vector>> cp_values = color_print_ranges(0, custom_gcode_per_print_z); - const int items_cnt = static_cast(cp_values.size()); - if (items_cnt == 0) // There are no color changes, but there are some pause print or custom Gcode - append_item(EItemType::Rect, m_tool_colors.front(), _u8L("Default color")); - else { - for (int i = items_cnt; i >= 0; --i) { - // create label for color change item - if (i == 0) { - append_item(EItemType::Rect, m_tool_colors[0], upto_label(cp_values.front().second.first)); - break; - } - else if (i == items_cnt) { - append_item(EItemType::Rect, cp_values[i - 1].first, above_label(cp_values[i - 1].second.second)); - continue; - } - append_item(EItemType::Rect, cp_values[i - 1].first, fromto_label(cp_values[i - 1].second.second, cp_values[i].second.first)); - } - } - } - else { // multi extruder use case - // shows only extruders actually used - for (unsigned char i : m_extruder_ids) { - const std::vector>> cp_values = color_print_ranges(i, custom_gcode_per_print_z); - const int items_cnt = static_cast(cp_values.size()); - if (items_cnt == 0) - // There are no color changes, but there are some pause print or custom Gcode - append_item(EItemType::Rect, m_tool_colors[i], _u8L("Extruder") + " " + std::to_string(i + 1) + " " + _u8L("default color")); - else { - for (int j = items_cnt; j >= 0; --j) { - // create label for color change item - std::string label = _u8L("Extruder") + " " + std::to_string(i + 1); - if (j == 0) { - label += " " + upto_label(cp_values.front().second.first); - append_item(EItemType::Rect, m_tool_colors[i], label); - break; - } - else if (j == items_cnt) { - label += " " + above_label(cp_values[j - 1].second.second); - append_item(EItemType::Rect, cp_values[j - 1].first, label); - continue; - } - - label += " " + fromto_label(cp_values[j - 1].second.second, cp_values[j].second.first); - append_item(EItemType::Rect, cp_values[j - 1].first, label); - } - } - } - } - if (need_scrollable) - ImGui::EndChild(); - - break; - } - default: { break; } - } -#if ENABLE_PREVIEW_LAYOUT - } -#endif // ENABLE_PREVIEW_LAYOUT - - // partial estimated printing time section - if (m_view_type == EViewType::ColorPrint) { - using Times = std::pair; - using TimesList = std::vector>; - - // helper structure containig the data needed to render the time items - struct PartialTime - { - enum class EType : unsigned char - { - Print, - ColorChange, - Pause - }; - EType type; - int extruder_id; - ColorRGBA color1; - ColorRGBA color2; - Times times; - std::pair used_filament{ 0.0f, 0.0f }; - }; - using PartialTimes = std::vector; - - auto generate_partial_times = [this, get_used_filament_from_volume](const TimesList& times, const std::vector& used_filaments) { - PartialTimes items; - - std::vector custom_gcode_per_print_z = wxGetApp().is_editor() ? wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes : m_custom_gcode_per_print_z; - int extruders_count = wxGetApp().extruders_edited_cnt(); - std::vector last_color(extruders_count); - for (int i = 0; i < extruders_count; ++i) { - last_color[i] = m_tool_colors[i]; - } - int last_extruder_id = 1; - int color_change_idx = 0; - for (const auto& time_rec : times) { - switch (time_rec.first) - { - case CustomGCode::PausePrint: { - auto it = std::find_if(custom_gcode_per_print_z.begin(), custom_gcode_per_print_z.end(), [time_rec](const CustomGCode::Item& item) { return item.type == time_rec.first; }); - if (it != custom_gcode_per_print_z.end()) { - items.push_back({ PartialTime::EType::Print, it->extruder, last_color[it->extruder - 1], ColorRGBA::BLACK(), time_rec.second }); - items.push_back({ PartialTime::EType::Pause, it->extruder, ColorRGBA::BLACK(), ColorRGBA::BLACK(), time_rec.second }); - custom_gcode_per_print_z.erase(it); - } - break; - } - case CustomGCode::ColorChange: { - auto it = std::find_if(custom_gcode_per_print_z.begin(), custom_gcode_per_print_z.end(), [time_rec](const CustomGCode::Item& item) { return item.type == time_rec.first; }); - if (it != custom_gcode_per_print_z.end()) { - items.push_back({ PartialTime::EType::Print, it->extruder, last_color[it->extruder - 1], ColorRGBA::BLACK(), time_rec.second, get_used_filament_from_volume(used_filaments[color_change_idx++], it->extruder - 1) }); - ColorRGBA color; - decode_color(it->color, color); - items.push_back({ PartialTime::EType::ColorChange, it->extruder, last_color[it->extruder - 1], color, time_rec.second }); - last_color[it->extruder - 1] = color; - last_extruder_id = it->extruder; - custom_gcode_per_print_z.erase(it); - } - else - items.push_back({ PartialTime::EType::Print, last_extruder_id, last_color[last_extruder_id - 1], ColorRGBA::BLACK(), time_rec.second, get_used_filament_from_volume(used_filaments[color_change_idx++], last_extruder_id - 1) }); - - break; - } - default: { break; } - } - } - - return items; - }; - - auto append_color_change = [&imgui](const ColorRGBA& color1, const ColorRGBA& color2, const std::array& offsets, const Times& times) { - imgui.text(_u8L("Color change")); - ImGui::SameLine(); - - float icon_size = ImGui::GetTextLineHeight(); - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - ImVec2 pos = ImGui::GetCursorScreenPos(); - pos.x -= 0.5f * ImGui::GetStyle().ItemSpacing.x; - - draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, - ImGuiWrapper::to_ImU32(color1)); - pos.x += icon_size; - draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, - ImGuiWrapper::to_ImU32(color2)); - - ImGui::SameLine(offsets[0]); - imgui.text(short_time(get_time_dhms(times.second - times.first))); - }; - - auto append_print = [&imgui, imperial_units](const ColorRGBA& color, const std::array& offsets, const Times& times, std::pair used_filament) { - imgui.text(_u8L("Print")); - ImGui::SameLine(); - - float icon_size = ImGui::GetTextLineHeight(); - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - ImVec2 pos = ImGui::GetCursorScreenPos(); - pos.x -= 0.5f * ImGui::GetStyle().ItemSpacing.x; - - draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, - ImGuiWrapper::to_ImU32(color)); - - ImGui::SameLine(offsets[0]); - imgui.text(short_time(get_time_dhms(times.second))); - ImGui::SameLine(offsets[1]); - imgui.text(short_time(get_time_dhms(times.first))); - if (used_filament.first > 0.0f) { - char buffer[64]; - ImGui::SameLine(offsets[2]); - ::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", used_filament.first); - imgui.text(buffer); - - ImGui::SameLine(offsets[3]); - ::sprintf(buffer, "%.2f g", used_filament.second); - imgui.text(buffer); - } - }; - - PartialTimes partial_times = generate_partial_times(time_mode.custom_gcode_times, m_print_statistics.volumes_per_color_change); - if (!partial_times.empty()) { - labels.clear(); - times.clear(); - - for (const PartialTime& item : partial_times) { - switch (item.type) - { - case PartialTime::EType::Print: { labels.push_back(_u8L("Print")); break; } - case PartialTime::EType::Pause: { labels.push_back(_u8L("Pause")); break; } - case PartialTime::EType::ColorChange: { labels.push_back(_u8L("Color change")); break; } - } - times.push_back(short_time(get_time_dhms(item.times.second))); - } - - - std::string longest_used_filament_string; - for (const PartialTime& item : partial_times) { - if (item.used_filament.first > 0.0f) { - char buffer[64]; - ::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", item.used_filament.first); - if (::strlen(buffer) > longest_used_filament_string.length()) - longest_used_filament_string = buffer; - } - } - - offsets = calculate_offsets(labels, times, { _u8L("Event"), _u8L("Remaining time"), _u8L("Duration"), longest_used_filament_string }, 2.0f * icon_size); - - ImGui::Spacing(); - append_headers({ _u8L("Event"), _u8L("Remaining time"), _u8L("Duration"), _u8L("Used filament") }, offsets); - const bool need_scrollable = static_cast(partial_times.size()) * icon_size + (static_cast(partial_times.size()) - 1.0f) * ImGui::GetStyle().ItemSpacing.y > child_height; - if (need_scrollable) - // add scrollable region - ImGui::BeginChild("events", { -1.0f, child_height }, false); - - for (const PartialTime& item : partial_times) { - switch (item.type) - { - case PartialTime::EType::Print: { - append_print(item.color1, offsets, item.times, item.used_filament); - break; - } - case PartialTime::EType::Pause: { - imgui.text(_u8L("Pause")); - ImGui::SameLine(offsets[0]); - imgui.text(short_time(get_time_dhms(item.times.second - item.times.first))); - break; - } - case PartialTime::EType::ColorChange: { - append_color_change(item.color1, item.color2, offsets, item.times); - break; - } - } - } - - if (need_scrollable) - ImGui::EndChild(); - } - } - -#if !ENABLE_PREVIEW_LAYOUT - // travel paths section - if (m_buffers[buffer_id(EMoveType::Travel)].visible) { - switch (m_view_type) - { - case EViewType::Feedrate: - case EViewType::Tool: - case EViewType::ColorPrint: { - break; - } - default: { - // title - ImGui::Spacing(); - imgui.title(_u8L("Travel")); - - // items - append_item(EItemType::Line, Travel_Colors[0], _u8L("Movement")); - append_item(EItemType::Line, Travel_Colors[1], _u8L("Extrusion")); - append_item(EItemType::Line, Travel_Colors[2], _u8L("Retraction")); - - break; - } - } - } - - // wipe paths section - if (m_buffers[buffer_id(EMoveType::Wipe)].visible) { - switch (m_view_type) - { - case EViewType::Feedrate: - case EViewType::Tool: - case EViewType::ColorPrint: { break; } - default: { - // title - ImGui::Spacing(); - imgui.title(_u8L("Wipe")); - - // items - append_item(EItemType::Line, Wipe_Color, _u8L("Wipe")); - - break; - } - } - } - - auto any_option_available = [this]() { - auto available = [this](EMoveType type) { - const TBuffer& buffer = m_buffers[buffer_id(type)]; - return buffer.visible && buffer.has_data(); - }; - - return available(EMoveType::Color_change) || - available(EMoveType::Custom_GCode) || - available(EMoveType::Pause_Print) || - available(EMoveType::Retract) || - available(EMoveType::Tool_change) || - available(EMoveType::Unretract) || - available(EMoveType::Seam); - }; - - auto add_option = [this, append_item](EMoveType move_type, EOptionsColors color, const std::string& text) { - const TBuffer& buffer = m_buffers[buffer_id(move_type)]; - if (buffer.visible && buffer.has_data()) - append_item(EItemType::Circle, Options_Colors[static_cast(color)], text); - }; - - // options section - if (any_option_available()) { - // title - ImGui::Spacing(); - imgui.title(_u8L("Options")); - - // items - add_option(EMoveType::Retract, EOptionsColors::Retractions, _u8L("Retractions")); - add_option(EMoveType::Unretract, EOptionsColors::Unretractions, _u8L("Deretractions")); - add_option(EMoveType::Seam, EOptionsColors::Seams, _u8L("Seams")); - add_option(EMoveType::Tool_change, EOptionsColors::ToolChanges, _u8L("Tool changes")); - add_option(EMoveType::Color_change, EOptionsColors::ColorChanges, _u8L("Color changes")); - add_option(EMoveType::Pause_Print, EOptionsColors::PausePrints, _u8L("Print pauses")); - add_option(EMoveType::Custom_GCode, EOptionsColors::CustomGCodes, _u8L("Custom G-codes")); - } -#endif // !ENABLE_PREVIEW_LAYOUT - - // settings section - bool has_settings = false; - has_settings |= !m_settings_ids.print.empty(); - has_settings |= !m_settings_ids.printer.empty(); - bool has_filament_settings = true; - has_filament_settings &= !m_settings_ids.filament.empty(); - for (const std::string& fs : m_settings_ids.filament) { - has_filament_settings &= !fs.empty(); - } - has_settings |= has_filament_settings; - bool show_settings = wxGetApp().is_gcode_viewer(); - show_settings &= (m_view_type == EViewType::FeatureType || m_view_type == EViewType::Tool); - show_settings &= has_settings; - if (show_settings) { - auto calc_offset = [this]() { - float ret = 0.0f; - if (!m_settings_ids.printer.empty()) - ret = std::max(ret, ImGui::CalcTextSize((_u8L("Printer") + std::string(":")).c_str()).x); - if (!m_settings_ids.print.empty()) - ret = std::max(ret, ImGui::CalcTextSize((_u8L("Print settings") + std::string(":")).c_str()).x); - if (!m_settings_ids.filament.empty()) { - for (unsigned char i : m_extruder_ids) { - ret = std::max(ret, ImGui::CalcTextSize((_u8L("Filament") + " " + std::to_string(i + 1) + ":").c_str()).x); - } - } - if (ret > 0.0f) - ret += 2.0f * ImGui::GetStyle().ItemSpacing.x; - return ret; - }; - - ImGui::Spacing(); - imgui.title(_u8L("Settings")); - - float offset = calc_offset(); - - if (!m_settings_ids.printer.empty()) { - imgui.text(_u8L("Printer") + ":"); - ImGui::SameLine(offset); - imgui.text(m_settings_ids.printer); - } - if (!m_settings_ids.print.empty()) { - imgui.text(_u8L("Print settings") + ":"); - ImGui::SameLine(offset); - imgui.text(m_settings_ids.print); - } - if (!m_settings_ids.filament.empty()) { - for (unsigned char i : m_extruder_ids) { - if (i < static_cast(m_settings_ids.filament.size()) && !m_settings_ids.filament[i].empty()) { - std::string txt = _u8L("Filament"); - txt += (m_extruder_ids.size() == 1) ? ":" : " " + std::to_string(i + 1); - imgui.text(txt); - ImGui::SameLine(offset); - imgui.text(m_settings_ids.filament[i]); - } - } - } - } - - // total estimated printing time section - if (show_estimated_time) { - ImGui::Spacing(); - std::string time_title = _u8L("Estimated printing times"); - auto can_show_mode_button = [this](PrintEstimatedStatistics::ETimeMode mode) { - bool show = false; - if (m_print_statistics.modes.size() > 1 && m_print_statistics.modes[static_cast(mode)].roles_times.size() > 0) { - for (size_t i = 0; i < m_print_statistics.modes.size(); ++i) { - if (i != static_cast(mode) && - m_print_statistics.modes[i].time > 0.0f && - short_time(get_time_dhms(m_print_statistics.modes[static_cast(mode)].time)) != short_time(get_time_dhms(m_print_statistics.modes[i].time))) { - show = true; - break; - } - } - } - return show; - }; - - if (can_show_mode_button(m_time_estimate_mode)) { - switch (m_time_estimate_mode) - { - case PrintEstimatedStatistics::ETimeMode::Normal: { time_title += " [" + _u8L("Normal mode") + "]"; break; } - case PrintEstimatedStatistics::ETimeMode::Stealth: { time_title += " [" + _u8L("Stealth mode") + "]"; break; } - default: { assert(false); break; } - } - } - - imgui.title(time_title + ":"); - - std::string first_str = _u8L("First layer"); - std::string total_str = _u8L("Total"); - - float max_len = 10.0f + ImGui::GetStyle().ItemSpacing.x; - if (time_mode.layers_times.empty()) - max_len += ImGui::CalcTextSize(total_str.c_str()).x; - else - max_len += std::max(ImGui::CalcTextSize(first_str.c_str()).x, ImGui::CalcTextSize(total_str.c_str()).x); - - if (!time_mode.layers_times.empty()) { - imgui.text(first_str + ":"); - ImGui::SameLine(max_len); - imgui.text(short_time(get_time_dhms(time_mode.layers_times.front()))); - } - - imgui.text(total_str + ":"); - ImGui::SameLine(max_len); - imgui.text(short_time(get_time_dhms(time_mode.time))); - - auto show_mode_button = [this, &imgui, can_show_mode_button](const wxString& label, PrintEstimatedStatistics::ETimeMode mode) { - if (can_show_mode_button(mode)) { - if (imgui.button(label)) { - m_time_estimate_mode = mode; -#if ENABLE_PREVIEW_LAYER_TIME - if (m_view_type == EViewType::LayerTimeLinear || m_view_type == EViewType::LayerTimeLogarithmic) - refresh_render_paths(false, false); -#endif // ENABLE_PREVIEW_LAYER_TIME - imgui.set_requires_extra_frame(); - } - } - }; - - switch (m_time_estimate_mode) { - case PrintEstimatedStatistics::ETimeMode::Normal: { - show_mode_button(_L("Show stealth mode"), PrintEstimatedStatistics::ETimeMode::Stealth); - break; - } - case PrintEstimatedStatistics::ETimeMode::Stealth: { - show_mode_button(_L("Show normal mode"), PrintEstimatedStatistics::ETimeMode::Normal); - break; - } - default : { assert(false); break; } - } - } - -#if ENABLE_PREVIEW_LAYOUT - // toolbar section - auto toggle_button = [this, &imgui, icon_size](Preview::OptionType type, const std::string& name, - std::function draw_callback) { - auto is_flag_set = [](unsigned int flags, unsigned int flag) { - return (flags & (1 << flag)) != 0; - }; - - auto set_flag = [](unsigned int flags, unsigned int flag, bool active) { - return active ? (flags | (1 << flag)) : (flags & ~(1 << flag)); - }; - - unsigned int flags = get_options_visibility_flags(); - unsigned int flag = static_cast(type); - bool active = is_flag_set(flags, flag); - - if (imgui.draw_radio_button(name, 1.5f * icon_size, active, draw_callback)) { - unsigned int new_flags = set_flag(flags, flag, !active); - set_options_visibility_from_flags(new_flags); - - const unsigned int diff_flags = flags ^ new_flags; - if (m_view_type == GCodeViewer::EViewType::Feedrate && is_flag_set(diff_flags, static_cast(Preview::OptionType::Travel))) - wxGetApp().plater()->refresh_print(); - else { - bool keep_first = m_sequential_view.current.first != m_sequential_view.global.first; - bool keep_last = m_sequential_view.current.last != m_sequential_view.global.last; - wxGetApp().plater()->get_current_canvas3D()->refresh_gcode_preview_render_paths(keep_first, keep_last); - } - wxGetApp().plater()->update_preview_moves_slider(); - } - - if (ImGui::IsItemHovered()) { - ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND); - ImGui::BeginTooltip(); - imgui.text(name); - ImGui::EndTooltip(); - ImGui::PopStyleColor(); - } - }; - -#if ENABLE_LEGEND_TOOLBAR_ICONS - auto image_icon = [&imgui](ImGuiWindow& window, const ImVec2& pos, float size, const wchar_t& icon_id) { - ImGuiIO& io = ImGui::GetIO(); - const ImTextureID tex_id = io.Fonts->TexID; - const float tex_w = static_cast(io.Fonts->TexWidth); - const float tex_h = static_cast(io.Fonts->TexHeight); - const ImFontAtlas::CustomRect* const rect = imgui.GetTextureCustomRect(icon_id); - const ImVec2 uv0 = { static_cast(rect->X) / tex_w, static_cast(rect->Y) / tex_h }; - const ImVec2 uv1 = { static_cast(rect->X + rect->Width) / tex_w, static_cast(rect->Y + rect->Height) / tex_h }; - window.DrawList->AddImage(tex_id, pos, { pos.x + size, pos.y + size }, uv0, uv1, ImGuiWrapper::to_ImU32({ 1.0f, 1.0f, 1.0f, 1.0f })); - }; -#else - auto circle_icon = [](ImGuiWindow& window, const ImVec2& pos, float size, const ColorRGBA& color) { - const float margin = 3.0f; - const ImVec2 center(0.5f * (pos.x + pos.x + size), 0.5f * (pos.y + pos.y + size)); - window.DrawList->AddCircleFilled(center, 0.5f * (size - 2.0f * margin), ImGuiWrapper::to_ImU32(color), 16); - }; - auto line_icon = [](ImGuiWindow& window, const ImVec2& pos, float size, const ColorRGBA& color) { - const float margin = 3.0f; - window.DrawList->AddLine({ pos.x + margin, pos.y + size - margin }, { pos.x + size - margin, pos.y + margin }, ImGuiWrapper::to_ImU32(color), 3.0f); - }; -#endif // ENABLE_LEGEND_TOOLBAR_ICONS - - ImGui::Spacing(); - ImGui::Separator(); - ImGui::Spacing(); - ImGui::Spacing(); -#if ENABLE_LEGEND_TOOLBAR_ICONS - toggle_button(Preview::OptionType::Travel, _u8L("Travel"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { - image_icon(window, pos, size, ImGui::LegendTravel); -#else - toggle_button(Preview::OptionType::Travel, _u8L("Travel"), [line_icon](ImGuiWindow& window, const ImVec2& pos, float size) { - line_icon(window, pos, size, Travel_Colors[0]); -#endif // ENABLE_LEGEND_TOOLBAR_ICONS - }); - ImGui::SameLine(); -#if ENABLE_LEGEND_TOOLBAR_ICONS - toggle_button(Preview::OptionType::Wipe, _u8L("Wipe"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { - image_icon(window, pos, size, ImGui::LegendWipe); -#else - toggle_button(Preview::OptionType::Wipe, _u8L("Wipe"), [line_icon](ImGuiWindow& window, const ImVec2& pos, float size) { - line_icon(window, pos, size, Wipe_Color); -#endif // ENABLE_LEGEND_TOOLBAR_ICONS - }); - ImGui::SameLine(); -#if ENABLE_LEGEND_TOOLBAR_ICONS - toggle_button(Preview::OptionType::Retractions, _u8L("Retractions"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { - image_icon(window, pos, size, ImGui::LegendRetract); -#else - toggle_button(Preview::OptionType::Retractions, _u8L("Retractions"), [circle_icon](ImGuiWindow& window, const ImVec2& pos, float size) { - circle_icon(window, pos, size, Options_Colors[static_cast(EOptionsColors::Retractions)]); -#endif // ENABLE_LEGEND_TOOLBAR_ICONS - }); - ImGui::SameLine(); -#if ENABLE_LEGEND_TOOLBAR_ICONS - toggle_button(Preview::OptionType::Unretractions, _u8L("Deretractions"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { - image_icon(window, pos, size, ImGui::LegendDeretract); -#else - toggle_button(Preview::OptionType::Unretractions, _u8L("Deretractions"), [circle_icon](ImGuiWindow& window, const ImVec2& pos, float size) { - circle_icon(window, pos, size, Options_Colors[static_cast(EOptionsColors::Unretractions)]); -#endif // ENABLE_LEGEND_TOOLBAR_ICONS - }); - ImGui::SameLine(); -#if ENABLE_LEGEND_TOOLBAR_ICONS - toggle_button(Preview::OptionType::Seams, _u8L("Seams"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { - image_icon(window, pos, size, ImGui::LegendSeams); -#else - toggle_button(Preview::OptionType::Seams, _u8L("Seams"), [circle_icon](ImGuiWindow& window, const ImVec2& pos, float size) { - circle_icon(window, pos, size, Options_Colors[static_cast(EOptionsColors::Seams)]); -#endif // ENABLE_LEGEND_TOOLBAR_ICONS - }); - ImGui::SameLine(); -#if ENABLE_LEGEND_TOOLBAR_ICONS - toggle_button(Preview::OptionType::ToolChanges, _u8L("Tool changes"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { - image_icon(window, pos, size, ImGui::LegendToolChanges); -#else - toggle_button(Preview::OptionType::ToolChanges, _u8L("Tool changes"), [circle_icon](ImGuiWindow& window, const ImVec2& pos, float size) { - circle_icon(window, pos, size, Options_Colors[static_cast(EOptionsColors::ToolChanges)]); -#endif // ENABLE_LEGEND_TOOLBAR_ICONS - }); - ImGui::SameLine(); -#if ENABLE_LEGEND_TOOLBAR_ICONS - toggle_button(Preview::OptionType::ColorChanges, _u8L("Color changes"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { - image_icon(window, pos, size, ImGui::LegendColorChanges); -#else - toggle_button(Preview::OptionType::ColorChanges, _u8L("Color changes"), [circle_icon](ImGuiWindow& window, const ImVec2& pos, float size) { - circle_icon(window, pos, size, Options_Colors[static_cast(EOptionsColors::ColorChanges)]); -#endif // ENABLE_LEGEND_TOOLBAR_ICONS - }); - ImGui::SameLine(); -#if ENABLE_LEGEND_TOOLBAR_ICONS - toggle_button(Preview::OptionType::PausePrints, _u8L("Print pauses"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { - image_icon(window, pos, size, ImGui::LegendPausePrints); -#else - toggle_button(Preview::OptionType::PausePrints, _u8L("Print pauses"), [circle_icon](ImGuiWindow& window, const ImVec2& pos, float size) { - circle_icon(window, pos, size, Options_Colors[static_cast(EOptionsColors::PausePrints)]); -#endif // ENABLE_LEGEND_TOOLBAR_ICONS - }); - ImGui::SameLine(); -#if ENABLE_LEGEND_TOOLBAR_ICONS - toggle_button(Preview::OptionType::CustomGCodes, _u8L("Custom G-codes"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { - image_icon(window, pos, size, ImGui::LegendCustomGCodes); -#else - toggle_button(Preview::OptionType::CustomGCodes, _u8L("Custom G-codes"), [circle_icon](ImGuiWindow& window, const ImVec2& pos, float size) { - circle_icon(window, pos, size, Options_Colors[static_cast(EOptionsColors::CustomGCodes)]); -#endif // ENABLE_LEGEND_TOOLBAR_ICONS - }); - ImGui::SameLine(); -#if ENABLE_SHOW_TOOLPATHS_COG -#if ENABLE_LEGEND_TOOLBAR_ICONS - toggle_button(Preview::OptionType::CenterOfGravity, _u8L("Center of gravity"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { - image_icon(window, pos, size, ImGui::LegendCOG); - }); -#else - toggle_button(Preview::OptionType::CenterOfGravity, _u8L("Center of gravity"), [](ImGuiWindow& window, const ImVec2& pos, float size) { - const ImU32 black = ImGuiWrapper::to_ImU32({ 0.0f, 0.0f, 0.0f, 1.0f }); - const ImU32 white = ImGuiWrapper::to_ImU32({ 1.0f, 1.0f, 1.0f, 1.0f }); - const float margin = 3.0f; - const ImVec2 center(0.5f * (pos.x + pos.x + size), 0.5f * (pos.y + pos.y + size)); - const float radius = 0.5f * (size - 2.0f * margin); - window.DrawList->PathArcToFast(center, radius, 0, 3); - window.DrawList->PathLineTo(center); - window.DrawList->PathFillConvex(black); - window.DrawList->PathArcToFast(center, radius, 3, 6); - window.DrawList->PathLineTo(center); - window.DrawList->PathFillConvex(white); - window.DrawList->PathArcToFast(center, radius, 6, 9); - window.DrawList->PathLineTo(center); - window.DrawList->PathFillConvex(black); - window.DrawList->PathArcToFast(center, radius, 9, 12); - window.DrawList->PathLineTo(center); - window.DrawList->PathFillConvex(white); - window.DrawList->AddCircle(center, radius, black, 16); - }); -#endif // ENABLE_LEGEND_TOOLBAR_ICONS - ImGui::SameLine(); -#endif // ENABLE_SHOW_TOOLPATHS_COG -#if ENABLE_LEGEND_TOOLBAR_ICONS - toggle_button(Preview::OptionType::Shells, _u8L("Shells"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { - image_icon(window, pos, size, ImGui::LegendShells); -#else - toggle_button(Preview::OptionType::Shells, _u8L("Shells"), [](ImGuiWindow& window, const ImVec2& pos, float size) { - const ImU32 color = ImGuiWrapper::to_ImU32({ 1.0f, 1.0f, 1.0f, 1.0f }); - const float margin = 3.0f; - const float proj = 0.25f * size; - window.DrawList->AddRect({ pos.x + margin, pos.y + size - margin }, { pos.x + size - margin - proj, pos.y + margin + proj }, color); - window.DrawList->AddLine({ pos.x + margin, pos.y + margin + proj }, { pos.x + margin + proj, pos.y + margin }, color); - window.DrawList->AddLine({ pos.x + size - margin - proj, pos.y + margin + proj }, { pos.x + size - margin, pos.y + margin }, color); - window.DrawList->AddLine({ pos.x + size - margin - proj, pos.y + size - margin }, { pos.x + size - margin, pos.y + size - margin - proj }, color); - window.DrawList->AddLine({ pos.x + margin + proj, pos.y + margin }, { pos.x + size - margin, pos.y + margin }, color); - window.DrawList->AddLine({ pos.x + size - margin, pos.y + margin }, { pos.x + size - margin, pos.y + size - margin - proj }, color); -#endif // ENABLE_LEGEND_TOOLBAR_ICONS - }); - ImGui::SameLine(); -#if ENABLE_LEGEND_TOOLBAR_ICONS - toggle_button(Preview::OptionType::ToolMarker, _u8L("Tool marker"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { - image_icon(window, pos, size, ImGui::LegendToolMarker); -#else - toggle_button(Preview::OptionType::ToolMarker, _u8L("Tool marker"), [](ImGuiWindow& window, const ImVec2& pos, float size) { - const ImU32 color = ImGuiWrapper::to_ImU32({ 1.0f, 1.0f, 1.0f, 0.8f }); - const float margin = 3.0f; - const ImVec2 p1(0.5f * (pos.x + pos.x + size), pos.y + size - margin); - const ImVec2 p2(p1.x + 0.25f * size, p1.y - 0.25f * size); - const ImVec2 p3(p1.x - 0.25f * size, p1.y - 0.25f * size); - window.DrawList->AddTriangleFilled(p1, p2, p3, color); - const float mid_x = 0.5f * (pos.x + pos.x + size); - window.DrawList->AddRectFilled({ mid_x - 0.09375f * size, p1.y - 0.25f * size }, { mid_x + 0.09375f * size, pos.y + margin }, color); -#endif // ENABLE_LEGEND_TOOLBAR_ICONS - }); - - bool size_dirty = !ImGui::GetCurrentWindow()->ScrollbarY && ImGui::CalcWindowNextAutoFitSize(ImGui::GetCurrentWindow()).x != ImGui::GetWindowWidth(); - if (m_legend_resizer.dirty || size_dirty != m_legend_resizer.dirty) { - wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); - wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); - } - m_legend_resizer.dirty = size_dirty; -#endif // ENABLE_PREVIEW_LAYOUT - - legend_height = ImGui::GetWindowHeight(); - - imgui.end(); - ImGui::PopStyleVar(); -} - -#if ENABLE_GCODE_VIEWER_STATISTICS -void GCodeViewer::render_statistics() -{ - static const float offset = 275.0f; - - ImGuiWrapper& imgui = *wxGetApp().imgui(); - - auto add_time = [&imgui](const std::string& label, int64_t time) { - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, label); - ImGui::SameLine(offset); - imgui.text(std::to_string(time) + " ms (" + get_time_dhms(static_cast(time) * 0.001f) + ")"); - }; - - auto add_memory = [&imgui](const std::string& label, int64_t memory) { - auto format_string = [memory](const std::string& units, float value) { - return std::to_string(memory) + " bytes (" + - Slic3r::float_to_string_decimal_point(float(memory) * value, 3) - + " " + units + ")"; - }; - - static const float kb = 1024.0f; - static const float inv_kb = 1.0f / kb; - static const float mb = 1024.0f * kb; - static const float inv_mb = 1.0f / mb; - static const float gb = 1024.0f * mb; - static const float inv_gb = 1.0f / gb; - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, label); - ImGui::SameLine(offset); - if (static_cast(memory) < mb) - imgui.text(format_string("KB", inv_kb)); - else if (static_cast(memory) < gb) - imgui.text(format_string("MB", inv_mb)); - else - imgui.text(format_string("GB", inv_gb)); - }; - - auto add_counter = [&imgui](const std::string& label, int64_t counter) { - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, label); - ImGui::SameLine(offset); - imgui.text(std::to_string(counter)); - }; - - imgui.set_next_window_pos(0.5f * wxGetApp().plater()->get_current_canvas3D()->get_canvas_size().get_width(), 0.0f, ImGuiCond_Once, 0.5f, 0.0f); - ImGui::SetNextWindowSizeConstraints({ 300.0f, 100.0f }, { 600.0f, 900.0f }); - imgui.begin(std::string("GCodeViewer Statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize); - ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow()); - - if (ImGui::CollapsingHeader("Time")) { - add_time(std::string("GCodeProcessor:"), m_statistics.results_time); - - ImGui::Separator(); - add_time(std::string("Load:"), m_statistics.load_time); - add_time(std::string(" Load vertices:"), m_statistics.load_vertices); - add_time(std::string(" Smooth vertices:"), m_statistics.smooth_vertices); - add_time(std::string(" Load indices:"), m_statistics.load_indices); - add_time(std::string("Refresh:"), m_statistics.refresh_time); - add_time(std::string("Refresh paths:"), m_statistics.refresh_paths_time); - } - - if (ImGui::CollapsingHeader("OpenGL calls")) { - add_counter(std::string("Multi GL_LINES:"), m_statistics.gl_multi_lines_calls_count); - add_counter(std::string("Multi GL_TRIANGLES:"), m_statistics.gl_multi_triangles_calls_count); - add_counter(std::string("GL_TRIANGLES:"), m_statistics.gl_triangles_calls_count); - ImGui::Separator(); - add_counter(std::string("Instanced models:"), m_statistics.gl_instanced_models_calls_count); - add_counter(std::string("Batched models:"), m_statistics.gl_batched_models_calls_count); - } - - if (ImGui::CollapsingHeader("CPU memory")) { - add_memory(std::string("GCodeProcessor results:"), m_statistics.results_size); - - ImGui::Separator(); - add_memory(std::string("Paths:"), m_statistics.paths_size); - add_memory(std::string("Render paths:"), m_statistics.render_paths_size); - add_memory(std::string("Models instances:"), m_statistics.models_instances_size); - } - - if (ImGui::CollapsingHeader("GPU memory")) { - add_memory(std::string("Vertices:"), m_statistics.total_vertices_gpu_size); - add_memory(std::string("Indices:"), m_statistics.total_indices_gpu_size); - add_memory(std::string("Instances:"), m_statistics.total_instances_gpu_size); - ImGui::Separator(); - add_memory(std::string("Max VBuffer:"), m_statistics.max_vbuffer_gpu_size); - add_memory(std::string("Max IBuffer:"), m_statistics.max_ibuffer_gpu_size); - } - - if (ImGui::CollapsingHeader("Other")) { - add_counter(std::string("Travel segments count:"), m_statistics.travel_segments_count); - add_counter(std::string("Wipe segments count:"), m_statistics.wipe_segments_count); - add_counter(std::string("Extrude segments count:"), m_statistics.extrude_segments_count); - add_counter(std::string("Instances count:"), m_statistics.instances_count); - add_counter(std::string("Batched count:"), m_statistics.batched_count); - ImGui::Separator(); - add_counter(std::string("VBuffers count:"), m_statistics.vbuffers_count); - add_counter(std::string("IBuffers count:"), m_statistics.ibuffers_count); - } - - imgui.end(); -} -#endif // ENABLE_GCODE_VIEWER_STATISTICS - -void GCodeViewer::log_memory_used(const std::string& label, int64_t additional) const -{ - if (Slic3r::get_logging_level() >= 5) { - int64_t paths_size = 0; - int64_t render_paths_size = 0; - for (const TBuffer& buffer : m_buffers) { - paths_size += SLIC3R_STDVEC_MEMSIZE(buffer.paths, Path); - render_paths_size += SLIC3R_STDUNORDEREDSET_MEMSIZE(buffer.render_paths, RenderPath); - for (const RenderPath& path : buffer.render_paths) { - render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.sizes, unsigned int); - render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.offsets, size_t); - } - } - int64_t layers_size = SLIC3R_STDVEC_MEMSIZE(m_layers.get_zs(), double); - layers_size += SLIC3R_STDVEC_MEMSIZE(m_layers.get_ranges(), Layers::Range); - BOOST_LOG_TRIVIAL(trace) << label - << "(" << format_memsize_MB(additional + paths_size + render_paths_size + layers_size) << ");" - << log_memory_info(); - } -} - -ColorRGBA GCodeViewer::option_color(EMoveType move_type) const -{ - switch (move_type) - { - case EMoveType::Tool_change: { return Options_Colors[static_cast(EOptionsColors::ToolChanges)]; } - case EMoveType::Color_change: { return Options_Colors[static_cast(EOptionsColors::ColorChanges)]; } - case EMoveType::Pause_Print: { return Options_Colors[static_cast(EOptionsColors::PausePrints)]; } - case EMoveType::Custom_GCode: { return Options_Colors[static_cast(EOptionsColors::CustomGCodes)]; } - case EMoveType::Retract: { return Options_Colors[static_cast(EOptionsColors::Retractions)]; } - case EMoveType::Unretract: { return Options_Colors[static_cast(EOptionsColors::Unretractions)]; } - case EMoveType::Seam: { return Options_Colors[static_cast(EOptionsColors::Seams)]; } - default: { return { 0.0f, 0.0f, 0.0f, 1.0f }; } - } -} - -} // namespace GUI -} // namespace Slic3r - +#include "libslic3r/libslic3r.h" +#include "GCodeViewer.hpp" + +#include "libslic3r/BuildVolume.hpp" +#include "libslic3r/Print.hpp" +#include "libslic3r/Geometry.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/Utils.hpp" +#include "libslic3r/LocalesUtils.hpp" +#include "libslic3r/PresetBundle.hpp" + +#include "GUI_App.hpp" +#include "MainFrame.hpp" +#include "Plater.hpp" +#include "Camera.hpp" +#include "I18N.hpp" +#include "GUI_Utils.hpp" +#include "GUI.hpp" +#include "DoubleSlider.hpp" +#include "GLCanvas3D.hpp" +#include "GLToolbar.hpp" +#include "GUI_Preview.hpp" +#include "GUI_ObjectManipulation.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace Slic3r { +namespace GUI { + +static unsigned char buffer_id(EMoveType type) { + return static_cast(type) - static_cast(EMoveType::Retract); +} + +static EMoveType buffer_type(unsigned char id) { + return static_cast(static_cast(EMoveType::Retract) + id); +} + +// Round to a bin with minimum two digits resolution. +// Equivalent to conversion to string with sprintf(buf, "%.2g", value) and conversion back to float, but faster. +static float round_to_bin(const float value) +{ +// assert(value > 0); + constexpr float const scale [5] = { 100.f, 1000.f, 10000.f, 100000.f, 1000000.f }; + constexpr float const invscale [5] = { 0.01f, 0.001f, 0.0001f, 0.00001f, 0.000001f }; + constexpr float const threshold[5] = { 0.095f, 0.0095f, 0.00095f, 0.000095f, 0.0000095f }; + // Scaling factor, pointer to the tables above. + int i = 0; + // While the scaling factor is not yet large enough to get two integer digits after scaling and rounding: + for (; value < threshold[i] && i < 4; ++ i) ; + return std::round(value * scale[i]) * invscale[i]; +} + +void GCodeViewer::VBuffer::reset() +{ + // release gpu memory + if (!vbos.empty()) { + glsafe(::glDeleteBuffers(static_cast(vbos.size()), static_cast(vbos.data()))); + vbos.clear(); + } + sizes.clear(); + count = 0; +} + +void GCodeViewer::InstanceVBuffer::Ranges::reset() +{ + for (Range& range : ranges) { + // release gpu memory + if (range.vbo > 0) + glsafe(::glDeleteBuffers(1, &range.vbo)); + } + + ranges.clear(); +} + +void GCodeViewer::InstanceVBuffer::reset() +{ + s_ids.clear(); + buffer.clear(); + render_ranges.reset(); +} + +void GCodeViewer::IBuffer::reset() +{ + // release gpu memory + if (ibo > 0) { + glsafe(::glDeleteBuffers(1, &ibo)); + ibo = 0; + } + + vbo = 0; + count = 0; +} + +#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC +bool GCodeViewer::Path::matches(const GCodeProcessorResult::MoveVertex& move, bool account_for_volumetric_rate) const +#else +bool GCodeViewer::Path::matches(const GCodeProcessorResult::MoveVertex& move) const +#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC +{ + auto matches_percent = [](float value1, float value2, float max_percent) { + return std::abs(value2 - value1) / value1 <= max_percent; + }; + + switch (move.type) + { + case EMoveType::Tool_change: + case EMoveType::Color_change: + case EMoveType::Pause_Print: + case EMoveType::Custom_GCode: + case EMoveType::Retract: + case EMoveType::Unretract: + case EMoveType::Seam: + case EMoveType::Extrude: { + // use rounding to reduce the number of generated paths +#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + if (account_for_volumetric_rate) + return type == move.type && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id && role == move.extrusion_role && + move.position.z() <= sub_paths.front().first.position.z() && feedrate == move.feedrate && fan_speed == move.fan_speed && + height == round_to_bin(move.height) && width == round_to_bin(move.width) && + matches_percent(volumetric_rate, move.volumetric_rate(), 0.001f); + else + return type == move.type && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id && role == move.extrusion_role && + move.position.z() <= sub_paths.front().first.position.z() && feedrate == move.feedrate && fan_speed == move.fan_speed && + height == round_to_bin(move.height) && width == round_to_bin(move.width); +#else + return type == move.type && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id && role == move.extrusion_role && + move.position.z() <= sub_paths.front().first.position.z() && feedrate == move.feedrate && fan_speed == move.fan_speed && + height == round_to_bin(move.height) && width == round_to_bin(move.width) && + matches_percent(volumetric_rate, move.volumetric_rate(), 0.05f); +#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + } + case EMoveType::Travel: { + return type == move.type && feedrate == move.feedrate && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id; + } + default: { return false; } + } +} + +void GCodeViewer::TBuffer::Model::reset() +{ + instances.reset(); +} + +void GCodeViewer::TBuffer::reset() +{ + vertices.reset(); + for (IBuffer& buffer : indices) { + buffer.reset(); + } + + indices.clear(); + paths.clear(); + render_paths.clear(); + model.reset(); +} + +void GCodeViewer::TBuffer::add_path(const GCodeProcessorResult::MoveVertex& move, unsigned int b_id, size_t i_id, size_t s_id) +{ + Path::Endpoint endpoint = { b_id, i_id, s_id, move.position }; + // use rounding to reduce the number of generated paths + paths.push_back({ move.type, move.extrusion_role, move.delta_extruder, + round_to_bin(move.height), round_to_bin(move.width), + move.feedrate, move.fan_speed, move.temperature, + move.volumetric_rate(), move.extruder_id, move.cp_color_id, { { endpoint, endpoint } } }); +} + +#if ENABLE_SHOW_TOOLPATHS_COG +void GCodeViewer::COG::render() +{ + if (!m_visible) + return; + + init(); + + GLShaderProgram* shader = wxGetApp().get_shader("toolpaths_cog"); + if (shader == nullptr) + return; + + shader->start_using(); + + glsafe(::glDisable(GL_DEPTH_TEST)); + +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Camera& camera = wxGetApp().plater()->get_camera(); + Transform3d matrix = camera.get_view_matrix() * Geometry::assemble_transform(cog()); + if (m_fixed_size) { + const double inv_zoom = wxGetApp().plater()->get_camera().get_inv_zoom(); + matrix = matrix * Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), inv_zoom * Vec3d::Ones()); + } + shader->set_uniform("view_model_matrix", matrix); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + shader->set_uniform("normal_matrix", (Matrix3d)matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); + m_model.render(); +#else + glsafe(::glPushMatrix()); + const Vec3d position = cog(); + glsafe(::glTranslated(position.x(), position.y(), position.z())); + if (m_fixed_size) { + const double inv_zoom = wxGetApp().plater()->get_camera().get_inv_zoom(); + glsafe(::glScaled(inv_zoom, inv_zoom, inv_zoom)); + } + m_model.render(); + glsafe(::glPopMatrix()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + shader->stop_using(); + + ////Show ImGui window + //static float last_window_width = 0.0f; + //static size_t last_text_length = 0; + + //ImGuiWrapper& imgui = *wxGetApp().imgui(); + //const Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size(); + //imgui.set_next_window_pos(0.5f * static_cast(cnv_size.get_width()), 0.0f, ImGuiCond_Always, 0.5f, 0.0f); + //ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + //ImGui::SetNextWindowBgAlpha(0.25f); + //imgui.begin(std::string("COG"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); + //imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _u8L("Center of mass") + ":"); + //ImGui::SameLine(); + //char buf[1024]; + //const Vec3d position = cog(); + //sprintf(buf, "X: %.3f, Y: %.3f, Z: %.3f", position.x(), position.y(), position.z()); + //imgui.text(std::string(buf)); + + //// force extra frame to automatically update window size + //const float width = ImGui::GetWindowWidth(); + //const size_t length = strlen(buf); + //if (width != last_window_width || length != last_text_length) { + // last_window_width = width; + // last_text_length = length; + // imgui.set_requires_extra_frame(); + //} + + //imgui.end(); + //ImGui::PopStyleVar(); +} +#endif // ENABLE_SHOW_TOOLPATHS_COG + +#if ENABLE_PREVIEW_LAYER_TIME +float GCodeViewer::Extrusions::Range::step_size(EType type) const +{ + switch (type) + { + default: + case EType::Linear: { return (max > min) ? (max - min) / (static_cast(Range_Colors.size()) - 1.0f) : 0.0f; } + case EType::Logarithmic: { return (max > min && min > 0.0f) ? ::log(max / min) / (static_cast(Range_Colors.size()) - 1.0f) : 0.0f; } + } +} + +ColorRGBA GCodeViewer::Extrusions::Range::get_color_at(float value, EType type) const +#else +ColorRGBA GCodeViewer::Extrusions::Range::get_color_at(float value) const +#endif // ENABLE_PREVIEW_LAYER_TIME +{ + // Input value scaled to the colors range +#if ENABLE_PREVIEW_LAYER_TIME + float global_t = 0.0f; + const float step = step_size(type); + if (step > 0.0f) { + switch (type) + { + default: + case EType::Linear: { global_t = (value > min) ? (value - min) / step : 0.0f; break; } + case EType::Logarithmic: { global_t = (value > min && min > 0.0f) ? ::log(value / min) / step : 0.0f; break; } + } + } +#else + const float step = step_size(); + const float global_t = (step != 0.0f) ? std::max(0.0f, value - min) / step : 0.0f; // lower limit of 0.0f +#endif // ENABLE_PREVIEW_LAYER_TIME + + const size_t color_max_idx = Range_Colors.size() - 1; + + // Compute the two colors just below (low) and above (high) the input value + const size_t color_low_idx = std::clamp(static_cast(global_t), 0, color_max_idx); + const size_t color_high_idx = std::clamp(color_low_idx + 1, 0, color_max_idx); + + // Interpolate between the low and high colors to find exactly which color the input value should get + return lerp(Range_Colors[color_low_idx], Range_Colors[color_high_idx], global_t - static_cast(color_low_idx)); +} + +GCodeViewer::SequentialRangeCap::~SequentialRangeCap() { + if (ibo > 0) + glsafe(::glDeleteBuffers(1, &ibo)); +} + +void GCodeViewer::SequentialRangeCap::reset() { + if (ibo > 0) + glsafe(::glDeleteBuffers(1, &ibo)); + + buffer = nullptr; + ibo = 0; + vbo = 0; + color = { 0.0f, 0.0f, 0.0f, 1.0f }; +} + +void GCodeViewer::SequentialView::Marker::init() +{ + m_model.init_from(stilized_arrow(16, 2.0f, 4.0f, 1.0f, 8.0f)); +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_model.set_color({ 1.0f, 1.0f, 1.0f, 0.5f }); +#else + m_model.set_color(-1, { 1.0f, 1.0f, 1.0f, 0.5f }); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +} + +void GCodeViewer::SequentialView::Marker::set_world_position(const Vec3f& position) +{ + m_world_position = position; + m_world_transform = (Geometry::assemble_transform((position + m_z_offset * Vec3f::UnitZ()).cast()) * Geometry::assemble_transform(m_model.get_bounding_box().size().z() * Vec3d::UnitZ(), { M_PI, 0.0, 0.0 })).cast(); +} + +void GCodeViewer::SequentialView::Marker::render() +{ + if (!m_visible) + return; + + GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); + if (shader == nullptr) + return; + + glsafe(::glEnable(GL_BLEND)); + glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + + shader->start_using(); + shader->set_uniform("emission_factor", 0.0f); +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d matrix = camera.get_view_matrix() * m_world_transform.cast(); + shader->set_uniform("view_model_matrix", matrix); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + shader->set_uniform("normal_matrix", (Matrix3d)matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); +#else + glsafe(::glPushMatrix()); + glsafe(::glMultMatrixf(m_world_transform.data())); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + m_model.render(); + +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + + shader->stop_using(); + + glsafe(::glDisable(GL_BLEND)); + + static float last_window_width = 0.0f; + static size_t last_text_length = 0; + + ImGuiWrapper& imgui = *wxGetApp().imgui(); + const Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size(); + imgui.set_next_window_pos(0.5f * static_cast(cnv_size.get_width()), static_cast(cnv_size.get_height()), ImGuiCond_Always, 0.5f, 1.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::SetNextWindowBgAlpha(0.25f); + imgui.begin(std::string("ToolPosition"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _u8L("Tool position") + ":"); + ImGui::SameLine(); + char buf[1024]; + const Vec3f position = m_world_position + m_world_offset; + sprintf(buf, "X: %.3f, Y: %.3f, Z: %.3f", position.x(), position.y(), position.z()); + imgui.text(std::string(buf)); + + // force extra frame to automatically update window size + const float width = ImGui::GetWindowWidth(); + const size_t length = strlen(buf); + if (width != last_window_width || length != last_text_length) { + last_window_width = width; + last_text_length = length; + imgui.set_requires_extra_frame(); + } + + imgui.end(); + ImGui::PopStyleVar(); +} + +void GCodeViewer::SequentialView::GCodeWindow::load_gcode(const std::string& filename, std::vector &&lines_ends) +{ + assert(! m_file.is_open()); + if (m_file.is_open()) + return; + + m_filename = filename; + m_lines_ends = std::move(lines_ends); + + m_selected_line_id = 0; + m_last_lines_size = 0; + + try + { + m_file.open(boost::filesystem::path(m_filename)); + } + catch (...) + { + BOOST_LOG_TRIVIAL(error) << "Unable to map file " << m_filename << ". Cannot show G-code window."; + reset(); + } +} + +void GCodeViewer::SequentialView::GCodeWindow::render(float top, float bottom, uint64_t curr_line_id) const +{ + auto update_lines = [this](uint64_t start_id, uint64_t end_id) { + std::vector ret; + ret.reserve(end_id - start_id + 1); + for (uint64_t id = start_id; id <= end_id; ++id) { + // read line from file + const size_t start = id == 1 ? 0 : m_lines_ends[id - 2]; + const size_t len = m_lines_ends[id - 1] - start; + std::string gline(m_file.data() + start, len); + + std::string command; + std::string parameters; + std::string comment; + + // extract comment + std::vector tokens; + boost::split(tokens, gline, boost::is_any_of(";"), boost::token_compress_on); + command = tokens.front(); + if (tokens.size() > 1) + comment = ";" + tokens.back(); + + // extract gcode command and parameters + if (!command.empty()) { + boost::split(tokens, command, boost::is_any_of(" "), boost::token_compress_on); + command = tokens.front(); + if (tokens.size() > 1) { + for (size_t i = 1; i < tokens.size(); ++i) { + parameters += " " + tokens[i]; + } + } + } + ret.push_back({ command, parameters, comment }); + } + return ret; + }; + + static const ImVec4 LINE_NUMBER_COLOR = ImGuiWrapper::COL_ORANGE_LIGHT; + static const ImVec4 SELECTION_RECT_COLOR = ImGuiWrapper::COL_ORANGE_DARK; + static const ImVec4 COMMAND_COLOR = { 0.8f, 0.8f, 0.0f, 1.0f }; + static const ImVec4 PARAMETERS_COLOR = { 1.0f, 1.0f, 1.0f, 1.0f }; + static const ImVec4 COMMENT_COLOR = { 0.7f, 0.7f, 0.7f, 1.0f }; + static const ImVec4 ELLIPSIS_COLOR = { 0.0f, 0.7f, 0.0f, 1.0f }; + + if (!m_visible || m_filename.empty() || m_lines_ends.empty() || curr_line_id == 0) + return; + + // window height + const float wnd_height = bottom - top; + + // number of visible lines + const float text_height = ImGui::CalcTextSize("0").y; + const ImGuiStyle& style = ImGui::GetStyle(); + const uint64_t lines_count = static_cast((wnd_height - 2.0f * style.WindowPadding.y + style.ItemSpacing.y) / (text_height + style.ItemSpacing.y)); + + if (lines_count == 0) + return; + + // visible range + const uint64_t half_lines_count = lines_count / 2; + uint64_t start_id = (curr_line_id >= half_lines_count) ? curr_line_id - half_lines_count : 0; + uint64_t end_id = start_id + lines_count - 1; + if (end_id >= static_cast(m_lines_ends.size())) { + end_id = static_cast(m_lines_ends.size()) - 1; + start_id = end_id - lines_count + 1; + } + + // updates list of lines to show, if needed + if (m_selected_line_id != curr_line_id || m_last_lines_size != end_id - start_id + 1) { + try + { + *const_cast*>(&m_lines) = update_lines(start_id, end_id); + } + catch (...) + { + BOOST_LOG_TRIVIAL(error) << "Error while loading from file " << m_filename << ". Cannot show G-code window."; + return; + } + *const_cast(&m_selected_line_id) = curr_line_id; + *const_cast(&m_last_lines_size) = m_lines.size(); + } + + // line number's column width + const float id_width = ImGui::CalcTextSize(std::to_string(end_id).c_str()).x; + + ImGuiWrapper& imgui = *wxGetApp().imgui(); + + auto add_item_to_line = [&imgui](const std::string& txt, const ImVec4& color, float spacing, size_t& current_length) { + static const size_t LENGTH_THRESHOLD = 60; + + if (txt.empty()) + return false; + + std::string out_text = txt; + bool reduced = false; + if (current_length + out_text.length() > LENGTH_THRESHOLD) { + out_text = out_text.substr(0, LENGTH_THRESHOLD - current_length); + reduced = true; + } + + current_length += out_text.length(); + + ImGui::SameLine(0.0f, spacing); + ImGui::PushStyleColor(ImGuiCol_Text, color); + imgui.text(out_text); + ImGui::PopStyleColor(); + if (reduced) { + ImGui::SameLine(0.0f, 0.0f); + ImGui::PushStyleColor(ImGuiCol_Text, ELLIPSIS_COLOR); + imgui.text("..."); + ImGui::PopStyleColor(); + } + + return reduced; + }; + + imgui.set_next_window_pos(0.0f, top, ImGuiCond_Always, 0.0f, 0.0f); + imgui.set_next_window_size(0.0f, wnd_height, ImGuiCond_Always); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::SetNextWindowBgAlpha(0.6f); + imgui.begin(std::string("G-code"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); + + // center the text in the window by pushing down the first line + const float f_lines_count = static_cast(lines_count); + ImGui::SetCursorPosY(0.5f * (wnd_height - f_lines_count * text_height - (f_lines_count - 1.0f) * style.ItemSpacing.y)); + + // render text lines + for (uint64_t id = start_id; id <= end_id; ++id) { + const Line& line = m_lines[id - start_id]; + + // rect around the current selected line + if (id == curr_line_id) { + const float pos_y = ImGui::GetCursorScreenPos().y; + const float half_ItemSpacing_y = 0.5f * style.ItemSpacing.y; + const float half_padding_x = 0.5f * style.WindowPadding.x; + ImGui::GetWindowDrawList()->AddRect({ half_padding_x, pos_y - half_ItemSpacing_y }, + { ImGui::GetCurrentWindow()->Size.x - half_padding_x, pos_y + text_height + half_ItemSpacing_y }, + ImGui::GetColorU32(SELECTION_RECT_COLOR)); + } + + const std::string id_str = std::to_string(id); + // spacer to right align text + ImGui::Dummy({ id_width - ImGui::CalcTextSize(id_str.c_str()).x, text_height }); + + size_t line_length = 0; + // render line number + bool stop_adding = add_item_to_line(id_str, LINE_NUMBER_COLOR, 0.0f, line_length); + if (!stop_adding && !line.command.empty()) + // render command + stop_adding = add_item_to_line(line.command, COMMAND_COLOR, -1.0f, line_length); + if (!stop_adding && !line.parameters.empty()) + // render parameters + stop_adding = add_item_to_line(line.parameters, PARAMETERS_COLOR, 0.0f, line_length); + if (!stop_adding && !line.comment.empty()) + // render comment + stop_adding = add_item_to_line(line.comment, COMMENT_COLOR, line.command.empty() ? -1.0f : 0.0f, line_length); + } + + imgui.end(); + ImGui::PopStyleVar(); +} + +void GCodeViewer::SequentialView::GCodeWindow::stop_mapping_file() +{ + if (m_file.is_open()) + m_file.close(); +} + +void GCodeViewer::SequentialView::render(float legend_height) +{ + marker.render(); + float bottom = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size().get_height(); + if (wxGetApp().is_editor()) + bottom -= wxGetApp().plater()->get_view_toolbar().get_height(); + gcode_window.render(legend_height, bottom, static_cast(gcode_ids[current.last])); +} + +const std::vector GCodeViewer::Extrusion_Role_Colors{ { + { 0.90f, 0.70f, 0.70f, 1.0f }, // erNone + { 1.00f, 0.90f, 0.30f, 1.0f }, // erPerimeter + { 1.00f, 0.49f, 0.22f, 1.0f }, // erExternalPerimeter + { 0.12f, 0.12f, 1.00f, 1.0f }, // erOverhangPerimeter + { 0.69f, 0.19f, 0.16f, 1.0f }, // erInternalInfill + { 0.59f, 0.33f, 0.80f, 1.0f }, // erSolidInfill + { 0.94f, 0.25f, 0.25f, 1.0f }, // erTopSolidInfill + { 1.00f, 0.55f, 0.41f, 1.0f }, // erIroning + { 0.30f, 0.50f, 0.73f, 1.0f }, // erBridgeInfill + { 1.00f, 1.00f, 1.00f, 1.0f }, // erGapFill + { 0.00f, 0.53f, 0.43f, 1.0f }, // erSkirt + { 0.00f, 1.00f, 0.00f, 1.0f }, // erSupportMaterial + { 0.00f, 0.50f, 0.00f, 1.0f }, // erSupportMaterialInterface + { 0.70f, 0.89f, 0.67f, 1.0f }, // erWipeTower + { 0.37f, 0.82f, 0.58f, 1.0f }, // erCustom + { 0.00f, 0.00f, 0.00f, 1.0f } // erMixed +}}; + +const std::vector GCodeViewer::Options_Colors{ { + { 0.803f, 0.135f, 0.839f, 1.0f }, // Retractions + { 0.287f, 0.679f, 0.810f, 1.0f }, // Unretractions + { 0.900f, 0.900f, 0.900f, 1.0f }, // Seams + { 0.758f, 0.744f, 0.389f, 1.0f }, // ToolChanges + { 0.856f, 0.582f, 0.546f, 1.0f }, // ColorChanges + { 0.322f, 0.942f, 0.512f, 1.0f }, // PausePrints + { 0.886f, 0.825f, 0.262f, 1.0f } // CustomGCodes +}}; + +const std::vector GCodeViewer::Travel_Colors{ { + { 0.219f, 0.282f, 0.609f, 1.0f }, // Move + { 0.112f, 0.422f, 0.103f, 1.0f }, // Extrude + { 0.505f, 0.064f, 0.028f, 1.0f } // Retract +}}; + +#if 1 +// Normal ranges +const std::vector GCodeViewer::Range_Colors{ { + { 0.043f, 0.173f, 0.478f, 1.0f }, // bluish + { 0.075f, 0.349f, 0.522f, 1.0f }, + { 0.110f, 0.533f, 0.569f, 1.0f }, + { 0.016f, 0.839f, 0.059f, 1.0f }, + { 0.667f, 0.949f, 0.000f, 1.0f }, + { 0.988f, 0.975f, 0.012f, 1.0f }, + { 0.961f, 0.808f, 0.039f, 1.0f }, + { 0.890f, 0.533f, 0.125f, 1.0f }, + { 0.820f, 0.408f, 0.188f, 1.0f }, + { 0.761f, 0.322f, 0.235f, 1.0f }, + { 0.581f, 0.149f, 0.087f, 1.0f } // reddish +}}; +#else +// Detailed ranges +const std::vector GCodeViewer::Range_Colors{ { + { 0.043f, 0.173f, 0.478f, 1.0f }, // bluish + { 0.5f * (0.043f + 0.075f), 0.5f * (0.173f + 0.349f), 0.5f * (0.478f + 0.522f), 1.0f }, + { 0.075f, 0.349f, 0.522f, 1.0f }, + { 0.5f * (0.075f + 0.110f), 0.5f * (0.349f + 0.533f), 0.5f * (0.522f + 0.569f), 1.0f }, + { 0.110f, 0.533f, 0.569f, 1.0f }, + { 0.5f * (0.110f + 0.016f), 0.5f * (0.533f + 0.839f), 0.5f * (0.569f + 0.059f), 1.0f }, + { 0.016f, 0.839f, 0.059f, 1.0f }, + { 0.5f * (0.016f + 0.667f), 0.5f * (0.839f + 0.949f), 0.5f * (0.059f + 0.000f), 1.0f }, + { 0.667f, 0.949f, 0.000f, 1.0f }, + { 0.5f * (0.667f + 0.988f), 0.5f * (0.949f + 0.975f), 0.5f * (0.000f + 0.012f), 1.0f }, + { 0.988f, 0.975f, 0.012f, 1.0f }, + { 0.5f * (0.988f + 0.961f), 0.5f * (0.975f + 0.808f), 0.5f * (0.012f + 0.039f), 1.0f }, + { 0.961f, 0.808f, 0.039f, 1.0f }, + { 0.5f * (0.961f + 0.890f), 0.5f * (0.808f + 0.533f), 0.5f * (0.039f + 0.125f), 1.0f }, + { 0.890f, 0.533f, 0.125f, 1.0f }, + { 0.5f * (0.890f + 0.820f), 0.5f * (0.533f + 0.408f), 0.5f * (0.125f + 0.188f), 1.0f }, + { 0.820f, 0.408f, 0.188f, 1.0f }, + { 0.5f * (0.820f + 0.761f), 0.5f * (0.408f + 0.322f), 0.5f * (0.188f + 0.235f), 1.0f }, + { 0.761f, 0.322f, 0.235f, 1.0f }, + { 0.5f * (0.761f + 0.581f), 0.5f * (0.322f + 0.149f), 0.5f * (0.235f + 0.087f), 1.0f }, + { 0.581f, 0.149f, 0.087f, 1.0f } // reddishgit +} }; +#endif + +const ColorRGBA GCodeViewer::Wipe_Color = ColorRGBA::YELLOW(); +const ColorRGBA GCodeViewer::Neutral_Color = ColorRGBA::DARK_GRAY(); + +GCodeViewer::GCodeViewer() +{ + m_extrusions.reset_role_visibility_flags(); + +// m_sequential_view.skip_invisible_moves = true; +} + +void GCodeViewer::init() +{ + if (m_gl_data_initialized) + return; + + // initializes opengl data of TBuffers + for (size_t i = 0; i < m_buffers.size(); ++i) { + TBuffer& buffer = m_buffers[i]; + EMoveType type = buffer_type(i); + switch (type) + { + default: { break; } + case EMoveType::Tool_change: + case EMoveType::Color_change: + case EMoveType::Pause_Print: + case EMoveType::Custom_GCode: + case EMoveType::Retract: + case EMoveType::Unretract: + case EMoveType::Seam: { +#if !DISABLE_GCODEVIEWER_INSTANCED_MODELS + if (wxGetApp().is_gl_version_greater_or_equal_to(3, 3)) { + buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::InstancedModel; + buffer.shader = "gouraud_light_instanced"; + buffer.model.model.init_from(diamond(16)); + buffer.model.color = option_color(type); + buffer.model.instances.format = InstanceVBuffer::EFormat::InstancedModel; + } + else { +#endif // !DISABLE_GCODEVIEWER_INSTANCED_MODELS + buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::BatchedModel; + buffer.vertices.format = VBuffer::EFormat::PositionNormal3; + buffer.shader = "gouraud_light"; + buffer.model.data = diamond(16); + buffer.model.color = option_color(type); + buffer.model.instances.format = InstanceVBuffer::EFormat::BatchedModel; +#if !DISABLE_GCODEVIEWER_INSTANCED_MODELS + } +#endif // !DISABLE_GCODEVIEWER_INSTANCED_MODELS + break; + } + case EMoveType::Wipe: + case EMoveType::Extrude: { + buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Triangle; + buffer.vertices.format = VBuffer::EFormat::PositionNormal3; + buffer.shader = "gouraud_light"; + break; + } + case EMoveType::Travel: { + buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Line; +#if ENABLE_GL_SHADERS_ATTRIBUTES + buffer.vertices.format = VBuffer::EFormat::Position; + buffer.shader = "flat"; +#else + buffer.vertices.format = VBuffer::EFormat::PositionNormal3; + buffer.shader = "toolpaths_lines"; +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + break; + } + } + + set_toolpath_move_type_visible(EMoveType::Extrude, true); + } + + // initializes tool marker + m_sequential_view.marker.init(); + + // initializes point sizes + std::array point_sizes; + ::glGetIntegerv(GL_ALIASED_POINT_SIZE_RANGE, point_sizes.data()); + m_detected_point_sizes = { static_cast(point_sizes[0]), static_cast(point_sizes[1]) }; + + m_gl_data_initialized = true; +} + +#if ENABLE_LEGACY_OPENGL_REMOVAL +void GCodeViewer::load(const GCodeProcessorResult& gcode_result, const Print& print) +#else +void GCodeViewer::load(const GCodeProcessorResult& gcode_result, const Print& print, bool initialized) +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +{ + // avoid processing if called with the same gcode_result +#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + if (m_last_result_id == gcode_result.id && + (m_last_view_type == m_view_type || (m_last_view_type != EViewType::VolumetricRate && m_view_type != EViewType::VolumetricRate))) + return; +#else + if (m_last_result_id == gcode_result.id) + return; +#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + + m_last_result_id = gcode_result.id; +#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + m_last_view_type = m_view_type; +#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + + // release gpu memory, if used + reset(); + + m_sequential_view.gcode_window.load_gcode(gcode_result.filename, + // Stealing out lines_ends should be safe because this gcode_result is processed only once (see the 1st if in this function). + std::move(const_cast&>(gcode_result.lines_ends))); + + if (wxGetApp().is_gcode_viewer()) + m_custom_gcode_per_print_z = gcode_result.custom_gcode_per_print_z; + + m_max_print_height = gcode_result.max_print_height; + + load_toolpaths(gcode_result); + + if (m_layers.empty()) + return; + + m_settings_ids = gcode_result.settings_ids; + m_filament_diameters = gcode_result.filament_diameters; + m_filament_densities = gcode_result.filament_densities; + + if (wxGetApp().is_editor()) +#if ENABLE_LEGACY_OPENGL_REMOVAL + load_shells(print); +#else + load_shells(print, initialized); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + else { + Pointfs bed_shape; + std::string texture; + std::string model; + + if (!gcode_result.bed_shape.empty()) { + // bed shape detected in the gcode + bed_shape = gcode_result.bed_shape; + const auto bundle = wxGetApp().preset_bundle; + if (bundle != nullptr && !m_settings_ids.printer.empty()) { + const Preset* preset = bundle->printers.find_preset(m_settings_ids.printer); + if (preset != nullptr) { + model = PresetUtils::system_printer_bed_model(*preset); + texture = PresetUtils::system_printer_bed_texture(*preset); + } + } + } + else { + // adjust printbed size in dependence of toolpaths bbox + const double margin = 10.0; + const Vec2d min(m_paths_bounding_box.min.x() - margin, m_paths_bounding_box.min.y() - margin); + const Vec2d max(m_paths_bounding_box.max.x() + margin, m_paths_bounding_box.max.y() + margin); + + const Vec2d size = max - min; + bed_shape = { + { min.x(), min.y() }, + { max.x(), min.y() }, + { max.x(), min.y() + 0.442265 * size.y()}, + { max.x() - 10.0, min.y() + 0.4711325 * size.y()}, + { max.x() + 10.0, min.y() + 0.5288675 * size.y()}, + { max.x(), min.y() + 0.557735 * size.y()}, + { max.x(), max.y() }, + { min.x() + 0.557735 * size.x(), max.y()}, + { min.x() + 0.5288675 * size.x(), max.y() - 10.0}, + { min.x() + 0.4711325 * size.x(), max.y() + 10.0}, + { min.x() + 0.442265 * size.x(), max.y()}, + { min.x(), max.y() } }; + } + + wxGetApp().plater()->set_bed_shape(bed_shape, gcode_result.max_print_height, texture, model, gcode_result.bed_shape.empty()); + } + + m_print_statistics = gcode_result.print_statistics; + + if (m_time_estimate_mode != PrintEstimatedStatistics::ETimeMode::Normal) { + const float time = m_print_statistics.modes[static_cast(m_time_estimate_mode)].time; + if (time == 0.0f || + short_time(get_time_dhms(time)) == short_time(get_time_dhms(m_print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].time))) + m_time_estimate_mode = PrintEstimatedStatistics::ETimeMode::Normal; + } +} + +void GCodeViewer::refresh(const GCodeProcessorResult& gcode_result, const std::vector& str_tool_colors) +{ +#if ENABLE_GCODE_VIEWER_STATISTICS + auto start_time = std::chrono::high_resolution_clock::now(); +#endif // ENABLE_GCODE_VIEWER_STATISTICS + + if (m_moves_count == 0) + return; + + wxBusyCursor busy; + + if (m_view_type == EViewType::Tool && !gcode_result.extruder_colors.empty()) + // update tool colors from config stored in the gcode + decode_colors(gcode_result.extruder_colors, m_tool_colors); + else + // update tool colors + decode_colors(str_tool_colors, m_tool_colors); + + ColorRGBA default_color; + decode_color("#FF8000", default_color); + + // ensure there are enough colors defined + while (m_tool_colors.size() < std::max(size_t(1), gcode_result.extruders_count)) + m_tool_colors.push_back(default_color); + + // update ranges for coloring / legend + m_extrusions.reset_ranges(); + for (size_t i = 0; i < m_moves_count; ++i) { + // skip first vertex + if (i == 0) + continue; + + const GCodeProcessorResult::MoveVertex& curr = gcode_result.moves[i]; + + switch (curr.type) + { + case EMoveType::Extrude: + { + m_extrusions.ranges.height.update_from(round_to_bin(curr.height)); + m_extrusions.ranges.width.update_from(round_to_bin(curr.width)); + m_extrusions.ranges.fan_speed.update_from(curr.fan_speed); + m_extrusions.ranges.temperature.update_from(curr.temperature); + if (curr.extrusion_role != erCustom || is_visible(erCustom)) + m_extrusions.ranges.volumetric_rate.update_from(round_to_bin(curr.volumetric_rate())); + [[fallthrough]]; + } + case EMoveType::Travel: + { + if (m_buffers[buffer_id(curr.type)].visible) + m_extrusions.ranges.feedrate.update_from(curr.feedrate); + + break; + } + default: { break; } + } + } + +#if ENABLE_PREVIEW_LAYER_TIME + for (size_t i = 0; i < gcode_result.print_statistics.modes.size(); ++i) { + m_layers_times[i] = gcode_result.print_statistics.modes[i].layers_times; + } + + for (size_t i = 0; i < m_layers_times.size(); ++i) { + for (float time : m_layers_times[i]) { + m_extrusions.ranges.layer_time[i].update_from(time); + } + } +#endif // ENABLE_PREVIEW_LAYER_TIME + +#if ENABLE_GCODE_VIEWER_STATISTICS + m_statistics.refresh_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); +#endif // ENABLE_GCODE_VIEWER_STATISTICS + + // update buffers' render paths +#if ENABLE_PREVIEW_LAYOUT + refresh_render_paths(false, false); +#else + refresh_render_paths(); +#endif // ENABLE_PREVIEW_LAYOUT + log_memory_used("Refreshed G-code extrusion paths, "); +} + +#if !ENABLE_PREVIEW_LAYOUT +void GCodeViewer::refresh_render_paths() +{ + refresh_render_paths(false, false); +} +#endif // !ENABLE_PREVIEW_LAYOUT + +void GCodeViewer::update_shells_color_by_extruder(const DynamicPrintConfig* config) +{ + if (config != nullptr) + m_shells.volumes.update_colors_by_extruder(config); +} + +void GCodeViewer::reset() +{ + m_moves_count = 0; + for (TBuffer& buffer : m_buffers) { + buffer.reset(); + } + + m_paths_bounding_box = BoundingBoxf3(); + m_max_bounding_box = BoundingBoxf3(); + m_max_print_height = 0.0f; + m_tool_colors = std::vector(); + m_extruders_count = 0; + m_extruder_ids = std::vector(); + m_filament_diameters = std::vector(); + m_filament_densities = std::vector(); + m_extrusions.reset_ranges(); + m_shells.volumes.clear(); + m_layers.reset(); + m_layers_z_range = { 0, 0 }; + m_roles = std::vector(); + m_print_statistics.reset(); +#if ENABLE_PREVIEW_LAYER_TIME + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + m_layers_times[i] = std::vector(); + } +#endif // ENABLE_PREVIEW_LAYER_TIME + m_custom_gcode_per_print_z = std::vector(); + m_sequential_view.gcode_window.reset(); +#if ENABLE_GCODE_VIEWER_STATISTICS + m_statistics.reset_all(); +#endif // ENABLE_GCODE_VIEWER_STATISTICS + m_contained_in_bed = true; +#if ENABLE_PREVIEW_LAYOUT + m_legend_resizer.reset(); +#endif // ENABLE_PREVIEW_LAYOUT +} + +void GCodeViewer::render() +{ +#if ENABLE_GCODE_VIEWER_STATISTICS + m_statistics.reset_opengl(); + m_statistics.total_instances_gpu_size = 0; +#endif // ENABLE_GCODE_VIEWER_STATISTICS + + if (m_roles.empty()) + return; + + glsafe(::glEnable(GL_DEPTH_TEST)); + render_toolpaths(); + render_shells(); + float legend_height = 0.0f; + render_legend(legend_height); + if (m_sequential_view.current.last != m_sequential_view.endpoints.last) { + m_sequential_view.marker.set_world_position(m_sequential_view.current_position); + m_sequential_view.marker.set_world_offset(m_sequential_view.current_offset); + m_sequential_view.render(legend_height); + } +#if ENABLE_GCODE_VIEWER_STATISTICS + render_statistics(); +#endif // ENABLE_GCODE_VIEWER_STATISTICS +} + +bool GCodeViewer::can_export_toolpaths() const +{ + return has_data() && m_buffers[buffer_id(EMoveType::Extrude)].render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle; +} + +void GCodeViewer::update_sequential_view_current(unsigned int first, unsigned int last) +{ + auto is_visible = [this](unsigned int id) { + for (const TBuffer& buffer : m_buffers) { + if (buffer.visible) { + for (const Path& path : buffer.paths) { + if (path.sub_paths.front().first.s_id <= id && id <= path.sub_paths.back().last.s_id) + return true; + } + } + } + return false; + }; + + const int first_diff = static_cast(first) - static_cast(m_sequential_view.last_current.first); + const int last_diff = static_cast(last) - static_cast(m_sequential_view.last_current.last); + + unsigned int new_first = first; + unsigned int new_last = last; + + if (m_sequential_view.skip_invisible_moves) { + while (!is_visible(new_first)) { + if (first_diff > 0) + ++new_first; + else + --new_first; + } + + while (!is_visible(new_last)) { + if (last_diff > 0) + ++new_last; + else + --new_last; + } + } + + m_sequential_view.current.first = new_first; + m_sequential_view.current.last = new_last; + m_sequential_view.last_current = m_sequential_view.current; + + refresh_render_paths(true, true); + + if (new_first != first || new_last != last) + wxGetApp().plater()->update_preview_moves_slider(); +} + +bool GCodeViewer::is_toolpath_move_type_visible(EMoveType type) const +{ + size_t id = static_cast(buffer_id(type)); + return (id < m_buffers.size()) ? m_buffers[id].visible : false; +} + +void GCodeViewer::set_toolpath_move_type_visible(EMoveType type, bool visible) +{ + size_t id = static_cast(buffer_id(type)); + if (id < m_buffers.size()) + m_buffers[id].visible = visible; +} + +unsigned int GCodeViewer::get_options_visibility_flags() const +{ + auto set_flag = [](unsigned int flags, unsigned int flag, bool active) { + return active ? (flags | (1 << flag)) : flags; + }; + + unsigned int flags = 0; + flags = set_flag(flags, static_cast(Preview::OptionType::Travel), is_toolpath_move_type_visible(EMoveType::Travel)); + flags = set_flag(flags, static_cast(Preview::OptionType::Wipe), is_toolpath_move_type_visible(EMoveType::Wipe)); + flags = set_flag(flags, static_cast(Preview::OptionType::Retractions), is_toolpath_move_type_visible(EMoveType::Retract)); + flags = set_flag(flags, static_cast(Preview::OptionType::Unretractions), is_toolpath_move_type_visible(EMoveType::Unretract)); + flags = set_flag(flags, static_cast(Preview::OptionType::Seams), is_toolpath_move_type_visible(EMoveType::Seam)); + flags = set_flag(flags, static_cast(Preview::OptionType::ToolChanges), is_toolpath_move_type_visible(EMoveType::Tool_change)); + flags = set_flag(flags, static_cast(Preview::OptionType::ColorChanges), is_toolpath_move_type_visible(EMoveType::Color_change)); + flags = set_flag(flags, static_cast(Preview::OptionType::PausePrints), is_toolpath_move_type_visible(EMoveType::Pause_Print)); + flags = set_flag(flags, static_cast(Preview::OptionType::CustomGCodes), is_toolpath_move_type_visible(EMoveType::Custom_GCode)); +#if ENABLE_SHOW_TOOLPATHS_COG + flags = set_flag(flags, static_cast(Preview::OptionType::CenterOfGravity), m_cog.is_visible()); +#endif // ENABLE_SHOW_TOOLPATHS_COG + flags = set_flag(flags, static_cast(Preview::OptionType::Shells), m_shells.visible); + flags = set_flag(flags, static_cast(Preview::OptionType::ToolMarker), m_sequential_view.marker.is_visible()); +#if !ENABLE_PREVIEW_LAYOUT + flags = set_flag(flags, static_cast(Preview::OptionType::Legend), is_legend_enabled()); +#endif // !ENABLE_PREVIEW_LAYOUT + return flags; +} + +void GCodeViewer::set_options_visibility_from_flags(unsigned int flags) +{ + auto is_flag_set = [flags](unsigned int flag) { + return (flags & (1 << flag)) != 0; + }; + + set_toolpath_move_type_visible(EMoveType::Travel, is_flag_set(static_cast(Preview::OptionType::Travel))); + set_toolpath_move_type_visible(EMoveType::Wipe, is_flag_set(static_cast(Preview::OptionType::Wipe))); + set_toolpath_move_type_visible(EMoveType::Retract, is_flag_set(static_cast(Preview::OptionType::Retractions))); + set_toolpath_move_type_visible(EMoveType::Unretract, is_flag_set(static_cast(Preview::OptionType::Unretractions))); + set_toolpath_move_type_visible(EMoveType::Seam, is_flag_set(static_cast(Preview::OptionType::Seams))); + set_toolpath_move_type_visible(EMoveType::Tool_change, is_flag_set(static_cast(Preview::OptionType::ToolChanges))); + set_toolpath_move_type_visible(EMoveType::Color_change, is_flag_set(static_cast(Preview::OptionType::ColorChanges))); + set_toolpath_move_type_visible(EMoveType::Pause_Print, is_flag_set(static_cast(Preview::OptionType::PausePrints))); + set_toolpath_move_type_visible(EMoveType::Custom_GCode, is_flag_set(static_cast(Preview::OptionType::CustomGCodes))); +#if ENABLE_SHOW_TOOLPATHS_COG + m_cog.set_visible(is_flag_set(static_cast(Preview::OptionType::CenterOfGravity))); +#endif // ENABLE_SHOW_TOOLPATHS_COG + m_shells.visible = is_flag_set(static_cast(Preview::OptionType::Shells)); + m_sequential_view.marker.set_visible(is_flag_set(static_cast(Preview::OptionType::ToolMarker))); +#if !ENABLE_PREVIEW_LAYOUT + enable_legend(is_flag_set(static_cast(Preview::OptionType::Legend))); +#endif // !ENABLE_PREVIEW_LAYOUT +} + +void GCodeViewer::set_layers_z_range(const std::array& layers_z_range) +{ + bool keep_sequential_current_first = layers_z_range[0] >= m_layers_z_range[0]; + bool keep_sequential_current_last = layers_z_range[1] <= m_layers_z_range[1]; + m_layers_z_range = layers_z_range; + refresh_render_paths(keep_sequential_current_first, keep_sequential_current_last); + wxGetApp().plater()->update_preview_moves_slider(); +} + +void GCodeViewer::export_toolpaths_to_obj(const char* filename) const +{ + if (filename == nullptr) + return; + + if (!has_data()) + return; + + wxBusyCursor busy; + + // the data needed is contained into the Extrude TBuffer + const TBuffer& t_buffer = m_buffers[buffer_id(EMoveType::Extrude)]; + if (!t_buffer.has_data()) + return; + + if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Triangle) + return; + + // collect color information to generate materials + std::vector colors; + for (const RenderPath& path : t_buffer.render_paths) { + colors.push_back(path.color); + } + sort_remove_duplicates(colors); + + // save materials file + boost::filesystem::path mat_filename(filename); + mat_filename.replace_extension("mtl"); + + CNumericLocalesSetter locales_setter; + + FILE* fp = boost::nowide::fopen(mat_filename.string().c_str(), "w"); + if (fp == nullptr) { + BOOST_LOG_TRIVIAL(error) << "GCodeViewer::export_toolpaths_to_obj: Couldn't open " << mat_filename.string().c_str() << " for writing"; + return; + } + + fprintf(fp, "# G-Code Toolpaths Materials\n"); + fprintf(fp, "# Generated by %s-%s based on Slic3r\n", SLIC3R_APP_NAME, SLIC3R_VERSION); + + unsigned int colors_count = 1; + for (const ColorRGBA& color : colors) { + fprintf(fp, "\nnewmtl material_%d\n", colors_count++); + fprintf(fp, "Ka 1 1 1\n"); + fprintf(fp, "Kd %g %g %g\n", color.r(), color.g(), color.b()); + fprintf(fp, "Ks 0 0 0\n"); + } + + fclose(fp); + + // save geometry file + fp = boost::nowide::fopen(filename, "w"); + if (fp == nullptr) { + BOOST_LOG_TRIVIAL(error) << "GCodeViewer::export_toolpaths_to_obj: Couldn't open " << filename << " for writing"; + return; + } + + fprintf(fp, "# G-Code Toolpaths\n"); + fprintf(fp, "# Generated by %s-%s based on Slic3r\n", SLIC3R_APP_NAME, SLIC3R_VERSION); + fprintf(fp, "\nmtllib ./%s\n", mat_filename.filename().string().c_str()); + + const size_t floats_per_vertex = t_buffer.vertices.vertex_size_floats(); + + std::vector out_vertices; + std::vector out_normals; + + struct VerticesOffset + { + unsigned int vbo; + size_t offset; + }; + std::vector vertices_offsets; + vertices_offsets.push_back({ t_buffer.vertices.vbos.front(), 0 }); + + // get vertices/normals data from vertex buffers on gpu + for (size_t i = 0; i < t_buffer.vertices.vbos.size(); ++i) { + const size_t floats_count = t_buffer.vertices.sizes[i] / sizeof(float); + VertexBuffer vertices(floats_count); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, t_buffer.vertices.vbos[i])); + glsafe(::glGetBufferSubData(GL_ARRAY_BUFFER, 0, static_cast(t_buffer.vertices.sizes[i]), static_cast(vertices.data()))); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + const size_t vertices_count = floats_count / floats_per_vertex; + for (size_t j = 0; j < vertices_count; ++j) { + const size_t base = j * floats_per_vertex; + out_vertices.push_back({ vertices[base + 0], vertices[base + 1], vertices[base + 2] }); + out_normals.push_back({ vertices[base + 3], vertices[base + 4], vertices[base + 5] }); + } + + if (i < t_buffer.vertices.vbos.size() - 1) + vertices_offsets.push_back({ t_buffer.vertices.vbos[i + 1], vertices_offsets.back().offset + vertices_count }); + } + + // save vertices to file + fprintf(fp, "\n# vertices\n"); + for (const Vec3f& v : out_vertices) { + fprintf(fp, "v %g %g %g\n", v.x(), v.y(), v.z()); + } + + // save normals to file + fprintf(fp, "\n# normals\n"); + for (const Vec3f& n : out_normals) { + fprintf(fp, "vn %g %g %g\n", n.x(), n.y(), n.z()); + } + + size_t i = 0; + for (const ColorRGBA& color : colors) { + // save material triangles to file + fprintf(fp, "\nusemtl material_%zu\n", i + 1); + fprintf(fp, "# triangles material %zu\n", i + 1); + + for (const RenderPath& render_path : t_buffer.render_paths) { + if (render_path.color != color) + continue; + + const IBuffer& ibuffer = t_buffer.indices[render_path.ibuffer_id]; + size_t vertices_offset = 0; + for (size_t j = 0; j < vertices_offsets.size(); ++j) { + const VerticesOffset& offset = vertices_offsets[j]; + if (offset.vbo == ibuffer.vbo) { + vertices_offset = offset.offset; + break; + } + } + + // get indices data from index buffer on gpu + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibuffer.ibo)); + for (size_t j = 0; j < render_path.sizes.size(); ++j) { + IndexBuffer indices(render_path.sizes[j]); + glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast(render_path.offsets[j]), + static_cast(render_path.sizes[j] * sizeof(IBufferType)), static_cast(indices.data()))); + + const size_t triangles_count = render_path.sizes[j] / 3; + for (size_t k = 0; k < triangles_count; ++k) { + const size_t base = k * 3; + const size_t v1 = 1 + static_cast(indices[base + 0]) + vertices_offset; + const size_t v2 = 1 + static_cast(indices[base + 1]) + vertices_offset; + const size_t v3 = 1 + static_cast(indices[base + 2]) + vertices_offset; + if (v1 != v2) + // do not export dummy triangles + fprintf(fp, "f %zu//%zu %zu//%zu %zu//%zu\n", v1, v1, v2, v2, v3, v3); + } + } + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } + ++i; + } + + fclose(fp); +} + +void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) +{ + // max index buffer size, in bytes + static const size_t IBUFFER_THRESHOLD_BYTES = 64 * 1024 * 1024; + + auto log_memory_usage = [this](const std::string& label, const std::vector& vertices, const std::vector& indices) { + int64_t vertices_size = 0; + for (const MultiVertexBuffer& buffers : vertices) { + for (const VertexBuffer& buffer : buffers) { + vertices_size += SLIC3R_STDVEC_MEMSIZE(buffer, float); + } + } + int64_t indices_size = 0; + for (const MultiIndexBuffer& buffers : indices) { + for (const IndexBuffer& buffer : buffers) { + indices_size += SLIC3R_STDVEC_MEMSIZE(buffer, IBufferType); + } + } + log_memory_used(label, vertices_size + indices_size); + }; + + // format data into the buffers to be rendered as lines + auto add_vertices_as_line = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, VertexBuffer& vertices) { +#if !ENABLE_GL_SHADERS_ATTRIBUTES + // x component of the normal to the current segment (the normal is parallel to the XY plane) + const Vec3f dir = (curr.position - prev.position).normalized(); + Vec3f normal(dir.y(), -dir.x(), 0.0); + normal.normalize(); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + +#if ENABLE_GL_SHADERS_ATTRIBUTES + auto add_vertex = [&vertices](const GCodeProcessorResult::MoveVertex& vertex) { + // add position + vertices.push_back(vertex.position.x()); + vertices.push_back(vertex.position.y()); + vertices.push_back(vertex.position.z()); + }; +#else + auto add_vertex = [&vertices, &normal](const GCodeProcessorResult::MoveVertex& vertex) { + // add position + vertices.push_back(vertex.position.x()); + vertices.push_back(vertex.position.y()); + vertices.push_back(vertex.position.z()); + // add normal + vertices.push_back(normal.x()); + vertices.push_back(normal.y()); + vertices.push_back(normal.z()); + }; +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + // add previous vertex + add_vertex(prev); + // add current vertex + add_vertex(curr); + }; +#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + auto add_indices_as_line = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, TBuffer& buffer, + unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id, bool account_for_volumetric_rate) { + if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr, account_for_volumetric_rate)) { +#else + auto add_indices_as_line = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, TBuffer& buffer, + unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id) { + if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr)) { +#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + // add starting index + indices.push_back(static_cast(indices.size())); + buffer.add_path(curr, ibuffer_id, indices.size() - 1, move_id - 1); + buffer.paths.back().sub_paths.front().first.position = prev.position; + } + + Path& last_path = buffer.paths.back(); + if (last_path.sub_paths.front().first.i_id != last_path.sub_paths.back().last.i_id) { + // add previous index + indices.push_back(static_cast(indices.size())); + } + + // add current index + indices.push_back(static_cast(indices.size())); + last_path.sub_paths.back().last = { ibuffer_id, indices.size() - 1, move_id, curr.position }; + }; + + // format data into the buffers to be rendered as solid +#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + auto add_vertices_as_solid = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, TBuffer& buffer, + unsigned int vbuffer_id, VertexBuffer& vertices, size_t move_id, bool account_for_volumetric_rate) { +#else + auto add_vertices_as_solid = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, TBuffer& buffer, + unsigned int vbuffer_id, VertexBuffer& vertices, size_t move_id) { +#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + auto store_vertex = [](VertexBuffer& vertices, const Vec3f& position, const Vec3f& normal) { + // append position + vertices.push_back(position.x()); + vertices.push_back(position.y()); + vertices.push_back(position.z()); + // append normal + vertices.push_back(normal.x()); + vertices.push_back(normal.y()); + vertices.push_back(normal.z()); + }; + +#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr, account_for_volumetric_rate)) { +#else + if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr)) { +#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + buffer.add_path(curr, vbuffer_id, vertices.size(), move_id - 1); + buffer.paths.back().sub_paths.back().first.position = prev.position; + } + + Path& last_path = buffer.paths.back(); + + const Vec3f dir = (curr.position - prev.position).normalized(); + const Vec3f right = Vec3f(dir.y(), -dir.x(), 0.0f).normalized(); + const Vec3f left = -right; + const Vec3f up = right.cross(dir); + const Vec3f down = -up; + const float half_width = 0.5f * last_path.width; + const float half_height = 0.5f * last_path.height; + const Vec3f prev_pos = prev.position - half_height * up; + const Vec3f curr_pos = curr.position - half_height * up; + const Vec3f d_up = half_height * up; + const Vec3f d_down = -half_height * up; + const Vec3f d_right = half_width * right; + const Vec3f d_left = -half_width * right; + + // vertices 1st endpoint + if (last_path.vertices_count() == 1 || vertices.empty()) { + // 1st segment or restart into a new vertex buffer + // =============================================== + store_vertex(vertices, prev_pos + d_up, up); + store_vertex(vertices, prev_pos + d_right, right); + store_vertex(vertices, prev_pos + d_down, down); + store_vertex(vertices, prev_pos + d_left, left); + } + else { + // any other segment + // ================= + store_vertex(vertices, prev_pos + d_right, right); + store_vertex(vertices, prev_pos + d_left, left); + } + + // vertices 2nd endpoint + store_vertex(vertices, curr_pos + d_up, up); + store_vertex(vertices, curr_pos + d_right, right); + store_vertex(vertices, curr_pos + d_down, down); + store_vertex(vertices, curr_pos + d_left, left); + + last_path.sub_paths.back().last = { vbuffer_id, vertices.size(), move_id, curr.position }; + }; +#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + auto add_indices_as_solid = [&](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, + const GCodeProcessorResult::MoveVertex* next, TBuffer& buffer, size_t& vbuffer_size, unsigned int ibuffer_id, + IndexBuffer& indices, size_t move_id, bool account_for_volumetric_rate) { +#else + auto add_indices_as_solid = [&](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, + const GCodeProcessorResult::MoveVertex* next, TBuffer& buffer, size_t& vbuffer_size, unsigned int ibuffer_id, + IndexBuffer& indices, size_t move_id) { +#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + static Vec3f prev_dir; + static Vec3f prev_up; + static float sq_prev_length; + auto store_triangle = [](IndexBuffer& indices, IBufferType i1, IBufferType i2, IBufferType i3) { + indices.push_back(i1); + indices.push_back(i2); + indices.push_back(i3); + }; + auto append_dummy_cap = [store_triangle](IndexBuffer& indices, IBufferType id) { + store_triangle(indices, id, id, id); + store_triangle(indices, id, id, id); + }; + auto convert_vertices_offset = [](size_t vbuffer_size, const std::array& v_offsets) { + std::array ret = { + static_cast(static_cast(vbuffer_size) + v_offsets[0]), + static_cast(static_cast(vbuffer_size) + v_offsets[1]), + static_cast(static_cast(vbuffer_size) + v_offsets[2]), + static_cast(static_cast(vbuffer_size) + v_offsets[3]), + static_cast(static_cast(vbuffer_size) + v_offsets[4]), + static_cast(static_cast(vbuffer_size) + v_offsets[5]), + static_cast(static_cast(vbuffer_size) + v_offsets[6]), + static_cast(static_cast(vbuffer_size) + v_offsets[7]) + }; + return ret; + }; + auto append_starting_cap_triangles = [&](IndexBuffer& indices, const std::array& v_offsets) { + store_triangle(indices, v_offsets[0], v_offsets[2], v_offsets[1]); + store_triangle(indices, v_offsets[0], v_offsets[3], v_offsets[2]); + }; + auto append_stem_triangles = [&](IndexBuffer& indices, const std::array& v_offsets) { + store_triangle(indices, v_offsets[0], v_offsets[1], v_offsets[4]); + store_triangle(indices, v_offsets[1], v_offsets[5], v_offsets[4]); + store_triangle(indices, v_offsets[1], v_offsets[2], v_offsets[5]); + store_triangle(indices, v_offsets[2], v_offsets[6], v_offsets[5]); + store_triangle(indices, v_offsets[2], v_offsets[3], v_offsets[6]); + store_triangle(indices, v_offsets[3], v_offsets[7], v_offsets[6]); + store_triangle(indices, v_offsets[3], v_offsets[0], v_offsets[7]); + store_triangle(indices, v_offsets[0], v_offsets[4], v_offsets[7]); + }; + auto append_ending_cap_triangles = [&](IndexBuffer& indices, const std::array& v_offsets) { + store_triangle(indices, v_offsets[4], v_offsets[6], v_offsets[7]); + store_triangle(indices, v_offsets[4], v_offsets[5], v_offsets[6]); + }; + +#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr, account_for_volumetric_rate)) { +#else + if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr)) { +#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + buffer.add_path(curr, ibuffer_id, indices.size(), move_id - 1); + buffer.paths.back().sub_paths.back().first.position = prev.position; + } + + Path& last_path = buffer.paths.back(); + + const Vec3f dir = (curr.position - prev.position).normalized(); + const Vec3f right = Vec3f(dir.y(), -dir.x(), 0.0f).normalized(); + const Vec3f up = right.cross(dir); + const float sq_length = (curr.position - prev.position).squaredNorm(); + + const std::array first_seg_v_offsets = convert_vertices_offset(vbuffer_size, { 0, 1, 2, 3, 4, 5, 6, 7 }); + const std::array non_first_seg_v_offsets = convert_vertices_offset(vbuffer_size, { -4, 0, -2, 1, 2, 3, 4, 5 }); + const bool is_first_segment = (last_path.vertices_count() == 1); + if (is_first_segment || vbuffer_size == 0) { + // 1st segment or restart into a new vertex buffer + // =============================================== + if (is_first_segment) + // starting cap triangles + append_starting_cap_triangles(indices, first_seg_v_offsets); + // dummy triangles outer corner cap + append_dummy_cap(indices, vbuffer_size); + + // stem triangles + append_stem_triangles(indices, first_seg_v_offsets); + + vbuffer_size += 8; + } + else { + // any other segment + // ================= + float displacement = 0.0f; + const float cos_dir = prev_dir.dot(dir); + if (cos_dir > -0.9998477f) { + // if the angle between adjacent segments is smaller than 179 degrees + const Vec3f med_dir = (prev_dir + dir).normalized(); + const float half_width = 0.5f * last_path.width; + displacement = half_width * ::tan(::acos(std::clamp(dir.dot(med_dir), -1.0f, 1.0f))); + } + + const float sq_displacement = sqr(displacement); + const bool can_displace = displacement > 0.0f && sq_displacement < sq_prev_length && sq_displacement < sq_length; + + const bool is_right_turn = prev_up.dot(prev_dir.cross(dir)) <= 0.0f; + // whether the angle between adjacent segments is greater than 45 degrees + const bool is_sharp = cos_dir < 0.7071068f; + + bool right_displaced = false; + bool left_displaced = false; + + if (!is_sharp && can_displace) { + if (is_right_turn) + left_displaced = true; + else + right_displaced = true; + } + + // triangles outer corner cap + if (is_right_turn) { + if (left_displaced) + // dummy triangles + append_dummy_cap(indices, vbuffer_size); + else { + store_triangle(indices, vbuffer_size - 4, vbuffer_size + 1, vbuffer_size - 1); + store_triangle(indices, vbuffer_size + 1, vbuffer_size - 2, vbuffer_size - 1); + } + } + else { + if (right_displaced) + // dummy triangles + append_dummy_cap(indices, vbuffer_size); + else { + store_triangle(indices, vbuffer_size - 4, vbuffer_size - 3, vbuffer_size + 0); + store_triangle(indices, vbuffer_size - 3, vbuffer_size - 2, vbuffer_size + 0); + } + } + + // stem triangles + append_stem_triangles(indices, non_first_seg_v_offsets); + + vbuffer_size += 6; + } + +#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + if (next != nullptr && (curr.type != next->type || !last_path.matches(*next, account_for_volumetric_rate))) +#else + if (next != nullptr && (curr.type != next->type || !last_path.matches(*next))) +#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + // ending cap triangles + append_ending_cap_triangles(indices, is_first_segment ? first_seg_v_offsets : non_first_seg_v_offsets); + + last_path.sub_paths.back().last = { ibuffer_id, indices.size() - 1, move_id, curr.position }; + prev_dir = dir; + prev_up = up; + sq_prev_length = sq_length; + }; + + // format data into the buffers to be rendered as instanced model + auto add_model_instance = [](const GCodeProcessorResult::MoveVertex& curr, InstanceBuffer& instances, InstanceIdBuffer& instances_ids, size_t move_id) { + // append position + instances.push_back(curr.position.x()); + instances.push_back(curr.position.y()); + instances.push_back(curr.position.z()); + // append width + instances.push_back(curr.width); + // append height + instances.push_back(curr.height); + + // append id + instances_ids.push_back(move_id); + }; + + // format data into the buffers to be rendered as batched model + auto add_vertices_as_model_batch = [](const GCodeProcessorResult::MoveVertex& curr, const GLModel::Geometry& data, VertexBuffer& vertices, InstanceBuffer& instances, InstanceIdBuffer& instances_ids, size_t move_id) { + const double width = static_cast(1.5f * curr.width); + const double height = static_cast(1.5f * curr.height); + + const Transform3d trafo = Geometry::assemble_transform((curr.position - 0.5f * curr.height * Vec3f::UnitZ()).cast(), Vec3d::Zero(), { width, width, height }); + const Eigen::Matrix normal_matrix = trafo.matrix().template block<3, 3>(0, 0).inverse().transpose(); + +#if ENABLE_LEGACY_OPENGL_REMOVAL + // append vertices + const size_t vertices_count = data.vertices_count(); + for (size_t i = 0; i < vertices_count; ++i) { + // append position + const Vec3d position = trafo * data.extract_position_3(i).cast(); + vertices.push_back(float(position.x())); + vertices.push_back(float(position.y())); + vertices.push_back(float(position.z())); + + // append normal + const Vec3d normal = normal_matrix * data.extract_normal_3(i).cast(); + vertices.push_back(float(normal.x())); + vertices.push_back(float(normal.y())); + vertices.push_back(float(normal.z())); + } +#else + for (const auto& entity : data.entities) { + // append vertices + for (size_t i = 0; i < entity.positions.size(); ++i) { + // append position + const Vec3d position = trafo * entity.positions[i].cast(); + vertices.push_back(static_cast(position.x())); + vertices.push_back(static_cast(position.y())); + vertices.push_back(static_cast(position.z())); + + // append normal + const Vec3d normal = normal_matrix * entity.normals[i].cast(); + vertices.push_back(static_cast(normal.x())); + vertices.push_back(static_cast(normal.y())); + vertices.push_back(static_cast(normal.z())); + } + } +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + // append instance position + instances.push_back(curr.position.x()); + instances.push_back(curr.position.y()); + instances.push_back(curr.position.z()); + // append instance id + instances_ids.push_back(move_id); + }; + + auto add_indices_as_model_batch = [](const GLModel::Geometry& data, IndexBuffer& indices, IBufferType base_index) { +#if ENABLE_LEGACY_OPENGL_REMOVAL + const size_t indices_count = data.indices_count(); + for (size_t i = 0; i < indices_count; ++i) { + indices.push_back(static_cast(data.extract_index(i) + base_index)); + } +#else + for (const auto& entity : data.entities) { + for (size_t i = 0; i < entity.indices.size(); ++i) { + indices.push_back(static_cast(entity.indices[i] + base_index)); + } + } +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + }; + +#if ENABLE_GCODE_VIEWER_STATISTICS + auto start_time = std::chrono::high_resolution_clock::now(); + m_statistics.results_size = SLIC3R_STDVEC_MEMSIZE(gcode_result.moves, GCodeProcessorResult::MoveVertex); + m_statistics.results_time = gcode_result.time; +#endif // ENABLE_GCODE_VIEWER_STATISTICS + + m_moves_count = gcode_result.moves.size(); + if (m_moves_count == 0) + return; + + m_extruders_count = gcode_result.extruders_count; + + unsigned int progress_count = 0; + static const unsigned int progress_threshold = 1000; + wxProgressDialog* progress_dialog = wxGetApp().is_gcode_viewer() ? + new wxProgressDialog(_L("Generating toolpaths"), "...", + 100, wxGetApp().mainframe, wxPD_AUTO_HIDE | wxPD_APP_MODAL) : nullptr; + + wxBusyCursor busy; + + // extract approximate paths bounding box from result + for (const GCodeProcessorResult::MoveVertex& move : gcode_result.moves) { + if (wxGetApp().is_gcode_viewer()) + // for the gcode viewer we need to take in account all moves to correctly size the printbed + m_paths_bounding_box.merge(move.position.cast()); + else { + if (move.type == EMoveType::Extrude && move.extrusion_role != erCustom && move.width != 0.0f && move.height != 0.0f) + m_paths_bounding_box.merge(move.position.cast()); + } + } + + // set approximate max bounding box (take in account also the tool marker) + m_max_bounding_box = m_paths_bounding_box; + m_max_bounding_box.merge(m_paths_bounding_box.max + m_sequential_view.marker.get_bounding_box().size().z() * Vec3d::UnitZ()); + + if (wxGetApp().is_editor()) + m_contained_in_bed = wxGetApp().plater()->build_volume().all_paths_inside(gcode_result, m_paths_bounding_box); + +#if ENABLE_SHOW_TOOLPATHS_COG + m_cog.reset(); +#endif // ENABLE_SHOW_TOOLPATHS_COG + + m_sequential_view.gcode_ids.clear(); + for (size_t i = 0; i < gcode_result.moves.size(); ++i) { + const GCodeProcessorResult::MoveVertex& move = gcode_result.moves[i]; + if (move.type != EMoveType::Seam) + m_sequential_view.gcode_ids.push_back(move.gcode_id); + } + +#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + bool account_for_volumetric_rate = m_view_type == EViewType::VolumetricRate; +#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + + std::vector vertices(m_buffers.size()); + std::vector indices(m_buffers.size()); + std::vector instances(m_buffers.size()); + std::vector instances_ids(m_buffers.size()); + std::vector instances_offsets(m_buffers.size()); + std::vector options_zs; + + std::vector biased_seams_ids; + + // toolpaths data -> extract vertices from result + for (size_t i = 0; i < m_moves_count; ++i) { + const GCodeProcessorResult::MoveVertex& curr = gcode_result.moves[i]; + if (curr.type == EMoveType::Seam) + biased_seams_ids.push_back(i - biased_seams_ids.size() - 1); + + const size_t move_id = i - biased_seams_ids.size(); + + // skip first vertex + if (i == 0) + continue; + + const GCodeProcessorResult::MoveVertex& prev = gcode_result.moves[i - 1]; + +#if ENABLE_SHOW_TOOLPATHS_COG + if (curr.type == EMoveType::Extrude && + curr.extrusion_role != erSkirt && + curr.extrusion_role != erSupportMaterial && + curr.extrusion_role != erSupportMaterialInterface && + curr.extrusion_role != erWipeTower && + curr.extrusion_role != erCustom && + curr.extrusion_role != erMixed) { + const Vec3d curr_pos = curr.position.cast(); + const Vec3d prev_pos = prev.position.cast(); + m_cog.add_segment(curr_pos, prev_pos, curr.mm3_per_mm * (curr_pos - prev_pos).norm()); + } +#endif // ENABLE_SHOW_TOOLPATHS_COG + + // update progress dialog + ++progress_count; + if (progress_dialog != nullptr && progress_count % progress_threshold == 0) { + progress_dialog->Update(int(100.0f * float(i) / (2.0f * float(m_moves_count))), + _L("Generating vertex buffer") + ": " + wxNumberFormatter::ToString(100.0 * double(i) / double(m_moves_count), 0, wxNumberFormatter::Style_None) + "%"); + progress_dialog->Fit(); + progress_count = 0; + } + + const unsigned char id = buffer_id(curr.type); + TBuffer& t_buffer = m_buffers[id]; + MultiVertexBuffer& v_multibuffer = vertices[id]; + InstanceBuffer& inst_buffer = instances[id]; + InstanceIdBuffer& inst_id_buffer = instances_ids[id]; + InstancesOffsets& inst_offsets = instances_offsets[id]; + + // ensure there is at least one vertex buffer + if (v_multibuffer.empty()) + v_multibuffer.push_back(VertexBuffer()); + + // if adding the vertices for the current segment exceeds the threshold size of the current vertex buffer + // add another vertex buffer + size_t vertices_size_to_add = (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) ? t_buffer.model.data.vertices_size_bytes() : t_buffer.max_vertices_per_segment_size_bytes(); + if (v_multibuffer.back().size() * sizeof(float) > t_buffer.vertices.max_size_bytes() - vertices_size_to_add) { + v_multibuffer.push_back(VertexBuffer()); + if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) { + Path& last_path = t_buffer.paths.back(); +#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + if (prev.type == curr.type && last_path.matches(curr, account_for_volumetric_rate)) +#else + if (prev.type == curr.type && last_path.matches(curr)) +#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + last_path.add_sub_path(prev, static_cast(v_multibuffer.size()) - 1, 0, move_id - 1); + } + } + + VertexBuffer& v_buffer = v_multibuffer.back(); + + switch (t_buffer.render_primitive_type) + { + case TBuffer::ERenderPrimitiveType::Line: { add_vertices_as_line(prev, curr, v_buffer); break; } +#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + case TBuffer::ERenderPrimitiveType::Triangle: { add_vertices_as_solid(prev, curr, t_buffer, static_cast(v_multibuffer.size()) - 1, v_buffer, move_id, account_for_volumetric_rate); break; } +#else + case TBuffer::ERenderPrimitiveType::Triangle: { add_vertices_as_solid(prev, curr, t_buffer, static_cast(v_multibuffer.size()) - 1, v_buffer, move_id); break; } +#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + case TBuffer::ERenderPrimitiveType::InstancedModel: + { + add_model_instance(curr, inst_buffer, inst_id_buffer, move_id); + inst_offsets.push_back(prev.position - curr.position); +#if ENABLE_GCODE_VIEWER_STATISTICS + ++m_statistics.instances_count; +#endif // ENABLE_GCODE_VIEWER_STATISTICS + break; + } + case TBuffer::ERenderPrimitiveType::BatchedModel: + { + add_vertices_as_model_batch(curr, t_buffer.model.data, v_buffer, inst_buffer, inst_id_buffer, move_id); + inst_offsets.push_back(prev.position - curr.position); +#if ENABLE_GCODE_VIEWER_STATISTICS + ++m_statistics.batched_count; +#endif // ENABLE_GCODE_VIEWER_STATISTICS + break; + } + } + + // collect options zs for later use + if (curr.type == EMoveType::Pause_Print || curr.type == EMoveType::Custom_GCode) { + const float* const last_z = options_zs.empty() ? nullptr : &options_zs.back(); + if (last_z == nullptr || curr.position[2] < *last_z - EPSILON || *last_z + EPSILON < curr.position[2]) + options_zs.emplace_back(curr.position[2]); + } + } + + // smooth toolpaths corners for the given TBuffer using triangles + auto smooth_triangle_toolpaths_corners = [&gcode_result, &biased_seams_ids](const TBuffer& t_buffer, MultiVertexBuffer& v_multibuffer) { + auto extract_position_at = [](const VertexBuffer& vertices, size_t offset) { + return Vec3f(vertices[offset + 0], vertices[offset + 1], vertices[offset + 2]); + }; + auto update_position_at = [](VertexBuffer& vertices, size_t offset, const Vec3f& position) { + vertices[offset + 0] = position.x(); + vertices[offset + 1] = position.y(); + vertices[offset + 2] = position.z(); + }; + auto match_right_vertices = [&](const Path::Sub_Path& prev_sub_path, const Path::Sub_Path& next_sub_path, + size_t curr_s_id, size_t vertex_size_floats, const Vec3f& displacement_vec) { + if (&prev_sub_path == &next_sub_path) { // previous and next segment are both contained into to the same vertex buffer + VertexBuffer& vbuffer = v_multibuffer[prev_sub_path.first.b_id]; + // offset into the vertex buffer of the next segment 1st vertex + const size_t next_1st_offset = (prev_sub_path.last.s_id - curr_s_id) * 6 * vertex_size_floats; + // offset into the vertex buffer of the right vertex of the previous segment + const size_t prev_right_offset = prev_sub_path.last.i_id - next_1st_offset - 3 * vertex_size_floats; + // new position of the right vertices + const Vec3f shared_vertex = extract_position_at(vbuffer, prev_right_offset) + displacement_vec; + // update previous segment + update_position_at(vbuffer, prev_right_offset, shared_vertex); + // offset into the vertex buffer of the right vertex of the next segment + const size_t next_right_offset = next_sub_path.last.i_id - next_1st_offset; + // update next segment + update_position_at(vbuffer, next_right_offset, shared_vertex); + } + else { // previous and next segment are contained into different vertex buffers + VertexBuffer& prev_vbuffer = v_multibuffer[prev_sub_path.first.b_id]; + VertexBuffer& next_vbuffer = v_multibuffer[next_sub_path.first.b_id]; + // offset into the previous vertex buffer of the right vertex of the previous segment + const size_t prev_right_offset = prev_sub_path.last.i_id - 3 * vertex_size_floats; + // new position of the right vertices + const Vec3f shared_vertex = extract_position_at(prev_vbuffer, prev_right_offset) + displacement_vec; + // update previous segment + update_position_at(prev_vbuffer, prev_right_offset, shared_vertex); + // offset into the next vertex buffer of the right vertex of the next segment + const size_t next_right_offset = next_sub_path.first.i_id + 1 * vertex_size_floats; + // update next segment + update_position_at(next_vbuffer, next_right_offset, shared_vertex); + } + }; + auto match_left_vertices = [&](const Path::Sub_Path& prev_sub_path, const Path::Sub_Path& next_sub_path, + size_t curr_s_id, size_t vertex_size_floats, const Vec3f& displacement_vec) { + if (&prev_sub_path == &next_sub_path) { // previous and next segment are both contained into to the same vertex buffer + VertexBuffer& vbuffer = v_multibuffer[prev_sub_path.first.b_id]; + // offset into the vertex buffer of the next segment 1st vertex + const size_t next_1st_offset = (prev_sub_path.last.s_id - curr_s_id) * 6 * vertex_size_floats; + // offset into the vertex buffer of the left vertex of the previous segment + const size_t prev_left_offset = prev_sub_path.last.i_id - next_1st_offset - 1 * vertex_size_floats; + // new position of the left vertices + const Vec3f shared_vertex = extract_position_at(vbuffer, prev_left_offset) + displacement_vec; + // update previous segment + update_position_at(vbuffer, prev_left_offset, shared_vertex); + // offset into the vertex buffer of the left vertex of the next segment + const size_t next_left_offset = next_sub_path.last.i_id - next_1st_offset + 1 * vertex_size_floats; + // update next segment + update_position_at(vbuffer, next_left_offset, shared_vertex); + } + else { // previous and next segment are contained into different vertex buffers + VertexBuffer& prev_vbuffer = v_multibuffer[prev_sub_path.first.b_id]; + VertexBuffer& next_vbuffer = v_multibuffer[next_sub_path.first.b_id]; + // offset into the previous vertex buffer of the left vertex of the previous segment + const size_t prev_left_offset = prev_sub_path.last.i_id - 1 * vertex_size_floats; + // new position of the left vertices + const Vec3f shared_vertex = extract_position_at(prev_vbuffer, prev_left_offset) + displacement_vec; + // update previous segment + update_position_at(prev_vbuffer, prev_left_offset, shared_vertex); + // offset into the next vertex buffer of the left vertex of the next segment + const size_t next_left_offset = next_sub_path.first.i_id + 3 * vertex_size_floats; + // update next segment + update_position_at(next_vbuffer, next_left_offset, shared_vertex); + } + }; + + auto extract_move_id = [&biased_seams_ids](size_t id) { + size_t new_id = size_t(-1); + auto it = std::lower_bound(biased_seams_ids.begin(), biased_seams_ids.end(), id); + if (it == biased_seams_ids.end()) + new_id = id + biased_seams_ids.size(); + else { + if (it == biased_seams_ids.begin() && *it < id) + new_id = id; + else if (it != biased_seams_ids.begin()) + new_id = id + std::distance(biased_seams_ids.begin(), it); + } + return (new_id == size_t(-1)) ? id : new_id; + }; + + const size_t vertex_size_floats = t_buffer.vertices.vertex_size_floats(); + for (const Path& path : t_buffer.paths) { + // the two segments of the path sharing the current vertex may belong + // to two different vertex buffers + size_t prev_sub_path_id = 0; + size_t next_sub_path_id = 0; + const size_t path_vertices_count = path.vertices_count(); + const float half_width = 0.5f * path.width; + for (size_t j = 1; j < path_vertices_count - 1; ++j) { + const size_t curr_s_id = path.sub_paths.front().first.s_id + j; + const size_t move_id = extract_move_id(curr_s_id); + const Vec3f& prev = gcode_result.moves[move_id - 1].position; + const Vec3f& curr = gcode_result.moves[move_id].position; + const Vec3f& next = gcode_result.moves[move_id + 1].position; + + // select the subpaths which contains the previous/next segments + if (!path.sub_paths[prev_sub_path_id].contains(curr_s_id)) + ++prev_sub_path_id; + if (!path.sub_paths[next_sub_path_id].contains(curr_s_id + 1)) + ++next_sub_path_id; + const Path::Sub_Path& prev_sub_path = path.sub_paths[prev_sub_path_id]; + const Path::Sub_Path& next_sub_path = path.sub_paths[next_sub_path_id]; + + const Vec3f prev_dir = (curr - prev).normalized(); + const Vec3f prev_right = Vec3f(prev_dir.y(), -prev_dir.x(), 0.0f).normalized(); + const Vec3f prev_up = prev_right.cross(prev_dir); + + const Vec3f next_dir = (next - curr).normalized(); + + const bool is_right_turn = prev_up.dot(prev_dir.cross(next_dir)) <= 0.0f; + const float cos_dir = prev_dir.dot(next_dir); + // whether the angle between adjacent segments is greater than 45 degrees + const bool is_sharp = cos_dir < 0.7071068f; + + float displacement = 0.0f; + if (cos_dir > -0.9998477f) { + // if the angle between adjacent segments is smaller than 179 degrees + const Vec3f med_dir = (prev_dir + next_dir).normalized(); + displacement = half_width * ::tan(::acos(std::clamp(next_dir.dot(med_dir), -1.0f, 1.0f))); + } + + const float sq_prev_length = (curr - prev).squaredNorm(); + const float sq_next_length = (next - curr).squaredNorm(); + const float sq_displacement = sqr(displacement); + const bool can_displace = displacement > 0.0f && sq_displacement < sq_prev_length && sq_displacement < sq_next_length; + + if (can_displace) { + // displacement to apply to the vertices to match + const Vec3f displacement_vec = displacement * prev_dir; + // matches inner corner vertices + if (is_right_turn) + match_right_vertices(prev_sub_path, next_sub_path, curr_s_id, vertex_size_floats, -displacement_vec); + else + match_left_vertices(prev_sub_path, next_sub_path, curr_s_id, vertex_size_floats, -displacement_vec); + + if (!is_sharp) { + // matches outer corner vertices + if (is_right_turn) + match_left_vertices(prev_sub_path, next_sub_path, curr_s_id, vertex_size_floats, displacement_vec); + else + match_right_vertices(prev_sub_path, next_sub_path, curr_s_id, vertex_size_floats, displacement_vec); + } + } + } + } + }; + +#if ENABLE_GCODE_VIEWER_STATISTICS + auto load_vertices_time = std::chrono::high_resolution_clock::now(); + m_statistics.load_vertices = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); +#endif // ENABLE_GCODE_VIEWER_STATISTICS + + // smooth toolpaths corners for TBuffers using triangles + for (size_t i = 0; i < m_buffers.size(); ++i) { + const TBuffer& t_buffer = m_buffers[i]; + if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) + smooth_triangle_toolpaths_corners(t_buffer, vertices[i]); + } + + // dismiss, no more needed + std::vector().swap(biased_seams_ids); + + for (MultiVertexBuffer& v_multibuffer : vertices) { + for (VertexBuffer& v_buffer : v_multibuffer) { + v_buffer.shrink_to_fit(); + } + } + + // move the wipe toolpaths half height up to render them on proper position + MultiVertexBuffer& wipe_vertices = vertices[buffer_id(EMoveType::Wipe)]; + for (VertexBuffer& v_buffer : wipe_vertices) { + for (size_t i = 2; i < v_buffer.size(); i += 3) { + v_buffer[i] += 0.5f * GCodeProcessor::Wipe_Height; + } + } + + // send vertices data to gpu, where needed + for (size_t i = 0; i < m_buffers.size(); ++i) { + TBuffer& t_buffer = m_buffers[i]; + if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::InstancedModel) { + const InstanceBuffer& inst_buffer = instances[i]; + if (!inst_buffer.empty()) { + t_buffer.model.instances.buffer = inst_buffer; + t_buffer.model.instances.s_ids = instances_ids[i]; + t_buffer.model.instances.offsets = instances_offsets[i]; + } + } + else { + if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) { + const InstanceBuffer& inst_buffer = instances[i]; + if (!inst_buffer.empty()) { + t_buffer.model.instances.buffer = inst_buffer; + t_buffer.model.instances.s_ids = instances_ids[i]; + t_buffer.model.instances.offsets = instances_offsets[i]; + } + } + const MultiVertexBuffer& v_multibuffer = vertices[i]; + for (const VertexBuffer& v_buffer : v_multibuffer) { + const size_t size_elements = v_buffer.size(); + const size_t size_bytes = size_elements * sizeof(float); + const size_t vertices_count = size_elements / t_buffer.vertices.vertex_size_floats(); + t_buffer.vertices.count += vertices_count; + +#if ENABLE_GCODE_VIEWER_STATISTICS + m_statistics.total_vertices_gpu_size += static_cast(size_bytes); + m_statistics.max_vbuffer_gpu_size = std::max(m_statistics.max_vbuffer_gpu_size, static_cast(size_bytes)); + ++m_statistics.vbuffers_count; +#endif // ENABLE_GCODE_VIEWER_STATISTICS + + GLuint id = 0; + glsafe(::glGenBuffers(1, &id)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, id)); + glsafe(::glBufferData(GL_ARRAY_BUFFER, size_bytes, v_buffer.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + + t_buffer.vertices.vbos.push_back(static_cast(id)); + t_buffer.vertices.sizes.push_back(size_bytes); + } + } + } + +#if ENABLE_GCODE_VIEWER_STATISTICS + auto smooth_vertices_time = std::chrono::high_resolution_clock::now(); + m_statistics.smooth_vertices = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - load_vertices_time).count(); +#endif // ENABLE_GCODE_VIEWER_STATISTICS + log_memory_usage("Loaded G-code generated vertex buffers ", vertices, indices); + + // dismiss vertices data, no more needed + std::vector().swap(vertices); + std::vector().swap(instances); + std::vector().swap(instances_ids); + + // toolpaths data -> extract indices from result + // paths may have been filled while extracting vertices, + // so reset them, they will be filled again while extracting indices + for (TBuffer& buffer : m_buffers) { + buffer.paths.clear(); + } + + // variable used to keep track of the current vertex buffers index and size + using CurrVertexBuffer = std::pair; + std::vector curr_vertex_buffers(m_buffers.size(), { 0, 0 }); + + // variable used to keep track of the vertex buffers ids + using VboIndexList = std::vector; + std::vector vbo_indices(m_buffers.size()); + + size_t seams_count = 0; + + for (size_t i = 0; i < m_moves_count; ++i) { + const GCodeProcessorResult::MoveVertex& curr = gcode_result.moves[i]; + if (curr.type == EMoveType::Seam) + ++seams_count; + + const size_t move_id = i - seams_count; + + // skip first vertex + if (i == 0) + continue; + + const GCodeProcessorResult::MoveVertex& prev = gcode_result.moves[i - 1]; + const GCodeProcessorResult::MoveVertex* next = nullptr; + if (i < m_moves_count - 1) + next = &gcode_result.moves[i + 1]; + + ++progress_count; + if (progress_dialog != nullptr && progress_count % progress_threshold == 0) { + progress_dialog->Update(int(100.0f * float(m_moves_count + i) / (2.0f * float(m_moves_count))), + _L("Generating index buffers") + ": " + wxNumberFormatter::ToString(100.0 * double(i) / double(m_moves_count), 0, wxNumberFormatter::Style_None) + "%"); + progress_dialog->Fit(); + progress_count = 0; + } + + const unsigned char id = buffer_id(curr.type); + TBuffer& t_buffer = m_buffers[id]; + MultiIndexBuffer& i_multibuffer = indices[id]; + CurrVertexBuffer& curr_vertex_buffer = curr_vertex_buffers[id]; + VboIndexList& vbo_index_list = vbo_indices[id]; + + // ensure there is at least one index buffer + if (i_multibuffer.empty()) { + i_multibuffer.push_back(IndexBuffer()); + if (!t_buffer.vertices.vbos.empty()) + vbo_index_list.push_back(t_buffer.vertices.vbos[curr_vertex_buffer.first]); + } + + // if adding the indices for the current segment exceeds the threshold size of the current index buffer + // create another index buffer + size_t indiced_size_to_add = (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) ? t_buffer.model.data.indices_size_bytes() : t_buffer.max_indices_per_segment_size_bytes(); + if (i_multibuffer.back().size() * sizeof(IBufferType) >= IBUFFER_THRESHOLD_BYTES - indiced_size_to_add) { + i_multibuffer.push_back(IndexBuffer()); + vbo_index_list.push_back(t_buffer.vertices.vbos[curr_vertex_buffer.first]); + if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::BatchedModel) { + Path& last_path = t_buffer.paths.back(); + last_path.add_sub_path(prev, static_cast(i_multibuffer.size()) - 1, 0, move_id - 1); + } + } + + // if adding the vertices for the current segment exceeds the threshold size of the current vertex buffer + // create another index buffer + size_t vertices_size_to_add = (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) ? t_buffer.model.data.vertices_size_bytes() : t_buffer.max_vertices_per_segment_size_bytes(); + if (curr_vertex_buffer.second * t_buffer.vertices.vertex_size_bytes() > t_buffer.vertices.max_size_bytes() - vertices_size_to_add) { + i_multibuffer.push_back(IndexBuffer()); + + ++curr_vertex_buffer.first; + curr_vertex_buffer.second = 0; + vbo_index_list.push_back(t_buffer.vertices.vbos[curr_vertex_buffer.first]); + + if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::BatchedModel) { + Path& last_path = t_buffer.paths.back(); + last_path.add_sub_path(prev, static_cast(i_multibuffer.size()) - 1, 0, move_id - 1); + } + } + + IndexBuffer& i_buffer = i_multibuffer.back(); + + switch (t_buffer.render_primitive_type) + { + case TBuffer::ERenderPrimitiveType::Line: { +#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + add_indices_as_line(prev, curr, t_buffer, static_cast(i_multibuffer.size()) - 1, i_buffer, move_id, account_for_volumetric_rate); +#else + add_indices_as_line(prev, curr, t_buffer, static_cast(i_multibuffer.size()) - 1, i_buffer, move_id); +#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + curr_vertex_buffer.second += t_buffer.max_vertices_per_segment(); + break; + } + case TBuffer::ERenderPrimitiveType::Triangle: { +#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + add_indices_as_solid(prev, curr, next, t_buffer, curr_vertex_buffer.second, static_cast(i_multibuffer.size()) - 1, i_buffer, move_id, account_for_volumetric_rate); +#else + add_indices_as_solid(prev, curr, next, t_buffer, curr_vertex_buffer.second, static_cast(i_multibuffer.size()) - 1, i_buffer, move_id); +#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + break; + } + case TBuffer::ERenderPrimitiveType::BatchedModel: { + add_indices_as_model_batch(t_buffer.model.data, i_buffer, curr_vertex_buffer.second); + curr_vertex_buffer.second += t_buffer.model.data.vertices_count(); + break; + } + default: { break; } + } + } + + for (MultiIndexBuffer& i_multibuffer : indices) { + for (IndexBuffer& i_buffer : i_multibuffer) { + i_buffer.shrink_to_fit(); + } + } + + // toolpaths data -> send indices data to gpu + for (size_t i = 0; i < m_buffers.size(); ++i) { + TBuffer& t_buffer = m_buffers[i]; + if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::InstancedModel) { + const MultiIndexBuffer& i_multibuffer = indices[i]; + for (const IndexBuffer& i_buffer : i_multibuffer) { + const size_t size_elements = i_buffer.size(); + const size_t size_bytes = size_elements * sizeof(IBufferType); + + // stores index buffer informations into TBuffer + t_buffer.indices.push_back(IBuffer()); + IBuffer& ibuf = t_buffer.indices.back(); + ibuf.count = size_elements; + ibuf.vbo = vbo_indices[i][t_buffer.indices.size() - 1]; + +#if ENABLE_GCODE_VIEWER_STATISTICS + m_statistics.total_indices_gpu_size += static_cast(size_bytes); + m_statistics.max_ibuffer_gpu_size = std::max(m_statistics.max_ibuffer_gpu_size, static_cast(size_bytes)); + ++m_statistics.ibuffers_count; +#endif // ENABLE_GCODE_VIEWER_STATISTICS + + glsafe(::glGenBuffers(1, &ibuf.ibo)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibuf.ibo)); + glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, size_bytes, i_buffer.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } + } + } + + if (progress_dialog != nullptr) { + progress_dialog->Update(100, ""); + progress_dialog->Fit(); + } + +#if ENABLE_GCODE_VIEWER_STATISTICS + for (const TBuffer& buffer : m_buffers) { + m_statistics.paths_size += SLIC3R_STDVEC_MEMSIZE(buffer.paths, Path); + } + + auto update_segments_count = [&](EMoveType type, int64_t& count) { + unsigned int id = buffer_id(type); + const MultiIndexBuffer& buffers = indices[id]; + int64_t indices_count = 0; + for (const IndexBuffer& buffer : buffers) { + indices_count += buffer.size(); + } + const TBuffer& t_buffer = m_buffers[id]; + if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) + indices_count -= static_cast(12 * t_buffer.paths.size()); // remove the starting + ending caps = 4 triangles + + count += indices_count / t_buffer.indices_per_segment(); + }; + + update_segments_count(EMoveType::Travel, m_statistics.travel_segments_count); + update_segments_count(EMoveType::Wipe, m_statistics.wipe_segments_count); + update_segments_count(EMoveType::Extrude, m_statistics.extrude_segments_count); + + m_statistics.load_indices = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - smooth_vertices_time).count(); +#endif // ENABLE_GCODE_VIEWER_STATISTICS + + log_memory_usage("Loaded G-code generated indices buffers ", vertices, indices); + + // dismiss indices data, no more needed + std::vector().swap(indices); + + // layers zs / roles / extruder ids -> extract from result + size_t last_travel_s_id = 0; + seams_count = 0; + for (size_t i = 0; i < m_moves_count; ++i) { + const GCodeProcessorResult::MoveVertex& move = gcode_result.moves[i]; + if (move.type == EMoveType::Seam) + ++seams_count; + + size_t move_id = i - seams_count; + + if (move.type == EMoveType::Extrude) { +#if ENABLE_PROCESS_G2_G3_LINES + if (move.extrusion_role != erNone && !move.internal_only) { +#endif // ENABLE_PROCESS_G2_G3_LINES + // layers zs + const double* const last_z = m_layers.empty() ? nullptr : &m_layers.get_zs().back(); + const double z = static_cast(move.position.z()); + if (last_z == nullptr || z < *last_z - EPSILON || *last_z + EPSILON < z) + m_layers.append(z, { last_travel_s_id, move_id }); + else + m_layers.get_ranges().back().last = move_id; +#if ENABLE_PROCESS_G2_G3_LINES + } +#endif // ENABLE_PROCESS_G2_G3_LINES + // extruder ids + m_extruder_ids.emplace_back(move.extruder_id); + // roles + if (i > 0) + m_roles.emplace_back(move.extrusion_role); + } + else if (move.type == EMoveType::Travel) { + if (move_id - last_travel_s_id > 1 && !m_layers.empty()) + m_layers.get_ranges().back().last = move_id; + + last_travel_s_id = move_id; + } + } + + // roles -> remove duplicates + sort_remove_duplicates(m_roles); + m_roles.shrink_to_fit(); + + // extruder ids -> remove duplicates + sort_remove_duplicates(m_extruder_ids); + m_extruder_ids.shrink_to_fit(); + + // replace layers for spiral vase mode + if (!gcode_result.spiral_vase_layers.empty()) { + m_layers.reset(); + for (const auto& layer : gcode_result.spiral_vase_layers) { + m_layers.append(layer.first, { layer.second.first, layer.second.second }); + } + } + + // set layers z range + if (!m_layers.empty()) + m_layers_z_range = { 0, static_cast(m_layers.size() - 1) }; + + // change color of paths whose layer contains option points + if (!options_zs.empty()) { + TBuffer& extrude_buffer = m_buffers[buffer_id(EMoveType::Extrude)]; + for (Path& path : extrude_buffer.paths) { + const float z = path.sub_paths.front().first.position.z(); + if (std::find_if(options_zs.begin(), options_zs.end(), [z](float f) { return f - EPSILON <= z && z <= f + EPSILON; }) != options_zs.end()) + path.cp_color_id = 255 - path.cp_color_id; + } + } + +#if ENABLE_GCODE_VIEWER_STATISTICS + m_statistics.load_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); +#endif // ENABLE_GCODE_VIEWER_STATISTICS + + if (progress_dialog != nullptr) + progress_dialog->Destroy(); +} + +#if ENABLE_LEGACY_OPENGL_REMOVAL +void GCodeViewer::load_shells(const Print& print) +#else +void GCodeViewer::load_shells(const Print& print, bool initialized) +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +{ + if (print.objects().empty()) + // no shells, return + return; + + // adds objects' volumes + int object_id = 0; + for (const PrintObject* obj : print.objects()) { + const ModelObject* model_obj = obj->model_object(); + + std::vector instance_ids(model_obj->instances.size()); + for (int i = 0; i < (int)model_obj->instances.size(); ++i) { + instance_ids[i] = i; + } + + size_t current_volumes_count = m_shells.volumes.volumes.size(); +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_shells.volumes.load_object(model_obj, object_id, instance_ids); +#else + m_shells.volumes.load_object(model_obj, object_id, instance_ids, initialized); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + // adjust shells' z if raft is present + const SlicingParameters& slicing_parameters = obj->slicing_parameters(); + if (slicing_parameters.object_print_z_min != 0.0) { + const Vec3d z_offset = slicing_parameters.object_print_z_min * Vec3d::UnitZ(); + for (size_t i = current_volumes_count; i < m_shells.volumes.volumes.size(); ++i) { + GLVolume* v = m_shells.volumes.volumes[i]; + v->set_volume_offset(v->get_volume_offset() + z_offset); + } + } + + ++object_id; + } + + if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF) { + // adds wipe tower's volume + const double max_z = print.objects()[0]->model_object()->get_model()->bounding_box().max(2); + const PrintConfig& config = print.config(); + const size_t extruders_count = config.nozzle_diameter.size(); + if (extruders_count > 1 && config.wipe_tower && !config.complete_objects) { + const float depth = print.wipe_tower_data(extruders_count).depth; + const float brim_width = print.wipe_tower_data(extruders_count).brim_width; + +#if ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + m_shells.volumes.load_wipe_tower_preview(config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle, + !print.is_step_done(psWipeTower), brim_width); +#else + m_shells.volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle, + !print.is_step_done(psWipeTower), brim_width); +#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL +#else +#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + m_shells.volumes.load_wipe_tower_preview(config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle, + !print.is_step_done(psWipeTower), brim_width, initialized); +#else + m_shells.volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle, + !print.is_step_done(psWipeTower), brim_width, initialized); +#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + } + + // remove modifiers + while (true) { + GLVolumePtrs::iterator it = std::find_if(m_shells.volumes.volumes.begin(), m_shells.volumes.volumes.end(), [](GLVolume* volume) { return volume->is_modifier; }); + if (it != m_shells.volumes.volumes.end()) { + delete (*it); + m_shells.volumes.volumes.erase(it); + } + else + break; + } + + for (GLVolume* volume : m_shells.volumes.volumes) { + volume->zoom_to_volumes = false; + volume->color.a(0.25f); + volume->force_native_color = true; + volume->set_render_color(true); + } +} + +void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const +{ +#if ENABLE_GCODE_VIEWER_STATISTICS + auto start_time = std::chrono::high_resolution_clock::now(); +#endif // ENABLE_GCODE_VIEWER_STATISTICS + + auto extrusion_color = [this](const Path& path) { + ColorRGBA color; + switch (m_view_type) + { + case EViewType::FeatureType: { color = Extrusion_Role_Colors[static_cast(path.role)]; break; } + case EViewType::Height: { color = m_extrusions.ranges.height.get_color_at(path.height); break; } + case EViewType::Width: { color = m_extrusions.ranges.width.get_color_at(path.width); break; } + case EViewType::Feedrate: { color = m_extrusions.ranges.feedrate.get_color_at(path.feedrate); break; } + case EViewType::FanSpeed: { color = m_extrusions.ranges.fan_speed.get_color_at(path.fan_speed); break; } + case EViewType::Temperature: { color = m_extrusions.ranges.temperature.get_color_at(path.temperature); break; } +#if ENABLE_PREVIEW_LAYER_TIME + case EViewType::LayerTimeLinear: + case EViewType::LayerTimeLogarithmic: { + const Path::Sub_Path& sub_path = path.sub_paths.front(); + double z = static_cast(sub_path.first.position.z()); + const std::vector& zs = m_layers.get_zs(); + const std::vector& ranges = m_layers.get_ranges(); + size_t time_mode_id = static_cast(m_time_estimate_mode); + for (size_t i = 0; i < zs.size(); ++i) { + if (std::abs(zs[i] - z) < EPSILON) { + if (ranges[i].contains(sub_path.first.s_id)) { + color = m_extrusions.ranges.layer_time[time_mode_id].get_color_at(m_layers_times[time_mode_id][i], + (m_view_type == EViewType::LayerTimeLinear) ? Extrusions::Range::EType::Linear : Extrusions::Range::EType::Logarithmic); + break; + } + } + } + break; + } +#endif // ENABLE_PREVIEW_LAYER_TIME + case EViewType::VolumetricRate: { color = m_extrusions.ranges.volumetric_rate.get_color_at(path.volumetric_rate); break; } + case EViewType::Tool: { color = m_tool_colors[path.extruder_id]; break; } + case EViewType::ColorPrint: { + if (path.cp_color_id >= static_cast(m_tool_colors.size())) + color = ColorRGBA::GRAY(); + else + color = m_tool_colors[path.cp_color_id]; + + break; + } + default: { color = ColorRGBA::WHITE(); break; } + } + + return color; + }; + + auto travel_color = [](const Path& path) { + return (path.delta_extruder < 0.0f) ? Travel_Colors[2] /* Retract */ : + ((path.delta_extruder > 0.0f) ? Travel_Colors[1] /* Extrude */ : + Travel_Colors[0] /* Move */); + }; + + auto is_in_layers_range = [this](const Path& path, size_t min_id, size_t max_id) { + auto in_layers_range = [this, min_id, max_id](size_t id) { + return m_layers.get_range_at(min_id).first <= id && id <= m_layers.get_range_at(max_id).last; + }; + + return in_layers_range(path.sub_paths.front().first.s_id) && in_layers_range(path.sub_paths.back().last.s_id); + }; + + auto is_travel_in_layers_range = [this](size_t path_id, size_t min_id, size_t max_id) { + const TBuffer& buffer = m_buffers[buffer_id(EMoveType::Travel)]; + if (path_id >= buffer.paths.size()) + return false; + + Path path = buffer.paths[path_id]; + size_t first = path_id; + size_t last = path_id; + + // check adjacent paths + while (first > 0 && path.sub_paths.front().first.position.isApprox(buffer.paths[first - 1].sub_paths.back().last.position)) { + --first; + path.sub_paths.front().first = buffer.paths[first].sub_paths.front().first; + } + while (last < buffer.paths.size() - 1 && path.sub_paths.back().last.position.isApprox(buffer.paths[last + 1].sub_paths.front().first.position)) { + ++last; + path.sub_paths.back().last = buffer.paths[last].sub_paths.back().last; + } + + const size_t min_s_id = m_layers.get_range_at(min_id).first; + const size_t max_s_id = m_layers.get_range_at(max_id).last; + + return (min_s_id <= path.sub_paths.front().first.s_id && path.sub_paths.front().first.s_id <= max_s_id) || + (min_s_id <= path.sub_paths.back().last.s_id && path.sub_paths.back().last.s_id <= max_s_id); + }; + +#if ENABLE_GCODE_VIEWER_STATISTICS + Statistics* statistics = const_cast(&m_statistics); + statistics->render_paths_size = 0; + statistics->models_instances_size = 0; +#endif // ENABLE_GCODE_VIEWER_STATISTICS + + const bool top_layer_only = get_app_config()->get("seq_top_layer_only") == "1"; + + SequentialView::Endpoints global_endpoints = { m_moves_count , 0 }; + SequentialView::Endpoints top_layer_endpoints = global_endpoints; + SequentialView* sequential_view = const_cast(&m_sequential_view); + if (top_layer_only || !keep_sequential_current_first) sequential_view->current.first = 0; + if (!keep_sequential_current_last) sequential_view->current.last = m_moves_count; + + // first pass: collect visible paths and update sequential view data + std::vector> paths; + for (size_t b = 0; b < m_buffers.size(); ++b) { + TBuffer& buffer = const_cast(m_buffers[b]); + // reset render paths + buffer.render_paths.clear(); + + if (!buffer.visible) + continue; + + if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::InstancedModel || + buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) { + for (size_t id : buffer.model.instances.s_ids) { + if (id < m_layers.get_range_at(m_layers_z_range[0]).first || m_layers.get_range_at(m_layers_z_range[1]).last < id) + continue; + + global_endpoints.first = std::min(global_endpoints.first, id); + global_endpoints.last = std::max(global_endpoints.last, id); + + if (top_layer_only) { + if (id < m_layers.get_range_at(m_layers_z_range[1]).first || m_layers.get_range_at(m_layers_z_range[1]).last < id) + continue; + + top_layer_endpoints.first = std::min(top_layer_endpoints.first, id); + top_layer_endpoints.last = std::max(top_layer_endpoints.last, id); + } + } + } + else { + for (size_t i = 0; i < buffer.paths.size(); ++i) { + const Path& path = buffer.paths[i]; + if (path.type == EMoveType::Travel) { + if (!is_travel_in_layers_range(i, m_layers_z_range[0], m_layers_z_range[1])) + continue; + } + else if (!is_in_layers_range(path, m_layers_z_range[0], m_layers_z_range[1])) + continue; + + if (path.type == EMoveType::Extrude && !is_visible(path)) + continue; + + // store valid path + for (size_t j = 0; j < path.sub_paths.size(); ++j) { + paths.push_back({ static_cast(b), path.sub_paths[j].first.b_id, static_cast(i), static_cast(j) }); + } + + global_endpoints.first = std::min(global_endpoints.first, path.sub_paths.front().first.s_id); + global_endpoints.last = std::max(global_endpoints.last, path.sub_paths.back().last.s_id); + + if (top_layer_only) { + if (path.type == EMoveType::Travel) { + if (is_travel_in_layers_range(i, m_layers_z_range[1], m_layers_z_range[1])) { + top_layer_endpoints.first = std::min(top_layer_endpoints.first, path.sub_paths.front().first.s_id); + top_layer_endpoints.last = std::max(top_layer_endpoints.last, path.sub_paths.back().last.s_id); + } + } + else if (is_in_layers_range(path, m_layers_z_range[1], m_layers_z_range[1])) { + top_layer_endpoints.first = std::min(top_layer_endpoints.first, path.sub_paths.front().first.s_id); + top_layer_endpoints.last = std::max(top_layer_endpoints.last, path.sub_paths.back().last.s_id); + } + } + } + } + } + + // update current sequential position + sequential_view->current.first = !top_layer_only && keep_sequential_current_first ? std::clamp(sequential_view->current.first, global_endpoints.first, global_endpoints.last) : global_endpoints.first; + sequential_view->current.last = keep_sequential_current_last ? std::clamp(sequential_view->current.last, global_endpoints.first, global_endpoints.last) : global_endpoints.last; + + // get the world position from the vertex buffer + bool found = false; + for (const TBuffer& buffer : m_buffers) { + if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::InstancedModel || + buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) { + for (size_t i = 0; i < buffer.model.instances.s_ids.size(); ++i) { + if (buffer.model.instances.s_ids[i] == m_sequential_view.current.last) { + size_t offset = i * buffer.model.instances.instance_size_floats(); + sequential_view->current_position.x() = buffer.model.instances.buffer[offset + 0]; + sequential_view->current_position.y() = buffer.model.instances.buffer[offset + 1]; + sequential_view->current_position.z() = buffer.model.instances.buffer[offset + 2]; + sequential_view->current_offset = buffer.model.instances.offsets[i]; + found = true; + break; + } + } + } + else { + // searches the path containing the current position + for (const Path& path : buffer.paths) { + if (path.contains(m_sequential_view.current.last)) { + const int sub_path_id = path.get_id_of_sub_path_containing(m_sequential_view.current.last); + if (sub_path_id != -1) { + const Path::Sub_Path& sub_path = path.sub_paths[sub_path_id]; + unsigned int offset = static_cast(m_sequential_view.current.last - sub_path.first.s_id); + if (offset > 0) { + if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Line) + offset = 2 * offset - 1; + else if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) { + unsigned int indices_count = buffer.indices_per_segment(); + offset = indices_count * (offset - 1) + (indices_count - 2); + if (sub_path_id == 0) + offset += 6; // add 2 triangles for starting cap + } + } + offset += static_cast(sub_path.first.i_id); + + // gets the vertex index from the index buffer on gpu + const IBuffer& i_buffer = buffer.indices[sub_path.first.b_id]; + IBufferType index = 0; + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo)); + glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast(offset * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&index))); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + + // gets the position from the vertices buffer on gpu + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, i_buffer.vbo)); + glsafe(::glGetBufferSubData(GL_ARRAY_BUFFER, static_cast(index * buffer.vertices.vertex_size_bytes()), static_cast(3 * sizeof(float)), static_cast(sequential_view->current_position.data()))); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + + sequential_view->current_offset = Vec3f::Zero(); + found = true; + break; + } + } + } + } + + if (found) + break; + } + + // second pass: filter paths by sequential data and collect them by color + RenderPath* render_path = nullptr; + for (const auto& [tbuffer_id, ibuffer_id, path_id, sub_path_id] : paths) { + TBuffer& buffer = const_cast(m_buffers[tbuffer_id]); + const Path& path = buffer.paths[path_id]; + const Path::Sub_Path& sub_path = path.sub_paths[sub_path_id]; + if (m_sequential_view.current.last < sub_path.first.s_id || sub_path.last.s_id < m_sequential_view.current.first) + continue; + + ColorRGBA color; + switch (path.type) + { + case EMoveType::Tool_change: + case EMoveType::Color_change: + case EMoveType::Pause_Print: + case EMoveType::Custom_GCode: + case EMoveType::Retract: + case EMoveType::Unretract: + case EMoveType::Seam: { color = option_color(path.type); break; } + case EMoveType::Extrude: { + if (!top_layer_only || + m_sequential_view.current.last == global_endpoints.last || + is_in_layers_range(path, m_layers_z_range[1], m_layers_z_range[1])) + color = extrusion_color(path); + else + color = Neutral_Color; + + break; + } + case EMoveType::Travel: { + if (!top_layer_only || m_sequential_view.current.last == global_endpoints.last || is_travel_in_layers_range(path_id, m_layers_z_range[1], m_layers_z_range[1])) + color = (m_view_type == EViewType::Feedrate || m_view_type == EViewType::Tool || m_view_type == EViewType::ColorPrint) ? extrusion_color(path) : travel_color(path); + else + color = Neutral_Color; + + break; + } + case EMoveType::Wipe: { color = Wipe_Color; break; } + default: { color = { 0.0f, 0.0f, 0.0f, 1.0f }; break; } + } + + RenderPath key{ tbuffer_id, color, static_cast(ibuffer_id), path_id }; + if (render_path == nullptr || !RenderPathPropertyEqual()(*render_path, key)) { + buffer.render_paths.emplace_back(key); + render_path = const_cast(&buffer.render_paths.back()); + } + + unsigned int delta_1st = 0; + if (sub_path.first.s_id < m_sequential_view.current.first && m_sequential_view.current.first <= sub_path.last.s_id) + delta_1st = static_cast(m_sequential_view.current.first - sub_path.first.s_id); + + unsigned int size_in_indices = 0; + switch (buffer.render_primitive_type) + { + case TBuffer::ERenderPrimitiveType::Line: + case TBuffer::ERenderPrimitiveType::Triangle: { + unsigned int segments_count = std::min(m_sequential_view.current.last, sub_path.last.s_id) - std::max(m_sequential_view.current.first, sub_path.first.s_id); + size_in_indices = buffer.indices_per_segment() * segments_count; + break; + } + default: { break; } + } + + if (size_in_indices == 0) + continue; + + if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) { + if (sub_path_id == 0 && delta_1st == 0) + size_in_indices += 6; // add 2 triangles for starting cap + if (sub_path_id == path.sub_paths.size() - 1 && path.sub_paths.back().last.s_id <= m_sequential_view.current.last) + size_in_indices += 6; // add 2 triangles for ending cap + if (delta_1st > 0) + size_in_indices -= 6; // remove 2 triangles for corner cap + } + + render_path->sizes.push_back(size_in_indices); + + if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) { + delta_1st *= buffer.indices_per_segment(); + if (delta_1st > 0) { + delta_1st += 6; // skip 2 triangles for corner cap + if (sub_path_id == 0) + delta_1st += 6; // skip 2 triangles for starting cap + } + } + + render_path->offsets.push_back(static_cast((sub_path.first.i_id + delta_1st) * sizeof(IBufferType))); + +#if 0 + // check sizes and offsets against index buffer size on gpu + GLint buffer_size; + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer->indices[render_path->ibuffer_id].ibo)); + glsafe(::glGetBufferParameteriv(GL_ELEMENT_ARRAY_BUFFER, GL_BUFFER_SIZE, &buffer_size)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + if (render_path->offsets.back() + render_path->sizes.back() * sizeof(IBufferType) > buffer_size) + BOOST_LOG_TRIVIAL(error) << "GCodeViewer::refresh_render_paths: Invalid render path data"; +#endif + } + + // Removes empty render paths and sort. + for (size_t b = 0; b < m_buffers.size(); ++b) { + TBuffer* buffer = const_cast(&m_buffers[b]); + buffer->render_paths.erase(std::remove_if(buffer->render_paths.begin(), buffer->render_paths.end(), + [](const auto &path){ return path.sizes.empty() || path.offsets.empty(); }), + buffer->render_paths.end()); + } + + // second pass: for buffers using instanced and batched models, update the instances render ranges + for (size_t b = 0; b < m_buffers.size(); ++b) { + TBuffer& buffer = const_cast(m_buffers[b]); + if (buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::InstancedModel && + buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::BatchedModel) + continue; + + buffer.model.instances.render_ranges.reset(); + + if (!buffer.visible || buffer.model.instances.s_ids.empty()) + continue; + + buffer.model.instances.render_ranges.ranges.push_back({ 0, 0, 0, buffer.model.color }); + bool has_second_range = top_layer_only && m_sequential_view.current.last != m_sequential_view.global.last; + if (has_second_range) + buffer.model.instances.render_ranges.ranges.push_back({ 0, 0, 0, Neutral_Color }); + + if (m_sequential_view.current.first <= buffer.model.instances.s_ids.back() && buffer.model.instances.s_ids.front() <= m_sequential_view.current.last) { + for (size_t id : buffer.model.instances.s_ids) { + if (has_second_range) { + if (id < m_sequential_view.endpoints.first) { + ++buffer.model.instances.render_ranges.ranges.front().offset; + if (id <= m_sequential_view.current.first) + ++buffer.model.instances.render_ranges.ranges.back().offset; + else + ++buffer.model.instances.render_ranges.ranges.back().count; + } + else if (id <= m_sequential_view.current.last) + ++buffer.model.instances.render_ranges.ranges.front().count; + else + break; + } + else { + if (id <= m_sequential_view.current.first) + ++buffer.model.instances.render_ranges.ranges.front().offset; + else if (id <= m_sequential_view.current.last) + ++buffer.model.instances.render_ranges.ranges.front().count; + else + break; + } + } + } + } + + // set sequential data to their final value + sequential_view->endpoints = top_layer_only ? top_layer_endpoints : global_endpoints; + sequential_view->current.first = !top_layer_only && keep_sequential_current_first ? std::clamp(sequential_view->current.first, sequential_view->endpoints.first, sequential_view->endpoints.last) : sequential_view->endpoints.first; + sequential_view->global = global_endpoints; + + // updates sequential range caps + std::array* sequential_range_caps = const_cast*>(&m_sequential_range_caps); + (*sequential_range_caps)[0].reset(); + (*sequential_range_caps)[1].reset(); + + if (m_sequential_view.current.first != m_sequential_view.current.last) { + for (const auto& [tbuffer_id, ibuffer_id, path_id, sub_path_id] : paths) { + TBuffer& buffer = const_cast(m_buffers[tbuffer_id]); + if (buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Triangle) + continue; + + const Path& path = buffer.paths[path_id]; + const Path::Sub_Path& sub_path = path.sub_paths[sub_path_id]; + if (m_sequential_view.current.last <= sub_path.first.s_id || sub_path.last.s_id <= m_sequential_view.current.first) + continue; + + // update cap for first endpoint of current range + if (m_sequential_view.current.first > sub_path.first.s_id) { + SequentialRangeCap& cap = (*sequential_range_caps)[0]; + const IBuffer& i_buffer = buffer.indices[ibuffer_id]; + cap.buffer = &buffer; + cap.vbo = i_buffer.vbo; + + // calculate offset into the index buffer + unsigned int offset = sub_path.first.i_id; + offset += 6; // add 2 triangles for corner cap + offset += static_cast(m_sequential_view.current.first - sub_path.first.s_id) * buffer.indices_per_segment(); + if (sub_path_id == 0) + offset += 6; // add 2 triangles for starting cap + + // extract indices from index buffer + std::array indices{ 0, 0, 0, 0, 0, 0 }; + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo)); + glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 0) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[0]))); + glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 7) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[1]))); + glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 1) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[2]))); + glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 13) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[4]))); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + indices[3] = indices[0]; + indices[5] = indices[1]; + + // send indices to gpu + glsafe(::glGenBuffers(1, &cap.ibo)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cap.ibo)); + glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(IBufferType), indices.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + + // extract color from render path + size_t offset_bytes = offset * sizeof(IBufferType); + for (const RenderPath& render_path : buffer.render_paths) { + if (render_path.ibuffer_id == ibuffer_id) { + for (size_t j = 0; j < render_path.offsets.size(); ++j) { + if (render_path.contains(offset_bytes)) { + cap.color = render_path.color; + break; + } + } + } + } + } + + // update cap for last endpoint of current range + if (m_sequential_view.current.last < sub_path.last.s_id) { + SequentialRangeCap& cap = (*sequential_range_caps)[1]; + const IBuffer& i_buffer = buffer.indices[ibuffer_id]; + cap.buffer = &buffer; + cap.vbo = i_buffer.vbo; + + // calculate offset into the index buffer + unsigned int offset = sub_path.first.i_id; + offset += 6; // add 2 triangles for corner cap + offset += static_cast(m_sequential_view.current.last - 1 - sub_path.first.s_id) * buffer.indices_per_segment(); + if (sub_path_id == 0) + offset += 6; // add 2 triangles for starting cap + + // extract indices from index buffer + std::array indices{ 0, 0, 0, 0, 0, 0 }; + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo)); + glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 2) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[0]))); + glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 4) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[1]))); + glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 10) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[2]))); + glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 16) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[5]))); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + indices[3] = indices[0]; + indices[4] = indices[2]; + + // send indices to gpu + glsafe(::glGenBuffers(1, &cap.ibo)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cap.ibo)); + glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * sizeof(IBufferType), indices.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + + // extract color from render path + size_t offset_bytes = offset * sizeof(IBufferType); + for (const RenderPath& render_path : buffer.render_paths) { + if (render_path.ibuffer_id == ibuffer_id) { + for (size_t j = 0; j < render_path.offsets.size(); ++j) { + if (render_path.contains(offset_bytes)) { + cap.color = render_path.color; + break; + } + } + } + } + } + + if ((*sequential_range_caps)[0].is_renderable() && (*sequential_range_caps)[1].is_renderable()) + break; + } + } + + wxGetApp().plater()->enable_preview_moves_slider(!paths.empty()); + +#if ENABLE_GCODE_VIEWER_STATISTICS + for (const TBuffer& buffer : m_buffers) { + statistics->render_paths_size += SLIC3R_STDUNORDEREDSET_MEMSIZE(buffer.render_paths, RenderPath); + for (const RenderPath& path : buffer.render_paths) { + statistics->render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.sizes, unsigned int); + statistics->render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.offsets, size_t); + } + statistics->models_instances_size += SLIC3R_STDVEC_MEMSIZE(buffer.model.instances.buffer, float); + statistics->models_instances_size += SLIC3R_STDVEC_MEMSIZE(buffer.model.instances.s_ids, size_t); + statistics->models_instances_size += SLIC3R_STDVEC_MEMSIZE(buffer.model.instances.render_ranges.ranges, InstanceVBuffer::Ranges::Range); + } + statistics->refresh_paths_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); +#endif // ENABLE_GCODE_VIEWER_STATISTICS +} + +void GCodeViewer::render_toolpaths() +{ +#if !ENABLE_GL_SHADERS_ATTRIBUTES + const std::array light_intensity = { 0.25f, 0.70f, 0.75f, 0.75f }; +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + const Camera& camera = wxGetApp().plater()->get_camera(); + const double zoom = camera.get_zoom(); + +#if !ENABLE_GL_SHADERS_ATTRIBUTES + auto shader_init_as_lines = [light_intensity](GLShaderProgram &shader) { + shader.set_uniform("light_intensity", light_intensity); + }; +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + auto render_as_lines = [ +#if ENABLE_GCODE_VIEWER_STATISTICS + this +#endif // ENABLE_GCODE_VIEWER_STATISTICS + ](std::vector::iterator it_path, std::vector::iterator it_end, GLShaderProgram& shader, int uniform_color) { + for (auto it = it_path; it != it_end && it_path->ibuffer_id == it->ibuffer_id; ++it) { + const RenderPath& path = *it; + // Some OpenGL drivers crash on empty glMultiDrawElements, see GH #7415. + assert(! path.sizes.empty()); + assert(! path.offsets.empty()); + shader.set_uniform(uniform_color, path.color); + glsafe(::glMultiDrawElements(GL_LINES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_SHORT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); +#if ENABLE_GCODE_VIEWER_STATISTICS + ++m_statistics.gl_multi_lines_calls_count; +#endif // ENABLE_GCODE_VIEWER_STATISTICS + } + }; + + auto render_as_triangles = [ +#if ENABLE_GCODE_VIEWER_STATISTICS + this +#endif // ENABLE_GCODE_VIEWER_STATISTICS + ](std::vector::iterator it_path, std::vector::iterator it_end, GLShaderProgram& shader, int uniform_color) { + for (auto it = it_path; it != it_end && it_path->ibuffer_id == it->ibuffer_id; ++it) { + const RenderPath& path = *it; + // Some OpenGL drivers crash on empty glMultiDrawElements, see GH #7415. + assert(! path.sizes.empty()); + assert(! path.offsets.empty()); + shader.set_uniform(uniform_color, path.color); + glsafe(::glMultiDrawElements(GL_TRIANGLES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_SHORT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); +#if ENABLE_GCODE_VIEWER_STATISTICS + ++m_statistics.gl_multi_triangles_calls_count; +#endif // ENABLE_GCODE_VIEWER_STATISTICS + } + }; + + auto render_as_instanced_model = [ +#if ENABLE_GCODE_VIEWER_STATISTICS + this +#endif // ENABLE_GCODE_VIEWER_STATISTICS + ](TBuffer& buffer, GLShaderProgram & shader) { + for (auto& range : buffer.model.instances.render_ranges.ranges) { + if (range.vbo == 0 && range.count > 0) { + glsafe(::glGenBuffers(1, &range.vbo)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, range.vbo)); + glsafe(::glBufferData(GL_ARRAY_BUFFER, range.count * buffer.model.instances.instance_size_bytes(), (const void*)&buffer.model.instances.buffer[range.offset * buffer.model.instances.instance_size_floats()], GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + } + + if (range.vbo > 0) { +#if ENABLE_LEGACY_OPENGL_REMOVAL + buffer.model.model.set_color(range.color); +#else + buffer.model.model.set_color(-1, range.color); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + buffer.model.model.render_instanced(range.vbo, range.count); +#if ENABLE_GCODE_VIEWER_STATISTICS + ++m_statistics.gl_instanced_models_calls_count; + m_statistics.total_instances_gpu_size += static_cast(range.count * buffer.model.instances.instance_size_bytes()); +#endif // ENABLE_GCODE_VIEWER_STATISTICS + } + } + }; + +#if ENABLE_GL_SHADERS_ATTRIBUTES +#if ENABLE_GCODE_VIEWER_STATISTICS + auto render_as_batched_model = [this](TBuffer& buffer, GLShaderProgram& shader, int position_id, int normal_id) { +#else + auto render_as_batched_model = [](TBuffer& buffer, GLShaderProgram& shader, int position_id, int normal_id) { +#endif // ENABLE_GCODE_VIEWER_STATISTICS +#else +#if ENABLE_GCODE_VIEWER_STATISTICS + auto render_as_batched_model = [this](TBuffer& buffer, GLShaderProgram& shader) { +#else + auto render_as_batched_model = [](TBuffer& buffer, GLShaderProgram& shader) { +#endif // ENABLE_GCODE_VIEWER_STATISTICS +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + struct Range + { + unsigned int first; + unsigned int last; + bool intersects(const Range& other) const { return (other.last < first || other.first > last) ? false : true; } + }; + Range buffer_range = { 0, 0 }; + const size_t indices_per_instance = buffer.model.data.indices_count(); + + for (size_t j = 0; j < buffer.indices.size(); ++j) { + const IBuffer& i_buffer = buffer.indices[j]; + buffer_range.last = buffer_range.first + i_buffer.count / indices_per_instance; + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, i_buffer.vbo)); +#if ENABLE_GL_SHADERS_ATTRIBUTES + if (position_id != -1) { + glsafe(::glVertexAttribPointer(position_id, buffer.vertices.position_size_floats(), GL_FLOAT, GL_FALSE, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_bytes())); + glsafe(::glEnableVertexAttribArray(position_id)); + } +#else + glsafe(::glVertexPointer(buffer.vertices.position_size_floats(), GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_bytes())); + glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + const bool has_normals = buffer.vertices.normal_size_floats() > 0; + if (has_normals) { +#if ENABLE_GL_SHADERS_ATTRIBUTES + if (normal_id != -1) { + glsafe(::glVertexAttribPointer(normal_id, buffer.vertices.normal_size_floats(), GL_FLOAT, GL_FALSE, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_bytes())); + glsafe(::glEnableVertexAttribArray(normal_id)); + } +#else + glsafe(::glNormalPointer(GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_bytes())); + glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + } + + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo)); + + for (auto& range : buffer.model.instances.render_ranges.ranges) { + const Range range_range = { range.offset, range.offset + range.count }; + if (range_range.intersects(buffer_range)) { + shader.set_uniform("uniform_color", range.color); + const unsigned int offset = (range_range.first > buffer_range.first) ? range_range.first - buffer_range.first : 0; + const size_t offset_bytes = static_cast(offset) * indices_per_instance * sizeof(IBufferType); + const Range render_range = { std::max(range_range.first, buffer_range.first), std::min(range_range.last, buffer_range.last) }; + const size_t count = static_cast(render_range.last - render_range.first) * indices_per_instance; + if (count > 0) { + glsafe(::glDrawElements(GL_TRIANGLES, (GLsizei)count, GL_UNSIGNED_SHORT, (const void*)offset_bytes)); +#if ENABLE_GCODE_VIEWER_STATISTICS + ++m_statistics.gl_batched_models_calls_count; +#endif // ENABLE_GCODE_VIEWER_STATISTICS + } + } + } + + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + +#if ENABLE_GL_SHADERS_ATTRIBUTES + if (normal_id != -1) + glsafe(::glDisableVertexAttribArray(normal_id)); + if (position_id != -1) + glsafe(::glDisableVertexAttribArray(position_id)); +#else + if (has_normals) + glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); + + glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + + buffer_range.first = buffer_range.last; + } + }; + + auto line_width = [](double zoom) { + return (zoom < 5.0) ? 1.0 : (1.0 + 5.0 * (zoom - 5.0) / (100.0 - 5.0)); + }; + + const unsigned char begin_id = buffer_id(EMoveType::Retract); + const unsigned char end_id = buffer_id(EMoveType::Count); + + for (unsigned char i = begin_id; i < end_id; ++i) { + TBuffer& buffer = m_buffers[i]; + if (!buffer.visible || !buffer.has_data()) + continue; + + GLShaderProgram* shader = wxGetApp().get_shader(buffer.shader.c_str()); + if (shader == nullptr) + continue; + + shader->start_using(); + +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Transform3d& view_matrix = camera.get_view_matrix(); + shader->set_uniform("view_model_matrix", view_matrix); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + shader->set_uniform("normal_matrix", (Matrix3d)view_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::InstancedModel) { + shader->set_uniform("emission_factor", 0.25f); + render_as_instanced_model(buffer, *shader); + shader->set_uniform("emission_factor", 0.0f); + } + else if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) { + shader->set_uniform("emission_factor", 0.25f); +#if ENABLE_GL_SHADERS_ATTRIBUTES + const int position_id = shader->get_attrib_location("v_position"); + const int normal_id = shader->get_attrib_location("v_normal"); + render_as_batched_model(buffer, *shader, position_id, normal_id); +#else + render_as_batched_model(buffer, *shader); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + shader->set_uniform("emission_factor", 0.0f); + } + else { +#if ENABLE_GL_SHADERS_ATTRIBUTES + const int position_id = shader->get_attrib_location("v_position"); + const int normal_id = shader->get_attrib_location("v_normal"); +#else + if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Line) + shader_init_as_lines(*shader); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + const int uniform_color = shader->get_uniform_location("uniform_color"); + + auto it_path = buffer.render_paths.begin(); + for (unsigned int ibuffer_id = 0; ibuffer_id < static_cast(buffer.indices.size()); ++ibuffer_id) { + const IBuffer& i_buffer = buffer.indices[ibuffer_id]; + // Skip all paths with ibuffer_id < ibuffer_id. + for (; it_path != buffer.render_paths.end() && it_path->ibuffer_id < ibuffer_id; ++it_path); + if (it_path == buffer.render_paths.end() || it_path->ibuffer_id > ibuffer_id) + // Not found. This shall not happen. + continue; + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, i_buffer.vbo)); +#if ENABLE_GL_SHADERS_ATTRIBUTES + if (position_id != -1) { + glsafe(::glVertexAttribPointer(position_id, buffer.vertices.position_size_floats(), GL_FLOAT, GL_FALSE, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_bytes())); + glsafe(::glEnableVertexAttribArray(position_id)); + } +#else + glsafe(::glVertexPointer(buffer.vertices.position_size_floats(), GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_bytes())); + glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + const bool has_normals = buffer.vertices.normal_size_floats() > 0; + if (has_normals) { +#if ENABLE_GL_SHADERS_ATTRIBUTES + if (normal_id != -1) { + glsafe(::glVertexAttribPointer(normal_id, buffer.vertices.normal_size_floats(), GL_FLOAT, GL_FALSE, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_bytes())); + glsafe(::glEnableVertexAttribArray(normal_id)); + } +#else + glsafe(::glNormalPointer(GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_bytes())); + glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + } + + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo)); + + // Render all elements with it_path->ibuffer_id == ibuffer_id, possible with varying colors. + switch (buffer.render_primitive_type) + { + case TBuffer::ERenderPrimitiveType::Line: { + glsafe(::glLineWidth(static_cast(line_width(zoom)))); + render_as_lines(it_path, buffer.render_paths.end(), *shader, uniform_color); + break; + } + case TBuffer::ERenderPrimitiveType::Triangle: { + render_as_triangles(it_path, buffer.render_paths.end(), *shader, uniform_color); + break; + } + default: { break; } + } + + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + +#if ENABLE_GL_SHADERS_ATTRIBUTES + if (normal_id != -1) + glsafe(::glDisableVertexAttribArray(normal_id)); + if (position_id != -1) + glsafe(::glDisableVertexAttribArray(position_id)); +#else + if (has_normals) + glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); + + glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + } + } + + shader->stop_using(); + } + +#if ENABLE_GCODE_VIEWER_STATISTICS + auto render_sequential_range_cap = [this, &camera] +#else + auto render_sequential_range_cap = [&camera] +#endif // ENABLE_GCODE_VIEWER_STATISTICS + (const SequentialRangeCap& cap) { + const TBuffer* buffer = cap.buffer; + GLShaderProgram* shader = wxGetApp().get_shader(buffer->shader.c_str()); + if (shader == nullptr) + return; + + shader->start_using(); + +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Transform3d& view_matrix = camera.get_view_matrix(); + shader->set_uniform("view_model_matrix", view_matrix); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + shader->set_uniform("normal_matrix", (Matrix3d)view_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); + + const int position_id = shader->get_attrib_location("v_position"); + const int normal_id = shader->get_attrib_location("v_normal"); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, cap.vbo)); +#if ENABLE_GL_SHADERS_ATTRIBUTES + if (position_id != -1) { + glsafe(::glVertexAttribPointer(position_id, buffer->vertices.position_size_floats(), GL_FLOAT, GL_FALSE, buffer->vertices.vertex_size_bytes(), (const void*)buffer->vertices.position_offset_bytes())); + glsafe(::glEnableVertexAttribArray(position_id)); + } +#else + glsafe(::glVertexPointer(buffer->vertices.position_size_floats(), GL_FLOAT, buffer->vertices.vertex_size_bytes(), (const void*)buffer->vertices.position_offset_bytes())); + glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + const bool has_normals = buffer->vertices.normal_size_floats() > 0; + if (has_normals) { +#if ENABLE_GL_SHADERS_ATTRIBUTES + if (normal_id != -1) { + glsafe(::glVertexAttribPointer(normal_id, buffer->vertices.normal_size_floats(), GL_FLOAT, GL_FALSE, buffer->vertices.vertex_size_bytes(), (const void*)buffer->vertices.normal_offset_bytes())); + glsafe(::glEnableVertexAttribArray(normal_id)); + } +#else + glsafe(::glNormalPointer(GL_FLOAT, buffer->vertices.vertex_size_bytes(), (const void*)buffer->vertices.normal_offset_bytes())); + glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + } + + shader->set_uniform("uniform_color", cap.color); + + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cap.ibo)); + glsafe(::glDrawElements(GL_TRIANGLES, (GLsizei)cap.indices_count(), GL_UNSIGNED_SHORT, nullptr)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + +#if ENABLE_GCODE_VIEWER_STATISTICS + ++m_statistics.gl_triangles_calls_count; +#endif // ENABLE_GCODE_VIEWER_STATISTICS + +#if ENABLE_GL_SHADERS_ATTRIBUTES + if (normal_id != -1) + glsafe(::glDisableVertexAttribArray(normal_id)); + if (position_id != -1) + glsafe(::glDisableVertexAttribArray(position_id)); +#else + if (has_normals) + glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); + + glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + + shader->stop_using(); + }; + + for (unsigned int i = 0; i < 2; ++i) { + if (m_sequential_range_caps[i].is_renderable()) + render_sequential_range_cap(m_sequential_range_caps[i]); + } +} + +void GCodeViewer::render_shells() +{ + if (!m_shells.visible || m_shells.volumes.empty()) + return; + + GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); + if (shader == nullptr) + return; + +#if !ENABLE_LEGACY_OPENGL_REMOVAL + // when the background processing is enabled, it may happen that the shells data have been loaded + // before opengl has been initialized for the preview canvas. + // when this happens, the volumes' data have not been sent to gpu yet. + for (GLVolume* v : m_shells.volumes.volumes) { + if (!v->indexed_vertex_array.has_VBOs()) + v->finalize_geometry(true); + } +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + +// glsafe(::glDepthMask(GL_FALSE)); + + shader->start_using(); +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Camera& camera = wxGetApp().plater()->get_camera(); + m_shells.volumes.render(GLVolumeCollection::ERenderType::Transparent, true, camera.get_view_matrix(), camera.get_projection_matrix()); +#else + m_shells.volumes.render(GLVolumeCollection::ERenderType::Transparent, true, wxGetApp().plater()->get_camera().get_view_matrix()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + shader->stop_using(); + +// glsafe(::glDepthMask(GL_TRUE)); +} + +void GCodeViewer::render_legend(float& legend_height) +{ + if (!m_legend_enabled) + return; + + const Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size(); + + ImGuiWrapper& imgui = *wxGetApp().imgui(); + + imgui.set_next_window_pos(0.0f, 0.0f, ImGuiCond_Always); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::SetNextWindowBgAlpha(0.6f); + const float max_height = 0.75f * static_cast(cnv_size.get_height()); + const float child_height = 0.3333f * max_height; + ImGui::SetNextWindowSizeConstraints({ 0.0f, 0.0f }, { -1.0f, max_height }); +#if ENABLE_PREVIEW_LAYOUT + imgui.begin(std::string("Legend"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove); +#else + imgui.begin(std::string("Legend"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove); +#endif // ENABLE_PREVIEW_LAYOUT + + enum class EItemType : unsigned char + { + Rect, + Circle, + Hexagon, + Line + }; + + const PrintEstimatedStatistics::Mode& time_mode = m_print_statistics.modes[static_cast(m_time_estimate_mode)]; +#if ENABLE_PREVIEW_LAYER_TIME + bool show_estimated_time = time_mode.time > 0.0f && (m_view_type == EViewType::FeatureType || + m_view_type == EViewType::LayerTimeLinear || m_view_type == EViewType::LayerTimeLogarithmic || + (m_view_type == EViewType::ColorPrint && !time_mode.custom_gcode_times.empty())); +#else + bool show_estimated_time = time_mode.time > 0.0f && (m_view_type == EViewType::FeatureType || + (m_view_type == EViewType::ColorPrint && !time_mode.custom_gcode_times.empty())); +#endif // ENABLE_PREVIEW_LAYER_TIME + + const float icon_size = ImGui::GetTextLineHeight(); + const float percent_bar_size = 2.0f * ImGui::GetTextLineHeight(); + + bool imperial_units = wxGetApp().app_config->get("use_inches") == "1"; + + auto append_item = [icon_size, percent_bar_size, &imgui, imperial_units](EItemType type, const ColorRGBA& color, const std::string& label, + bool visible = true, const std::string& time = "", float percent = 0.0f, float max_percent = 0.0f, const std::array& offsets = { 0.0f, 0.0f, 0.0f, 0.0f }, + double used_filament_m = 0.0, double used_filament_g = 0.0, + std::function callback = nullptr) { + if (!visible) + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); + + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + ImVec2 pos = ImGui::GetCursorScreenPos(); + switch (type) { + default: + case EItemType::Rect: { + draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, + ImGuiWrapper::to_ImU32(color)); + break; + } + case EItemType::Circle: { + ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); + draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGuiWrapper::to_ImU32(color), 16); + break; + } + case EItemType::Hexagon: { + ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); + draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGuiWrapper::to_ImU32(color), 6); + break; + } + case EItemType::Line: { + draw_list->AddLine({ pos.x + 1, pos.y + icon_size - 1 }, { pos.x + icon_size - 1, pos.y + 1 }, ImGuiWrapper::to_ImU32(color), 3.0f); + break; + } + } + + // draw text + ImGui::Dummy({ icon_size, icon_size }); + ImGui::SameLine(); + if (callback != nullptr) { + if (ImGui::MenuItem(label.c_str())) + callback(); + else { + // show tooltip + if (ImGui::IsItemHovered()) { + if (!visible) + ImGui::PopStyleVar(); + ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND); + ImGui::BeginTooltip(); + imgui.text(visible ? _u8L("Click to hide") : _u8L("Click to show")); + ImGui::EndTooltip(); + ImGui::PopStyleColor(); + if (!visible) + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); + + // to avoid the tooltip to change size when moving the mouse + imgui.set_requires_extra_frame(); + } + } + + if (!time.empty()) { + ImGui::SameLine(offsets[0]); + imgui.text(time); + ImGui::SameLine(offsets[1]); + pos = ImGui::GetCursorScreenPos(); + const float width = std::max(1.0f, percent_bar_size * percent / max_percent); + draw_list->AddRectFilled({ pos.x, pos.y + 2.0f }, { pos.x + width, pos.y + icon_size - 2.0f }, + ImGui::GetColorU32(ImGuiWrapper::COL_ORANGE_LIGHT)); + ImGui::Dummy({ percent_bar_size, icon_size }); + ImGui::SameLine(); + char buf[64]; + ::sprintf(buf, "%.1f%%", 100.0f * percent); + ImGui::TextUnformatted((percent > 0.0f) ? buf : ""); + ImGui::SameLine(offsets[2]); + ::sprintf(buf, imperial_units ? "%.2f in" : "%.2f m", used_filament_m); + imgui.text(buf); + ImGui::SameLine(offsets[3]); + ::sprintf(buf, "%.2f g", used_filament_g); + imgui.text(buf); + } + } + else { + imgui.text(label); +#if ENABLE_TRAVEL_TIME + if (!time.empty()) { + ImGui::SameLine(offsets[0]); + imgui.text(time); + ImGui::SameLine(offsets[1]); + pos = ImGui::GetCursorScreenPos(); + const float width = std::max(1.0f, percent_bar_size * percent / max_percent); + draw_list->AddRectFilled({ pos.x, pos.y + 2.0f }, { pos.x + width, pos.y + icon_size - 2.0f }, + ImGui::GetColorU32(ImGuiWrapper::COL_ORANGE_LIGHT)); + ImGui::Dummy({ percent_bar_size, icon_size }); + ImGui::SameLine(); + char buf[64]; + ::sprintf(buf, "%.1f%%", 100.0f * percent); + ImGui::TextUnformatted((percent > 0.0f) ? buf : ""); + } + else if (used_filament_m > 0.0) { +#else + if (used_filament_m > 0.0) { +#endif // ENABLE_TRAVEL_TIME + char buf[64]; + ImGui::SameLine(offsets[0]); + ::sprintf(buf, imperial_units ? "%.2f in" : "%.2f m", used_filament_m); + imgui.text(buf); + ImGui::SameLine(offsets[1]); + ::sprintf(buf, "%.2f g", used_filament_g); + imgui.text(buf); + } + } + + if (!visible) + ImGui::PopStyleVar(); + }; + + auto append_range = [append_item](const Extrusions::Range& range, unsigned int decimals) { + auto append_range_item = [append_item](int i, float value, unsigned int decimals) { + char buf[1024]; + ::sprintf(buf, "%.*f", decimals, value); + append_item(EItemType::Rect, Range_Colors[i], buf); + }; + + if (range.count == 1) + // single item use case + append_range_item(0, range.min, decimals); + else if (range.count == 2) { + // two items use case + append_range_item(static_cast(Range_Colors.size()) - 1, range.max, decimals); + append_range_item(0, range.min, decimals); + } + else { + const float step_size = range.step_size(); + for (int i = static_cast(Range_Colors.size()) - 1; i >= 0; --i) { + append_range_item(i, range.min + static_cast(i) * step_size, decimals); + } + } + }; + +#if ENABLE_PREVIEW_LAYER_TIME + auto append_time_range = [append_item](const Extrusions::Range& range, Extrusions::Range::EType type) { + auto append_range_item = [append_item](int i, float value) { + std::string str_value = get_time_dhms(value); + if (str_value == "0s") + str_value = "< 1s"; + append_item(EItemType::Rect, Range_Colors[i], str_value); + }; + + if (range.count == 1) + // single item use case + append_range_item(0, range.min); + else if (range.count == 2) { + // two items use case + append_range_item(static_cast(Range_Colors.size()) - 1, range.max); + append_range_item(0, range.min); + } + else { + float step_size = range.step_size(type); + for (int i = static_cast(Range_Colors.size()) - 1; i >= 0; --i) { + float value = 0.0f; + switch (type) + { + default: + case Extrusions::Range::EType::Linear: { value = range.min + static_cast(i) * step_size; break; } + case Extrusions::Range::EType::Logarithmic: { value = ::exp(::log(range.min) + static_cast(i) * step_size); break; } + } + append_range_item(i, value); + } + } + }; +#endif // ENABLE_PREVIEW_LAYER_TIME + + auto append_headers = [&imgui](const std::array& texts, const std::array& offsets) { + size_t i = 0; + for (; i < offsets.size(); i++) { + imgui.text(texts[i]); + ImGui::SameLine(offsets[i]); + } + imgui.text(texts[i]); + ImGui::Separator(); + }; + + auto max_width = [](const std::vector& items, const std::string& title, float extra_size = 0.0f) { + float ret = ImGui::CalcTextSize(title.c_str()).x; + for (const std::string& item : items) { + ret = std::max(ret, extra_size + ImGui::CalcTextSize(item.c_str()).x); + } + return ret; + }; + + auto calculate_offsets = [max_width](const std::vector& labels, const std::vector& times, + const std::array& titles, float extra_size = 0.0f) { + const ImGuiStyle& style = ImGui::GetStyle(); + std::array ret = { 0.0f, 0.0f, 0.0f, 0.0f }; + ret[0] = max_width(labels, titles[0], extra_size) + 3.0f * style.ItemSpacing.x; + for (size_t i = 1; i < titles.size(); i++) + ret[i] = ret[i-1] + max_width(times, titles[i]) + style.ItemSpacing.x; + return ret; + }; + + auto color_print_ranges = [this](unsigned char extruder_id, const std::vector& custom_gcode_per_print_z) { + std::vector>> ret; + ret.reserve(custom_gcode_per_print_z.size()); + + for (const auto& item : custom_gcode_per_print_z) { + if (extruder_id + 1 != static_cast(item.extruder)) + continue; + + if (item.type != ColorChange) + continue; + + const std::vector zs = m_layers.get_zs(); + auto lower_b = std::lower_bound(zs.begin(), zs.end(), item.print_z - Slic3r::DoubleSlider::epsilon()); + if (lower_b == zs.end()) + continue; + + const double current_z = *lower_b; + const double previous_z = (lower_b == zs.begin()) ? 0.0 : *(--lower_b); + + // to avoid duplicate values, check adding values + if (ret.empty() || !(ret.back().second.first == previous_z && ret.back().second.second == current_z)) { + ColorRGBA color; + decode_color(item.color, color); + ret.push_back({ color, { previous_z, current_z } }); + } + } + + return ret; + }; + + auto upto_label = [](double z) { + char buf[64]; + ::sprintf(buf, "%.2f", z); + return _u8L("up to") + " " + std::string(buf) + " " + _u8L("mm"); + }; + + auto above_label = [](double z) { + char buf[64]; + ::sprintf(buf, "%.2f", z); + return _u8L("above") + " " + std::string(buf) + " " + _u8L("mm"); + }; + + auto fromto_label = [](double z1, double z2) { + char buf1[64]; + ::sprintf(buf1, "%.2f", z1); + char buf2[64]; + ::sprintf(buf2, "%.2f", z2); + return _u8L("from") + " " + std::string(buf1) + " " + _u8L("to") + " " + std::string(buf2) + " " + _u8L("mm"); + }; + + auto role_time_and_percent = [time_mode](ExtrusionRole role) { + auto it = std::find_if(time_mode.roles_times.begin(), time_mode.roles_times.end(), [role](const std::pair& item) { return role == item.first; }); + return (it != time_mode.roles_times.end()) ? std::make_pair(it->second, it->second / time_mode.time) : std::make_pair(0.0f, 0.0f); + }; + + auto used_filament_per_role = [this, imperial_units](ExtrusionRole role) { + auto it = m_print_statistics.used_filaments_per_role.find(role); + if (it == m_print_statistics.used_filaments_per_role.end()) + return std::make_pair(0.0, 0.0); + + double koef = imperial_units ? 1000.0 / ObjectManipulation::in_to_mm : 1.0; + return std::make_pair(it->second.first * koef, it->second.second); + }; + + // data used to properly align items in columns when showing time + std::array offsets = { 0.0f, 0.0f, 0.0f, 0.0f }; + std::vector labels; + std::vector times; + std::vector percents; + std::vector used_filaments_m; + std::vector used_filaments_g; + float max_time_percent = 0.0f; + + if (m_view_type == EViewType::FeatureType) { + // calculate offsets to align time/percentage data + for (size_t i = 0; i < m_roles.size(); ++i) { + ExtrusionRole role = m_roles[i]; + if (role < erCount) { + labels.push_back(_u8L(ExtrusionEntity::role_to_string(role))); + auto [time, percent] = role_time_and_percent(role); + times.push_back((time > 0.0f) ? short_time(get_time_dhms(time)) : ""); + percents.push_back(percent); + max_time_percent = std::max(max_time_percent, percent); + auto [used_filament_m, used_filament_g] = used_filament_per_role(role); + used_filaments_m.push_back(used_filament_m); + used_filaments_g.push_back(used_filament_g); + } + } + + std::string longest_percentage_string; + for (double item : percents) { + char buffer[64]; + ::sprintf(buffer, "%.2f %%", item); + if (::strlen(buffer) > longest_percentage_string.length()) + longest_percentage_string = buffer; + } + longest_percentage_string += " "; + if (_u8L("Percentage").length() > longest_percentage_string.length()) + longest_percentage_string = _u8L("Percentage"); + + std::string longest_used_filament_string; + for (double item : used_filaments_m) { + char buffer[64]; + ::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", item); + if (::strlen(buffer) > longest_used_filament_string.length()) + longest_used_filament_string = buffer; + } + + offsets = calculate_offsets(labels, times, { _u8L("Feature type"), _u8L("Time"), longest_percentage_string, longest_used_filament_string }, icon_size); + } + + // get used filament (meters and grams) from used volume in respect to the active extruder + auto get_used_filament_from_volume = [this, imperial_units](double volume, int extruder_id) { + double koef = imperial_units ? 1.0 / ObjectManipulation::in_to_mm : 0.001; + std::pair ret = { koef * volume / (PI * sqr(0.5 * m_filament_diameters[extruder_id])), + volume * m_filament_densities[extruder_id] * 0.001 }; + return ret; + }; + + if (m_view_type == EViewType::Tool) { + // calculate used filaments data + for (size_t extruder_id : m_extruder_ids) { + if (m_print_statistics.volumes_per_extruder.find(extruder_id) == m_print_statistics.volumes_per_extruder.end()) + continue; + double volume = m_print_statistics.volumes_per_extruder.at(extruder_id); + + auto [used_filament_m, used_filament_g] = get_used_filament_from_volume(volume, extruder_id); + used_filaments_m.push_back(used_filament_m); + used_filaments_g.push_back(used_filament_g); + } + + std::string longest_used_filament_string; + for (double item : used_filaments_m) { + char buffer[64]; + ::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", item); + if (::strlen(buffer) > longest_used_filament_string.length()) + longest_used_filament_string = buffer; + } + + offsets = calculate_offsets(labels, times, { "Extruder NNN", longest_used_filament_string }, icon_size); + } + +#if ENABLE_PREVIEW_LAYOUT + // selection section + bool view_type_changed = false; + int old_view_type = static_cast(get_view_type()); + int view_type = old_view_type; + + if (!m_legend_resizer.dirty) + ImGui::SetNextItemWidth(-1.0f); + + ImGui::PushStyleColor(ImGuiCol_FrameBg, { 0.1f, 0.1f, 0.1f, 0.8f }); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, { 0.2f, 0.2f, 0.2f, 0.8f }); + imgui.combo("", { _u8L("Feature type"), + _u8L("Height (mm)"), + _u8L("Width (mm)"), + _u8L("Speed (mm/s)"), + _u8L("Fan speed (%)"), + _u8L("Temperature (°C)"), + _u8L("Volumetric flow rate (mm³/s)"), +#if ENABLE_PREVIEW_LAYER_TIME + _u8L("Layer time (linear)"), + _u8L("Layer time (logarithmic)"), +#endif // ENABLE_PREVIEW_LAYER_TIME + _u8L("Tool"), + _u8L("Color Print") }, view_type, ImGuiComboFlags_HeightLargest); + ImGui::PopStyleColor(2); + + if (old_view_type != view_type) { + set_view_type(static_cast(view_type)); + wxGetApp().plater()->set_keep_current_preview_type(true); + wxGetApp().plater()->refresh_print(); + view_type_changed = true; + } + + // extrusion paths section -> title + if (m_view_type == EViewType::FeatureType) + append_headers({ _u8L(""), _u8L("Time"), _u8L("Percentage"), _u8L("Used filament") }, offsets); + else if (m_view_type == EViewType::Tool) + append_headers({ _u8L(""), _u8L("Used filament"), _u8L(""), _u8L("") }, offsets); + else + ImGui::Separator(); +#else + // extrusion paths section -> title + switch (m_view_type) + { + case EViewType::FeatureType: + { + append_headers({ _u8L("Feature type"), _u8L("Time"), _u8L("Percentage"), _u8L("Used filament") }, offsets); + break; + } + case EViewType::Height: { imgui.title(_u8L("Height (mm)")); break; } + case EViewType::Width: { imgui.title(_u8L("Width (mm)")); break; } + case EViewType::Feedrate: { imgui.title(_u8L("Speed (mm/s)")); break; } + case EViewType::FanSpeed: { imgui.title(_u8L("Fan Speed (%)")); break; } + case EViewType::Temperature: { imgui.title(_u8L("Temperature (°C)")); break; } + case EViewType::VolumetricRate: { imgui.title(_u8L("Volumetric flow rate (mm³/s)")); break; } +#if ENABLE_PREVIEW_LAYER_TIME + case EViewType::LayerTimeLinear: { imgui.title(_u8L("Layer time (linear)")); break; } + case EViewType::LayerTimeLogarithmic: { imgui.title(_u8L("Layer time (logarithmic)")); break; } +#endif // ENABLE_PREVIEW_LAYER_TIME + case EViewType::Tool: { + append_headers({ _u8L("Tool"), _u8L("Used filament") }, offsets); + break; + } + case EViewType::ColorPrint: { imgui.title(_u8L("Color Print")); break; } + default: { break; } + } +#endif // ENABLE_PREVIEW_LAYOUT + +#if ENABLE_PREVIEW_LAYOUT + if (!view_type_changed) { +#endif // ENABLE_PREVIEW_LAYOUT + // extrusion paths section -> items + switch (m_view_type) + { + case EViewType::FeatureType: + { +#if ENABLE_TRAVEL_TIME + max_time_percent = std::max(max_time_percent, time_mode.travel_time / time_mode.time); +#endif // ENABLE_TRAVEL_TIME + + for (size_t i = 0; i < m_roles.size(); ++i) { + ExtrusionRole role = m_roles[i]; + if (role >= erCount) + continue; + const bool visible = is_visible(role); + append_item(EItemType::Rect, Extrusion_Role_Colors[static_cast(role)], labels[i], + visible, times[i], percents[i], max_time_percent, offsets, used_filaments_m[i], used_filaments_g[i], [this, role, visible]() { + m_extrusions.role_visibility_flags = visible ? m_extrusions.role_visibility_flags & ~(1 << role) : m_extrusions.role_visibility_flags | (1 << role); + // update buffers' render paths + refresh_render_paths(false, false); + wxGetApp().plater()->update_preview_moves_slider(); + wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); +#if !ENABLE_PREVIEW_LAYOUT + wxGetApp().plater()->update_preview_bottom_toolbar(); +#endif // !ENABLE_PREVIEW_LAYOUT + } + ); + } + +#if ENABLE_TRAVEL_TIME + if (m_buffers[buffer_id(EMoveType::Travel)].visible) + append_item(EItemType::Line, Travel_Colors[0], _u8L("Travel"), true, short_time(get_time_dhms(time_mode.travel_time)), + time_mode.travel_time / time_mode.time, max_time_percent, offsets, 0.0f, 0.0f); +#endif // ENABLE_TRAVEL_TIME + + break; + } + case EViewType::Height: { append_range(m_extrusions.ranges.height, 3); break; } + case EViewType::Width: { append_range(m_extrusions.ranges.width, 3); break; } + case EViewType::Feedrate: { append_range(m_extrusions.ranges.feedrate, 1); break; } + case EViewType::FanSpeed: { append_range(m_extrusions.ranges.fan_speed, 0); break; } + case EViewType::Temperature: { append_range(m_extrusions.ranges.temperature, 0); break; } + case EViewType::VolumetricRate: { append_range(m_extrusions.ranges.volumetric_rate, 3); break; } +#if ENABLE_PREVIEW_LAYER_TIME + case EViewType::LayerTimeLinear: { append_time_range(m_extrusions.ranges.layer_time[static_cast(m_time_estimate_mode)], Extrusions::Range::EType::Linear); break; } + case EViewType::LayerTimeLogarithmic: { append_time_range(m_extrusions.ranges.layer_time[static_cast(m_time_estimate_mode)], Extrusions::Range::EType::Logarithmic); break; } +#endif // ENABLE_PREVIEW_LAYER_TIME + case EViewType::Tool: { + // shows only extruders actually used + size_t i = 0; + for (unsigned char extruder_id : m_extruder_ids) { + append_item(EItemType::Rect, m_tool_colors[extruder_id], _u8L("Extruder") + " " + std::to_string(extruder_id + 1), + true, "", 0.0f, 0.0f, offsets, used_filaments_m[i], used_filaments_g[i]); + ++i; + } + break; + } + case EViewType::ColorPrint: + { + const std::vector& custom_gcode_per_print_z = wxGetApp().is_editor() ? wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes : m_custom_gcode_per_print_z; + size_t total_items = 1; + for (unsigned char i : m_extruder_ids) { + total_items += color_print_ranges(i, custom_gcode_per_print_z).size(); + } + + const bool need_scrollable = static_cast(total_items) * icon_size + (static_cast(total_items) - 1.0f) * ImGui::GetStyle().ItemSpacing.y > child_height; + + // add scrollable region, if needed + if (need_scrollable) + ImGui::BeginChild("color_prints", { -1.0f, child_height }, false); + if (m_extruders_count == 1) { // single extruder use case + const std::vector>> cp_values = color_print_ranges(0, custom_gcode_per_print_z); + const int items_cnt = static_cast(cp_values.size()); + if (items_cnt == 0) // There are no color changes, but there are some pause print or custom Gcode + append_item(EItemType::Rect, m_tool_colors.front(), _u8L("Default color")); + else { + for (int i = items_cnt; i >= 0; --i) { + // create label for color change item + if (i == 0) { + append_item(EItemType::Rect, m_tool_colors[0], upto_label(cp_values.front().second.first)); + break; + } + else if (i == items_cnt) { + append_item(EItemType::Rect, cp_values[i - 1].first, above_label(cp_values[i - 1].second.second)); + continue; + } + append_item(EItemType::Rect, cp_values[i - 1].first, fromto_label(cp_values[i - 1].second.second, cp_values[i].second.first)); + } + } + } + else { // multi extruder use case + // shows only extruders actually used + for (unsigned char i : m_extruder_ids) { + const std::vector>> cp_values = color_print_ranges(i, custom_gcode_per_print_z); + const int items_cnt = static_cast(cp_values.size()); + if (items_cnt == 0) + // There are no color changes, but there are some pause print or custom Gcode + append_item(EItemType::Rect, m_tool_colors[i], _u8L("Extruder") + " " + std::to_string(i + 1) + " " + _u8L("default color")); + else { + for (int j = items_cnt; j >= 0; --j) { + // create label for color change item + std::string label = _u8L("Extruder") + " " + std::to_string(i + 1); + if (j == 0) { + label += " " + upto_label(cp_values.front().second.first); + append_item(EItemType::Rect, m_tool_colors[i], label); + break; + } + else if (j == items_cnt) { + label += " " + above_label(cp_values[j - 1].second.second); + append_item(EItemType::Rect, cp_values[j - 1].first, label); + continue; + } + + label += " " + fromto_label(cp_values[j - 1].second.second, cp_values[j].second.first); + append_item(EItemType::Rect, cp_values[j - 1].first, label); + } + } + } + } + if (need_scrollable) + ImGui::EndChild(); + + break; + } + default: { break; } + } +#if ENABLE_PREVIEW_LAYOUT + } +#endif // ENABLE_PREVIEW_LAYOUT + + // partial estimated printing time section + if (m_view_type == EViewType::ColorPrint) { + using Times = std::pair; + using TimesList = std::vector>; + + // helper structure containig the data needed to render the time items + struct PartialTime + { + enum class EType : unsigned char + { + Print, + ColorChange, + Pause + }; + EType type; + int extruder_id; + ColorRGBA color1; + ColorRGBA color2; + Times times; + std::pair used_filament{ 0.0f, 0.0f }; + }; + using PartialTimes = std::vector; + + auto generate_partial_times = [this, get_used_filament_from_volume](const TimesList& times, const std::vector& used_filaments) { + PartialTimes items; + + std::vector custom_gcode_per_print_z = wxGetApp().is_editor() ? wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes : m_custom_gcode_per_print_z; + int extruders_count = wxGetApp().extruders_edited_cnt(); + std::vector last_color(extruders_count); + for (int i = 0; i < extruders_count; ++i) { + last_color[i] = m_tool_colors[i]; + } + int last_extruder_id = 1; + int color_change_idx = 0; + for (const auto& time_rec : times) { + switch (time_rec.first) + { + case CustomGCode::PausePrint: { + auto it = std::find_if(custom_gcode_per_print_z.begin(), custom_gcode_per_print_z.end(), [time_rec](const CustomGCode::Item& item) { return item.type == time_rec.first; }); + if (it != custom_gcode_per_print_z.end()) { + items.push_back({ PartialTime::EType::Print, it->extruder, last_color[it->extruder - 1], ColorRGBA::BLACK(), time_rec.second }); + items.push_back({ PartialTime::EType::Pause, it->extruder, ColorRGBA::BLACK(), ColorRGBA::BLACK(), time_rec.second }); + custom_gcode_per_print_z.erase(it); + } + break; + } + case CustomGCode::ColorChange: { + auto it = std::find_if(custom_gcode_per_print_z.begin(), custom_gcode_per_print_z.end(), [time_rec](const CustomGCode::Item& item) { return item.type == time_rec.first; }); + if (it != custom_gcode_per_print_z.end()) { + items.push_back({ PartialTime::EType::Print, it->extruder, last_color[it->extruder - 1], ColorRGBA::BLACK(), time_rec.second, get_used_filament_from_volume(used_filaments[color_change_idx++], it->extruder - 1) }); + ColorRGBA color; + decode_color(it->color, color); + items.push_back({ PartialTime::EType::ColorChange, it->extruder, last_color[it->extruder - 1], color, time_rec.second }); + last_color[it->extruder - 1] = color; + last_extruder_id = it->extruder; + custom_gcode_per_print_z.erase(it); + } + else + items.push_back({ PartialTime::EType::Print, last_extruder_id, last_color[last_extruder_id - 1], ColorRGBA::BLACK(), time_rec.second, get_used_filament_from_volume(used_filaments[color_change_idx++], last_extruder_id - 1) }); + + break; + } + default: { break; } + } + } + + return items; + }; + + auto append_color_change = [&imgui](const ColorRGBA& color1, const ColorRGBA& color2, const std::array& offsets, const Times& times) { + imgui.text(_u8L("Color change")); + ImGui::SameLine(); + + float icon_size = ImGui::GetTextLineHeight(); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + ImVec2 pos = ImGui::GetCursorScreenPos(); + pos.x -= 0.5f * ImGui::GetStyle().ItemSpacing.x; + + draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, + ImGuiWrapper::to_ImU32(color1)); + pos.x += icon_size; + draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, + ImGuiWrapper::to_ImU32(color2)); + + ImGui::SameLine(offsets[0]); + imgui.text(short_time(get_time_dhms(times.second - times.first))); + }; + + auto append_print = [&imgui, imperial_units](const ColorRGBA& color, const std::array& offsets, const Times& times, std::pair used_filament) { + imgui.text(_u8L("Print")); + ImGui::SameLine(); + + float icon_size = ImGui::GetTextLineHeight(); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + ImVec2 pos = ImGui::GetCursorScreenPos(); + pos.x -= 0.5f * ImGui::GetStyle().ItemSpacing.x; + + draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, + ImGuiWrapper::to_ImU32(color)); + + ImGui::SameLine(offsets[0]); + imgui.text(short_time(get_time_dhms(times.second))); + ImGui::SameLine(offsets[1]); + imgui.text(short_time(get_time_dhms(times.first))); + if (used_filament.first > 0.0f) { + char buffer[64]; + ImGui::SameLine(offsets[2]); + ::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", used_filament.first); + imgui.text(buffer); + + ImGui::SameLine(offsets[3]); + ::sprintf(buffer, "%.2f g", used_filament.second); + imgui.text(buffer); + } + }; + + PartialTimes partial_times = generate_partial_times(time_mode.custom_gcode_times, m_print_statistics.volumes_per_color_change); + if (!partial_times.empty()) { + labels.clear(); + times.clear(); + + for (const PartialTime& item : partial_times) { + switch (item.type) + { + case PartialTime::EType::Print: { labels.push_back(_u8L("Print")); break; } + case PartialTime::EType::Pause: { labels.push_back(_u8L("Pause")); break; } + case PartialTime::EType::ColorChange: { labels.push_back(_u8L("Color change")); break; } + } + times.push_back(short_time(get_time_dhms(item.times.second))); + } + + + std::string longest_used_filament_string; + for (const PartialTime& item : partial_times) { + if (item.used_filament.first > 0.0f) { + char buffer[64]; + ::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", item.used_filament.first); + if (::strlen(buffer) > longest_used_filament_string.length()) + longest_used_filament_string = buffer; + } + } + + offsets = calculate_offsets(labels, times, { _u8L("Event"), _u8L("Remaining time"), _u8L("Duration"), longest_used_filament_string }, 2.0f * icon_size); + + ImGui::Spacing(); + append_headers({ _u8L("Event"), _u8L("Remaining time"), _u8L("Duration"), _u8L("Used filament") }, offsets); + const bool need_scrollable = static_cast(partial_times.size()) * icon_size + (static_cast(partial_times.size()) - 1.0f) * ImGui::GetStyle().ItemSpacing.y > child_height; + if (need_scrollable) + // add scrollable region + ImGui::BeginChild("events", { -1.0f, child_height }, false); + + for (const PartialTime& item : partial_times) { + switch (item.type) + { + case PartialTime::EType::Print: { + append_print(item.color1, offsets, item.times, item.used_filament); + break; + } + case PartialTime::EType::Pause: { + imgui.text(_u8L("Pause")); + ImGui::SameLine(offsets[0]); + imgui.text(short_time(get_time_dhms(item.times.second - item.times.first))); + break; + } + case PartialTime::EType::ColorChange: { + append_color_change(item.color1, item.color2, offsets, item.times); + break; + } + } + } + + if (need_scrollable) + ImGui::EndChild(); + } + } + +#if !ENABLE_PREVIEW_LAYOUT + // travel paths section + if (m_buffers[buffer_id(EMoveType::Travel)].visible) { + switch (m_view_type) + { + case EViewType::Feedrate: + case EViewType::Tool: + case EViewType::ColorPrint: { + break; + } + default: { + // title + ImGui::Spacing(); + imgui.title(_u8L("Travel")); + + // items + append_item(EItemType::Line, Travel_Colors[0], _u8L("Movement")); + append_item(EItemType::Line, Travel_Colors[1], _u8L("Extrusion")); + append_item(EItemType::Line, Travel_Colors[2], _u8L("Retraction")); + + break; + } + } + } + + // wipe paths section + if (m_buffers[buffer_id(EMoveType::Wipe)].visible) { + switch (m_view_type) + { + case EViewType::Feedrate: + case EViewType::Tool: + case EViewType::ColorPrint: { break; } + default: { + // title + ImGui::Spacing(); + imgui.title(_u8L("Wipe")); + + // items + append_item(EItemType::Line, Wipe_Color, _u8L("Wipe")); + + break; + } + } + } + + auto any_option_available = [this]() { + auto available = [this](EMoveType type) { + const TBuffer& buffer = m_buffers[buffer_id(type)]; + return buffer.visible && buffer.has_data(); + }; + + return available(EMoveType::Color_change) || + available(EMoveType::Custom_GCode) || + available(EMoveType::Pause_Print) || + available(EMoveType::Retract) || + available(EMoveType::Tool_change) || + available(EMoveType::Unretract) || + available(EMoveType::Seam); + }; + + auto add_option = [this, append_item](EMoveType move_type, EOptionsColors color, const std::string& text) { + const TBuffer& buffer = m_buffers[buffer_id(move_type)]; + if (buffer.visible && buffer.has_data()) + append_item(EItemType::Circle, Options_Colors[static_cast(color)], text); + }; + + // options section + if (any_option_available()) { + // title + ImGui::Spacing(); + imgui.title(_u8L("Options")); + + // items + add_option(EMoveType::Retract, EOptionsColors::Retractions, _u8L("Retractions")); + add_option(EMoveType::Unretract, EOptionsColors::Unretractions, _u8L("Deretractions")); + add_option(EMoveType::Seam, EOptionsColors::Seams, _u8L("Seams")); + add_option(EMoveType::Tool_change, EOptionsColors::ToolChanges, _u8L("Tool changes")); + add_option(EMoveType::Color_change, EOptionsColors::ColorChanges, _u8L("Color changes")); + add_option(EMoveType::Pause_Print, EOptionsColors::PausePrints, _u8L("Print pauses")); + add_option(EMoveType::Custom_GCode, EOptionsColors::CustomGCodes, _u8L("Custom G-codes")); + } +#endif // !ENABLE_PREVIEW_LAYOUT + + // settings section + bool has_settings = false; + has_settings |= !m_settings_ids.print.empty(); + has_settings |= !m_settings_ids.printer.empty(); + bool has_filament_settings = true; + has_filament_settings &= !m_settings_ids.filament.empty(); + for (const std::string& fs : m_settings_ids.filament) { + has_filament_settings &= !fs.empty(); + } + has_settings |= has_filament_settings; + bool show_settings = wxGetApp().is_gcode_viewer(); + show_settings &= (m_view_type == EViewType::FeatureType || m_view_type == EViewType::Tool); + show_settings &= has_settings; + if (show_settings) { + auto calc_offset = [this]() { + float ret = 0.0f; + if (!m_settings_ids.printer.empty()) + ret = std::max(ret, ImGui::CalcTextSize((_u8L("Printer") + std::string(":")).c_str()).x); + if (!m_settings_ids.print.empty()) + ret = std::max(ret, ImGui::CalcTextSize((_u8L("Print settings") + std::string(":")).c_str()).x); + if (!m_settings_ids.filament.empty()) { + for (unsigned char i : m_extruder_ids) { + ret = std::max(ret, ImGui::CalcTextSize((_u8L("Filament") + " " + std::to_string(i + 1) + ":").c_str()).x); + } + } + if (ret > 0.0f) + ret += 2.0f * ImGui::GetStyle().ItemSpacing.x; + return ret; + }; + + ImGui::Spacing(); + imgui.title(_u8L("Settings")); + + float offset = calc_offset(); + + if (!m_settings_ids.printer.empty()) { + imgui.text(_u8L("Printer") + ":"); + ImGui::SameLine(offset); + imgui.text(m_settings_ids.printer); + } + if (!m_settings_ids.print.empty()) { + imgui.text(_u8L("Print settings") + ":"); + ImGui::SameLine(offset); + imgui.text(m_settings_ids.print); + } + if (!m_settings_ids.filament.empty()) { + for (unsigned char i : m_extruder_ids) { + if (i < static_cast(m_settings_ids.filament.size()) && !m_settings_ids.filament[i].empty()) { + std::string txt = _u8L("Filament"); + txt += (m_extruder_ids.size() == 1) ? ":" : " " + std::to_string(i + 1); + imgui.text(txt); + ImGui::SameLine(offset); + imgui.text(m_settings_ids.filament[i]); + } + } + } + } + + // total estimated printing time section + if (show_estimated_time) { + ImGui::Spacing(); + std::string time_title = _u8L("Estimated printing times"); + auto can_show_mode_button = [this](PrintEstimatedStatistics::ETimeMode mode) { + bool show = false; + if (m_print_statistics.modes.size() > 1 && m_print_statistics.modes[static_cast(mode)].roles_times.size() > 0) { + for (size_t i = 0; i < m_print_statistics.modes.size(); ++i) { + if (i != static_cast(mode) && + m_print_statistics.modes[i].time > 0.0f && + short_time(get_time_dhms(m_print_statistics.modes[static_cast(mode)].time)) != short_time(get_time_dhms(m_print_statistics.modes[i].time))) { + show = true; + break; + } + } + } + return show; + }; + + if (can_show_mode_button(m_time_estimate_mode)) { + switch (m_time_estimate_mode) + { + case PrintEstimatedStatistics::ETimeMode::Normal: { time_title += " [" + _u8L("Normal mode") + "]"; break; } + case PrintEstimatedStatistics::ETimeMode::Stealth: { time_title += " [" + _u8L("Stealth mode") + "]"; break; } + default: { assert(false); break; } + } + } + + imgui.title(time_title + ":"); + + std::string first_str = _u8L("First layer"); + std::string total_str = _u8L("Total"); + + float max_len = 10.0f + ImGui::GetStyle().ItemSpacing.x; + if (time_mode.layers_times.empty()) + max_len += ImGui::CalcTextSize(total_str.c_str()).x; + else + max_len += std::max(ImGui::CalcTextSize(first_str.c_str()).x, ImGui::CalcTextSize(total_str.c_str()).x); + + if (!time_mode.layers_times.empty()) { + imgui.text(first_str + ":"); + ImGui::SameLine(max_len); + imgui.text(short_time(get_time_dhms(time_mode.layers_times.front()))); + } + + imgui.text(total_str + ":"); + ImGui::SameLine(max_len); + imgui.text(short_time(get_time_dhms(time_mode.time))); + + auto show_mode_button = [this, &imgui, can_show_mode_button](const wxString& label, PrintEstimatedStatistics::ETimeMode mode) { + if (can_show_mode_button(mode)) { + if (imgui.button(label)) { + m_time_estimate_mode = mode; +#if ENABLE_PREVIEW_LAYER_TIME + if (m_view_type == EViewType::LayerTimeLinear || m_view_type == EViewType::LayerTimeLogarithmic) + refresh_render_paths(false, false); +#endif // ENABLE_PREVIEW_LAYER_TIME + imgui.set_requires_extra_frame(); + } + } + }; + + switch (m_time_estimate_mode) { + case PrintEstimatedStatistics::ETimeMode::Normal: { + show_mode_button(_L("Show stealth mode"), PrintEstimatedStatistics::ETimeMode::Stealth); + break; + } + case PrintEstimatedStatistics::ETimeMode::Stealth: { + show_mode_button(_L("Show normal mode"), PrintEstimatedStatistics::ETimeMode::Normal); + break; + } + default : { assert(false); break; } + } + } + +#if ENABLE_PREVIEW_LAYOUT + // toolbar section + auto toggle_button = [this, &imgui, icon_size](Preview::OptionType type, const std::string& name, + std::function draw_callback) { + auto is_flag_set = [](unsigned int flags, unsigned int flag) { + return (flags & (1 << flag)) != 0; + }; + + auto set_flag = [](unsigned int flags, unsigned int flag, bool active) { + return active ? (flags | (1 << flag)) : (flags & ~(1 << flag)); + }; + + unsigned int flags = get_options_visibility_flags(); + unsigned int flag = static_cast(type); + bool active = is_flag_set(flags, flag); + + if (imgui.draw_radio_button(name, 1.5f * icon_size, active, draw_callback)) { + unsigned int new_flags = set_flag(flags, flag, !active); + set_options_visibility_from_flags(new_flags); + + const unsigned int diff_flags = flags ^ new_flags; + if (m_view_type == GCodeViewer::EViewType::Feedrate && is_flag_set(diff_flags, static_cast(Preview::OptionType::Travel))) + wxGetApp().plater()->refresh_print(); + else { + bool keep_first = m_sequential_view.current.first != m_sequential_view.global.first; + bool keep_last = m_sequential_view.current.last != m_sequential_view.global.last; + wxGetApp().plater()->get_current_canvas3D()->refresh_gcode_preview_render_paths(keep_first, keep_last); + } + wxGetApp().plater()->update_preview_moves_slider(); + } + + if (ImGui::IsItemHovered()) { + ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND); + ImGui::BeginTooltip(); + imgui.text(name); + ImGui::EndTooltip(); + ImGui::PopStyleColor(); + } + }; + +#if ENABLE_LEGEND_TOOLBAR_ICONS + auto image_icon = [&imgui](ImGuiWindow& window, const ImVec2& pos, float size, const wchar_t& icon_id) { + ImGuiIO& io = ImGui::GetIO(); + const ImTextureID tex_id = io.Fonts->TexID; + const float tex_w = static_cast(io.Fonts->TexWidth); + const float tex_h = static_cast(io.Fonts->TexHeight); + const ImFontAtlas::CustomRect* const rect = imgui.GetTextureCustomRect(icon_id); + const ImVec2 uv0 = { static_cast(rect->X) / tex_w, static_cast(rect->Y) / tex_h }; + const ImVec2 uv1 = { static_cast(rect->X + rect->Width) / tex_w, static_cast(rect->Y + rect->Height) / tex_h }; + window.DrawList->AddImage(tex_id, pos, { pos.x + size, pos.y + size }, uv0, uv1, ImGuiWrapper::to_ImU32({ 1.0f, 1.0f, 1.0f, 1.0f })); + }; +#else + auto circle_icon = [](ImGuiWindow& window, const ImVec2& pos, float size, const ColorRGBA& color) { + const float margin = 3.0f; + const ImVec2 center(0.5f * (pos.x + pos.x + size), 0.5f * (pos.y + pos.y + size)); + window.DrawList->AddCircleFilled(center, 0.5f * (size - 2.0f * margin), ImGuiWrapper::to_ImU32(color), 16); + }; + auto line_icon = [](ImGuiWindow& window, const ImVec2& pos, float size, const ColorRGBA& color) { + const float margin = 3.0f; + window.DrawList->AddLine({ pos.x + margin, pos.y + size - margin }, { pos.x + size - margin, pos.y + margin }, ImGuiWrapper::to_ImU32(color), 3.0f); + }; +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + + ImGui::Spacing(); + ImGui::Separator(); + ImGui::Spacing(); + ImGui::Spacing(); +#if ENABLE_LEGEND_TOOLBAR_ICONS + toggle_button(Preview::OptionType::Travel, _u8L("Travel"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + image_icon(window, pos, size, ImGui::LegendTravel); +#else + toggle_button(Preview::OptionType::Travel, _u8L("Travel"), [line_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + line_icon(window, pos, size, Travel_Colors[0]); +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + }); + ImGui::SameLine(); +#if ENABLE_LEGEND_TOOLBAR_ICONS + toggle_button(Preview::OptionType::Wipe, _u8L("Wipe"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + image_icon(window, pos, size, ImGui::LegendWipe); +#else + toggle_button(Preview::OptionType::Wipe, _u8L("Wipe"), [line_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + line_icon(window, pos, size, Wipe_Color); +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + }); + ImGui::SameLine(); +#if ENABLE_LEGEND_TOOLBAR_ICONS + toggle_button(Preview::OptionType::Retractions, _u8L("Retractions"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + image_icon(window, pos, size, ImGui::LegendRetract); +#else + toggle_button(Preview::OptionType::Retractions, _u8L("Retractions"), [circle_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + circle_icon(window, pos, size, Options_Colors[static_cast(EOptionsColors::Retractions)]); +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + }); + ImGui::SameLine(); +#if ENABLE_LEGEND_TOOLBAR_ICONS + toggle_button(Preview::OptionType::Unretractions, _u8L("Deretractions"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + image_icon(window, pos, size, ImGui::LegendDeretract); +#else + toggle_button(Preview::OptionType::Unretractions, _u8L("Deretractions"), [circle_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + circle_icon(window, pos, size, Options_Colors[static_cast(EOptionsColors::Unretractions)]); +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + }); + ImGui::SameLine(); +#if ENABLE_LEGEND_TOOLBAR_ICONS + toggle_button(Preview::OptionType::Seams, _u8L("Seams"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + image_icon(window, pos, size, ImGui::LegendSeams); +#else + toggle_button(Preview::OptionType::Seams, _u8L("Seams"), [circle_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + circle_icon(window, pos, size, Options_Colors[static_cast(EOptionsColors::Seams)]); +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + }); + ImGui::SameLine(); +#if ENABLE_LEGEND_TOOLBAR_ICONS + toggle_button(Preview::OptionType::ToolChanges, _u8L("Tool changes"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + image_icon(window, pos, size, ImGui::LegendToolChanges); +#else + toggle_button(Preview::OptionType::ToolChanges, _u8L("Tool changes"), [circle_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + circle_icon(window, pos, size, Options_Colors[static_cast(EOptionsColors::ToolChanges)]); +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + }); + ImGui::SameLine(); +#if ENABLE_LEGEND_TOOLBAR_ICONS + toggle_button(Preview::OptionType::ColorChanges, _u8L("Color changes"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + image_icon(window, pos, size, ImGui::LegendColorChanges); +#else + toggle_button(Preview::OptionType::ColorChanges, _u8L("Color changes"), [circle_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + circle_icon(window, pos, size, Options_Colors[static_cast(EOptionsColors::ColorChanges)]); +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + }); + ImGui::SameLine(); +#if ENABLE_LEGEND_TOOLBAR_ICONS + toggle_button(Preview::OptionType::PausePrints, _u8L("Print pauses"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + image_icon(window, pos, size, ImGui::LegendPausePrints); +#else + toggle_button(Preview::OptionType::PausePrints, _u8L("Print pauses"), [circle_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + circle_icon(window, pos, size, Options_Colors[static_cast(EOptionsColors::PausePrints)]); +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + }); + ImGui::SameLine(); +#if ENABLE_LEGEND_TOOLBAR_ICONS + toggle_button(Preview::OptionType::CustomGCodes, _u8L("Custom G-codes"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + image_icon(window, pos, size, ImGui::LegendCustomGCodes); +#else + toggle_button(Preview::OptionType::CustomGCodes, _u8L("Custom G-codes"), [circle_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + circle_icon(window, pos, size, Options_Colors[static_cast(EOptionsColors::CustomGCodes)]); +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + }); + ImGui::SameLine(); +#if ENABLE_SHOW_TOOLPATHS_COG +#if ENABLE_LEGEND_TOOLBAR_ICONS + toggle_button(Preview::OptionType::CenterOfGravity, _u8L("Center of gravity"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + image_icon(window, pos, size, ImGui::LegendCOG); + }); +#else + toggle_button(Preview::OptionType::CenterOfGravity, _u8L("Center of gravity"), [](ImGuiWindow& window, const ImVec2& pos, float size) { + const ImU32 black = ImGuiWrapper::to_ImU32({ 0.0f, 0.0f, 0.0f, 1.0f }); + const ImU32 white = ImGuiWrapper::to_ImU32({ 1.0f, 1.0f, 1.0f, 1.0f }); + const float margin = 3.0f; + const ImVec2 center(0.5f * (pos.x + pos.x + size), 0.5f * (pos.y + pos.y + size)); + const float radius = 0.5f * (size - 2.0f * margin); + window.DrawList->PathArcToFast(center, radius, 0, 3); + window.DrawList->PathLineTo(center); + window.DrawList->PathFillConvex(black); + window.DrawList->PathArcToFast(center, radius, 3, 6); + window.DrawList->PathLineTo(center); + window.DrawList->PathFillConvex(white); + window.DrawList->PathArcToFast(center, radius, 6, 9); + window.DrawList->PathLineTo(center); + window.DrawList->PathFillConvex(black); + window.DrawList->PathArcToFast(center, radius, 9, 12); + window.DrawList->PathLineTo(center); + window.DrawList->PathFillConvex(white); + window.DrawList->AddCircle(center, radius, black, 16); + }); +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + ImGui::SameLine(); +#endif // ENABLE_SHOW_TOOLPATHS_COG +#if ENABLE_LEGEND_TOOLBAR_ICONS + toggle_button(Preview::OptionType::Shells, _u8L("Shells"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + image_icon(window, pos, size, ImGui::LegendShells); +#else + toggle_button(Preview::OptionType::Shells, _u8L("Shells"), [](ImGuiWindow& window, const ImVec2& pos, float size) { + const ImU32 color = ImGuiWrapper::to_ImU32({ 1.0f, 1.0f, 1.0f, 1.0f }); + const float margin = 3.0f; + const float proj = 0.25f * size; + window.DrawList->AddRect({ pos.x + margin, pos.y + size - margin }, { pos.x + size - margin - proj, pos.y + margin + proj }, color); + window.DrawList->AddLine({ pos.x + margin, pos.y + margin + proj }, { pos.x + margin + proj, pos.y + margin }, color); + window.DrawList->AddLine({ pos.x + size - margin - proj, pos.y + margin + proj }, { pos.x + size - margin, pos.y + margin }, color); + window.DrawList->AddLine({ pos.x + size - margin - proj, pos.y + size - margin }, { pos.x + size - margin, pos.y + size - margin - proj }, color); + window.DrawList->AddLine({ pos.x + margin + proj, pos.y + margin }, { pos.x + size - margin, pos.y + margin }, color); + window.DrawList->AddLine({ pos.x + size - margin, pos.y + margin }, { pos.x + size - margin, pos.y + size - margin - proj }, color); +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + }); + ImGui::SameLine(); +#if ENABLE_LEGEND_TOOLBAR_ICONS + toggle_button(Preview::OptionType::ToolMarker, _u8L("Tool marker"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + image_icon(window, pos, size, ImGui::LegendToolMarker); +#else + toggle_button(Preview::OptionType::ToolMarker, _u8L("Tool marker"), [](ImGuiWindow& window, const ImVec2& pos, float size) { + const ImU32 color = ImGuiWrapper::to_ImU32({ 1.0f, 1.0f, 1.0f, 0.8f }); + const float margin = 3.0f; + const ImVec2 p1(0.5f * (pos.x + pos.x + size), pos.y + size - margin); + const ImVec2 p2(p1.x + 0.25f * size, p1.y - 0.25f * size); + const ImVec2 p3(p1.x - 0.25f * size, p1.y - 0.25f * size); + window.DrawList->AddTriangleFilled(p1, p2, p3, color); + const float mid_x = 0.5f * (pos.x + pos.x + size); + window.DrawList->AddRectFilled({ mid_x - 0.09375f * size, p1.y - 0.25f * size }, { mid_x + 0.09375f * size, pos.y + margin }, color); +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + }); + + bool size_dirty = !ImGui::GetCurrentWindow()->ScrollbarY && ImGui::CalcWindowNextAutoFitSize(ImGui::GetCurrentWindow()).x != ImGui::GetWindowWidth(); + if (m_legend_resizer.dirty || size_dirty != m_legend_resizer.dirty) { + wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); + wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); + } + m_legend_resizer.dirty = size_dirty; +#endif // ENABLE_PREVIEW_LAYOUT + + legend_height = ImGui::GetWindowHeight(); + + imgui.end(); + ImGui::PopStyleVar(); +} + +#if ENABLE_GCODE_VIEWER_STATISTICS +void GCodeViewer::render_statistics() +{ + static const float offset = 275.0f; + + ImGuiWrapper& imgui = *wxGetApp().imgui(); + + auto add_time = [&imgui](const std::string& label, int64_t time) { + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, label); + ImGui::SameLine(offset); + imgui.text(std::to_string(time) + " ms (" + get_time_dhms(static_cast(time) * 0.001f) + ")"); + }; + + auto add_memory = [&imgui](const std::string& label, int64_t memory) { + auto format_string = [memory](const std::string& units, float value) { + return std::to_string(memory) + " bytes (" + + Slic3r::float_to_string_decimal_point(float(memory) * value, 3) + + " " + units + ")"; + }; + + static const float kb = 1024.0f; + static const float inv_kb = 1.0f / kb; + static const float mb = 1024.0f * kb; + static const float inv_mb = 1.0f / mb; + static const float gb = 1024.0f * mb; + static const float inv_gb = 1.0f / gb; + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, label); + ImGui::SameLine(offset); + if (static_cast(memory) < mb) + imgui.text(format_string("KB", inv_kb)); + else if (static_cast(memory) < gb) + imgui.text(format_string("MB", inv_mb)); + else + imgui.text(format_string("GB", inv_gb)); + }; + + auto add_counter = [&imgui](const std::string& label, int64_t counter) { + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, label); + ImGui::SameLine(offset); + imgui.text(std::to_string(counter)); + }; + + imgui.set_next_window_pos(0.5f * wxGetApp().plater()->get_current_canvas3D()->get_canvas_size().get_width(), 0.0f, ImGuiCond_Once, 0.5f, 0.0f); + ImGui::SetNextWindowSizeConstraints({ 300.0f, 100.0f }, { 600.0f, 900.0f }); + imgui.begin(std::string("GCodeViewer Statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize); + ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow()); + + if (ImGui::CollapsingHeader("Time")) { + add_time(std::string("GCodeProcessor:"), m_statistics.results_time); + + ImGui::Separator(); + add_time(std::string("Load:"), m_statistics.load_time); + add_time(std::string(" Load vertices:"), m_statistics.load_vertices); + add_time(std::string(" Smooth vertices:"), m_statistics.smooth_vertices); + add_time(std::string(" Load indices:"), m_statistics.load_indices); + add_time(std::string("Refresh:"), m_statistics.refresh_time); + add_time(std::string("Refresh paths:"), m_statistics.refresh_paths_time); + } + + if (ImGui::CollapsingHeader("OpenGL calls")) { + add_counter(std::string("Multi GL_LINES:"), m_statistics.gl_multi_lines_calls_count); + add_counter(std::string("Multi GL_TRIANGLES:"), m_statistics.gl_multi_triangles_calls_count); + add_counter(std::string("GL_TRIANGLES:"), m_statistics.gl_triangles_calls_count); + ImGui::Separator(); + add_counter(std::string("Instanced models:"), m_statistics.gl_instanced_models_calls_count); + add_counter(std::string("Batched models:"), m_statistics.gl_batched_models_calls_count); + } + + if (ImGui::CollapsingHeader("CPU memory")) { + add_memory(std::string("GCodeProcessor results:"), m_statistics.results_size); + + ImGui::Separator(); + add_memory(std::string("Paths:"), m_statistics.paths_size); + add_memory(std::string("Render paths:"), m_statistics.render_paths_size); + add_memory(std::string("Models instances:"), m_statistics.models_instances_size); + } + + if (ImGui::CollapsingHeader("GPU memory")) { + add_memory(std::string("Vertices:"), m_statistics.total_vertices_gpu_size); + add_memory(std::string("Indices:"), m_statistics.total_indices_gpu_size); + add_memory(std::string("Instances:"), m_statistics.total_instances_gpu_size); + ImGui::Separator(); + add_memory(std::string("Max VBuffer:"), m_statistics.max_vbuffer_gpu_size); + add_memory(std::string("Max IBuffer:"), m_statistics.max_ibuffer_gpu_size); + } + + if (ImGui::CollapsingHeader("Other")) { + add_counter(std::string("Travel segments count:"), m_statistics.travel_segments_count); + add_counter(std::string("Wipe segments count:"), m_statistics.wipe_segments_count); + add_counter(std::string("Extrude segments count:"), m_statistics.extrude_segments_count); + add_counter(std::string("Instances count:"), m_statistics.instances_count); + add_counter(std::string("Batched count:"), m_statistics.batched_count); + ImGui::Separator(); + add_counter(std::string("VBuffers count:"), m_statistics.vbuffers_count); + add_counter(std::string("IBuffers count:"), m_statistics.ibuffers_count); + } + + imgui.end(); +} +#endif // ENABLE_GCODE_VIEWER_STATISTICS + +void GCodeViewer::log_memory_used(const std::string& label, int64_t additional) const +{ + if (Slic3r::get_logging_level() >= 5) { + int64_t paths_size = 0; + int64_t render_paths_size = 0; + for (const TBuffer& buffer : m_buffers) { + paths_size += SLIC3R_STDVEC_MEMSIZE(buffer.paths, Path); + render_paths_size += SLIC3R_STDUNORDEREDSET_MEMSIZE(buffer.render_paths, RenderPath); + for (const RenderPath& path : buffer.render_paths) { + render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.sizes, unsigned int); + render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.offsets, size_t); + } + } + int64_t layers_size = SLIC3R_STDVEC_MEMSIZE(m_layers.get_zs(), double); + layers_size += SLIC3R_STDVEC_MEMSIZE(m_layers.get_ranges(), Layers::Range); + BOOST_LOG_TRIVIAL(trace) << label + << "(" << format_memsize_MB(additional + paths_size + render_paths_size + layers_size) << ");" + << log_memory_info(); + } +} + +ColorRGBA GCodeViewer::option_color(EMoveType move_type) const +{ + switch (move_type) + { + case EMoveType::Tool_change: { return Options_Colors[static_cast(EOptionsColors::ToolChanges)]; } + case EMoveType::Color_change: { return Options_Colors[static_cast(EOptionsColors::ColorChanges)]; } + case EMoveType::Pause_Print: { return Options_Colors[static_cast(EOptionsColors::PausePrints)]; } + case EMoveType::Custom_GCode: { return Options_Colors[static_cast(EOptionsColors::CustomGCodes)]; } + case EMoveType::Retract: { return Options_Colors[static_cast(EOptionsColors::Retractions)]; } + case EMoveType::Unretract: { return Options_Colors[static_cast(EOptionsColors::Unretractions)]; } + case EMoveType::Seam: { return Options_Colors[static_cast(EOptionsColors::Seams)]; } + default: { return { 0.0f, 0.0f, 0.0f, 1.0f }; } + } +} + +} // namespace GUI +} // namespace Slic3r +