ImguiDoubleSlider: WIP: Functions which was related to manipulate with ticks are moved into TickCodeInfo

* TickCodeInfo is renamed to TickCodesManager and moved to separate files
 * Added more callbacks to avoid use of wxWidgets
 * Added function for color picker rendering

 * For string formatting is used Slic3r::format instead of GUI::format (To avoid including wx/string)
 * All code related to DoubleSlider extracted from Slic3r name space
This commit is contained in:
YuSanka 2024-04-22 16:37:36 +02:00 committed by Lukas Matena
parent 6b83f135c9
commit 785ad4ce95
16 changed files with 1677 additions and 1479 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -7,8 +7,6 @@
#include "ImGuiDoubleSlider.hpp"
namespace Slic3r {
namespace DoubleSlider {
class DSForGcode : public Manager<unsigned int>
@ -36,7 +34,5 @@ private:
} // DoubleSlider;
} // Slic3r
#endif // slic3r_GUI_DoubleSliderForGcode_hpp_

File diff suppressed because it is too large Load Diff

View File

@ -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 <vector>
#include <set>
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<bool(const Layer*)> 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<std::string>* m_colors {nullptr};
std::string get_color_for_tick(TickCode tick, Type type, const int extruder);
public:
std::set<TickCode> 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<TickCode>::iterator it, double print_z);
void switch_code(Type type_from, Type type_to);
bool switch_code_for_tick(std::set<TickCode>::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<int> 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<std::string>* 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<size_t> 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<double>
{
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<std::string>& 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<void()> cb) { m_cb_ticks_changed = cb; };
// jump to selected layer
void jump_to_value();
// just for editor
void SetExtruderColors(const std::vector<std::string>& 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<void()> cb)
{ m_cb_ticks_changed = cb; };
void set_callback_on_check_gcode(std::function<void(Type)> cb )
{ m_ticks.set_callback_on_check_gcode(cb); }
void set_callback_on_get_extruder_colors(std::function<std::vector<std::string>()> cb)
{ m_cb_get_extruder_colors = cb; }
void set_callback_on_get_print (std::function<const Slic3r::Print& ()> cb)
{ m_cb_get_print = cb; }
void set_callback_on_empty_auto_color_change(std::function<void()> cb)
{ m_ticks.set_callback_on_empty_auto_color_change(cb); }
void set_callback_on_get_custom_code(std::function<std::string(const std::string&, double)> cb)
{ m_ticks.set_callback_on_get_custom_code(cb); }
void set_callback_on_get_pause_print_msg(std::function<std::string(const std::string&, double)> cb)
{ m_ticks.set_callback_on_get_pause_print_msg(cb); }
void set_callback_on_get_new_color(std::function<std::string(const std::string&)> cb)
{ m_ticks.set_callback_on_get_new_color(cb); }
void set_callback_on_show_info_msg(std::function<int(const std::string&, int)> cb)
{ m_ticks.set_callback_on_show_info_msg(cb); }
void set_callback_on_show_warning_msg(std::function<int(const std::string&, int)> cb)
{ m_ticks.set_callback_on_show_warning_msg(cb); }
void set_callback_on_get_extruders_cnt(std::function<int()> cb)
{ m_ticks.set_callback_on_get_extruders_cnt(cb); }
void set_callback_on_get_extruders_sequence(std::function<bool(ExtrudersSequence&)> 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<TickCode>::const_iterator it) const;
std::string get_color_for_color_change_tick(std::set<TickCode>::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<int, 2> 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<double> m_layers_times;
std::vector<double> m_layers_values;
std::vector<std::string> 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<void()> 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<void()> m_cb_ticks_changed { nullptr };
std::function<std::vector<std::string>()> m_cb_get_extruder_colors { nullptr };
std::function<const Slic3r::Print&()> m_cb_get_print { nullptr };
};
} // DoubleSlider;
} // Slic3r
#endif // slic3r_GUI_DoubleSliderForLayers_hpp_

View File

