diff --git a/resources/localization/list.txt b/resources/localization/list.txt index 9d8429f41c..77b7fe8c83 100644 --- a/resources/localization/list.txt +++ b/resources/localization/list.txt @@ -30,6 +30,7 @@ src/slic3r/GUI/ConfigManipulation.cpp src/slic3r/GUI/ConfigSnapshotDialog.cpp src/slic3r/GUI/ConfigWizard.cpp src/slic3r/GUI/DesktopIntegrationDialog.cpp +src/slic3r/GUI/TickCodesManager.cpp src/slic3r/GUI/DoubleSliderForLayers.cpp src/slic3r/GUI/Downloader.cpp src/slic3r/GUI/DownloaderFileGet.cpp diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index f877148a03..65ab160b91 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -254,6 +254,8 @@ set(SLIC3R_GUI_SOURCES GUI/Mouse3DController.hpp GUI/ImGuiDoubleSlider.cpp GUI/ImGuiDoubleSlider.hpp + GUI/TickCodesManager.cpp + GUI/TickCodesManager.hpp GUI/DoubleSliderForLayers.cpp GUI/DoubleSliderForLayers.hpp GUI/DoubleSliderForGcode.cpp diff --git a/src/slic3r/GUI/DoubleSliderForGcode.cpp b/src/slic3r/GUI/DoubleSliderForGcode.cpp index 65e8663b1d..5a4833a824 100644 --- a/src/slic3r/GUI/DoubleSliderForGcode.cpp +++ b/src/slic3r/GUI/DoubleSliderForGcode.cpp @@ -5,8 +5,6 @@ #include "DoubleSliderForGcode.hpp" -namespace Slic3r { - namespace DoubleSlider { static const float LEFT_MARGIN = 13.0f + 100.0f; // avoid thumbnail toolbar @@ -28,6 +26,4 @@ void DSForGcode::Render(const int canvas_width, const int canvas_height, float e } // DoubleSlider -} // Slic3r - diff --git a/src/slic3r/GUI/DoubleSliderForGcode.hpp b/src/slic3r/GUI/DoubleSliderForGcode.hpp index c830701637..c216fe3017 100644 --- a/src/slic3r/GUI/DoubleSliderForGcode.hpp +++ b/src/slic3r/GUI/DoubleSliderForGcode.hpp @@ -7,8 +7,6 @@ #include "ImGuiDoubleSlider.hpp" -namespace Slic3r { - namespace DoubleSlider { class DSForGcode : public Manager @@ -36,7 +34,5 @@ private: } // DoubleSlider; -} // Slic3r - #endif // slic3r_GUI_DoubleSliderForGcode_hpp_ diff --git a/src/slic3r/GUI/DoubleSliderForLayers.cpp b/src/slic3r/GUI/DoubleSliderForLayers.cpp index ded272e298..49fb7dae0f 100644 --- a/src/slic3r/GUI/DoubleSliderForLayers.cpp +++ b/src/slic3r/GUI/DoubleSliderForLayers.cpp @@ -4,63 +4,28 @@ ///|/ #include "DoubleSliderForLayers.hpp" -#include "libslic3r/libslic3r.h" -#include "libslic3r/GCode.hpp" -#include "GUI.hpp" -#include "GUI_App.hpp" -#include "Plater.hpp" +#include "libslic3r/Utils.hpp" // -> get_time_dhms() +#include "libslic3r/format.hpp" // -> format() #include "I18N.hpp" -#include "ExtruderSequenceDialog.hpp" -#include "libslic3r/Print.hpp" -#include "libslic3r/AppConfig.hpp" -#include "GUI_Utils.hpp" -#include "MsgDialog.hpp" - -#include -#include -#include #include -#include -#include -#include "format.hpp" -#include "NotificationManager.hpp" - -#include "ImGuiPureWrap.hpp" +#include "ImGuiWrapper.hpp" +#include "imgui/imgui_internal.h" #ifndef IMGUI_DEFINE_MATH_OPERATORS #define IMGUI_DEFINE_MATH_OPERATORS #endif #include -namespace Slic3r { - -using namespace GUI; +using namespace Slic3r; +using namespace CustomGCode; +using Slic3r::format; 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 float VERTICAL_SLIDER_WIDTH = 105.0f; -bool equivalent_areas(const double& bottom_area, const double& top_area) -{ - return fabs(bottom_area - top_area) <= miscalculation; -} - -static std::string gcode(Type type) -{ - const PrintConfig& config = GUI::wxGetApp().plater()->fff_print().config(); // ! ys_FIXME - switch (type) { - case ColorChange: return config.color_change_gcode; - case PausePrint: return config.pause_print_gcode; - case Template: return config.template_custom_gcode; - default: return ""; - } -} - DSForLayers::DSForLayers( int lowerValue, int higherValue, int minValue, @@ -72,26 +37,12 @@ DSForLayers::DSForLayers( int lowerValue, is_osx = true; #endif //__WXOSX__ Init(lowerValue, higherValue, minValue, maxValue, "layers_slider", false); + m_ctrl.ShowLabelOnMouseMove(true); - m_ctrl.set_get_label_on_move_cb([this](int pos) { return m_show_estimated_times ? into_u8(get_label(pos, ltEstimatedTime)) : ""; }); + m_ctrl.set_get_label_on_move_cb([this](int pos) { return m_show_estimated_times ? get_label(pos, ltEstimatedTime) : ""; }); m_ctrl.set_extra_draw_cb([this](const ImRect& draw_rc) {return draw_ticks(draw_rc); }); - m_ticks.set_pause_print_msg(_u8L("Place bearings in slots and resume printing")); - m_ticks.set_extruder_colors(&m_extruder_colors); -} - -int DSForLayers::get_tick_from_value(double value, bool force_lower_bound/* = false*/) -{ - std::vector::iterator it; - if (m_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()); + m_ticks.set_values(&m_values); } Info DSForLayers::GetTicksValues() const @@ -119,27 +70,18 @@ void DSForLayers::SetTicksValues(const Info& custom_gcode_per_print_z) return; } + if (m_cb_get_print) + m_ticks.set_print(m_cb_get_print()); + const bool was_empty = m_ticks.empty(); - m_ticks.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) - m_ticks.ticks.emplace(TickCode{ tick, h.type, h.extruder, h.color, h.extra }); - } + m_ticks.set_ticks(custom_gcode_per_print_z); if (!was_empty && m_ticks.empty()) // Switch to the "Feature type"/"Tool" from the very beginning of a new object slicing after deleting of the old one process_ticks_changed(); - // init extruder sequence in respect to the extruders count - if (m_ticks.empty()) - m_extruders_sequence.init(m_extruder_colors.size()); update_draw_scroll_line_cb(); - - if (custom_gcode_per_print_z.mode && !custom_gcode_per_print_z.gcodes.empty()) - m_ticks.mode = custom_gcode_per_print_z.mode; } void DSForLayers::SetLayersTimes(const std::vector& layers_times, float total_time) @@ -155,7 +97,7 @@ void DSForLayers::SetLayersTimes(const std::vector& layers_times, float t // Erase duplicates values from m_values and save it to the m_layers_values // They will be used for show the correct estimated time for MM print, when "No sparce layer" is enabled // See https://github.com/prusa3d/PrusaSlicer/issues/6232 - if (m_is_wipe_tower && m_values.size() != m_layers_times.size()) { + if (m_ticks.is_wipe_tower && m_values.size() != m_layers_times.size()) { m_layers_values = m_values; sort(m_layers_values.begin(), m_layers_values.end()); m_layers_values.erase(unique(m_layers_values.begin(), m_layers_values.end()), m_layers_values.end()); @@ -170,7 +112,7 @@ void DSForLayers::SetLayersTimes(const std::vector& layers_times, float t void DSForLayers::SetLayersTimes(const std::vector& layers_times) { - m_is_wipe_tower = false; + m_ticks.is_wipe_tower = false; m_layers_times = layers_times; for (size_t i = 1; i < m_layers_times.size(); i++) m_layers_times[i] += m_layers_times[i - 1]; @@ -192,20 +134,20 @@ void DSForLayers::SetModeAndOnlyExtruder(const bool is_one_extruder_printed_mode MultiAsSingle; if (!m_ticks.mode || (m_ticks.empty() && m_ticks.mode != m_mode)) m_ticks.mode = m_mode; - m_only_extruder = only_extruder; + + m_ticks.only_extruder_id = only_extruder; + m_ticks.is_wipe_tower = m_mode != SingleExtruder; if (m_mode != SingleExtruder) UseDefaultColors(false); - - m_is_wipe_tower = m_mode != SingleExtruder; } void DSForLayers::SetExtruderColors( const std::vector& extruder_colors) { - m_extruder_colors = extruder_colors; + m_ticks.colors = extruder_colors; } -bool DSForLayers::IsNewPrint(const std::string& idxs) +bool DSForLayers::is_new_print(const std::string& idxs) { if (idxs == "sla" || idxs == m_print_obj_idxs) return false; @@ -233,13 +175,11 @@ void DSForLayers::draw_ticks(const ImRect& slideable_region) if (m_ticks.empty()) return; - ImGuiContext& context = *GImGui; - const ImVec2 tick_border = ImVec2(23.0f, 2.0f) * m_scale; // distance form center begin end const ImVec2 tick_size = ImVec2(19.0f, 11.0f) * m_scale; const float tick_width = 1.0f * m_scale; - const float icon_side = wxGetApp().imgui()->GetTextureCustomRect(ImGui::PausePrint)->Height; + const float icon_side = m_imgui->GetTextureCustomRect(ImGui::PausePrint)->Height; const float icon_offset = 0.5f * icon_side;; const ImU32 tick_clr = ImGui::ColorConvertFloat4ToU32(ImGuiPureWrap::COL_ORANGE_DARK); @@ -286,7 +226,6 @@ void DSForLayers::draw_ticks(const ImRect& slideable_region) if (tick_it == active_tick_it && m_allow_editing) { // delete tick if (render_button(ImGui::RemoveTick, ImGui::RemoveTickHovered, btn_label, icon_pos, m_ctrl.IsActiveHigherThumb() ? fiHigherThumb : fiLowerThumb, tick_it->tick)) { - Type type = tick_it->type; m_ticks.ticks.erase(tick_it); process_ticks_changed(); break; @@ -295,10 +234,10 @@ void DSForLayers::draw_ticks(const ImRect& slideable_region) else if (m_draw_mode != dmRegular)// if we have non-regular draw mode, all ticks should be marked with error icon activate_this_tick = render_button(ImGui::ErrorTick, ImGui::ErrorTickHovered, btn_label, icon_pos, fiActionIcon, tick_it->tick); else if (tick_it->type == ColorChange || tick_it->type == ToolChange) { - if (m_ticks.is_conflict_tick(*tick_it, m_mode, m_only_extruder, m_values[tick_it->tick])) + if (m_ticks.is_conflict_tick(*tick_it, m_mode, m_values[tick_it->tick])) activate_this_tick = render_button(ImGui::ErrorTick, ImGui::ErrorTickHovered, btn_label, icon_pos, fiActionIcon, tick_it->tick); } - else if (tick_it->type == PausePrint) + else if (tick_it->type == CustomGCode::PausePrint) activate_this_tick = render_button(ImGui::PausePrint, ImGui::PausePrintHovered, btn_label, icon_pos, fiActionIcon, tick_it->tick); else activate_this_tick = render_button(ImGui::EditGCode, ImGui::EditGCodeHovered, btn_label, icon_pos, fiActionIcon, tick_it->tick); @@ -312,20 +251,22 @@ void DSForLayers::draw_ticks(const ImRect& slideable_region) } } -inline int hex_to_int(const char c) -{ - return (c >= '0' && c <= '9') ? int(c - '0') : (c >= 'A' && c <= 'F') ? int(c - 'A') + 10 : (c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1; -} - static std::array decode_color_to_float_array(const std::string color) { + auto hex_digit_to_int = [](const char c) { + return + (c >= '0' && c <= '9') ? int(c - '0') : + (c >= 'A' && c <= 'F') ? int(c - 'A') + 10 : + (c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1; + }; + // set alpha to 1.0f by default std::array ret = { 0, 0, 0, 1.0f }; const char* c = color.data() + 1; if (color.size() == 7 && color.front() == '#') { for (size_t j = 0; j < 3; ++j) { - int digit1 = hex_to_int(*c++); - int digit2 = hex_to_int(*c++); + int digit1 = hex_digit_to_int(*c++); + int digit2 = hex_digit_to_int(*c++); if (digit1 == -1 || digit2 == -1) break; ret[j] = float(digit1 * 16 + digit2) / 255.0f; } @@ -333,6 +274,13 @@ static std::array decode_color_to_float_array(const std::string color) return ret; } +std::string encode_color_from_float_array(const std::array& color) +{ + char buffer[64]; + ::sprintf(buffer, "#%02X%02X%02X", int(color[0] * 255.0f), int(color[1] * 255.0f), int(color[2] * 255.0f)); + return std::string(buffer); +} + void DSForLayers::draw_colored_band(const ImRect& groove, const ImRect& slideable_region) { if (m_ticks.empty() || m_draw_mode == dmSequentialFffPrint) @@ -357,8 +305,8 @@ void DSForLayers::draw_colored_band(const ImRect& groove, const ImRect& slideabl }; //draw main colored band - const int default_color_idx = m_mode == MultiAsSingle ? std::max(m_only_extruder - 1, 0) : 0; - std::arrayrgba = decode_color_to_float_array(m_extruder_colors[default_color_idx]); + const int default_color_idx = m_mode == MultiAsSingle ? std::max(m_ticks.only_extruder_id - 1, 0) : 0; + std::arrayrgba = decode_color_to_float_array(m_ticks.colors[default_color_idx]); ImU32 band_clr = IM_COL32(rgba[0] * 255.0f, rgba[1] * 255.0f, rgba[2] * 255.0f, rgba[3] * 255.0f); draw_main_band(band_clr); @@ -378,8 +326,8 @@ void DSForLayers::draw_colored_band(const ImRect& groove, const ImRect& slideabl { const std::string clr_str = m_mode == SingleExtruder ? tick_it->color : tick_it->type == ToolChange ? - get_color_for_tool_change_tick(tick_it) : - get_color_for_color_change_tick(tick_it); + m_ticks.get_color_for_tool_change_tick(tick_it) : + m_ticks.get_color_for_color_change_tick(tick_it); if (!clr_str.empty()) { std::arrayrgba = decode_color_to_float_array(clr_str); @@ -400,67 +348,176 @@ void DSForLayers::render_menu() ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(10.0f, 10.0f) * m_scale); ImGui::PushStyleVar(ImGuiStyleVar_PopupRounding, 4.0f * m_scale); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, { 1, ImGui::GetStyle().ItemSpacing.y }); + ImGui::PushStyleVar(ImGuiStyleVar_::ImGuiStyleVar_ChildRounding, 4.0f * m_scale); ImGui::PushStyleColor(ImGuiCol_::ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); - std::vector colors = wxGetApp().plater()->get_extruder_colors_from_plater_config(); - int extruder_num = colors.size(); + if (m_ctrl.IsRClickOnThumb()) + ImGui::OpenPopup("slider_full_menu_popup"); + else if (m_show_just_color_change_menu) + ImGui::OpenPopup("slider_add_tick_menu_popup"); + else if (m_show_cog_menu) + ImGui::OpenPopup("cog_menu_popup"); - if (m_ctrl.IsRClickOnThumb()) { - ImGui::OpenPopup("slider_menu_popup"); - } - - ImGui::PushStyleVar(ImGuiStyleVar_::ImGuiStyleVar_ChildRounding, 4.0f * m_scale); - if (ImGui::BeginPopup("slider_menu_popup")) { - if ((!m_ctrl.IsActiveHigherThumb() && GetLowerValue() == 0.0) || - (m_ctrl.IsActiveHigherThumb() && GetHigherValue() == 0.0)) - { - menu_item_with_icon(_u8L("Add Pause").c_str(), "", ImVec2(0, 0), 0, false, false); - } - else - { - if (menu_item_with_icon(_u8L("Add Color Change").c_str(), "")) { - add_code_as_tick(ColorChange); - } - if (menu_item_with_icon(_u8L("Add Pause").c_str(), "")) { - add_code_as_tick(PausePrint); - } - if (menu_item_with_icon(_u8L("Add Custom G-code").c_str(), "")) { - add_code_as_tick(Custom); - } - if (!gcode(Template).empty()) { - if (menu_item_with_icon(_u8L("Add Custom Template").c_str(), "")) { - add_code_as_tick(Template); - } - } - } - - //BBS render this menu item only when extruder_num > 1 - if (extruder_num > 1) { - if (!m_can_change_color || m_draw_mode == dmSequentialFffPrint) { - begin_menu(_u8L("Change Filament").c_str(), false); - } - else if (begin_menu(_u8L("Change Filament").c_str())) { - for (int i = 0; i < extruder_num; i++) { - std::array rgba = decode_color_to_float_array(colors[i]); - ImU32 icon_clr = IM_COL32(rgba[0] * 255.0f, rgba[1] * 255.0f, rgba[2] * 255.0f, rgba[3] * 255.0f); - if (menu_item_with_icon((_u8L("Filament ") + std::to_string(i + 1)).c_str(), "", ImVec2(14, 14) * m_scale, icon_clr)) add_code_as_tick(ToolChange, i + 1); - } - end_menu(); - } - } - ImGui::EndPopup(); - } - ImGui::PopStyleVar(1); + if (m_allow_editing) + render_add_tick_menu(); + render_cog_menu(); ImGui::PopStyleColor(1); - ImGui::PopStyleVar(3); + ImGui::PopStyleVar(4); + + ImGuiContext& context = *GImGui; + if (context.IO.MouseReleased[0]) { + m_show_just_color_change_menu = false; + m_show_cog_menu = false; + } +} + +void DSForLayers::render_add_tick_menu() +{ + if (ImGui::BeginPopup("slider_full_menu_popup")) { + if (m_mode == SingleExtruder) { + if (ImGuiPureWrap::menu_item_with_icon(_u8L("Add Color Change").c_str(), "")) { + add_code_as_tick(ColorChange); + } + } + else + render_multi_extruders_menu(); + + if (ImGuiPureWrap::menu_item_with_icon(_u8L("Add Pause").c_str(), "")) { + add_code_as_tick(CustomGCode::PausePrint); + } + if (ImGuiPureWrap::menu_item_with_icon(_u8L("Add Custom G-code").c_str(), "")) { + add_code_as_tick(Custom); + } + if (!gcode(Template).empty() && + ImGuiPureWrap::menu_item_with_icon(_u8L("Add Custom Template").c_str(), "")) { + add_code_as_tick(Template); + } + + ImGui::EndPopup(); + return; + } + + const std::string longest_menu_name = format(_u8L("Add color change (%1%) for:"), gcode(ColorChange)); + + const ImVec2 label_size = ImGui::CalcTextSize(longest_menu_name.c_str(), NULL, true); + const ImRect active_thumb_rect = m_ctrl.GetActiveThumbRect(); + const ImVec2 pos = active_thumb_rect.GetCenter(); + + ImGui::SetNextWindowPos(ImVec2(pos.x - label_size.x - active_thumb_rect.GetWidth(), pos.y)); + + if (ImGui::BeginPopup("slider_add_tick_menu_popup")) { + render_multi_extruders_menu(); + ImGui::EndPopup(); + } +} + +void DSForLayers::render_multi_extruders_menu() +{ + std::vector colors; + if (m_cb_get_extruder_colors) + colors = m_cb_get_extruder_colors(); + + int extruders_cnt = colors.size(); + + if (extruders_cnt > 1) { + const int tick = m_ctrl.GetActivePos(); + + if (m_mode == MultiAsSingle) { + const std::string menu_name = _u8L("Change extruder"); + if (ImGuiPureWrap::begin_menu(menu_name.c_str())) { + std::array active_extruders = m_ticks.get_active_extruders_for_tick(tick, m_mode); + for (int i = 1; i <= extruders_cnt; i++) { + const bool is_active_extruder = i == active_extruders[0] || i == active_extruders[1]; + std::string item_name = format(_u8L("Extruder %d"), i); + if (is_active_extruder) + item_name += " (" + _u8L("active") + ")"; + + std::array rgba = decode_color_to_float_array(colors[i - 1]); + ImU32 icon_clr = IM_COL32(rgba[0] * 255.0f, rgba[1] * 255.0f, rgba[2] * 255.0f, rgba[3] * 255.0f); + if (ImGuiPureWrap::menu_item_with_icon(item_name.c_str(), "", ImVec2(14, 14) * m_scale, icon_clr, false, !is_active_extruder)) + add_code_as_tick(ToolChange, i); + } + ImGuiPureWrap::end_menu(); + } + } + + const std::string menu_name = format(_u8L("Add color change (%1%) for:"), gcode(ColorChange)); + if (ImGuiPureWrap::begin_menu(menu_name.c_str())) { + std::set used_extruders_for_tick = m_ticks.get_used_extruders_for_tick(tick, m_values[tick]); + + for (int i = 1; i <= extruders_cnt; i++) { + const bool is_used_extruder = used_extruders_for_tick.empty() ? true : // #ys_FIXME till used_extruders_for_tick doesn't filled correct for mmMultiExtruder + used_extruders_for_tick.find(i) != used_extruders_for_tick.end(); + std::string item_name = format(_u8L("Extruder %d"), i); + if (is_used_extruder) + item_name += " (" + _u8L("used") + ")"; + + if (ImGuiPureWrap::menu_item_with_icon(item_name.c_str(), "")) + add_code_as_tick(ColorChange, i); + } + ImGuiPureWrap::end_menu(); + } + } +} + +void DSForLayers::render_color_picker() +{ + ImGuiContext& context = *GImGui; + const std::string title = _u8L("Select color for color change"); + if (m_show_color_picker) { + + ImGuiPureWrap::set_next_window_pos(1200, 200, ImGuiCond_Always, 0.5f, 0.0f); + ImGuiPureWrap::begin(title, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoScrollbar + | ImGuiWindowFlags_NoScrollWithMouse); + + ImGuiColorEditFlags misc_flags = ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoDragDrop; + + auto col = decode_color_to_float_array(m_selectable_color); + if (ImGui::ColorPicker4("color_picker", (float*)&col, misc_flags)) { + m_selectable_color = encode_color_from_float_array(col); + m_show_color_picker = false; + } + ImGuiPureWrap::end(); + } + + if (auto clr_pcr_win = ImGui::FindWindowByName(title.c_str()); clr_pcr_win && context.CurrentWindow != clr_pcr_win) + m_show_color_picker = false; +} + +void DSForLayers::render_cog_menu() +{ + const ImVec2 icon_sz = ImVec2(14, 14); + if (ImGui::BeginPopup("cog_menu_popup")) { + if (ImGuiPureWrap::menu_item_with_icon(_u8L("Jump to height").c_str(), "Shift+G")) { + jump_to_value(); + } + if (ImGuiPureWrap::menu_item_with_icon(_u8L("Show estimated print time on mouse moving").c_str(), "", icon_sz, 0, m_show_estimated_times)) { + m_show_estimated_times = !m_show_estimated_times; + } + if (m_mode == MultiAsSingle && m_draw_mode == dmRegular && + ImGuiPureWrap::menu_item_with_icon(_u8L("Set extruder sequence for the entire print").c_str(), "")) { + if (m_ticks.edit_extruder_sequence(m_ctrl.GetMaxPos(), m_mode)) + process_ticks_changed(); + } + if (m_allow_editing) { + if (ImGuiPureWrap::menu_item_with_icon(_u8L("Use default colors").c_str(), "", icon_sz, 0, m_ticks.used_default_colors())) { + UseDefaultColors(!m_ticks.used_default_colors()); + } + + if (m_mode != MultiExtruder && m_draw_mode == dmRegular && + ImGuiPureWrap::menu_item_with_icon(_u8L("Set auto color changes").c_str(), "")) { + auto_color_change(); + } + } + + ImGui::EndPopup(); + } } bool DSForLayers::render_button(const wchar_t btn_icon, const wchar_t btn_icon_hovered, const std::string& label_id, const ImVec2& pos, FocusedItem focus, int tick /*= -1*/) { - float scale = (float)wxGetApp().em_unit() / 10.0f; - ImGui::PushStyleVar(ImGuiStyleVar_::ImGuiStyleVar_WindowBorderSize, 0); ImGui::PushStyleVar(ImGuiStyleVar_::ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f)); ImGui::PushStyleVar(ImGuiStyleVar_::ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); @@ -475,7 +532,6 @@ bool DSForLayers::render_button(const wchar_t btn_icon, const wchar_t btn_icon_h | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse; - auto m_imgui = wxGetApp().imgui(); ImGuiPureWrap::set_next_window_pos(pos.x, pos.y, ImGuiCond_Always); std::string win_name = label_id + "##btn_win"; ImGuiPureWrap::begin(win_name, windows_flag); @@ -501,24 +557,23 @@ void DSForLayers::Render(const int canvas_width, const int canvas_height, float return; m_scale = extra_scale * 0.1f * m_em; - const float action_btn_sz = wxGetApp().imgui()->GetTextureCustomRect(ImGui::DSRevert)->Height; - const float tick_icon_side = wxGetApp().imgui()->GetTextureCustomRect(ImGui::PausePrint)->Height; + const float action_btn_sz = m_imgui->GetTextureCustomRect(ImGui::DSRevert)->Height; + const float tick_icon_side = m_imgui->GetTextureCustomRect(ImGui::PausePrint)->Height; ImVec2 pos; - ImVec2 size; pos.x = canvas_width - VERTICAL_SLIDER_WIDTH * m_scale - tick_icon_side; pos.y = 1.f * action_btn_sz; if (m_allow_editing) pos.y += 2.f; - size = ImVec2(VERTICAL_SLIDER_WIDTH * m_scale, canvas_height - 4.f * action_btn_sz); - m_ctrl.ShowLabelOnMouseMove(true); + + ImVec2 size = ImVec2(VERTICAL_SLIDER_WIDTH * m_scale, canvas_height - 4.f * action_btn_sz); m_ctrl.Init(pos, size, m_scale); if (m_ctrl.render()) { // request one more frame if value was changes with mouse wheel if (GImGui->IO.MouseWheel != 0.0f) - wxGetApp().imgui()->set_requires_extra_frame(); + m_imgui->set_requires_extra_frame(); process_thumb_move(); } @@ -538,21 +593,25 @@ void DSForLayers::Render(const int canvas_width, const int canvas_height, float ChangeOneLayerLock(); btn_pos.y += 1.2f * action_btn_sz; - if (render_button(ImGui::DSSettings, ImGui::DSSettingsHovered, "settings", btn_pos, fiCogIcon)) - show_cog_icon_context_menu(); + if (render_button(ImGui::DSSettings, ImGui::DSSettingsHovered, "settings", btn_pos, fiCogIcon)) { + m_show_cog_menu = true; + } if (m_draw_mode == dmSequentialFffPrint && m_ctrl.IsRClickOnThumb()) { std::string tooltip = _u8L("The sequential print is on.\n" "It's impossible to apply any custom G-code for objects printing sequentually."); ImGuiPureWrap::tooltip(tooltip, ImGui::GetFontSize() * 20.0f); } - else if (m_allow_editing) + else render_menu(); + + if (m_allow_editing) + render_color_picker(); } bool DSForLayers::is_wipe_tower_layer(int tick) const { - if (!m_is_wipe_tower || tick >= (int)m_values.size()) + if (!m_ticks.is_wipe_tower || tick >= (int)m_values.size()) return false; if (tick == 0 || (tick == (int)m_values.size() - 1 && m_values[tick] > m_values[tick - 1])) return false; @@ -580,36 +639,36 @@ static std::string short_and_splitted_time(const std::string& time) ::sscanf(time.c_str(), "%ds", &seconds); // Format the dhm time. - auto get_d = [days]() { return GUI::format(_u8L("%1%d"), days); }; - auto get_h = [hours]() { return GUI::format(_u8L("%1%h"), hours); }; - auto get_m = [minutes](){ return GUI::format(_u8L("%1%m"), minutes); }; - auto get_s = [seconds](){ return GUI::format(_u8L("%1%s"), seconds); }; + auto get_d = [days]() { return format(_u8L("%1%d"), days); }; + auto get_h = [hours]() { return format(_u8L("%1%h"), hours); }; + auto get_m = [minutes](){ return format(_u8L("%1%m"), minutes); }; + auto get_s = [seconds](){ return format(_u8L("%1%s"), seconds); }; if (days > 0) - return GUI::format("%1%%2%\n%3%", get_d(), get_h(), get_m()); + return format("%1%%2%\n%3%", get_d(), get_h(), get_m()); if (hours > 0) { if (hours < 10 && minutes < 10 && seconds < 10) - return GUI::format("%1%%2%%3%", get_h(), get_m(), get_s()); + return format("%1%%2%%3%", get_h(), get_m(), get_s()); if (hours > 10 && minutes > 10 && seconds > 10) - return GUI::format("%1%\n%2%\n%3%", get_h(), get_m(), get_s()); + return format("%1%\n%2%\n%3%", get_h(), get_m(), get_s()); if ((minutes < 10 && seconds > 10) || (minutes > 10 && seconds < 10)) - return GUI::format("%1%\n%2%%3%", get_h(), get_m(), get_s()); - return GUI::format("%1%%2%\n%3%", get_h(), get_m(), get_s()); + return format("%1%\n%2%%3%", get_h(), get_m(), get_s()); + return format("%1%%2%\n%3%", get_h(), get_m(), get_s()); } if (minutes > 0) { if (minutes > 10 && seconds > 10) - return GUI::format("%1%\n%2%", get_m(), get_s()); - return GUI::format("%1%%2%", get_m(), get_s()); + return format("%1%\n%2%", get_m(), get_s()); + return format("%1%%2%", get_m(), get_s()); } return get_s(); } -std::string DSForLayers::get_label(int pos, LabelType label_type/* = ltHeightWithLayer*/) const +std::string DSForLayers::get_label(int pos, LabelType label_type) const { const size_t value = pos; if (m_values.empty()) - return GUI::format("%1%", pos); + return format("%1%", pos); if (value >= m_values.size()) return "ErrVal"; @@ -635,59 +694,23 @@ std::string DSForLayers::get_label(int pos, LabelType label_type/* = ltHeightWit }; if (label_type == ltEstimatedTime) { - if (m_is_wipe_tower) { + if (m_ticks.is_wipe_tower) { size_t layer_number = get_layer_number(value, label_type); return (layer_number == size_t(-1) || layer_number == m_layers_times.size()) ? "" : short_and_splitted_time(get_time_dhms(m_layers_times[layer_number])); } return value < m_layers_times.size() ? short_and_splitted_time(get_time_dhms(m_layers_times[value])) : ""; } - std::string str = GUI::format("%1$.2f", m_values[value]); + std::string str = format("%1$.2f", m_values[value]); if (label_type == ltHeight) return str; if (label_type == ltHeightWithLayer) { - size_t layer_number = m_is_wipe_tower ? get_layer_number(value, label_type) + 1 : (m_values.empty() ? value : value + 1); - return GUI::format("%1%\n(%2%)", str, layer_number); + size_t layer_number = m_ticks.is_wipe_tower ? get_layer_number(value, label_type) + 1 : (m_values.empty() ? value : value + 1); + return format("%1%\n(%2%)", str, layer_number); } return ""; } -std::string DSForLayers::get_color_for_tool_change_tick(std::set::const_iterator it) const -{ - const int current_extruder = it->extruder == 0 ? std::max(m_only_extruder, 1) : it->extruder; - - auto it_n = it; - while (it_n != m_ticks.ticks.begin()) { - --it_n; - if (it_n->type == ColorChange && it_n->extruder == current_extruder) - return it_n->color; - } - - return m_extruder_colors[current_extruder-1]; // return a color for a specific extruder from the colors list -} - -std::string DSForLayers::get_color_for_color_change_tick(std::set::const_iterator it) const -{ - const int def_extruder = std::max(1, m_only_extruder); - auto it_n = it; - bool is_tool_change = false; - while (it_n != m_ticks.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 ""; -} - void DSForLayers::ChangeOneLayerLock() { m_ctrl.CombineThumbs(!m_ctrl.IsCombineThumbs()); @@ -775,7 +798,7 @@ std::string DSForLayers::get_tooltip(int tick/*=-1*/) for (size_t i = 1; i < MAX_LINES; ++i) { gcode += lines[i] + '\n'; } - gcode += "[" + into_u8(_L("continue")) + "]\n"; + gcode += "[" + _u8L("continue") + "]\n"; } boost::replace_all(gcode, "\n", "\n" + space); return gcode; @@ -783,19 +806,19 @@ std::string DSForLayers::get_tooltip(int tick/*=-1*/) tooltip += tick_code_it->type == ColorChange ? (m_mode == SingleExtruder ? - GUI::format(_L("Color change (\"%1%\")"), gcode(ColorChange)) : - GUI::format(_L("Color change (\"%1%\") for Extruder %2%"), gcode(ColorChange), tick_code_it->extruder)) : - tick_code_it->type == PausePrint ? - GUI::format(_L("Pause print (\"%1%\")"), gcode(PausePrint)) : + format(_u8L("Color change (\"%1%\")"), gcode(ColorChange)) : + format(_u8L("Color change (\"%1%\") for Extruder %2%"), gcode(ColorChange), tick_code_it->extruder)) : + tick_code_it->type == CustomGCode::PausePrint ? + format(_u8L("Pause print (\"%1%\")"), gcode(CustomGCode::PausePrint)) : tick_code_it->type == Template ? - GUI::format(_L("Custom template (\"%1%\")"), gcode(Template)) : + format(_u8L("Custom template (\"%1%\")"), gcode(Template)) : tick_code_it->type == ToolChange ? - GUI::format(_L("Extruder (tool) is changed to Extruder \"%1%\""), tick_code_it->extruder) : + format(_u8L("Extruder (tool) is changed to Extruder \"%1%\""), tick_code_it->extruder) : format_gcode(tick_code_it->extra);// tick_code_it->type == Custom // If tick is marked as a conflict (exclamation icon), // we should to explain why - ConflictType conflict = m_ticks.is_conflict_tick(*tick_code_it, m_mode, m_only_extruder, m_values[tick]); + ConflictType conflict = m_ticks.is_conflict_tick(*tick_code_it, m_mode, m_values[tick]); if (conflict != ctNone) tooltip += "\n\n" + _u8L("Note") + "! "; if (conflict == ctModeConflict) @@ -822,325 +845,38 @@ std::string DSForLayers::get_tooltip(int tick/*=-1*/) return tooltip; } -void DSForLayers::append_change_extruder_menu_item(wxMenu* menu, bool switch_current_code/* = false*/) -{ - const int extruders_cnt = GUI::wxGetApp().extruders_edited_cnt(); - if (extruders_cnt > 1) { - std::array active_extruders = get_active_extruders_for_tick(m_ctrl.GetActivePos()); - - std::vector icons = get_extruder_color_icons(true); - - wxMenu* change_extruder_menu = new wxMenu(); - - for (int i = 1; i <= extruders_cnt; i++) { - const bool is_active_extruder = i == active_extruders[0] || i == active_extruders[1]; - const wxString item_name = wxString::Format(_L("Extruder %d"), i) + - (is_active_extruder ? " (" + _L("active") + ")" : ""); - - if (m_mode == MultiAsSingle) - append_menu_item(change_extruder_menu, wxID_ANY, item_name, "", - [this, i](wxCommandEvent&) { add_code_as_tick(ToolChange, i); }, icons[i-1], menu, - [is_active_extruder]() { return !is_active_extruder; }, GUI::wxGetApp().plater()); - } - - const wxString change_extruder_menu_name = m_mode == MultiAsSingle ? - (switch_current_code ? _L("Switch code to Change extruder") : _L("Change extruder") ) : - _L("Change extruder (N/A)"); - - append_submenu(menu, change_extruder_menu, wxID_ANY, change_extruder_menu_name, _L("Use another extruder"), - active_extruders[1] > 0 ? "edit_uni" : "change_extruder", - [this]() {return m_mode == MultiAsSingle /*&& !GUI::wxGetApp().obj_list()->has_paint_on_segmentation()*/; }, GUI::wxGetApp().plater()); // !ysFIXME has_paint_on_segmentation - } -} - -void DSForLayers::append_add_color_change_menu_item(wxMenu* menu, bool switch_current_code/* = false*/) -{ - const int extruders_cnt = GUI::wxGetApp().extruders_edited_cnt(); - if (extruders_cnt > 1) { - int tick = m_ctrl.GetActivePos(); - std::set used_extruders_for_tick = m_ticks.get_used_extruders_for_tick(tick, m_only_extruder, m_values[tick]); - - wxMenu* add_color_change_menu = new wxMenu(); - - for (int i = 1; i <= extruders_cnt; i++) { - const bool is_used_extruder = used_extruders_for_tick.empty() ? true : // #ys_FIXME till used_extruders_for_tick doesn't filled correct for mmMultiExtruder - used_extruders_for_tick.find(i) != used_extruders_for_tick.end(); - const wxString item_name = wxString::Format(_L("Extruder %d"), i) + - (is_used_extruder ? " (" + _L("used") + ")" : ""); - - append_menu_item(add_color_change_menu, wxID_ANY, item_name, "", - [this, i](wxCommandEvent&) { add_code_as_tick(ColorChange, i); }, "", menu, - []() { return true; }, GUI::wxGetApp().plater()); - } - - const wxString menu_name = switch_current_code ? - format_wxstr(_L("Switch code to Color change (%1%) for:"), gcode(ColorChange)) : - format_wxstr(_L("Add color change (%1%) for:"), gcode(ColorChange)); - wxMenuItem* add_color_change_menu_item = menu->AppendSubMenu(add_color_change_menu, menu_name, ""); - add_color_change_menu_item->SetBitmap(*get_bmp_bundle("colorchange_add_m")); - } -} - void DSForLayers::UseDefaultColors(bool def_colors_on) { m_ticks.set_default_colors(def_colors_on); } -// 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 DSForLayers::get_active_extruders_for_tick(int tick) const -{ - int default_initial_extruder = m_mode == MultiAsSingle ? std::max(1, m_only_extruder) : 1; - std::array extruders = { default_initial_extruder, -1 }; - if (m_ticks.empty()) - return extruders; - - auto it = m_ticks.ticks.lower_bound(TickCode{tick}); - - if (it != m_ticks.ticks.end() && it->tick == tick) // current tick exists - extruders[1] = it->extruder; - - while (it != m_ticks.ticks.begin()) { - --it; - if(it->type == ToolChange) { - extruders[0] = it->extruder; - break; - } - } - - return extruders; -} - -void DSForLayers::show_cog_icon_context_menu() -{ - wxMenu menu; - - append_menu_item(&menu, wxID_ANY, _L("Jump to height") + " (Shift+G)", "", - [this](wxCommandEvent&) { jump_to_value(); }, "", & menu); - - if (m_allow_editing) - append_menu_check_item(&menu, wxID_ANY, _L("Use default colors"), _L("Use default colors on color change"), - [this](wxCommandEvent&) { - UseDefaultColors(!m_ticks.used_default_colors()); }, &menu, - []() { return true; }, [this]() { return m_ticks.used_default_colors(); }, GUI::wxGetApp().plater()); - - append_menu_check_item(&menu, wxID_ANY, _L("Show estimated print time on mouse moving"), _L("Show estimated print time on the ruler"), - [this](wxCommandEvent&) { m_show_estimated_times = !m_show_estimated_times; }, &menu, - []() { return true; }, [this]() { return m_show_estimated_times; }, GUI::wxGetApp().plater()); - - if (m_mode == MultiAsSingle && m_draw_mode == dmRegular) - append_menu_item(&menu, wxID_ANY, _L("Set extruder sequence for the entire print"), "", - [this](wxCommandEvent&) { edit_extruder_sequence(); }, "", &menu); - - if (GUI::wxGetApp().is_editor() && m_mode != MultiExtruder && m_draw_mode == dmRegular) - append_menu_item(&menu, wxID_ANY, _L("Set auto color changes"), "", - [this](wxCommandEvent&) { auto_color_change(); }, "", &menu); - - GUI::wxGetApp().plater()->PopupMenu(&menu); -} - -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; -} - +// !ysFIXME draw with imgui void DSForLayers::auto_color_change() { - if (!m_ticks.empty()) { - wxString msg_text = _L("This action will cause deletion of all ticks on vertical slider.") + "\n\n" + - _L("This action is not revertible.\nDo you want to proceed?"); - GUI::WarningDialog dialog(nullptr, msg_text, _L("Warning"), wxYES | wxNO); - if (dialog.ShowModal() == wxID_NO) - return; - m_ticks.ticks.clear(); + if (m_ticks.auto_color_change(m_mode)) { + update_draw_scroll_line_cb(); + process_ticks_changed(); } - - int extruders_cnt = GUI::wxGetApp().extruders_edited_cnt(); -// int extruder = 2; - - const Print& print = GUI::wxGetApp().plater()->fff_print(); - for (auto object : 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](const Layer* layer) - { - int tick = get_tick_from_value(layer->print_z); - if (tick >= 0 && !m_ticks.has_tick(tick)) { - if (m_mode == SingleExtruder) { - m_ticks.set_default_colors(true); - m_ticks.add_tick(tick, ColorChange, 1, layer->print_z); - } - else { - int extruder = 2; - if (!m_ticks.empty()) { - auto it = m_ticks.ticks.end(); - it--; - extruder = it->extruder + 1; - if (extruder > extruders_cnt) - extruder = 1; - } - m_ticks.add_tick(tick, ToolChange, extruder, layer->print_z); - } - } - // allow max 3 auto color changes - return m_ticks.ticks.size() > 2; - }); - } - - if (m_ticks.empty()) - GUI::wxGetApp().plater()->get_notification_manager()->push_notification(GUI::NotificationType::EmptyAutoColorChange); - update_draw_scroll_line_cb(); - - process_ticks_changed(); } - -static std::string get_new_color(const std::string& color) -{ - wxColour clr(color); - if (!clr.IsOk()) - clr = wxColour(0, 0, 0); // Don't set alfa to transparence - - auto data = new wxColourData(); - data->SetChooseFull(1); - data->SetColour(clr); - - wxColourDialog dialog(GUI::wxGetApp().GetTopWindow(), data); - dialog.CenterOnParent(); - if (dialog.ShowModal() == wxID_OK) - return dialog.GetColourData().GetColour().GetAsString(wxC2S_HTML_SYNTAX).ToStdString(); - return ""; -} - -/* To avoid get an empty string from wxTextEntryDialog - * Let disable OK button, if TextCtrl is empty - * OR input value is our of range (min..max), when min a nd max are positive - * */ -static void upgrade_text_entry_dialog(wxTextEntryDialog* dlg, double min = -1.0, double max = -1.0) -{ - GUI::wxGetApp().UpdateDlgDarkUI(dlg); - - // detect TextCtrl and OK button - wxTextCtrl* textctrl {nullptr}; - wxWindowList& dlg_items = dlg->GetChildren(); - for (auto item : dlg_items) { - textctrl = dynamic_cast(item); - if (textctrl) - break; - } - - if (!textctrl) - return; - - textctrl->SetInsertionPointEnd(); - - wxButton* btn_OK = static_cast(dlg->FindWindowById(wxID_OK)); - btn_OK->Bind(wxEVT_UPDATE_UI, [textctrl, min, max](wxUpdateUIEvent& evt) - { - bool disable = textctrl->IsEmpty(); - if (!disable && min >= 0.0 && max >= 0.0) { - double value = -1.0; - if (!textctrl->GetValue().ToDouble(&value)) // input value couldn't be converted to double - disable = true; - else - disable = value < min - epsilon() || value > max + epsilon(); // is input value is out of valid range ? - } - - evt.Enable(!disable); - }, btn_OK->GetId()); -} - -static std::string get_custom_code(const std::string& code_in, double height) -{ - wxString msg_text = _L("Enter custom G-code used on current layer") + ":"; - wxString msg_header = format_wxstr(_L("Custom G-code on current layer (%1% mm)."), height); - - // get custom gcode - wxTextEntryDialog dlg(nullptr, msg_text, msg_header, code_in, - wxTextEntryDialogStyle | wxTE_MULTILINE); - upgrade_text_entry_dialog(&dlg); - - bool valid = true; - std::string value; - do { - if (dlg.ShowModal() != wxID_OK) - return ""; - - value = into_u8(dlg.GetValue()); - valid = true;// GUI::Tab::validate_custom_gcode("Custom G-code", value); // !ysFIXME validate_custom_gcode - } while (!valid); - return value; -} - -static std::string get_pause_print_msg(const std::string& msg_in, double height) -{ - wxString msg_text = _L("Enter short message shown on Printer display when a print is paused") + ":"; - wxString msg_header = format_wxstr(_L("Message for pause print on current layer (%1% mm)."), height); - - // get custom gcode - wxTextEntryDialog dlg(nullptr, msg_text, msg_header, from_u8(msg_in), - wxTextEntryDialogStyle); - upgrade_text_entry_dialog(&dlg); - - if (dlg.ShowModal() != wxID_OK || dlg.GetValue().IsEmpty()) - return ""; - - return into_u8(dlg.GetValue()); -} - -// draw with imgui static double get_value_to_jump(double active_value, double min_z, double max_z, DrawMode mode) { wxString msg_text = _L("Enter the height you want to jump to") + ":"; wxString msg_header = _L("Jump to height"); wxString msg_in = "";// GUI::double_to_string(active_value); - // get custom gcode - wxTextEntryDialog dlg(nullptr, msg_text, msg_header, msg_in, wxTextEntryDialogStyle); - upgrade_text_entry_dialog(&dlg, min_z, max_z); - - if (dlg.ShowModal() != wxID_OK || dlg.GetValue().IsEmpty()) - return -1.0; - - double value = -1.0; - return dlg.GetValue().ToDouble(&value) ? value : -1.0; + return -1.0; } void DSForLayers::add_code_as_tick(Type type, int selected_extruder/* = -1*/) { const int tick = m_ctrl.GetActivePos(); - if ( !check_ticks_changed_event(type) ) + if (!m_ticks.check_ticks_changed_event(type, m_mode)) { + process_ticks_changed(); return; + } - if (type == ColorChange && gcode(ColorChange).empty()) - GUI::wxGetApp().plater()->get_notification_manager()->push_notification(GUI::NotificationType::EmptyColorChangeCode); - - const int extruder = selected_extruder > 0 ? selected_extruder : std::max(1, m_only_extruder); + const int extruder = selected_extruder > 0 ? selected_extruder : std::max(1, m_ticks.only_extruder_id); const auto it = m_ticks.ticks.find(TickCode{ tick }); bool was_ticks = m_ticks.empty(); @@ -1161,6 +897,7 @@ void DSForLayers::add_code_as_tick(Type type, int selected_extruder/* = -1*/) if (was_ticks != m_ticks.empty()) update_draw_scroll_line_cb(); + m_show_just_color_change_menu = false; process_ticks_changed(); } @@ -1172,52 +909,27 @@ void DSForLayers::add_current_tick() const int tick = m_ctrl.GetActivePos(); auto it = m_ticks.ticks.find(TickCode{ tick }); - if (it != m_ticks.ticks.end() || // this tick is already exist - !check_ticks_changed_event(m_mode == MultiAsSingle ? ToolChange : ColorChange)) + if (it != m_ticks.ticks.end()) // this tick is already exist return; + if (!m_ticks.check_ticks_changed_event(m_mode == MultiAsSingle ? ToolChange : ColorChange, m_mode)) { + process_ticks_changed(); + return; + } if (m_mode == SingleExtruder) add_code_as_tick(ColorChange); - else - render_menu(); - /* - { - wxMenu menu; - - if (m_mode == MultiAsSingle) - append_change_extruder_menu_item(&menu); - else - append_add_color_change_menu_item(&menu); - - wxPoint pos = wxDefaultPosition; - /* Menu position will be calculated from mouse click position, but... - * if function is called from keyboard (pressing "+"), we should to calculate it - * * / - if (call_from_keyboard) { - int width, height; - get_size(&width, &height); - - const wxCoord coord = 0.75 * (is_horizontal() ? height : width); - this->GetPosition(&width, &height); - - pos = is_horizontal() ? - wxPoint(get_position_from_value(tick), height + coord) : - wxPoint(width + coord, get_position_from_value(tick)); - } - - GUI::wxGetApp().plater()->PopupMenu(&menu, pos); + else { + m_show_just_color_change_menu = true; + m_imgui->set_requires_extra_frame(); } - */ } void DSForLayers::delete_current_tick() { auto it = m_ticks.ticks.find(TickCode{ m_ctrl.GetActivePos()}); - if (it == m_ticks.ticks.end() || - !check_ticks_changed_event(it->type)) + if (it == m_ticks.ticks.end()) // this tick doesn't exist return; - Type type = it->type; m_ticks.ticks.erase(it); process_ticks_changed(); } @@ -1228,11 +940,11 @@ void DSForLayers::edit_tick(int tick/* = -1*/) tick = m_ctrl.GetActivePos(); const std::set::iterator it = m_ticks.ticks.find(TickCode{ tick }); - if (it == m_ticks.ticks.end() || - !check_ticks_changed_event(it->type)) + if (it == m_ticks.ticks.end()) // this tick doesn't exist return; - if (m_ticks.edit_tick(it, m_values[it->tick])) + if (!m_ticks.check_ticks_changed_event(it->type, m_mode) || + m_ticks.edit_tick(it, m_values[it->tick])) process_ticks_changed(); } @@ -1245,67 +957,6 @@ void DSForLayers::discard_all_thicks() process_ticks_changed(); } -void DSForLayers::edit_extruder_sequence() -{ - if (!check_ticks_changed_event(ToolChange)) - return; - - GUI::ExtruderSequenceDialog dlg(m_extruders_sequence); - if (dlg.ShowModal() != wxID_OK) - return; - m_extruders_sequence = dlg.GetValue(); - - m_ticks.erase_all_ticks_with_code(ToolChange); - - const int extr_cnt = m_extruders_sequence.extruders.size(); - if (extr_cnt == 1) - return; - - 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 <= m_ctrl.GetMaxPos()) - { - 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) - m_ticks.ticks.emplace(TickCode{tick, ToolChange,cur_extruder + 1, m_extruder_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; - } - - process_ticks_changed(); -} - void DSForLayers::jump_to_value() { double value = get_value_to_jump(m_values[m_ctrl.GetActivePos()], @@ -1313,7 +964,7 @@ void DSForLayers::jump_to_value() if (value < 0.0) return; - int tick_value = get_tick_from_value(value); + int tick_value = m_ticks.get_tick_from_value(value); if (m_ctrl.IsActiveHigherThumb()) SetHigherPos(tick_value); @@ -1321,402 +972,6 @@ void DSForLayers::jump_to_value() SetLowerPos(tick_value); } -bool DSForLayers::check_ticks_changed_event(Type type) -{ - if ( m_ticks.mode == m_mode || - (type != ColorChange && type != ToolChange) || - (m_ticks.mode == SingleExtruder && m_mode == MultiAsSingle) || // All ColorChanges will be applied for 1st extruder - (m_ticks.mode == MultiExtruder && m_mode == MultiAsSingle) ) // Just mark ColorChanges for all unused extruders - return true; - - if ((m_ticks.mode == SingleExtruder && m_mode == MultiExtruder ) || - (m_ticks.mode == MultiExtruder && m_mode == SingleExtruder) ) - { - if (!m_ticks.has_tick_with_code(ColorChange)) - return true; - - wxString message = (m_ticks.mode == SingleExtruder ? - _L("The last color change data was saved for a single extruder printing.") : - _L("The last color change data was saved for a multi extruder printing.") - ) + "\n" + - _L("Your current changes will delete all saved color changes.") + "\n\n\t" + - _L("Are you sure you want to continue?"); - - //wxMessageDialog msg(this, message, _L("Notice"), wxYES_NO); - GUI::MessageDialog msg(nullptr, message, _L("Notice"), wxYES_NO); - if (msg.ShowModal() == wxID_YES) { - m_ticks.erase_all_ticks_with_code(ColorChange); - process_ticks_changed(); - } - return false; - } - // m_ticks_mode == MultiAsSingle - if( m_ticks.has_tick_with_code(ToolChange) ) { - wxString message = m_mode == SingleExtruder ? ( - _L("The last color change data was saved for a multi extruder printing.") + "\n\n" + - _L("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" + - _L("Do you want to delete all saved tool changes?") - ): ( // MultiExtruder - _L("The last color change data was saved for a multi extruder printing with tool changes for whole print.") + "\n\n" + - _L("Your current changes will delete all saved extruder (tool) changes.") + "\n\n\t" + - _L("Are you sure you want to continue?") ) ; - - GUI::MessageDialog msg(nullptr, message, _L("Notice"), wxYES_NO | (m_mode == SingleExtruder ? wxCANCEL : 0)); - const int answer = msg.ShowModal(); - if (answer == wxID_YES) { - m_ticks.erase_all_ticks_with_code(ToolChange); - process_ticks_changed(); - } - else if (m_mode == SingleExtruder && answer == wxID_NO) { - m_ticks.switch_code(ToolChange, ColorChange); - process_ticks_changed(); - } - return false; - } - - 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 TickCodeInfo::get_used_extruders_for_tick(int tick, int only_extruder, double print_z, Mode force_mode/* = Undef*/) const -{ - Mode e_mode = !force_mode ? mode : force_mode; - - if (e_mode == MultiExtruder) { - // #ys_FIXME: get tool ordering from _correct_ place - const ToolOrdering& tool_ordering = GUI::wxGetApp().plater()->fff_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, 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 TickCodeInfo::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((*m_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((*m_colors)[0]); - } - - if (before_tick_it == ticks.begin()) { - const std::string& frst_color = (*m_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->type == ColorChange) - break; - if (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->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((*m_colors)[0]); - - return opposite_two_colors((*m_colors)[0], frst_color); - } - - std::string color = (*m_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 TickCodeInfo::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(custom_gcode, print_z); - if (extra.empty()) - return false; - custom_gcode = extra; - } - else if (type == PausePrint) { - extra = get_pause_print_msg(pause_print_msg, print_z); - if (extra.empty()) - return false; - 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 TickCodeInfo::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; - } - - ticks.erase(it); - ticks.emplace(changed_tick); - - return true; -} - -void TickCodeInfo::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 TickCodeInfo::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 TickCodeInfo::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 TickCodeInfo::has_tick_with_code(Type type) -{ - for (const TickCode& tick : ticks) - if (tick.type == type) - return true; - - return false; -} - -bool TickCodeInfo::has_tick(int tick) -{ - return ticks.find(TickCode{ tick }) != ticks.end(); -} - -ConflictType TickCodeInfo::is_conflict_tick(const TickCode& tick, Mode out_mode, int only_extruder, double print_z) -{ - if ((tick.type == ColorChange && ( - (mode == SingleExtruder && out_mode == MultiExtruder ) || - (mode == MultiExtruder && out_mode == SingleExtruder) )) || - (tick.type == ToolChange && - (mode == MultiAsSingle && out_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, only_extruder, print_z, out_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, 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 == ticks.begin()) - return tick.extruder == std::max(only_extruder, 1) ? ctMeaninglessToolChange : ctNone; - - while (it != ticks.begin()) { - --it; - if (it->type == ToolChange) - return tick.extruder == it->extruder ? ctMeaninglessToolChange : ctNone; - } - } - - return ctNone; -} - } // DoubleSlider -} // Slic3r - diff --git a/src/slic3r/GUI/DoubleSliderForLayers.hpp b/src/slic3r/GUI/DoubleSliderForLayers.hpp index e486371f6a..79a05c7653 100644 --- a/src/slic3r/GUI/DoubleSliderForLayers.hpp +++ b/src/slic3r/GUI/DoubleSliderForLayers.hpp @@ -5,34 +5,25 @@ #ifndef slic3r_GUI_DoubleSliderForLayers_hpp_ #define slic3r_GUI_DoubleSliderForLayers_hpp_ -#include "libslic3r/CustomGCode.hpp" #include "ImGuiDoubleSlider.hpp" +#include "TickCodesManager.hpp" #include #include -class wxMenu; - namespace Slic3r { +class Print; -using namespace CustomGCode; -class PrintObject; -class Layer; +namespace GUI +{ +class ImGuiWrapper; +} +} + +using namespace Slic3r::CustomGCode; namespace DoubleSlider { -using namespace GUI; - -// return true when areas are mostly equivalent -bool equivalent_areas(const double& bottom_area, const double& top_area); - -// return true if color change was detected -bool check_color_change(const PrintObject* object, size_t frst_layer_id, size_t layers_cnt, bool check_overhangs, - // what to do with detected color change - // and return true when detection have to be desturbed - std::function break_condition); - - enum FocusedItem { fiNone, fiRevertIcon, @@ -46,15 +37,6 @@ enum FocusedItem { fiTick }; -enum ConflictType -{ - ctNone, - ctModeConflict, - ctMeaninglessColorChange, - ctMeaninglessToolChange, - ctRedundant -}; - enum DrawMode { dmRegular, @@ -69,111 +51,6 @@ enum LabelType ltEstimatedTime, }; -struct TickCode -{ - bool operator<(const TickCode& other) const { return other.tick > this->tick; } - bool operator>(const TickCode& other) const { return other.tick < this->tick; } - - int tick = 0; - Type type = ColorChange; - int extruder = 0; - std::string color; - std::string extra; -}; - -class TickCodeInfo -{ - std::string custom_gcode; - std::string pause_print_msg; - bool m_suppress_plus = false; - bool m_suppress_minus = false; - bool m_use_default_colors{ true }; - - std::vector* m_colors {nullptr}; - - std::string get_color_for_tick(TickCode tick, Type type, const int extruder); - -public: - std::set ticks {}; - Mode mode = Undef; - - bool empty() const { return ticks.empty(); } - void set_pause_print_msg(const std::string& message) { pause_print_msg = message; } - - bool add_tick(const int tick, Type type, int extruder, double print_z); - bool edit_tick(std::set::iterator it, double print_z); - void switch_code(Type type_from, Type type_to); - bool switch_code_for_tick(std::set::iterator it, Type type_to, const int extruder); - void erase_all_ticks_with_code(Type type); - - bool has_tick_with_code(Type type); - bool has_tick(int tick); - ConflictType is_conflict_tick(const TickCode& tick, Mode out_mode, int only_extruder, double print_z); - - // Get used extruders for tick. - // Means all extruders(tools) which will be used during printing from current tick to the end - std::set get_used_extruders_for_tick(int tick, int only_extruder, double print_z, Mode force_mode = Undef) const; - - void suppress_plus (bool suppress) { m_suppress_plus = suppress; } - void suppress_minus(bool suppress) { m_suppress_minus = suppress; } - bool suppressed_plus () { return m_suppress_plus; } - bool suppressed_minus() { return m_suppress_minus; } - void set_default_colors(bool default_colors_on) { m_use_default_colors = default_colors_on; } - bool used_default_colors() const { return m_use_default_colors; } - - void set_extruder_colors(std::vector* extruder_colors) { m_colors = extruder_colors; } -}; - - -struct ExtrudersSequence -{ - bool is_mm_intervals = true; - double interval_by_mm = 3.0; - int interval_by_layers = 10; - bool random_sequence { false }; - bool color_repetition { false }; - std::vector extruders = { 0 }; - - bool operator==(const ExtrudersSequence& other) const - { - return (other.is_mm_intervals == this->is_mm_intervals ) && - (other.interval_by_mm == this->interval_by_mm ) && - (other.interval_by_layers == this->interval_by_layers ) && - (other.random_sequence == this->random_sequence ) && - (other.color_repetition == this->color_repetition ) && - (other.extruders == this->extruders ) ; - } - bool operator!=(const ExtrudersSequence& other) const - { - return (other.is_mm_intervals != this->is_mm_intervals ) || - (other.interval_by_mm != this->interval_by_mm ) || - (other.interval_by_layers != this->interval_by_layers ) || - (other.random_sequence != this->random_sequence ) || - (other.color_repetition != this->color_repetition ) || - (other.extruders != this->extruders ) ; - } - - void add_extruder(size_t pos, size_t extruder_id = size_t(0)) - { - extruders.insert(extruders.begin() + pos+1, extruder_id); - } - - void delete_extruder(size_t pos) - { - if (extruders.size() == 1) - return;// last item can't be deleted - extruders.erase(extruders.begin() + pos); - } - - void init(size_t extruders_count) - { - extruders.clear(); - for (size_t extruder = 0; extruder < extruders_count; extruder++) - extruders.push_back(extruder); - } -}; - - class DSForLayers : public Manager { public: @@ -194,17 +71,19 @@ public: void SetDrawMode(bool is_sla_print, bool is_sequential_print); - void SetManipulationMode(Mode mode) { m_mode = mode; } - Mode GetManipulationMode() const { return m_mode; } void SetModeAndOnlyExtruder(const bool is_one_extruder_printed_model, const int only_extruder); - void SetExtruderColors(const std::vector& extruder_colors); - - void UseDefaultColors(bool def_colors_on); - bool IsNewPrint(const std::string& print_obj_idxs); void Render(const int canvas_width, const int canvas_height, float extra_scale = 1.f) override; - void set_callback_on_ticks_changed(std::function cb) { m_cb_ticks_changed = cb; }; + // jump to selected layer + void jump_to_value(); + + // just for editor + + void SetExtruderColors(const std::vector& extruder_colors); + void UseDefaultColors(bool def_colors_on); + bool is_new_print(const std::string& print_obj_idxs); + void set_imgui_wrapper(Slic3r::GUI::ImGuiWrapper* imgui) { m_imgui = imgui; } // manipulation with slider from keyboard @@ -214,82 +93,107 @@ public: void delete_current_tick(); // process adding of auto color change void auto_color_change(); - // jump to selected layer - void jump_to_value(); + + void set_callback_on_ticks_changed(std::function cb) + { m_cb_ticks_changed = cb; }; + + void set_callback_on_check_gcode(std::function cb ) + { m_ticks.set_callback_on_check_gcode(cb); } + + void set_callback_on_get_extruder_colors(std::function()> cb) + { m_cb_get_extruder_colors = cb; } + + void set_callback_on_get_print (std::function cb) + { m_cb_get_print = cb; } + + void set_callback_on_empty_auto_color_change(std::function cb) + { m_ticks.set_callback_on_empty_auto_color_change(cb); } + + void set_callback_on_get_custom_code(std::function cb) + { m_ticks.set_callback_on_get_custom_code(cb); } + + void set_callback_on_get_pause_print_msg(std::function cb) + { m_ticks.set_callback_on_get_pause_print_msg(cb); } + + void set_callback_on_get_new_color(std::function cb) + { m_ticks.set_callback_on_get_new_color(cb); } + + void set_callback_on_show_info_msg(std::function cb) + { m_ticks.set_callback_on_show_info_msg(cb); } + + void set_callback_on_show_warning_msg(std::function cb) + { m_ticks.set_callback_on_show_warning_msg(cb); } + + void set_callback_on_get_extruders_cnt(std::function cb) + { m_ticks.set_callback_on_get_extruders_cnt(cb); } + + void set_callback_on_get_extruders_sequence(std::function cb) + { m_ticks.set_callback_on_get_extruders_sequence(cb); } + + std::string gcode(Type type) { return m_ticks.gcode(type); } private: - void add_code_as_tick(Type type, int selected_extruder = -1); - void edit_tick(int tick = -1); - void discard_all_thicks(); - void edit_extruder_sequence(); - void show_cog_icon_context_menu(); - - bool is_wipe_tower_layer(int tick) const; - - std::string get_label(int tick, LabelType label_type = ltHeightWithLayer) const; - - int get_tick_from_value(double value, bool force_lower_bound = false); - std::string get_tooltip(int tick = -1); - - std::string get_color_for_tool_change_tick(std::set::const_iterator it) const; - std::string get_color_for_color_change_tick(std::set::const_iterator it) const; - - void process_ticks_changed() { - if (m_cb_ticks_changed) - m_cb_ticks_changed(); - } - - // Get active extruders for tick. - // Means one current extruder for not existing tick OR - // 2 extruders - for existing tick (extruder before ToolChangeCode and extruder of current existing tick) - // Use those values to disable selection of active extruders - std::array get_active_extruders_for_tick(int tick) const; - - bool check_ticks_changed_event(Type type); - - void append_change_extruder_menu_item(wxMenu*, bool switch_current_code = false); // ysFIXME ! - void append_add_color_change_menu_item(wxMenu*, bool switch_current_code = false); // ysFIXME ! - bool is_osx { false }; bool m_allow_editing { true }; - bool m_is_wipe_tower { false }; //This flag indicates that there is multiple extruder print with wipe tower bool m_show_estimated_times { false }; + bool m_show_cog_menu { false }; DrawMode m_draw_mode { dmRegular }; Mode m_mode { SingleExtruder }; FocusedItem m_focus { fiNone }; - int m_only_extruder { -1 }; - - std::string m_print_obj_idxs; + TickCodeManager m_ticks; + Slic3r::GUI::ImGuiWrapper* m_imgui { nullptr }; std::vector m_layers_times; std::vector m_layers_values; - std::vector m_extruder_colors; - TickCodeInfo m_ticks; + bool is_wipe_tower_layer(int tick) const; - ExtrudersSequence m_extruders_sequence; + std::string get_label(int tick, LabelType label_type) const; - std::function m_cb_ticks_changed{ nullptr }; + std::string get_tooltip(int tick = -1); - bool m_can_change_color{ true }; + void update_draw_scroll_line_cb(); // functions for extend rendering of m_ctrl void draw_colored_band(const ImRect& groove, const ImRect& slideable_region); void draw_ticks(const ImRect& slideable_region); void render_menu(); + void render_cog_menu(); bool render_button(const wchar_t btn_icon, const wchar_t btn_icon_hovered, const std::string& label_id, const ImVec2& pos, FocusedItem focus, int tick = -1); - void update_draw_scroll_line_cb(); + void add_code_as_tick(Type type, int selected_extruder = -1); + void edit_tick(int tick = -1); + void discard_all_thicks(); + + std::string get_label(int pos) const override { return get_label(pos, ltHeightWithLayer); } + + void process_ticks_changed() { + if (m_cb_ticks_changed) + m_cb_ticks_changed(); + } + + bool m_show_just_color_change_menu { false }; + bool m_show_color_picker { false }; + bool m_close { false }; + + std::string m_print_obj_idxs; + std::string m_selectable_color; + + void render_add_tick_menu(); + void render_multi_extruders_menu(); + void render_color_picker(); + + std::function m_cb_ticks_changed { nullptr }; + std::function()> m_cb_get_extruder_colors { nullptr }; + std::function m_cb_get_print { nullptr }; }; } // DoubleSlider; -} // Slic3r - #endif // slic3r_GUI_DoubleSliderForLayers_hpp_ diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 07d2717c6a..b46c52f88f 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -21,8 +21,10 @@ #include "libslic3r/PresetBundle.hpp" #include "DoubleSliderForGcode.hpp" #include "DoubleSliderForLayers.hpp" +#include "ExtruderSequenceDialog.hpp" #include "Plater.hpp" #include "MainFrame.hpp" +#include "MsgDialog.hpp" #include "format.hpp" #include @@ -34,6 +36,7 @@ #include #include #include +#include // this include must follow the wxWidgets ones or it won't compile on Windows -> see http://trac.wxwidgets.org/ticket/2421 #include "libslic3r/Print.hpp" @@ -258,11 +261,6 @@ Preview::~Preview() if (m_canvas_widget != nullptr) delete m_canvas_widget; - - if (m_layers_slider) - delete m_layers_slider; - if (m_moves_slider) - delete m_moves_slider; } void Preview::set_as_dirty() @@ -357,33 +355,163 @@ void Preview::on_size(wxSizeEvent& evt) Refresh(); } +/* To avoid get an empty string from wxTextEntryDialog + * Let disable OK button, if TextCtrl is empty + * */ +static void upgrade_text_entry_dialog(wxTextEntryDialog* dlg, double min = -1.0, double max = -1.0) +{ + GUI::wxGetApp().UpdateDlgDarkUI(dlg); + + // detect TextCtrl and OK button + wxWindowList& dlg_items = dlg->GetChildren(); + for (auto item : dlg_items) { + if (wxTextCtrl* textctrl = dynamic_cast(item)) { + textctrl->SetInsertionPointEnd(); + + wxButton* btn_OK = static_cast(dlg->FindWindowById(wxID_OK)); + btn_OK->Bind(wxEVT_UPDATE_UI, [textctrl](wxUpdateUIEvent& evt) { + evt.Enable(!textctrl->IsEmpty()); + }, btn_OK->GetId()); + + break; + } + } +} + void Preview::create_sliders() { // Layers Slider - m_layers_slider = new DoubleSlider::DSForLayers(0, 0, 0, 100, wxGetApp().is_editor()); + m_layers_slider = std::make_unique(0, 0, 0, 100, wxGetApp().is_editor()); m_layers_slider->SetEmUnit(wxGetApp().em_unit()); + m_layers_slider->set_imgui_wrapper(wxGetApp().imgui()); m_layers_slider->SetDrawMode(wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA, wxGetApp().preset_bundle->prints.get_edited_preset().config.opt_bool("complete_objects")); m_layers_slider->set_callback_on_thumb_move( [this]() -> void { Preview::on_layers_slider_scroll_changed(); } ); - m_layers_slider->set_callback_on_ticks_changed( [this]() -> void { - Model& model = wxGetApp().plater()->model(); - model.custom_gcode_per_print_z = m_layers_slider->GetTicksValues(); - m_schedule_background_process(); + if (wxGetApp().is_editor()) { + m_layers_slider->set_callback_on_ticks_changed([this]() -> void { + Model& model = wxGetApp().plater()->model(); + model.custom_gcode_per_print_z = m_layers_slider->GetTicksValues(); + m_schedule_background_process(); - m_keep_current_preview_type = false; - reload_print(); - }); + m_keep_current_preview_type = false; + reload_print(); + }); + + m_layers_slider->set_callback_on_check_gcode([this](CustomGCode::Type type) -> void { + if (type == ColorChange && m_layers_slider->gcode(ColorChange).empty()) + GUI::wxGetApp().plater()->get_notification_manager()->push_notification(GUI::NotificationType::EmptyColorChangeCode); + }); + + m_layers_slider->set_callback_on_empty_auto_color_change([]() -> void { + GUI::wxGetApp().plater()->get_notification_manager()->push_notification(GUI::NotificationType::EmptyAutoColorChange); + }); + + m_layers_slider->set_callback_on_get_extruder_colors([]() -> std::vector { + return wxGetApp().plater()->get_extruder_colors_from_plater_config(); + }); + + m_layers_slider->set_callback_on_get_print([]() -> const Print& { + return GUI::wxGetApp().plater()->fff_print(); + }); + + m_layers_slider->set_callback_on_get_custom_code([](const std::string& code_in, double height) -> std::string + { + wxString msg_text = _L("Enter custom G-code used on current layer") + ":"; + wxString msg_header = format_wxstr(_L("Custom G-code on current layer (%1% mm)."), height); + + // get custom gcode + wxTextEntryDialog dlg(nullptr, msg_text, msg_header, code_in, + wxTextEntryDialogStyle | wxTE_MULTILINE); + upgrade_text_entry_dialog(&dlg); + + bool valid = true; + std::string value; + do { + if (dlg.ShowModal() != wxID_OK) + return ""; + + value = into_u8(dlg.GetValue()); + valid = true;// GUI::Tab::validate_custom_gcode("Custom G-code", value); // !ysFIXME validate_custom_gcode + } while (!valid); + return value; + }); + + m_layers_slider->set_callback_on_get_pause_print_msg([](const std::string& msg_in, double height) -> std::string + { + wxString msg_text = _L("Enter short message shown on Printer display when a print is paused") + ":"; + wxString msg_header = format_wxstr(_L("Message for pause print on current layer (%1% mm)."), height); + + // get custom gcode + wxTextEntryDialog dlg(nullptr, msg_text, msg_header, from_u8(msg_in), + wxTextEntryDialogStyle); + upgrade_text_entry_dialog(&dlg); + + if (dlg.ShowModal() != wxID_OK || dlg.GetValue().IsEmpty()) + return ""; + + return into_u8(dlg.GetValue()); + }); + + m_layers_slider->set_callback_on_get_new_color([](const std::string& color) -> std::string + { + wxColour clr(color); + if (!clr.IsOk()) + clr = wxColour(0, 0, 0); // Don't set alfa to transparence + + auto data = new wxColourData(); + data->SetChooseFull(1); + data->SetColour(clr); + + wxColourDialog dialog(GUI::wxGetApp().GetTopWindow(), data); + dialog.CenterOnParent(); + if (dialog.ShowModal() == wxID_OK) + return dialog.GetColourData().GetColour().GetAsString(wxC2S_HTML_SYNTAX).ToStdString(); + return ""; + }); + + m_layers_slider->set_callback_on_show_info_msg([this](const std::string& message, int btns_flag) -> int + { + GUI::MessageDialog msg(this, from_u8(message), _L("Notice"), btns_flag); + int ret = msg.ShowModal(); + return ret == wxID_YES ? wxYES : + ret == wxID_NO ? wxNO : + ret == wxID_CANCEL ? wxCANCEL : -1; + }); + + m_layers_slider->set_callback_on_show_warning_msg([this](const std::string& message, int btns_flag) -> int + { + GUI::WarningDialog msg(this, from_u8(message), _L("Warning"), btns_flag); + int ret = msg.ShowModal(); + return ret == wxID_YES ? wxYES : + ret == wxID_NO ? wxNO : + ret == wxID_CANCEL ? wxCANCEL : -1; + }); + + m_layers_slider->set_callback_on_get_extruders_cnt([]() -> int + { + return GUI::wxGetApp().extruders_edited_cnt(); + }); + + m_layers_slider->set_callback_on_get_extruders_sequence([](DoubleSlider::ExtrudersSequence& extruders_sequence) -> bool + { + GUI::ExtruderSequenceDialog dlg(extruders_sequence); + if (dlg.ShowModal() != wxID_OK) + return false; + extruders_sequence = dlg.GetValue(); + return true; + }); + } // Move Gcode Slider - m_moves_slider = new DoubleSlider::DSForGcode(0, 0, 0, 100); + m_moves_slider = std::make_unique(0, 0, 0, 100); m_moves_slider->SetEmUnit(wxGetApp().em_unit()); - m_moves_slider->set_callback_on_thumb_move([this]() ->void { Preview::on_moves_slider_scroll_changed(); }); + m_moves_slider->set_callback_on_thumb_move([this]() ->void { on_moves_slider_scroll_changed(); }); // m_canvas_widget m_canvas_widget->Bind(wxEVT_KEY_DOWN, &Preview::update_sliders_from_canvas, this); @@ -514,7 +642,7 @@ void Preview::update_layers_slider(const std::vector& layers_z, bool kee // Suggest the auto color change, if model looks like sign if (!color_change_already_exists && wxGetApp().app_config->get_bool("allow_auto_color_change") && - m_layers_slider->IsNewPrint(get_print_obj_idxs())) + m_layers_slider->is_new_print(get_print_obj_idxs())) { const Print& print = wxGetApp().plater()->fff_print(); @@ -805,8 +933,6 @@ void Preview::load_print_as_fff(bool keep_z_range) // the view type may have been changed by the call m_canvas->load_gcode_preview() gcode_view_type = m_canvas->get_gcode_view_type(); zs = m_canvas->get_gcode_layers_zs(); - if (!zs.empty()) - m_moves_slider->Show(); m_loaded = true; } else if (is_pregcode_preview) { @@ -815,12 +941,9 @@ void Preview::load_print_as_fff(bool keep_z_range) // the view type has been changed by the call m_canvas->load_gcode_preview() if (gcode_view_type == libvgcode::EViewType::ColorPrint && !color_print_values.empty()) m_canvas->set_gcode_view_type(gcode_view_type); - m_moves_slider->Hide(); zs = m_canvas->get_gcode_layers_zs(); } - else { - m_moves_slider->Hide(); - } + m_moves_slider->Show(gcode_preview_data_valid && !zs.empty()); if (!zs.empty() && !m_keep_current_preview_type) { const unsigned int number_extruders = wxGetApp().is_editor() ? diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index 5118532965..b2dd52885c 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -20,6 +20,11 @@ class wxComboBox; class wxComboCtrl; class wxCheckBox; +namespace DoubleSlider { + class DSForGcode; + class DSForLayers; +}; + namespace Slic3r { class DynamicPrintConfig; @@ -27,11 +32,6 @@ class Print; class BackgroundSlicingProcess; class Model; -namespace DoubleSlider { - class DSForGcode; - class DSForLayers; -}; - namespace GUI { class GLCanvas3D; @@ -96,8 +96,8 @@ class Preview : public wxPanel bool m_loaded { false }; - DoubleSlider::DSForLayers* m_layers_slider{ nullptr }; - DoubleSlider::DSForGcode* m_moves_slider { nullptr }; + std::unique_ptr m_layers_slider{ nullptr }; + std::unique_ptr m_moves_slider { nullptr }; public: enum class OptionType : unsigned int diff --git a/src/slic3r/GUI/ImGuiDoubleSlider.cpp b/src/slic3r/GUI/ImGuiDoubleSlider.cpp index a9e20a04c5..f70af66e2b 100644 --- a/src/slic3r/GUI/ImGuiDoubleSlider.cpp +++ b/src/slic3r/GUI/ImGuiDoubleSlider.cpp @@ -5,11 +5,8 @@ #include "ImGuiDoubleSlider.hpp" -namespace Slic3r { namespace DoubleSlider { -using namespace GUI; - const ImU32 tooltip_bg_clr = ImGui::ColorConvertFloat4ToU32(ImGuiPureWrap::COL_GREY_LIGHT); const ImU32 thumb_bg_clr = ImGui::ColorConvertFloat4ToU32(ImGuiPureWrap::COL_ORANGE_LIGHT); const ImU32 groove_bg_clr = ImGui::ColorConvertFloat4ToU32(ImGuiPureWrap::COL_WINDOW_BACKGROUND); @@ -255,6 +252,11 @@ float ImGuiControl::GetPositionInRect(int pos, const ImRect& rect) const return thumb_pos; } +ImRect ImGuiControl::GetActiveThumbRect() const +{ + return m_selection == ssLower ? m_regions.lower_thumb : m_regions.higher_thumb; +} + void ImGuiControl::draw_scroll_line(const ImRect& scroll_line, const ImRect& slideable_region) { if (m_cb_draw_scroll_line) @@ -548,9 +550,5 @@ bool ImGuiControl::render() return result; } -//} // DoubleSlider - -} // GUI - -} // Slic3r +} // DoubleSlider diff --git a/src/slic3r/GUI/ImGuiDoubleSlider.hpp b/src/slic3r/GUI/ImGuiDoubleSlider.hpp index b09f864acf..db3318706f 100644 --- a/src/slic3r/GUI/ImGuiDoubleSlider.hpp +++ b/src/slic3r/GUI/ImGuiDoubleSlider.hpp @@ -23,7 +23,6 @@ std::string to_string_with_precision(const T a_value, const int n = 2) return std::move(out).str(); } -namespace Slic3r { namespace DoubleSlider { enum SelectedSlider { @@ -80,6 +79,7 @@ public: void ShowLabelOnMouseMove(bool show = true) { m_show_move_label = show; } ImRect GetGrooveRect() const { return m_draw_opts.groove(m_pos, m_size, is_horizontal()); } float GetPositionInRect(int pos, const ImRect& rect) const; + ImRect GetActiveThumbRect() const; bool IsRClickOnThumb() const { return m_rclick_on_selected_thumb; } @@ -192,7 +192,7 @@ public: int minPos, int maxPos, const std::string& name, - bool is_horizontal) + bool is_horizontal) { Init (lowerPos, higherPos, minPos, maxPos, name, is_horizontal); } @@ -257,10 +257,10 @@ protected: int m_em{ 10 }; float m_scale{ 1.f }; - std::string get_label(int pos) const { + virtual std::string get_label(int pos) const { if (m_values.empty()) return std::to_string(pos); - if (pos >= m_values.size()) + if (pos >= int(m_values.size())) return "ErrVal"; return to_string_with_precision(static_cast(m_alternate_values.empty() ? m_values[pos] : m_alternate_values[pos])); } @@ -276,10 +276,7 @@ private: }; - - -} // GUI -} // Slic3r +} // DoubleSlider #endif // slic3r_ImGUI_DoubleSlider_hpp_ diff --git a/src/slic3r/GUI/ImGuiPureWrap.cpp b/src/slic3r/GUI/ImGuiPureWrap.cpp index 8128602fd3..4b3111acd8 100644 --- a/src/slic3r/GUI/ImGuiPureWrap.cpp +++ b/src/slic3r/GUI/ImGuiPureWrap.cpp @@ -617,4 +617,232 @@ bool is_chars_in_ranges(const ImWchar *ranges, return true; } +bool begin_menu(const char* label, bool enabled) +{ + ImGuiWindow* window = ImGui::GetCurrentWindow(); + if (window->SkipItems) return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + bool menu_is_open = ImGui::IsPopupOpen(id, ImGuiPopupFlags_None); + + // Sub-menus are ChildWindow so that mouse can be hovering across them (otherwise top-most popup menu would steal focus and not allow hovering on parent menu) + ImGuiWindowFlags flags = ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus; + if (window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) flags |= ImGuiWindowFlags_ChildWindow; + + // If a menu with same the ID was already submitted, we will append to it, matching the behavior of Begin(). + // We are relying on a O(N) search - so O(N log N) over the frame - which seems like the most efficient for the expected small amount of BeginMenu() calls per frame. + // If somehow this is ever becoming a problem we can switch to use e.g. ImGuiStorage mapping key to last frame used. + if (g.MenusIdSubmittedThisFrame.contains(id)) { + if (menu_is_open) + menu_is_open = ImGui::BeginPopupEx(id, flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display) + else + g.NextWindowData.ClearFlags(); // we behave like Begin() and need to consume those values + return menu_is_open; + } + + // Tag menu as used. Next time BeginMenu() with same ID is called it will append to existing menu + g.MenusIdSubmittedThisFrame.push_back(id); + + ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true); + bool pressed; + bool menuset_is_open = !(window->Flags & ImGuiWindowFlags_Popup) && + (g.OpenPopupStack.Size > g.BeginPopupStack.Size && g.OpenPopupStack[g.BeginPopupStack.Size].OpenParentId == window->IDStack.back()); + ImGuiWindow* backed_nav_window = g.NavWindow; + if (menuset_is_open) g.NavWindow = window; // Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent) + + // The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu, + // However the final position is going to be different! It is chosen by FindBestWindowPosForPopup(). + // e.g. Menus tend to overlap each other horizontally to amplify relative Z-ordering. + ImVec2 popup_pos, pos = window->DC.CursorPos; + if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) { + // Menu inside an horizontal menu bar + // Selectable extend their highlight by half ItemSpacing in each direction. + // For ChildMenu, the popup position will be overwritten by the call to FindBestWindowPosForPopup() in Begin() + popup_pos = ImVec2(pos.x - 1.0f - IM_FLOOR(style.ItemSpacing.x * 0.5f), pos.y - style.FramePadding.y + window->MenuBarHeight()); + window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * 0.5f); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x * 2.0f, style.ItemSpacing.y)); + float w = label_size.x; + pressed = /*selectable*/ImGui::Selectable(label, menu_is_open, + ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_DontClosePopups | + (!enabled ? ImGuiSelectableFlags_Disabled : 0), + ImVec2(w, 0.0f)); + ImGui::PopStyleVar(); + window->DC.CursorPos.x += IM_FLOOR( + style.ItemSpacing.x * + (-1.0f + + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). + } + else { + // Menu inside a menu + // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f. + // Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system. + popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y); + float min_w = window->DC.MenuColumns.DeclColumns(label_size.x, 0.0f, IM_FLOOR(g.FontSize * 1.20f)); // Feedback to next frame + float extra_w = ImMax(0.0f, ImGui::GetContentRegionAvail().x - min_w); + pressed = /*selectable*/ImGui::Selectable(label, menu_is_open, + ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_DontClosePopups | + ImGuiSelectableFlags_SpanAvailWidth | (!enabled ? ImGuiSelectableFlags_Disabled : 0), + ImVec2(min_w, 0.0f)); + ImU32 text_col = ImGui::GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled); + ImGui::RenderArrow(window->DrawList, pos + ImVec2(window->DC.MenuColumns.Pos[2] + extra_w + g.FontSize * 0.30f, 0.0f), text_col, ImGuiDir_Right); + } + + const bool hovered = enabled && ImGui::ItemHoverable(window->DC.LastItemRect, id); + if (menuset_is_open) g.NavWindow = backed_nav_window; + + bool want_open = false; + bool want_close = false; + if (window->DC.LayoutType == ImGuiLayoutType_Vertical) // (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu)) + { + // Close menu when not hovering it anymore unless we are moving roughly in the direction of the menu + // Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive. + bool moving_toward_other_child_menu = false; + + ImGuiWindow* child_menu_window = (g.BeginPopupStack.Size < g.OpenPopupStack.Size && g.OpenPopupStack[g.BeginPopupStack.Size].SourceWindow == window) ? + g.OpenPopupStack[g.BeginPopupStack.Size].Window : + NULL; + if (g.HoveredWindow == window && child_menu_window != NULL && !(window->Flags & ImGuiWindowFlags_MenuBar)) { + // FIXME-DPI: Values should be derived from a master "scale" factor. + ImRect next_window_rect = child_menu_window->Rect(); + ImVec2 ta = g.IO.MousePos - g.IO.MouseDelta; + ImVec2 tb = (window->Pos.x < child_menu_window->Pos.x) ? next_window_rect.GetTL() : next_window_rect.GetTR(); + ImVec2 tc = (window->Pos.x < child_menu_window->Pos.x) ? next_window_rect.GetBL() : next_window_rect.GetBR(); + float extra = ImClamp(ImFabs(ta.x - tb.x) * 0.30f, 5.0f, 30.0f); // add a bit of extra slack. + ta.x += (window->Pos.x < child_menu_window->Pos.x) ? -0.5f : +0.5f; // to avoid numerical issues + tb.y = ta.y + + ImMax((tb.y - extra) - ta.y, -100.0f); // triangle is maximum 200 high to limit the slope and the bias toward large sub-menus // FIXME: Multiply by fb_scale? + tc.y = ta.y + ImMin((tc.y + extra) - ta.y, +100.0f); + moving_toward_other_child_menu = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos); + // GetForegroundDrawList()->AddTriangleFilled(ta, tb, tc, moving_within_opened_triangle ? IM_COL32(0,128,0,128) : IM_COL32(128,0,0,128)); // [DEBUG] + } + if (menu_is_open && !hovered && g.HoveredWindow == window && g.HoveredIdPreviousFrame != 0 && g.HoveredIdPreviousFrame != id && !moving_toward_other_child_menu) + want_close = true; + + if (!menu_is_open && hovered && pressed) // Click to open + want_open = true; + else if (!menu_is_open && hovered && !moving_toward_other_child_menu) // Hover to open + want_open = true; + + if (g.NavActivateId == id) { + want_close = menu_is_open; + want_open = !menu_is_open; + } + if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right) // Nav-Right to open + { + want_open = true; + ImGui::NavMoveRequestCancel(); + } + } + else { + // Menu bar + if (menu_is_open && pressed && menuset_is_open) // Click an open menu again to close it + { + want_close = true; + want_open = menu_is_open = false; + } + else if (pressed || (hovered && menuset_is_open && !menu_is_open)) // First click to open, then hover to open others + { + want_open = true; + } + else if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Down) // Nav-Down to open + { + want_open = true; + ImGui::NavMoveRequestCancel(); + } + } + + if (!enabled) // explicitly close if an open menu becomes disabled, facilitate users code a lot in pattern such as 'if (BeginMenu("options", has_object)) { ..use object.. }' + want_close = true; + if (want_close && ImGui::IsPopupOpen(id, ImGuiPopupFlags_None)) ImGui::ClosePopupToLevel(g.BeginPopupStack.Size, true); + + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.LastItemStatusFlags | ImGuiItemStatusFlags_Openable | (menu_is_open ? ImGuiItemStatusFlags_Opened : 0)); + + if (!menu_is_open && want_open && g.OpenPopupStack.Size > g.BeginPopupStack.Size) { + // Don't recycle same menu level in the same frame, first close the other menu and yield for a frame. + ImGui::OpenPopup(label); + return false; + } + + menu_is_open |= want_open; + if (want_open) ImGui::OpenPopup(label); + + if (menu_is_open) { + ImGui::SetNextWindowPos(popup_pos, + ImGuiCond_Always); // Note: this is super misleading! The value will serve as reference for FindBestWindowPosForPopup(), not actual pos. + menu_is_open = ImGui::BeginPopupEx(id, flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display) + } + else { + g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values + } + + return menu_is_open; +} + +void end_menu() +{ + ImGui::EndMenu(); +} + +bool menu_item_with_icon(const char* label, const char* shortcut, ImVec2 icon_size /* = ImVec2(0, 0)*/, ImU32 icon_color /* = 0*/, bool selected /* = false*/, bool enabled /* = true*/) +{ + ImGuiWindow* window = ImGui::GetCurrentWindow(); + if (window->SkipItems) return false; + + ImGuiContext& g = *GImGui; + ImGuiStyle& style = g.Style; + ImVec2 pos = window->DC.CursorPos; + ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true); + + // We've been using the equivalent of ImGuiSelectableFlags_SetNavIdOnHover on all Selectable() since early Nav system days (commit 43ee5d73), + // but I am unsure whether this should be kept at all. For now moved it to be an opt-in feature used by menus only. + ImGuiSelectableFlags flags = ImGuiSelectableFlags_SelectOnRelease | ImGuiSelectableFlags_SetNavIdOnHover | (enabled ? 0 : ImGuiSelectableFlags_Disabled); + bool pressed; + if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) { + // Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful + // Note that in this situation: we don't render the shortcut, we render a highlight instead of the selected tick mark. + float w = label_size.x; + window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * 0.5f); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x * 2.0f, style.ItemSpacing.y)); + pressed = ImGui::Selectable(label, selected, flags, ImVec2(w, 0.0f)); + ImGui::PopStyleVar(); + window->DC.CursorPos.x += IM_FLOOR( + style.ItemSpacing.x * + (-1.0f + + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). + } + else { + // Menu item inside a vertical menu + // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f. + // Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system. + float shortcut_w = shortcut ? ImGui::CalcTextSize(shortcut, NULL).x : 0.0f; + float min_w = window->DC.MenuColumns.DeclColumns(label_size.x, shortcut_w, IM_FLOOR(g.FontSize * 1.20f)); // Feedback for next frame + float extra_w = std::max(0.0f, ImGui::GetContentRegionAvail().x - min_w); + pressed = /*selectable*/ImGui::Selectable(label, false, flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, 0.0f)); + + if (icon_size.x != 0 && icon_size.y != 0) { + float selectable_pos_y = pos.y + -0.5f * style.ItemSpacing.y; + float icon_pos_y = selectable_pos_y + (label_size.y + style.ItemSpacing.y - icon_size.y) / 2; + float icon_pos_x = pos.x + window->DC.MenuColumns.Pos[2] + extra_w + g.FontSize * 0.40f; + ImVec2 icon_pos = ImVec2(icon_pos_x, icon_pos_y); + ImGui::RenderFrame(icon_pos, icon_pos + icon_size, icon_color); + } + + if (shortcut_w > 0.0f) { + ImGui::PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); + ImGui::RenderText(pos + ImVec2(window->DC.MenuColumns.Pos[1] + extra_w, 0.0f), shortcut, NULL, false); + ImGui::PopStyleColor(); + } + if (selected) { + ImGui::RenderCheckMark(window->DrawList, pos + ImVec2(window->DC.MenuColumns.Pos[2] + extra_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), + ImGui::GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled), g.FontSize * 0.866f); + } + } + + IMGUI_TEST_ENGINE_ITEM_INFO(window->DC.LastItemId, label, window->DC.LastItemStatusFlags | ImGuiItemStatusFlags_Checkable | (selected ? ImGuiItemStatusFlags_Checked : 0)); + return pressed; +} + } // ImGuiPureWrap diff --git a/src/slic3r/GUI/ImGuiPureWrap.hpp b/src/slic3r/GUI/ImGuiPureWrap.hpp index 5af36ace83..ab4eccab1b 100644 --- a/src/slic3r/GUI/ImGuiPureWrap.hpp +++ b/src/slic3r/GUI/ImGuiPureWrap.hpp @@ -148,6 +148,10 @@ namespace ImGuiPureWrap bool is_chars_in_ranges(const ImWchar *ranges, const char *chars_ptr); bool is_char_in_ranges(const ImWchar *ranges, unsigned int letter); + bool begin_menu(const char* label, bool enabled = true); + void end_menu(); + bool menu_item_with_icon(const char* label, const char* shortcut, ImVec2 icon_size = ImVec2(0, 0), ImU32 icon_color = 0, bool selected = false, bool enabled = true); + const ImVec4 COL_GREY_DARK = { 0.33f, 0.33f, 0.33f, 1.0f }; const ImVec4 COL_GREY_LIGHT = { 0.4f, 0.4f, 0.4f, 1.0f }; const ImVec4 COL_ORANGE_DARK = { 0.67f, 0.36f, 0.19f, 1.0f }; diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 52a3d84494..25f3e342cd 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -1552,234 +1552,6 @@ void ImGuiWrapper::clipboard_set(void* /* user_data */, const char* text) } } -bool begin_menu(const char* label, bool enabled) -{ - ImGuiWindow* window = ImGui::GetCurrentWindow(); - if (window->SkipItems) return false; - - ImGuiContext& g = *GImGui; - const ImGuiStyle& style = g.Style; - const ImGuiID id = window->GetID(label); - bool menu_is_open = ImGui::IsPopupOpen(id, ImGuiPopupFlags_None); - - // Sub-menus are ChildWindow so that mouse can be hovering across them (otherwise top-most popup menu would steal focus and not allow hovering on parent menu) - ImGuiWindowFlags flags = ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | - ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus; - if (window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) flags |= ImGuiWindowFlags_ChildWindow; - - // If a menu with same the ID was already submitted, we will append to it, matching the behavior of Begin(). - // We are relying on a O(N) search - so O(N log N) over the frame - which seems like the most efficient for the expected small amount of BeginMenu() calls per frame. - // If somehow this is ever becoming a problem we can switch to use e.g. ImGuiStorage mapping key to last frame used. - if (g.MenusIdSubmittedThisFrame.contains(id)) { - if (menu_is_open) - menu_is_open = ImGui::BeginPopupEx(id, flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display) - else - g.NextWindowData.ClearFlags(); // we behave like Begin() and need to consume those values - return menu_is_open; - } - - // Tag menu as used. Next time BeginMenu() with same ID is called it will append to existing menu - g.MenusIdSubmittedThisFrame.push_back(id); - - ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true); - bool pressed; - bool menuset_is_open = !(window->Flags & ImGuiWindowFlags_Popup) && - (g.OpenPopupStack.Size > g.BeginPopupStack.Size && g.OpenPopupStack[g.BeginPopupStack.Size].OpenParentId == window->IDStack.back()); - ImGuiWindow* backed_nav_window = g.NavWindow; - if (menuset_is_open) g.NavWindow = window; // Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent) - - // The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu, - // However the final position is going to be different! It is chosen by FindBestWindowPosForPopup(). - // e.g. Menus tend to overlap each other horizontally to amplify relative Z-ordering. - ImVec2 popup_pos, pos = window->DC.CursorPos; - if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) { - // Menu inside an horizontal menu bar - // Selectable extend their highlight by half ItemSpacing in each direction. - // For ChildMenu, the popup position will be overwritten by the call to FindBestWindowPosForPopup() in Begin() - popup_pos = ImVec2(pos.x - 1.0f - IM_FLOOR(style.ItemSpacing.x * 0.5f), pos.y - style.FramePadding.y + window->MenuBarHeight()); - window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * 0.5f); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x * 2.0f, style.ItemSpacing.y)); - float w = label_size.x; - pressed = selectable(label, menu_is_open, - ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_DontClosePopups | - (!enabled ? ImGuiSelectableFlags_Disabled : 0), - ImVec2(w, 0.0f)); - ImGui::PopStyleVar(); - window->DC.CursorPos.x += IM_FLOOR( - style.ItemSpacing.x * - (-1.0f + - 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). - } - else { - // Menu inside a menu - // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f. - // Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system. - popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y); - float min_w = window->DC.MenuColumns.DeclColumns(label_size.x, 0.0f, IM_FLOOR(g.FontSize * 1.20f)); // Feedback to next frame - float extra_w = ImMax(0.0f, ImGui::GetContentRegionAvail().x - min_w); - pressed = selectable(label, menu_is_open, - ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_DontClosePopups | - ImGuiSelectableFlags_SpanAvailWidth | (!enabled ? ImGuiSelectableFlags_Disabled : 0), - ImVec2(min_w, 0.0f)); - ImU32 text_col = ImGui::GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled); - ImGui::RenderArrow(window->DrawList, pos + ImVec2(window->DC.MenuColumns.Pos[2] + extra_w + g.FontSize * 0.30f, 0.0f), text_col, ImGuiDir_Right); - } - - const bool hovered = enabled && ImGui::ItemHoverable(window->DC.LastItemRect, id); - if (menuset_is_open) g.NavWindow = backed_nav_window; - - bool want_open = false; - bool want_close = false; - if (window->DC.LayoutType == ImGuiLayoutType_Vertical) // (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu)) - { - // Close menu when not hovering it anymore unless we are moving roughly in the direction of the menu - // Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive. - bool moving_toward_other_child_menu = false; - - ImGuiWindow* child_menu_window = (g.BeginPopupStack.Size < g.OpenPopupStack.Size && g.OpenPopupStack[g.BeginPopupStack.Size].SourceWindow == window) ? - g.OpenPopupStack[g.BeginPopupStack.Size].Window : - NULL; - if (g.HoveredWindow == window && child_menu_window != NULL && !(window->Flags & ImGuiWindowFlags_MenuBar)) { - // FIXME-DPI: Values should be derived from a master "scale" factor. - ImRect next_window_rect = child_menu_window->Rect(); - ImVec2 ta = g.IO.MousePos - g.IO.MouseDelta; - ImVec2 tb = (window->Pos.x < child_menu_window->Pos.x) ? next_window_rect.GetTL() : next_window_rect.GetTR(); - ImVec2 tc = (window->Pos.x < child_menu_window->Pos.x) ? next_window_rect.GetBL() : next_window_rect.GetBR(); - float extra = ImClamp(ImFabs(ta.x - tb.x) * 0.30f, 5.0f, 30.0f); // add a bit of extra slack. - ta.x += (window->Pos.x < child_menu_window->Pos.x) ? -0.5f : +0.5f; // to avoid numerical issues - tb.y = ta.y + - ImMax((tb.y - extra) - ta.y, -100.0f); // triangle is maximum 200 high to limit the slope and the bias toward large sub-menus // FIXME: Multiply by fb_scale? - tc.y = ta.y + ImMin((tc.y + extra) - ta.y, +100.0f); - moving_toward_other_child_menu = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos); - // GetForegroundDrawList()->AddTriangleFilled(ta, tb, tc, moving_within_opened_triangle ? IM_COL32(0,128,0,128) : IM_COL32(128,0,0,128)); // [DEBUG] - } - if (menu_is_open && !hovered && g.HoveredWindow == window && g.HoveredIdPreviousFrame != 0 && g.HoveredIdPreviousFrame != id && !moving_toward_other_child_menu) - want_close = true; - - if (!menu_is_open && hovered && pressed) // Click to open - want_open = true; - else if (!menu_is_open && hovered && !moving_toward_other_child_menu) // Hover to open - want_open = true; - - if (g.NavActivateId == id) { - want_close = menu_is_open; - want_open = !menu_is_open; - } - if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right) // Nav-Right to open - { - want_open = true; - ImGui::NavMoveRequestCancel(); - } - } - else { - // Menu bar - if (menu_is_open && pressed && menuset_is_open) // Click an open menu again to close it - { - want_close = true; - want_open = menu_is_open = false; - } - else if (pressed || (hovered && menuset_is_open && !menu_is_open)) // First click to open, then hover to open others - { - want_open = true; - } - else if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Down) // Nav-Down to open - { - want_open = true; - ImGui::NavMoveRequestCancel(); - } - } - - if (!enabled) // explicitly close if an open menu becomes disabled, facilitate users code a lot in pattern such as 'if (BeginMenu("options", has_object)) { ..use object.. }' - want_close = true; - if (want_close && ImGui::IsPopupOpen(id, ImGuiPopupFlags_None)) ImGui::ClosePopupToLevel(g.BeginPopupStack.Size, true); - - IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.LastItemStatusFlags | ImGuiItemStatusFlags_Openable | (menu_is_open ? ImGuiItemStatusFlags_Opened : 0)); - - if (!menu_is_open && want_open && g.OpenPopupStack.Size > g.BeginPopupStack.Size) { - // Don't recycle same menu level in the same frame, first close the other menu and yield for a frame. - ImGui::OpenPopup(label); - return false; - } - - menu_is_open |= want_open; - if (want_open) ImGui::OpenPopup(label); - - if (menu_is_open) { - ImGui::SetNextWindowPos(popup_pos, - ImGuiCond_Always); // Note: this is super misleading! The value will serve as reference for FindBestWindowPosForPopup(), not actual pos. - menu_is_open = ImGui::BeginPopupEx(id, flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display) - } - else { - g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values - } - - return menu_is_open; -} - -void end_menu() -{ - ImGui::EndMenu(); -} - -bool menu_item_with_icon(const char* label, const char* shortcut, ImVec2 icon_size /* = ImVec2(0, 0)*/, ImU32 icon_color /* = 0*/, bool selected /* = false*/, bool enabled /* = true*/) -{ - ImGuiWindow* window = ImGui::GetCurrentWindow(); - if (window->SkipItems) return false; - - ImGuiContext& g = *GImGui; - ImGuiStyle& style = g.Style; - ImVec2 pos = window->DC.CursorPos; - ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true); - - // We've been using the equivalent of ImGuiSelectableFlags_SetNavIdOnHover on all Selectable() since early Nav system days (commit 43ee5d73), - // but I am unsure whether this should be kept at all. For now moved it to be an opt-in feature used by menus only. - ImGuiSelectableFlags flags = ImGuiSelectableFlags_SelectOnRelease | ImGuiSelectableFlags_SetNavIdOnHover | (enabled ? 0 : ImGuiSelectableFlags_Disabled); - bool pressed; - if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) { - // Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful - // Note that in this situation: we don't render the shortcut, we render a highlight instead of the selected tick mark. - float w = label_size.x; - window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * 0.5f); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x * 2.0f, style.ItemSpacing.y)); - pressed = ImGui::Selectable(label, selected, flags, ImVec2(w, 0.0f)); - ImGui::PopStyleVar(); - window->DC.CursorPos.x += IM_FLOOR( - style.ItemSpacing.x * - (-1.0f + - 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). - } - else { - // Menu item inside a vertical menu - // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f. - // Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system. - float shortcut_w = shortcut ? ImGui::CalcTextSize(shortcut, NULL).x : 0.0f; - float min_w = window->DC.MenuColumns.DeclColumns(label_size.x, shortcut_w, IM_FLOOR(g.FontSize * 1.20f)); // Feedback for next frame - float extra_w = std::max(0.0f, ImGui::GetContentRegionAvail().x - min_w); - pressed = selectable(label, false, flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, 0.0f)); - - if (icon_size.x != 0 && icon_size.y != 0) { - float selectable_pos_y = pos.y + -0.5f * style.ItemSpacing.y; - float icon_pos_y = selectable_pos_y + (label_size.y + style.ItemSpacing.y - icon_size.y) / 2; - float icon_pos_x = pos.x + window->DC.MenuColumns.Pos[2] + extra_w + g.FontSize * 0.40f; - ImVec2 icon_pos = ImVec2(icon_pos_x, icon_pos_y); - ImGui::RenderFrame(icon_pos, icon_pos + icon_size, icon_color); - } - - if (shortcut_w > 0.0f) { - ImGui::PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); - ImGui::RenderText(pos + ImVec2(window->DC.MenuColumns.Pos[1] + extra_w, 0.0f), shortcut, NULL, false); - ImGui::PopStyleColor(); - } - if (selected) { - //ImGui::RenderCheckMark(window->DrawList, pos + ImVec2(window->DC.MenuColumns.Pos[2] + extra_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), - // ImGui::GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled), g.FontSize * 0.866f); - } - } - - IMGUI_TEST_ENGINE_ITEM_INFO(window->DC.LastItemId, label, window->DC.LastItemStatusFlags | ImGuiItemStatusFlags_Checkable | (selected ? ImGuiItemStatusFlags_Checked : 0)); - return pressed; -} - } // namespace GUI diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index 8cbec8b390..052eba2a3c 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -153,9 +153,6 @@ namespace ImGuiPSWrap ColorRGBA from_ImU32(const ImU32& color); ColorRGBA from_ImVec4(const ImVec4& color); } -bool begin_menu(const char* label, bool enabled = true); -void end_menu(); -bool menu_item_with_icon(const char* label, const char* shortcut, ImVec2 icon_size = ImVec2(0, 0), ImU32 icon_color = 0, bool selected = false, bool enabled = true); } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/TickCodesManager.cpp b/src/slic3r/GUI/TickCodesManager.cpp new file mode 100644 index 0000000000..c0bdfc563a --- /dev/null +++ b/src/slic3r/GUI/TickCodesManager.cpp @@ -0,0 +1,704 @@ +///|/ 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 "I18N.hpp" +#include "libslic3r/Print.hpp" +#include "libslic3r/Color.hpp" + +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 == 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; + + 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 + + diff --git a/src/slic3r/GUI/TickCodesManager.hpp b/src/slic3r/GUI/TickCodesManager.hpp new file mode 100644 index 0000000000..4eb28dce0f --- /dev/null +++ b/src/slic3r/GUI/TickCodesManager.hpp @@ -0,0 +1,221 @@ +///|/ Copyright (c) Prusa Research 2020 - 2022 Vojtěch Bubník @bubnikv, Oleksandra Iushchenko @YuSanka, Enrico Turri @enricoturri1966, Lukáš Matěna @lukasmatena +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ +#ifndef slic3r_GUI_TickCodesManager_hpp_ +#define slic3r_GUI_TickCodesManager_hpp_ + +#include "libslic3r/CustomGCode.hpp" + +#include +#include + +using namespace Slic3r::CustomGCode; +namespace Slic3r { +class PrintObject; +class Print; +class Layer; +} + +namespace DoubleSlider { + +// return true when areas are mostly equivalent +bool equivalent_areas(const double& bottom_area, const double& top_area); + +// return true if color change was detected +bool check_color_change(const Slic3r::PrintObject* object, size_t frst_layer_id, size_t layers_cnt, bool check_overhangs, + // what to do with detected color change + // and return true when detection have to be desturbed + std::function break_condition); +enum ConflictType +{ + ctNone, + ctModeConflict, + ctMeaninglessColorChange, + ctMeaninglessToolChange, + ctRedundant +}; + +struct ExtrudersSequence +{ + bool is_mm_intervals = true; + double interval_by_mm = 3.0; + int interval_by_layers = 10; + bool random_sequence { false }; + bool color_repetition { false }; + std::vector extruders = { 0 }; + + bool operator==(const ExtrudersSequence& other) const + { + return (other.is_mm_intervals == this->is_mm_intervals ) && + (other.interval_by_mm == this->interval_by_mm ) && + (other.interval_by_layers == this->interval_by_layers ) && + (other.random_sequence == this->random_sequence ) && + (other.color_repetition == this->color_repetition ) && + (other.extruders == this->extruders ) ; + } + bool operator!=(const ExtrudersSequence& other) const + { + return (other.is_mm_intervals != this->is_mm_intervals ) || + (other.interval_by_mm != this->interval_by_mm ) || + (other.interval_by_layers != this->interval_by_layers ) || + (other.random_sequence != this->random_sequence ) || + (other.color_repetition != this->color_repetition ) || + (other.extruders != this->extruders ) ; + } + + void add_extruder(size_t pos, size_t extruder_id = size_t(0)) + { + extruders.insert(extruders.begin() + pos+1, extruder_id); + } + + void delete_extruder(size_t pos) + { + if (extruders.size() == 1) + return;// last item can't be deleted + extruders.erase(extruders.begin() + pos); + } + + void init(size_t extruders_count) + { + extruders.clear(); + for (size_t extruder = 0; extruder < extruders_count; extruder++) + extruders.push_back(extruder); + } +}; + + +struct TickCode +{ + bool operator<(const TickCode& other) const { return other.tick > this->tick; } + bool operator>(const TickCode& other) const { return other.tick < this->tick; } + + int tick = 0; + Type type = ColorChange; + int extruder = 0; + std::string color; + std::string extra; +}; + + +class TickCodeManager +{ + std::string m_custom_gcode; + std::string m_pause_print_msg; + bool m_use_default_colors { true }; + + const Slic3r::Print* m_print{ nullptr }; + // pointer to the m_values from DSForLayers + const std::vector* m_values{ nullptr }; + + ExtrudersSequence m_extruders_sequence; + + bool has_tick_with_code(Type type); + bool has_tick(int tick); + + std::string get_color_for_tick(TickCode tick, Type type, const int extruder); + + std::string get_custom_code(const std::string& code_in, double height); + std::string get_pause_print_msg(const std::string& msg_in, double height); + std::string get_new_color(const std::string& color); + + std::function m_cb_notify_empty_color_change { nullptr }; + std::function m_cb_check_gcode_and_notify { nullptr }; + + std::function m_cb_get_custom_code { nullptr }; + std::function m_cb_get_pause_print_msg { nullptr }; + std::function m_cb_get_new_color { nullptr }; + + std::function m_cb_show_info_msg { nullptr }; + std::function m_cb_show_warning_msg { nullptr }; + std::function m_cb_get_extruders_cnt { nullptr }; + std::function m_cb_get_extruders_sequence { nullptr }; + +public: + + TickCodeManager(); + ~TickCodeManager() {} + std::set ticks {}; + Mode mode { Undef }; + bool is_wipe_tower { false }; //This flag indicates that there is multiple extruder print with wipe tower + int only_extruder_id{ -1 }; + + // colors per extruder + std::vector colors {}; + + bool empty() const { return ticks.empty(); } + + void set_ticks(const Info& custom_gcode_per_print_z); + + bool add_tick(const int tick, Type type, int extruder, double print_z); + bool edit_tick(std::set::iterator it, double print_z); + void switch_code(Type type_from, Type type_to); + bool switch_code_for_tick(std::set::iterator it, Type type_to, const int extruder); + void erase_all_ticks_with_code(Type type); + + ConflictType is_conflict_tick(const TickCode& tick, Mode main_mode, double print_z); + + int get_tick_from_value(double value, bool force_lower_bound = false); + + std::string gcode(Slic3r::CustomGCode::Type type); + + // Get used extruders for tick. + // Means all extruders(tools) which will be used during printing from current tick to the end + std::set get_used_extruders_for_tick(int tick, double print_z, Mode force_mode = Undef) const; + + // Get active extruders for tick. + // Means one current extruder for not existing tick OR + // 2 extruders - for existing tick (extruder before ToolChangeCode and extruder of current existing tick) + // Use those values to disable selection of active extruders + std::array get_active_extruders_for_tick(int tick, Mode main_mode) const; + + std::string get_color_for_tool_change_tick(std::set::const_iterator it) const; + std::string get_color_for_color_change_tick(std::set::const_iterator it) const; + + // true -> if manipulation with ticks with selected type and in respect to the main_mode (slider mode) is possible + // false -> otherwise + bool check_ticks_changed_event(Type type, Mode main_mode); + + // return true, if extruder sequence was changed + bool edit_extruder_sequence(const int max_tick, Mode main_mode); + + // return true, if auto color change was successfully processed + bool auto_color_change(Mode main_mode); + + void set_default_colors(bool default_colors_on) { m_use_default_colors = default_colors_on; } + bool used_default_colors() const { return m_use_default_colors; } + + void set_print(const Slic3r::Print& print) { if (!m_print) m_print = &print; } + void set_values(const std::vector* values) { m_values = values; } + + void set_callback_on_empty_auto_color_change(std::function cb) + { m_cb_notify_empty_color_change = cb; } + + void set_callback_on_check_gcode(std::function cb ) + { m_cb_check_gcode_and_notify = cb; } + + void set_callback_on_get_custom_code(std::function cb) + { m_cb_get_custom_code = cb; } + + void set_callback_on_get_pause_print_msg(std::function cb) + { m_cb_get_pause_print_msg = cb; } + + void set_callback_on_get_new_color(std::function cb) + { m_cb_get_new_color = cb; } + + void set_callback_on_show_info_msg(std::function cb) + { m_cb_show_info_msg = cb; } + + void set_callback_on_show_warning_msg(std::function cb) + { m_cb_show_warning_msg = cb; } + + void set_callback_on_get_extruders_cnt(std::function cb) + { m_cb_get_extruders_cnt = cb; } + + void set_callback_on_get_extruders_sequence(std::function cb) + { m_cb_get_extruders_sequence = cb; } +}; + +} // DoubleSlider; + +#endif // slic3r_GUI_TickCodesManager_hpp_