///|/ 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 "DoubleSliderForLayers.hpp" #include "libslic3r/Utils.hpp" // -> get_time_dhms() #include "libslic3r/format.hpp" // -> format() #include "I18N.hpp" #include #include "ImGuiWrapper.hpp" #include "imgui/imgui_internal.h" #ifndef IMGUI_DEFINE_MATH_OPERATORS #define IMGUI_DEFINE_MATH_OPERATORS #endif #include using namespace Slic3r; using namespace CustomGCode; using Slic3r::format; namespace DoubleSlider { static const float VERTICAL_SLIDER_WIDTH = 105.0f; DSForLayers::DSForLayers( int lowerValue, int higherValue, int minValue, int maxValue, bool allow_editing) : m_allow_editing(allow_editing) { #ifdef __WXOSX__ 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 ? get_label(pos, ltEstimatedTime) : ""; }); m_ctrl.set_extra_draw_cb([this](const ImRect& draw_rc) {return draw_ticks(draw_rc); }); m_ticks.set_values(&m_values); } Info DSForLayers::GetTicksValues() const { Info custom_gcode_per_print_z; std::vector& values = custom_gcode_per_print_z.gcodes; const int val_size = m_values.size(); if (!m_values.empty()) for (const TickCode& tick : m_ticks.ticks) { if (tick.tick > val_size) break; values.emplace_back(CustomGCode::Item{ m_values[tick.tick], tick.type, tick.extruder, tick.color, tick.extra }); } custom_gcode_per_print_z.mode = m_mode; return custom_gcode_per_print_z; } void DSForLayers::SetTicksValues(const Info& custom_gcode_per_print_z) { if (m_values.empty()) { m_ticks.mode = m_mode; return; } if (m_cb_get_print) m_ticks.set_print(m_cb_get_print()); const bool was_empty = m_ticks.empty(); 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(); update_draw_scroll_line_cb(); } void DSForLayers::SetLayersTimes(const std::vector& layers_times, float total_time) { m_layers_times.clear(); if (layers_times.empty()) return; m_layers_times.resize(layers_times.size(), 0.0); m_layers_times[0] = layers_times[0]; for (size_t i = 1; i < layers_times.size(); i++) m_layers_times[i] = m_layers_times[i - 1] + layers_times[i]; // 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_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()); // When whipe tower is used to the end of print, there is one layer which is not marked in layers_times // So, add this value from the total print time value if (m_layers_values.size() != m_layers_times.size()) for (size_t i = m_layers_times.size(); i < m_layers_values.size(); i++) m_layers_times.push_back(total_time); } } void DSForLayers::SetLayersTimes(const std::vector& layers_times) { 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]; } void DSForLayers::SetDrawMode(bool is_sla_print, bool is_sequential_print) { m_draw_mode = is_sla_print ? dmSlaPrint : is_sequential_print ? dmSequentialFffPrint : dmRegular; update_draw_scroll_line_cb(); } void DSForLayers::SetModeAndOnlyExtruder(const bool is_one_extruder_printed_model, const int only_extruder) { m_mode = !is_one_extruder_printed_model ? MultiExtruder : only_extruder < 0 ? SingleExtruder : MultiAsSingle; if (!m_ticks.mode || (m_ticks.empty() && m_ticks.mode != m_mode)) m_ticks.mode = m_mode; m_ticks.only_extruder_id = only_extruder; m_ticks.is_wipe_tower = m_mode != SingleExtruder; if (m_mode != SingleExtruder) UseDefaultColors(false); } void DSForLayers::SetExtruderColors( const std::vector& extruder_colors) { m_ticks.colors = extruder_colors; } bool DSForLayers::is_new_print(const std::string& idxs) { if (idxs == "sla" || idxs == m_print_obj_idxs) return false; m_print_obj_idxs = idxs; return true; } void DSForLayers::update_draw_scroll_line_cb() { if (m_ticks.empty() || m_draw_mode == dmSequentialFffPrint || m_draw_mode == dmSlaPrint) m_ctrl.set_draw_scroll_line_cb(nullptr); else m_ctrl.set_draw_scroll_line_cb([this](const ImRect& scroll_line, const ImRect& slideable_region) { draw_colored_band(scroll_line, slideable_region); }); } using namespace ImGui; void DSForLayers::draw_ticks(const ImRect& slideable_region) { //if(m_draw_mode != dmRegular) // return; //if (m_ticks.empty() || m_mode == MultiExtruder) // return; if (m_ticks.empty()) return; 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 = m_imgui->GetTextureCustomRect(ImGui::PausePrint)->Height; const float icon_offset = 0.5f * icon_side;; const ImU32 tick_clr = ImGui::ColorConvertFloat4ToU32(ImGuiPureWrap::COL_ORANGE_DARK); const ImU32 tick_hovered_clr = ImGui::ColorConvertFloat4ToU32(ImGuiPureWrap::COL_WINDOW_BACKGROUND); auto get_tick_pos = [this, slideable_region](int tick) { return m_ctrl.GetPositionInRect(tick, slideable_region); }; std::set::const_iterator tick_it = m_ticks.ticks.begin(); while (tick_it != m_ticks.ticks.end()) { float tick_pos = get_tick_pos(tick_it->tick); //draw tick hover box when hovered ImRect tick_hover_box = ImRect(slideable_region.GetCenter().x - tick_border.x, tick_pos - tick_border.y, slideable_region.GetCenter().x + tick_border.x, tick_pos + tick_border.y - tick_width); if (ImGui::IsMouseHoveringRect(tick_hover_box.Min, tick_hover_box.Max)) { ImGui::RenderFrame(tick_hover_box.Min, tick_hover_box.Max, tick_hovered_clr, false); break; } ++tick_it; } auto active_tick_it = m_ticks.ticks.find(TickCode{ m_ctrl.GetActivePos() }); tick_it = m_ticks.ticks.begin(); while (tick_it != m_ticks.ticks.end()) { float tick_pos = get_tick_pos(tick_it->tick); //draw ticks ImRect tick_left = ImRect(slideable_region.GetCenter().x - tick_size.x, tick_pos - tick_width, slideable_region.GetCenter().x - tick_size.y, tick_pos); ImRect tick_right = ImRect(slideable_region.GetCenter().x + tick_size.y, tick_pos - tick_width, slideable_region.GetCenter().x + tick_size.x, tick_pos); ImGui::RenderFrame(tick_left.Min, tick_left.Max, tick_clr, false); ImGui::RenderFrame(tick_right.Min, tick_right.Max, tick_clr, false); ImVec2 icon_pos = ImVec2(tick_right.Max.x + 0.5f * icon_offset, tick_pos - icon_offset); std::string btn_label = "tick " + std::to_string(tick_it->tick); //draw tick icon-buttons bool activate_this_tick = false; 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)) { m_ticks.ticks.erase(tick_it); process_ticks_changed(); break; } } 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_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 == 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); if (activate_this_tick) { m_ctrl.IsActiveHigherThumb() ? SetHigherPos(tick_it->tick) : SetLowerPos(tick_it->tick); break; } ++tick_it; } } 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_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; } } 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) return; ImVec2 blank_padding = ImVec2(0.5f * m_ctrl.GetGrooveRect().GetWidth(), 2.0f * m_scale); float blank_width = 1.0f * m_scale; ImRect blank_rect = ImRect(groove.GetCenter().x - blank_width, groove.Min.y, groove.GetCenter().x + blank_width, groove.Max.y); ImRect main_band = ImRect(blank_rect); main_band.Expand(blank_padding); auto draw_band = [](const ImU32& clr, const ImRect& band_rc) { ImGui::RenderFrame(band_rc.Min, band_rc.Max, clr, false, band_rc.GetWidth() * 0.5); //cover round corner ImGui::RenderFrame(ImVec2(band_rc.Min.x, band_rc.Max.y - band_rc.GetWidth() * 0.5), band_rc.Max, clr, false); }; auto draw_main_band = [&main_band](const ImU32& clr) { ImGui::RenderFrame(main_band.Min, main_band.Max, clr, false, main_band.GetWidth() * 0.5); }; //draw main colored band 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); static float tick_pos; std::set::const_iterator tick_it = m_ticks.ticks.begin(); while (tick_it != m_ticks.ticks.end()) { //get position from tick tick_pos = m_ctrl.GetPositionInRect(tick_it->tick, slideable_region); ImRect band_rect = ImRect(ImVec2(main_band.Min.x, std::min(tick_pos, main_band.Min.y)), ImVec2(main_band.Max.x, std::min(tick_pos, main_band.Max.y))); if (main_band.Contains(band_rect)) { if ((m_mode == SingleExtruder && tick_it->type == ColorChange) || (m_mode == MultiAsSingle && (tick_it->type == ToolChange || tick_it->type == ColorChange))) { const std::string clr_str = m_mode == SingleExtruder ? tick_it->color : tick_it->type == ToolChange ? 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); ImU32 band_clr = IM_COL32(rgba[0] * 255.0f, rgba[1] * 255.0f, rgba[2] * 255.0f, rgba[3] * 255.0f); if (tick_it->tick == 0) draw_main_band(band_clr); else draw_band(band_clr, band_rect); } } } tick_it++; } } 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)); 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_allow_editing) render_add_tick_menu(); render_cog_menu(); ImGui::PopStyleColor(1); 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*/) { 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)); ImGui::PushStyleColor(ImGuiCol_::ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_Text)); int windows_flag = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse; 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); ImGuiContext& g = *GImGui; m_focus = focus; std::string tooltip = m_allow_editing ? get_tooltip(tick) : ""; ImGui::SetCursorPos(ImVec2(0, 0)); const bool ret = m_imgui->image_button(g.HoveredWindow == g.CurrentWindow ? btn_icon_hovered : btn_icon, tooltip, false); ImGuiPureWrap::end(); ImGui::PopStyleColor(2); ImGui::PopStyleVar(3); return ret; } bool DSForLayers::render_jump_to_window(const ImVec2& pos, double* active_value, double min_z, double max_z) { if (!m_show_get_jump_value) return false; const std::string msg_text = _u8L("Enter the height you want to jump to") + ":"; const std::string win_name = _u8L("Jump to height") + "##btn_win"; const ImVec2 msg_size = ImGui::CalcTextSize(msg_text.c_str(), NULL, true); const float ctrl_pos_x = msg_size.x + 15 * m_scale; const float ctrl_width = 50.f * m_scale; ImGui::SetNextWindowPos(pos, ImGuiCond_Always); ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 4.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, { 12.0f, 8.0f }); ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.13f, 0.13f, 0.13f, 0.8f)); ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_Text)); ImGuiWindowFlags windows_flag = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse; ImGui::Begin(win_name.c_str(), &m_show_get_jump_value, windows_flag); ImGui::AlignTextToFramePadding(); ImGui::Text("%s", msg_text.c_str()); ImGui::SameLine(ctrl_pos_x); ImGui::PushItemWidth(ctrl_width); ImGui::InputDouble("##jump_to", active_value, 0.0, 0.0, "%.2f", ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_AutoSelectAll); //check if Enter was pressed bool enter_pressed = ImGui::IsItemDeactivatedAfterEdit(); //check out of range bool disable_ok = *active_value < min_z || *active_value > max_z; ImGui::Text("%s", ""); ImGui::SameLine(ctrl_pos_x); if (disable_ok) { ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f); } bool ok_pressed = ImGui::Button("OK##jump_to", ImVec2(ctrl_width, 0.f)); if (disable_ok) { ImGui::PopItemFlag(); ImGui::PopStyleVar(); } ImGui::End(); ImGui::PopStyleColor(2); ImGui::PopStyleVar(3); return enter_pressed || ok_pressed; } void DSForLayers::Render(const int canvas_width, const int canvas_height, float extra_scale/* = 0.1f*/, float offset /*= 0.f*/) { if (!m_ctrl.IsShown()) return; m_scale = extra_scale * 0.1f * m_em; const float action_btn_sz = m_imgui->GetTextureCustomRect(ImGui::DSRevert)->Height; const float tick_icon_side = m_imgui->GetTextureCustomRect(ImGui::PausePrint)->Height; ImVec2 pos; pos.x = canvas_width - VERTICAL_SLIDER_WIDTH * m_scale - tick_icon_side; pos.y = 1.5f * action_btn_sz + offset; if (m_allow_editing) pos.y += 2.f; ImVec2 size = ImVec2(VERTICAL_SLIDER_WIDTH * m_scale, canvas_height - 4.f * action_btn_sz - offset); 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) m_imgui->set_requires_extra_frame(); process_thumb_move(); // discard all getters dialogs m_show_get_jump_value = false; } // draw action buttons const float groove_center_x = m_ctrl.GetGrooveRect().GetCenter().x; ImVec2 btn_pos = ImVec2(groove_center_x - 0.5f * action_btn_sz, pos.y - 0.75f * action_btn_sz); if (!m_ticks.empty() && m_allow_editing && render_button(ImGui::DSRevert, ImGui::DSRevertHovered, "revert", btn_pos, fiRevertIcon)) discard_all_thicks(); btn_pos.y += 0.5f * action_btn_sz + size.y; const bool is_one_layer = m_ctrl.IsCombineThumbs(); if (render_button(is_one_layer ? ImGui::Lock : ImGui::Unlock, is_one_layer ? ImGui::LockHovered : ImGui::UnlockHovered, "one_layer", btn_pos, fiOneLayerIcon)) ChangeOneLayerLock(); btn_pos.y += 1.2f * action_btn_sz; 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 render_menu(); if (render_jump_to_window(ImVec2(0.5f * canvas_width, 0.5f*canvas_height), &m_jump_to_value, m_values[m_ctrl.GetMinPos()], m_values[m_ctrl.GetMaxPos()])) process_jump_to_value(); if (m_allow_editing) render_color_picker(); } bool DSForLayers::is_wipe_tower_layer(int tick) const { 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; if ((m_values[tick - 1] == m_values[tick + 1] && m_values[tick] < m_values[tick + 1]) || (tick > 0 && m_values[tick] < m_values[tick - 1]) ) // if there is just one wiping on the layer return true; return false; } static std::string short_and_splitted_time(const std::string& time) { // Parse the dhms time format. int days = 0; int hours = 0; int minutes = 0; int seconds = 0; if (time.find('d') != std::string::npos) ::sscanf(time.c_str(), "%dd %dh %dm %ds", &days, &hours, &minutes, &seconds); else if (time.find('h') != std::string::npos) ::sscanf(time.c_str(), "%dh %dm %ds", &hours, &minutes, &seconds); else if (time.find('m') != std::string::npos) ::sscanf(time.c_str(), "%dm %ds", &minutes, &seconds); else if (time.find('s') != std::string::npos) ::sscanf(time.c_str(), "%ds", &seconds); // Format the dhm time. 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 format("%1%%2%\n%3%", get_d(), get_h(), get_m()); if (hours > 0) { if (hours < 10 && minutes < 10 && seconds < 10) return format("%1%%2%%3%", get_h(), get_m(), get_s()); if (hours > 10 && minutes > 10 && seconds > 10) return format("%1%\n%2%\n%3%", get_h(), get_m(), get_s()); if ((minutes < 10 && seconds > 10) || (minutes > 10 && seconds < 10)) 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 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) const { const size_t value = pos; if (m_values.empty()) return format("%1%", pos); if (value >= m_values.size()) return "ErrVal"; // When "Print Settings -> Multiple Extruders -> No sparse layer" is enabled, then "Smart" Wipe Tower is used for wiping. // As a result, each layer with tool changes is splited for min 3 parts: first tool, wiping, second tool ... // So, vertical slider have to respect to this case. // see https://github.com/prusa3d/PrusaSlicer/issues/6232. // m_values contains data for all layer's parts, // but m_layers_values contains just unique Z values. // Use this function for correct conversion slider position to number of printed layer auto get_layer_number = [this](int value, LabelType label_type) { if (label_type == ltEstimatedTime && m_layers_times.empty()) return size_t(-1); double layer_print_z = m_values[is_wipe_tower_layer(value) ? std::max(value - 1, 0) : value]; auto it = std::lower_bound(m_layers_values.begin(), m_layers_values.end(), layer_print_z - epsilon()); if (it == m_layers_values.end()) { it = std::lower_bound(m_values.begin(), m_values.end(), layer_print_z - epsilon()); if (it == m_values.end()) return size_t(-1); return size_t(value); } return size_t(it - m_layers_values.begin()); }; if (label_type == ltEstimatedTime) { 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 = format("%1$.2f", m_values[value]); if (label_type == ltHeight) return str; if (label_type == ltHeightWithLayer) { 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 ""; } void DSForLayers::ChangeOneLayerLock() { m_ctrl.CombineThumbs(!m_ctrl.IsCombineThumbs()); process_thumb_move(); } std::string DSForLayers::get_tooltip(int tick/*=-1*/) { if (m_focus == fiNone) return ""; if (m_focus == fiOneLayerIcon) return _u8L("One layer mode"); if (m_focus == fiRevertIcon) return _u8L("Discard all custom changes"); if (m_focus == fiCogIcon) { return m_mode == MultiAsSingle ? (boost::format(_u8L("Jump to height %s\n" "Set ruler mode\n" "or Set extruder sequence for the entire print")) % "(Shift + G)").str() : (boost::format(_u8L("Jump to height %s\n" "or Set ruler mode")) % "(Shift + G)").str(); } if (m_focus == fiColorBand) return m_mode != SingleExtruder ? "" : _u8L("Edit current color - Right click the colored slider segment"); if (m_focus == fiSmartWipeTower) return _u8L("This is wipe tower layer"); if (m_draw_mode == dmSlaPrint) return ""; // no drawn ticks and no tooltips for them in SlaPrinting mode std::string tooltip; const auto tick_code_it = m_ticks.ticks.find(TickCode{tick}); if (tick_code_it == m_ticks.ticks.end() && m_focus == fiActionIcon) // tick doesn't exist { if (m_draw_mode == dmSequentialFffPrint) return (_u8L("The sequential print is on.\n" "It's impossible to apply any custom G-code for objects printing sequentually.") + "\n"); // Show mode as a first string of tooltop tooltip = " " + _u8L("Print mode") + ": "; tooltip += (m_mode == SingleExtruder ? SingleExtruderMode : m_mode == MultiAsSingle ? MultiAsSingleMode : MultiExtruderMode ); tooltip += "\n\n"; /* Note: just on OSX!!! * Right click event causes a little scrolling. * So, as a workaround we use Ctrl+LeftMouseClick instead of RightMouseClick * Show this information in tooltip * */ // Show list of actions with new tick tooltip += ( m_mode == MultiAsSingle ? _u8L("Add extruder change - Left click") : m_mode == SingleExtruder ? _u8L("Add color change - Left click for predefined color or " "Shift + Left click for custom color selection") : _u8L("Add color change - Left click") ) + " " + _u8L("or press \"+\" key") + "\n" + ( is_osx ? _u8L("Add another code - Ctrl + Left click") : _u8L("Add another code - Right click") ); } if (tick_code_it != m_ticks.ticks.end()) // tick exists { if (m_draw_mode == dmSequentialFffPrint) return _u8L("The sequential print is on.\n" "It's impossible to apply any custom G-code for objects printing sequentually.\n" "This code won't be processed during G-code generation."); // Show custom Gcode as a first string of tooltop std::string space = " "; tooltip = space; auto format_gcode = [space](std::string gcode) -> std::string { // when the tooltip is too long, it starts to flicker, see: https://github.com/prusa3d/PrusaSlicer/issues/7368 // so we limit the number of lines shown std::vector lines; boost::split(lines, gcode, boost::is_any_of("\n"), boost::token_compress_off); static const size_t MAX_LINES = 10; if (lines.size() > MAX_LINES) { gcode = lines.front() + '\n'; for (size_t i = 1; i < MAX_LINES; ++i) { gcode += lines[i] + '\n'; } gcode += "[" + _u8L("continue") + "]\n"; } boost::replace_all(gcode, "\n", "\n" + space); return gcode; }; tooltip += tick_code_it->type == ColorChange ? (m_mode == SingleExtruder ? 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 ? format(_u8L("Custom template (\"%1%\")"), gcode(Template)) : tick_code_it->type == ToolChange ? 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_values[tick]); if (conflict != ctNone) tooltip += "\n\n" + _u8L("Note") + "! "; if (conflict == ctModeConflict) tooltip += _u8L("G-code associated to this tick mark is in a conflict with print mode.\n" "Editing it will cause changes of Slider data."); else if (conflict == ctMeaninglessColorChange) tooltip += _u8L("There is a color change for extruder that won't be used till the end of print job.\n" "This code won't be processed during G-code generation."); else if (conflict == ctMeaninglessToolChange) tooltip += _u8L("There is an extruder change set to the same extruder.\n" "This code won't be processed during G-code generation."); else if (conflict == ctRedundant) tooltip += _u8L("There is a color change for extruder that has not been used before.\n" "Check your settings to avoid redundant color changes."); // Show list of actions with existing tick if (m_focus == fiActionIcon) tooltip += "\n\n" + _u8L("Delete tick mark - Left click or press \"-\" key") + "\n" + ( is_osx ? _u8L("Edit tick mark - Ctrl + Left click") : _u8L("Edit tick mark - Right click") ); } return tooltip; } void DSForLayers::UseDefaultColors(bool def_colors_on) { m_ticks.set_default_colors(def_colors_on); } // !ysFIXME draw with imgui void DSForLayers::auto_color_change() { if (m_ticks.auto_color_change(m_mode)) { update_draw_scroll_line_cb(); process_ticks_changed(); } } void DSForLayers::add_code_as_tick(Type type, int selected_extruder/* = -1*/) { const int tick = m_ctrl.GetActivePos(); if (!m_ticks.check_ticks_changed_event(type, m_mode)) { process_ticks_changed(); return; } 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(); if ( it == m_ticks.ticks.end() ) { // try to add tick if (!m_ticks.add_tick(tick, type, extruder, m_values[tick])) return; } else if (type == ToolChange || type == ColorChange) { // try to switch tick code to ToolChange or ColorChange accordingly if (!m_ticks.switch_code_for_tick(it, type, extruder)) return; } else return; if (was_ticks != m_ticks.empty()) update_draw_scroll_line_cb(); m_show_just_color_change_menu = false; process_ticks_changed(); } void DSForLayers::add_current_tick() { if (!m_allow_editing) return; 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 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 { 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()) // this tick doesn't exist return; m_ticks.ticks.erase(it); process_ticks_changed(); } void DSForLayers::edit_tick(int tick/* = -1*/) { if (tick < 0) tick = m_ctrl.GetActivePos(); const std::set::iterator it = m_ticks.ticks.find(TickCode{ tick }); if (it == m_ticks.ticks.end()) // this tick doesn't exist return; if (!m_ticks.check_ticks_changed_event(it->type, m_mode) || m_ticks.edit_tick(it, m_values[it->tick])) process_ticks_changed(); } // discard all custom changes on DoubleSlider void DSForLayers::discard_all_thicks() { m_ticks.ticks.clear(); m_ctrl.ResetPositions(); update_draw_scroll_line_cb(); process_ticks_changed(); } void DSForLayers::jump_to_value() { //Init "jump to value"; m_show_get_jump_value = true; m_jump_to_value = m_values[m_ctrl.GetActivePos()]; m_imgui->set_requires_extra_frame(); } void DSForLayers::process_jump_to_value() { if (int tick_value = m_ticks.get_tick_from_value(m_jump_to_value, true); tick_value > 0.0) { m_show_get_jump_value = false; if (m_ctrl.IsActiveHigherThumb()) SetHigherPos(tick_value); else SetLowerPos(tick_value); } } } // DoubleSlider