///|/ Copyright (c) Prusa Research 2020 - 2023 Oleksandra Iushchenko @YuSanka, Vojtěch Bubník @bubnikv, Tomáš Mészáros @tamasmeszaros, Lukáš Matěna @lukasmatena, Enrico Turri @enricoturri1966 ///|/ ///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher ///|/ #include "TickCodesManager.hpp" #include #include #include #include "I18N.hpp" #include "libslic3r/Print.hpp" #include "libslic3r/Color.hpp" #include "libslic3r/ExPolygon.hpp" #include "libslic3r/GCode/ToolOrdering.hpp" #include "libslic3r/Layer.hpp" #include "libslic3r/PrintConfig.hpp" #include "libslic3r/libslic3r.h" using namespace Slic3r; using namespace CustomGCode; namespace DoubleSlider { constexpr double min_delta_area = scale_(scale_(25)); // equal to 25 mm2 constexpr double miscalculation = scale_(scale_(1)); // equal to 1 mm2 static const int YES = 0x00000002; // an analogue of wxYES static const int NO = 0x00000008; // an analogue of wxNO static const int CANCEL = 0x00000010; // an analogue of wxCANCEL bool equivalent_areas(const double& bottom_area, const double& top_area) { return fabs(bottom_area - top_area) <= miscalculation; } TickCodeManager::TickCodeManager() { m_pause_print_msg = _u8L("Place bearings in slots and resume printing"); } std::string TickCodeManager::gcode(CustomGCode::Type type) { if (m_print) { const Slic3r::PrintConfig& config = m_print->config(); switch (type) { case CustomGCode::ColorChange: return config.color_change_gcode; case CustomGCode::PausePrint: return config.pause_print_gcode; case CustomGCode::Template: return config.template_custom_gcode; default: return std::string(); } } return std::string(); } int TickCodeManager::get_tick_from_value(double value, bool force_lower_bound/* = false*/) { if (!m_values) return -1; std::vector::const_iterator it; if (is_wipe_tower && !force_lower_bound) it = std::find_if(m_values->begin(), m_values->end(), [value](const double & val) { return fabs(value - val) <= epsilon(); }); else it = std::lower_bound(m_values->begin(), m_values->end(), value - epsilon()); if (it == m_values->end()) return -1; return int(it - m_values->begin()); } void TickCodeManager::set_ticks(const Info& custom_gcode_per_print_z) { ticks.clear(); const std::vector& heights = custom_gcode_per_print_z.gcodes; for (auto h : heights) { int tick = get_tick_from_value(h.print_z); if (tick >=0) ticks.emplace(TickCode{ tick, h.type, h.extruder, h.color, h.extra }); } if (custom_gcode_per_print_z.mode && !custom_gcode_per_print_z.gcodes.empty()) mode = custom_gcode_per_print_z.mode; } // Get active extruders for tick. // Means one current extruder for not existing tick OR // 2 extruders - for existing tick (extruder before ToolChange and extruder of current existing tick) // Use those values to disable selection of active extruders std::array TickCodeManager::get_active_extruders_for_tick(int tick, Mode main_mode) const { int default_initial_extruder = main_mode == MultiAsSingle ? std::max(1, only_extruder_id) : 1; std::array extruders = { default_initial_extruder, -1 }; if (empty()) return extruders; auto it = ticks.lower_bound(TickCode{tick}); if (it != ticks.end() && it->tick == tick) // current tick exists extruders[1] = it->extruder; while (it != ticks.begin()) { --it; if(it->type == ToolChange) { extruders[0] = it->extruder; break; } } return extruders; } bool check_color_change(const PrintObject* object, size_t frst_layer_id, size_t layers_cnt, bool check_overhangs, std::function break_condition) { double prev_area = area(object->get_layer(frst_layer_id)->lslices); bool detected = false; for (size_t i = frst_layer_id+1; i < layers_cnt; i++) { const Layer* layer = object->get_layer(i); double cur_area = area(layer->lslices); // check for overhangs if (check_overhangs && cur_area > prev_area && !equivalent_areas(prev_area, cur_area)) break; // Check percent of the area decrease. // This value have to be more than min_delta_area and more then 10% if ((prev_area - cur_area > min_delta_area) && (cur_area / prev_area < 0.9)) { detected = true; if (break_condition(layer)) break; } prev_area = cur_area; } return detected; } bool TickCodeManager::auto_color_change(Mode main_mode) { if (!m_print) return false; if (!empty()) { if (m_cb_show_warning_msg) { std::string msg_text = _u8L("This action will cause deletion of all ticks on vertical slider.") + "\n\n" + _u8L("This action is not revertible.\nDo you want to proceed?"); if (m_cb_show_warning_msg(msg_text, YES | NO) == NO) return false; } ticks.clear(); } int extruders_cnt = m_cb_get_extruders_cnt ? m_cb_get_extruders_cnt() : 0; for (auto object : m_print->objects()) { // An object should to have at least 2 layers to apply an auto color change if (object->layer_count() < 2) continue; check_color_change(object, 1, object->layers().size(), false, [this, extruders_cnt, main_mode](const Layer* layer) { int tick = get_tick_from_value(layer->print_z); if (tick >= 0 && !has_tick(tick)) { if (main_mode == SingleExtruder) { set_default_colors(true); add_tick(tick, ColorChange, 1, layer->print_z); } else { int extruder = 2; if (!empty()) { auto it = ticks.end(); it--; extruder = it->extruder + 1; if (extruder > extruders_cnt) extruder = 1; } add_tick(tick, ToolChange, extruder, layer->print_z); } } // allow max 3 auto color changes return ticks.size() > 2; }); } if (empty() && m_cb_notify_empty_color_change) m_cb_notify_empty_color_change(); return true; } std::string TickCodeManager::get_new_color(const std::string& color) { if (m_cb_get_new_color) return m_cb_get_new_color(color); return std::string(); } std::string TickCodeManager::get_custom_code(const std::string& code_in, double height) { if (m_cb_get_custom_code) return m_cb_get_custom_code(code_in, height); return std::string(); } std::string TickCodeManager::get_pause_print_msg(const std::string& msg_in, double height) { if (m_cb_get_pause_print_msg) return m_cb_get_pause_print_msg(msg_in, height); return std::string(); } bool TickCodeManager::edit_extruder_sequence(const int max_tick, Mode main_mode) { if (!check_ticks_changed_event(ToolChange, main_mode) || !m_cb_get_extruders_sequence) return false; // init extruder sequence in respect to the extruders count if (empty()) m_extruders_sequence.init(colors.size()); if(!m_cb_get_extruders_sequence(m_extruders_sequence)) return false; erase_all_ticks_with_code(ToolChange); const int extr_cnt = m_extruders_sequence.extruders.size(); if (extr_cnt == 1) return true; int tick = 0; double value = 0.0; int extruder = -1; std::random_device rd; //Will be used to obtain a seed for the random number engine std::mt19937 gen(rd()); //Standard mersenne_twister_engine seeded with rd() std::uniform_int_distribution<> distrib(0, extr_cnt-1); while (tick <= max_tick) { bool color_repetition = false; if (m_extruders_sequence.random_sequence) { int rand_extr = distrib(gen); if (m_extruders_sequence.color_repetition) color_repetition = rand_extr == extruder; else while (rand_extr == extruder) rand_extr = distrib(gen); extruder = rand_extr; } else { extruder++; if (extruder == extr_cnt) extruder = 0; } const int cur_extruder = m_extruders_sequence.extruders[extruder]; bool meaningless_tick = tick == 0.0 && cur_extruder == extruder; if (!meaningless_tick && !color_repetition) ticks.emplace(TickCode{tick, ToolChange,cur_extruder + 1, colors[cur_extruder]}); if (m_extruders_sequence.is_mm_intervals) { value += m_extruders_sequence.interval_by_mm; tick = get_tick_from_value(value, true); if (tick < 0) break; } else tick += m_extruders_sequence.interval_by_layers; } return true; } bool TickCodeManager::check_ticks_changed_event(Type type, Mode main_mode) { if ( mode == main_mode || (type != ColorChange && type != ToolChange) || (mode == SingleExtruder && main_mode == MultiAsSingle) || // All ColorChanges will be applied for 1st extruder (mode == MultiExtruder && main_mode == MultiAsSingle) ) // Just mark ColorChanges for all unused extruders return true; if ((mode == SingleExtruder && main_mode == MultiExtruder ) || (mode == MultiExtruder && main_mode == SingleExtruder) ) { if (!has_tick_with_code(ColorChange)) return true; if (m_cb_show_info_msg) { std::string message = (mode == SingleExtruder ? _u8L("The last color change data was saved for a single extruder printing.") : _u8L("The last color change data was saved for a multi extruder printing.") ) + "\n" + _u8L("Your current changes will delete all saved color changes.") + "\n\n\t" + _u8L("Are you sure you want to continue?"); if ( m_cb_show_info_msg(message, YES | NO) == YES) erase_all_ticks_with_code(ColorChange); } return false; } // m_ticks_mode == MultiAsSingle if( has_tick_with_code(ToolChange) ) { if (m_cb_show_info_msg) { std::string message = main_mode == SingleExtruder ? ( _u8L("The last color change data was saved for a multi extruder printing.") + "\n\n" + _u8L("Select YES if you want to delete all saved tool changes, \n" "NO if you want all tool changes switch to color changes, \n" "or CANCEL to leave it unchanged.") + "\n\n\t" + _u8L("Do you want to delete all saved tool changes?") ): ( // MultiExtruder _u8L("The last color change data was saved for a multi extruder printing with tool changes for whole print.") + "\n\n" + _u8L("Your current changes will delete all saved extruder (tool) changes.") + "\n\n\t" + _u8L("Are you sure you want to continue?") ) ; const int answer = m_cb_show_info_msg(message, YES | NO | (main_mode == SingleExtruder ? CANCEL : 0)); if (answer == YES) { erase_all_ticks_with_code(ToolChange); } else if (main_mode == SingleExtruder && answer == NO) { switch_code(ToolChange, ColorChange); } } return false; } if (m_cb_check_gcode_and_notify) m_cb_check_gcode_and_notify(type); return true; } // Get used extruders for tick. // Means all extruders(tools) which will be used during printing from current tick to the end std::set TickCodeManager::get_used_extruders_for_tick(int tick, double print_z, Mode force_mode/* = Undef*/) const { if (!m_print) return {}; Mode e_mode = !force_mode ? mode : force_mode; if (e_mode == MultiExtruder) { const ToolOrdering& tool_ordering = m_print->get_tool_ordering(); if (tool_ordering.empty()) return {}; std::set used_extruders; auto it_layer_tools = std::lower_bound(tool_ordering.begin(), tool_ordering.end(), print_z, [](const LayerTools& lhs, double rhs) { return lhs.print_z < rhs; }); for (; it_layer_tools != tool_ordering.end(); ++it_layer_tools) { const std::vector& extruders = it_layer_tools->extruders; for (const auto& extruder : extruders) used_extruders.emplace(extruder + 1); } return used_extruders; } const int default_initial_extruder = e_mode == MultiAsSingle ? std::max(only_extruder_id, 1) : 1; if (ticks.empty() || e_mode == SingleExtruder) return { default_initial_extruder }; std::set used_extruders; auto it_start = ticks.lower_bound(TickCode{ tick }); auto it = it_start; if (it == ticks.begin() && it->type == ToolChange && tick != it->tick) // In case of switch of ToolChange to ColorChange, when tick exists, // we shouldn't change color for extruder, which will be deleted { used_extruders.emplace(it->extruder); if (tick < it->tick) used_extruders.emplace(default_initial_extruder); } while (it != ticks.begin()) { --it; if (it->type == ToolChange && tick != it->tick) { used_extruders.emplace(it->extruder); break; } } if (it == ticks.begin() && used_extruders.empty()) used_extruders.emplace(default_initial_extruder); for (it = it_start; it != ticks.end(); ++it) if (it->type == ToolChange && tick != it->tick) used_extruders.emplace(it->extruder); return used_extruders; } std::string TickCodeManager::get_color_for_tick(TickCode tick, Type type, const int extruder) { auto opposite_one_color = [](const std::string& color) { ColorRGB rgb; decode_color(color, rgb); return encode_color(opposite(rgb)); }; auto opposite_two_colors = [](const std::string& a, const std::string& b) { ColorRGB rgb1; decode_color(a, rgb1); ColorRGB rgb2; decode_color(b, rgb2); return encode_color(opposite(rgb1, rgb2)); }; if (mode == SingleExtruder && type == ColorChange && m_use_default_colors) { if (ticks.empty()) return opposite_one_color(colors[0]); auto before_tick_it = std::lower_bound(ticks.begin(), ticks.end(), tick); if (before_tick_it == ticks.end()) { while (before_tick_it != ticks.begin()) if (--before_tick_it; before_tick_it->type == ColorChange) break; if (before_tick_it->type == ColorChange) return opposite_one_color(before_tick_it->color); return opposite_one_color(colors[0]); } if (before_tick_it == ticks.begin()) { const std::string& frst_color = colors[0]; if (before_tick_it->type == ColorChange) return opposite_two_colors(frst_color, before_tick_it->color); auto next_tick_it = before_tick_it; while (next_tick_it != ticks.end()) if (++next_tick_it; next_tick_it != ticks.end() && next_tick_it->type == ColorChange) break; if (next_tick_it != ticks.end() && next_tick_it->type == ColorChange) return opposite_two_colors(frst_color, next_tick_it->color); return opposite_one_color(frst_color); } std::string frst_color = ""; if (before_tick_it->type == ColorChange) frst_color = before_tick_it->color; else { auto next_tick_it = before_tick_it; while (next_tick_it != ticks.end()) if (++next_tick_it; next_tick_it != ticks.end() && next_tick_it->type == ColorChange) { frst_color = next_tick_it->color; break; } } while (before_tick_it != ticks.begin()) if (--before_tick_it; before_tick_it->type == ColorChange) break; if (before_tick_it->type == ColorChange) { if (frst_color.empty()) return opposite_one_color(before_tick_it->color); return opposite_two_colors(before_tick_it->color, frst_color); } if (frst_color.empty()) return opposite_one_color(colors[0]); return opposite_two_colors(colors[0], frst_color); } std::string color = colors[extruder - 1]; if (type == ColorChange) { if (!ticks.empty()) { auto before_tick_it = std::lower_bound(ticks.begin(), ticks.end(), tick ); while (before_tick_it != ticks.begin()) { --before_tick_it; if (before_tick_it->type == ColorChange && before_tick_it->extruder == extruder) { color = before_tick_it->color; break; } } } color = get_new_color(color); } return color; } bool TickCodeManager::add_tick(const int tick, Type type, const int extruder, double print_z) { std::string color; std::string extra; if (type == Custom) // custom Gcode { extra = get_custom_code(m_custom_gcode, print_z); if (extra.empty()) return false; m_custom_gcode = extra; } else if (type == PausePrint) { extra = get_pause_print_msg(m_pause_print_msg, print_z); if (extra.empty()) return false; m_pause_print_msg = extra; } else { color = get_color_for_tick(TickCode{ tick }, type, extruder); if (color.empty()) return false; } ticks.emplace(TickCode{ tick, type, extruder, color, extra }); return true; } bool TickCodeManager::edit_tick(std::set::iterator it, double print_z) { // Save previously value of the tick before the call a Dialog from get_... functions, // otherwise a background process can change ticks values and current iterator wouldn't be valid for the moment of a Dialog close // and PS will crash (see https://github.com/prusa3d/PrusaSlicer/issues/10941) TickCode changed_tick = *it; std::string edited_value; if (it->type == ColorChange) edited_value = get_new_color(it->color); else if (it->type == PausePrint) edited_value = get_pause_print_msg(it->extra, print_z); else edited_value = get_custom_code(it->type == Template ? gcode(Template) : it->extra, print_z); if (edited_value.empty()) return false; // Update iterator. For this moment its value can be invalid if (it = ticks.find(changed_tick); it == ticks.end()) return false; if (it->type == ColorChange) { if (it->color == edited_value) return false; changed_tick.color = edited_value; } else if (it->type == Template) { if (gcode(Template) == edited_value) return false; changed_tick.extra = edited_value; changed_tick.type = Custom; } else if (it->type == Custom || it->type == PausePrint) { if (it->extra == edited_value) return false; changed_tick.extra = edited_value; if (it->type == Template) changed_tick.type = Custom; } ticks.erase(it); ticks.emplace(changed_tick); return true; } void TickCodeManager::switch_code(Type type_from, Type type_to) { for (auto it{ ticks.begin() }, end{ ticks.end() }; it != end; ) if (it->type == type_from) { TickCode tick = *it; tick.type = type_to; tick.extruder = 1; ticks.erase(it); it = ticks.emplace(tick).first; } else ++it; } bool TickCodeManager::switch_code_for_tick(std::set::iterator it, Type type_to, const int extruder) { const std::string color = get_color_for_tick(*it, type_to, extruder); if (color.empty()) return false; TickCode changed_tick = *it; changed_tick.type = type_to; changed_tick.extruder = extruder; changed_tick.color = color; ticks.erase(it); ticks.emplace(changed_tick); return true; } void TickCodeManager::erase_all_ticks_with_code(Type type) { for (auto it{ ticks.begin() }, end{ ticks.end() }; it != end; ) { if (it->type == type) it = ticks.erase(it); else ++it; } } bool TickCodeManager::has_tick_with_code(Type type) { for (const TickCode& tick : ticks) if (tick.type == type) return true; return false; } bool TickCodeManager::has_tick(int tick) { return ticks.find(TickCode{ tick }) != ticks.end(); } ConflictType TickCodeManager::is_conflict_tick(const TickCode& tick, Mode main_mode, double print_z) { if ((tick.type == ColorChange && ( (mode == SingleExtruder && main_mode == MultiExtruder ) || (mode == MultiExtruder && main_mode == SingleExtruder) )) || (tick.type == ToolChange && (mode == MultiAsSingle && main_mode != MultiAsSingle)) ) return ctModeConflict; // check ColorChange tick if (tick.type == ColorChange) { // We should mark a tick as a "MeaninglessColorChange", // if it has a ColorChange for unused extruder from current print to end of the print std::set used_extruders_for_tick = get_used_extruders_for_tick(tick.tick, print_z, main_mode); if (used_extruders_for_tick.find(tick.extruder) == used_extruders_for_tick.end()) return ctMeaninglessColorChange; // We should mark a tick as a "Redundant", // if it has a ColorChange for extruder that has not been used before if (mode == MultiAsSingle && tick.extruder != std::max(only_extruder_id, 1) ) { auto it = ticks.lower_bound( tick ); if (it == ticks.begin() && it->type == ToolChange && tick.extruder == it->extruder) return ctNone; while (it != ticks.begin()) { --it; if (it->type == ToolChange && tick.extruder == it->extruder) return ctNone; } return ctRedundant; } } // check ToolChange tick if (mode == MultiAsSingle && tick.type == ToolChange) { // We should mark a tick as a "MeaninglessToolChange", // if it has a ToolChange to the same extruder auto it = ticks.find(tick); if (it->extruder > colors.size()) return ctNotPossibleToolChange; if (it == ticks.begin()) return tick.extruder == std::max(only_extruder_id, 1) ? ctMeaninglessToolChange : ctNone; while (it != ticks.begin()) { --it; if (it->type == ToolChange) return tick.extruder == it->extruder ? ctMeaninglessToolChange : ctNone; } } return ctNone; } std::string TickCodeManager::get_color_for_tool_change_tick(std::set::const_iterator it) const { const int current_extruder = it->extruder == 0 ? std::max(only_extruder_id, 1) : it->extruder; if (current_extruder > colors.size()) return it->color; auto it_n = it; while (it_n != ticks.begin()) { --it_n; if (it_n->type == ColorChange && it_n->extruder == current_extruder) return it_n->color; } return colors[current_extruder-1]; // return a color for a specific extruder from the colors list } std::string TickCodeManager::get_color_for_color_change_tick(std::set::const_iterator it) const { const int def_extruder = std::max(1, only_extruder_id); auto it_n = it; bool is_tool_change = false; while (it_n != ticks.begin()) { --it_n; if (it_n->type == ToolChange) { is_tool_change = true; if (it_n->extruder == it->extruder) return it->color; break; } if (it_n->type == ColorChange && it_n->extruder == it->extruder) return it->color; } if (!is_tool_change && it->extruder == def_extruder) return it->color; return ""; } } // DoubleSlider