@ -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 <wx/listbook.h>
@ -34,6 +36,7 @@
#include <wx/combo.h>
#include <wx/combobox.h>
#include <wx/checkbox.h>
#include <wx/colordlg.h>
// 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<wxTextCtrl*>(item)) {
textctrl->SetInsertionPointEnd();
wxButton* btn_OK = static_cast<wxButton*>(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<DoubleSlider::DSForLayers>(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<std::string> {
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<DoubleSlider::DSForGcode>(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<double>& 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() ?

View File

@ -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<DoubleSlider::DSForLayers> m_layers_slider{ nullptr };
std::unique_ptr<DoubleSlider::DSForGcode> m_moves_slider { nullptr };
public:
enum class OptionType : unsigned int

View File

@ -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

View File

@ -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<ValType>(m_alternate_values.empty() ? m_values[pos] : m_alternate_values[pos]));
}
@ -276,10 +276,7 @@ private:
};
} // GUI
} // Slic3r
} // DoubleSlider
#endif // slic3r_ImGUI_DoubleSlider_hpp_

View File

@ -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

View File

@ -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 };

View File

@ -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

View File

@ -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

View File

@ -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<double>::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<CustomGCode::Item>& 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<int, 2> TickCodeManager::get_active_extruders_for_tick(int tick, Mode main_mode) const
{
int default_initial_extruder = main_mode == MultiAsSingle ? std::max<int>(1, only_extruder_id) : 1;
std::array<int, 2> 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<bool(const Layer*)> 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<int> 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<int> 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<unsigned>& 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<int> 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<TickCode>::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<TickCode>::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<int> 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<int>(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<int>(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<TickCode>::const_iterator it) const
{
const int current_extruder = it->extruder == 0 ? std::max<int>(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<TickCode>::const_iterator it) const
{
const int def_extruder = std::max<int>(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

View File

@ -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 <vector>
#include <set>
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<bool(const Slic3r::Layer*)> 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<size_t> 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<double>* 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<void()> m_cb_notify_empty_color_change { nullptr };
std::function<void(Type type)> m_cb_check_gcode_and_notify { nullptr };
std::function<std::string(const std::string&, double)> m_cb_get_custom_code { nullptr };
std::function<std::string(const std::string&, double)> m_cb_get_pause_print_msg { nullptr };
std::function<std::string(const std::string&)> m_cb_get_new_color { nullptr };
std::function<int(const std::string&, int)> m_cb_show_info_msg { nullptr };
std::function<int(const std::string&, int)> m_cb_show_warning_msg { nullptr };
std::function<int()> m_cb_get_extruders_cnt { nullptr };
std::function<bool(ExtrudersSequence&)> m_cb_get_extruders_sequence { nullptr };
public:
TickCodeManager();
~TickCodeManager() {}
std::set<TickCode> 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<std::string> 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<TickCode>::iterator it, double print_z);
void switch_code(Type type_from, Type type_to);
bool switch_code_for_tick(std::set<TickCode>::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<int> 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<int, 2> get_active_extruders_for_tick(int tick, Mode main_mode) const;
std::string get_color_for_tool_change_tick(std::set<TickCode>::const_iterator it) const;
std::string get_color_for_color_change_tick(std::set<TickCode>::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<double>* values) { m_values = values; }
void set_callback_on_empty_auto_color_change(std::function<void()> cb)
{ m_cb_notify_empty_color_change = cb; }
void set_callback_on_check_gcode(std::function<void(Type)> cb )
{ m_cb_check_gcode_and_notify = cb; }
void set_callback_on_get_custom_code(std::function<std::string(const std::string&, double)> cb)
{ m_cb_get_custom_code = cb; }
void set_callback_on_get_pause_print_msg(std::function<std::string(const std::string&, double)> cb)
{ m_cb_get_pause_print_msg = cb; }
void set_callback_on_get_new_color(std::function<std::string(const std::string&)> cb)
{ m_cb_get_new_color = cb; }
void set_callback_on_show_info_msg(std::function<int(const std::string&, int)> cb)
{ m_cb_show_info_msg = cb; }
void set_callback_on_show_warning_msg(std::function<int(const std::string&, int)> cb)
{ m_cb_show_warning_msg = cb; }
void set_callback_on_get_extruders_cnt(std::function<int()> cb)
{ m_cb_get_extruders_cnt = cb; }
void set_callback_on_get_extruders_sequence(std::function<bool(ExtrudersSequence&)> cb)
{ m_cb_get_extruders_sequence = cb; }
};
} // DoubleSlider;
#endif // slic3r_GUI_TickCodesManager_hpp_