diff --git a/resources/images/plate_settings_arrow.svg b/resources/images/plate_settings_arrow.svg new file mode 100644 index 000000000..e772a9671 --- /dev/null +++ b/resources/images/plate_settings_arrow.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 42d5d7628..ed0b0051c 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -370,6 +370,8 @@ set(SLIC3R_GUI_SOURCES GUI/ProjectDirtyStateManager.cpp GUI/DesktopIntegrationDialog.cpp GUI/DesktopIntegrationDialog.hpp + GUI/DragCanvas.cpp + GUI/DragCanvas.hpp GUI/PublishDialog.cpp GUI/PublishDialog.hpp GUI/RecenterDialog.cpp diff --git a/src/slic3r/GUI/DragCanvas.cpp b/src/slic3r/GUI/DragCanvas.cpp new file mode 100644 index 000000000..38a827dca --- /dev/null +++ b/src/slic3r/GUI/DragCanvas.cpp @@ -0,0 +1,250 @@ +#include "DragCanvas.hpp" +#include "wxExtensions.hpp" +#include "GUI_App.hpp" + +namespace Slic3r { namespace GUI { + +#define CANVAS_WIDTH FromDIP(240) +#define SHAPE_SIZE FromDIP(20) +#define SHAPE_GAP (2 * SHAPE_SIZE) +#define LINE_HEIGHT (SHAPE_SIZE + FromDIP(5)) +static const wxColour CANVAS_BORDER_COLOR = wxColour(0xCECECE); + +DragCanvas::DragCanvas(wxWindow* parent, const std::vector& colors, const std::vector& order) + : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize) + , m_drag_mode(DragMode::NONE) + , m_max_shape_pos(wxPoint(0, 0)) +{ + SetBackgroundColour(*wxWHITE); + + m_arrow_bmp = create_scaled_bitmap("plate_settings_arrow", this, 16); + + set_shape_list(colors, order); + + Bind(wxEVT_PAINT, &DragCanvas::on_paint, this); + Bind(wxEVT_ERASE_BACKGROUND, &DragCanvas::on_erase, this); + Bind(wxEVT_LEFT_DOWN, &DragCanvas::on_mouse, this); + Bind(wxEVT_LEFT_UP, &DragCanvas::on_mouse, this); + Bind(wxEVT_MOTION, &DragCanvas::on_mouse, this); + Bind(wxEVT_ENTER_WINDOW, &DragCanvas::on_mouse, this); + Bind(wxEVT_LEAVE_WINDOW, &DragCanvas::on_mouse, this); +} + +DragCanvas::~DragCanvas() +{ + for (int i = 0; i < m_dragshape_list.size(); i++) { + delete m_dragshape_list[i]; + } + m_dragshape_list.clear(); + + if (m_drag_image) + delete m_drag_image; +} + +void DragCanvas::set_shape_list(const std::vector& colors, const std::vector& order) +{ + m_dragshape_list.clear(); + + for (int i = 0; i < order.size(); i++) { + wxBitmap* bmp = get_extruder_color_icon(colors[order[i] - 1], std::to_string(order[i]), SHAPE_SIZE, SHAPE_SIZE); + DragShape* shape = new DragShape(*bmp, order[i]); + m_dragshape_list.push_back(shape); + } + + // wrapping lines + for (int i = 0; i < order.size(); i++) { + int shape_pos_x = FromDIP(10) + i * SHAPE_GAP; + int shape_pos_y = FromDIP(5); + while (shape_pos_x + SHAPE_SIZE > CANVAS_WIDTH) { + shape_pos_x -= CANVAS_WIDTH; + shape_pos_y += LINE_HEIGHT; + + int row = shape_pos_y / LINE_HEIGHT + 1; + if (row > 1) { + if (row % 2 == 0) { + shape_pos_x += (SHAPE_GAP - SHAPE_SIZE); + } + else { + shape_pos_x -= (SHAPE_GAP - SHAPE_SIZE); + shape_pos_x += SHAPE_GAP; + } + } + } + + m_max_shape_pos.x = std::max(m_max_shape_pos.x, shape_pos_x); + m_max_shape_pos.y = std::max(m_max_shape_pos.y, shape_pos_y); + m_dragshape_list[i]->SetPosition(wxPoint(shape_pos_x, shape_pos_y)); + } + + int rows = m_max_shape_pos.y / LINE_HEIGHT + 1; + SetMinSize(wxSize(CANVAS_WIDTH, LINE_HEIGHT * rows + FromDIP(5))); +} + +std::vector DragCanvas::get_shape_list_order() +{ + std::vector res; + std::vector ordered_list = get_ordered_shape_list(); + res.reserve(ordered_list.size()); + for (auto& item : ordered_list) { + res.push_back(item->get_index()); + } + return res; +} + +std::vector DragCanvas::get_ordered_shape_list() +{ + std::vector ordered_list = m_dragshape_list; + std::sort(ordered_list.begin(), ordered_list.end(), [](const DragShape* l, const DragShape* r) { + if (l->GetPosition().y < r->GetPosition().y) + return true; + else if (l->GetPosition().y == r->GetPosition().y) { + return l->GetPosition().x < r->GetPosition().x; + } + else { + return false; + } + }); + return ordered_list; +} + +void DragCanvas::on_paint(wxPaintEvent& event) +{ + wxPaintDC dc(this); + + for (int i = 0; i < m_dragshape_list.size(); i++) { + m_dragshape_list[i]->paint(dc, m_dragshape_list[i] == m_slot_shape); + + auto arrow_pos = m_dragshape_list[i]->GetPosition() - wxSize(SHAPE_GAP - SHAPE_SIZE, 0); + if (arrow_pos.x < 0) { + arrow_pos.x = m_max_shape_pos.x; + arrow_pos.y -= LINE_HEIGHT; + } + arrow_pos += wxSize((SHAPE_GAP - SHAPE_SIZE - m_arrow_bmp.GetWidth() / dc.GetContentScaleFactor()) / 2, (SHAPE_SIZE - m_arrow_bmp.GetHeight() / dc.GetContentScaleFactor()) / 2); + dc.DrawBitmap(m_arrow_bmp, arrow_pos); + } +} + +void DragCanvas::on_erase(wxEraseEvent& event) +{ + wxSize size = GetSize(); + if (event.GetDC()) + { + auto& dc = *(event.GetDC()); + dc.SetPen(CANVAS_BORDER_COLOR); + dc.SetBrush(*wxWHITE_BRUSH); + dc.DrawRectangle({ 0,0 }, size); + } + else + { + wxClientDC dc(this); + dc.SetPen(CANVAS_BORDER_COLOR); + dc.SetBrush(*wxWHITE_BRUSH); + dc.DrawRectangle({ 0,0 }, size); + } +} + +void DragCanvas::on_mouse(wxMouseEvent& event) +{ + if (event.LeftDown()) + { + DragShape* shape = find_shape(event.GetPosition()); + if (shape) + { + m_drag_mode = DragMode::DRAGGING; + m_drag_start_pos = event.GetPosition(); + m_dragging_shape = shape; + + if (m_drag_image) { + delete m_drag_image; + m_drag_image = nullptr; + } + m_drag_image = new wxDragImage(m_dragging_shape->GetBitmap()); + + wxPoint offset = m_drag_start_pos - m_dragging_shape->GetPosition(); + bool success = m_drag_image->BeginDrag(offset, this); + if (!success) + { + delete m_drag_image; + m_drag_image = nullptr; + m_drag_mode = DragMode::NONE; + } + } + } + else if (event.Dragging() && m_drag_mode == DragMode::DRAGGING) + { + DragShape* shape = find_shape(event.GetPosition()); + + if (shape) { + if (shape != m_dragging_shape) { + m_slot_shape = shape; + Refresh(); + Update(); + } + } + else { + if (m_slot_shape) { + m_slot_shape = nullptr; + Refresh(); + Update(); + } + } + m_drag_image->Move(event.GetPosition()); + m_drag_image->Show(); + } + else if (event.LeftUp() && m_drag_mode != DragMode::NONE) + { + m_drag_mode = DragMode::NONE; + + if (m_drag_image) { + m_drag_image->Hide(); + m_drag_image->EndDrag(); + + // swap position + if (m_slot_shape && m_dragging_shape) { + auto highlighted_pos = m_slot_shape->GetPosition(); + m_slot_shape->SetPosition(m_dragging_shape->GetPosition()); + m_dragging_shape->SetPosition(highlighted_pos); + m_slot_shape = nullptr; + m_dragging_shape = nullptr; + } + } + Refresh(); + Update(); + } +} + +DragShape* DragCanvas::find_shape(const wxPoint& pt) const +{ + for (auto& shape : m_dragshape_list) { + if (shape->hit_test(pt)) + return shape; + } + return nullptr; +} + + +DragShape::DragShape(const wxBitmap& bitmap, int index) + : m_bitmap(bitmap) + , m_pos(wxPoint(0,0)) + , m_index(index) +{ +} + +bool DragShape::hit_test(const wxPoint& pt) const +{ + wxRect rect(wxRect(m_pos.x, m_pos.y, m_bitmap.GetWidth(), m_bitmap.GetHeight())); + return rect.Contains(pt.x, pt.y); +} + +void DragShape::paint(wxDC& dc, bool highlight) +{ + dc.DrawBitmap(m_bitmap, m_pos); + if (highlight) + { + dc.SetPen(*wxWHITE_PEN); + dc.SetBrush(*wxTRANSPARENT_BRUSH); + dc.DrawRectangle(m_pos.x, m_pos.y, m_bitmap.GetWidth(), m_bitmap.GetHeight()); + } +} + +}} \ No newline at end of file diff --git a/src/slic3r/GUI/DragCanvas.hpp b/src/slic3r/GUI/DragCanvas.hpp new file mode 100644 index 000000000..7d4c72052 --- /dev/null +++ b/src/slic3r/GUI/DragCanvas.hpp @@ -0,0 +1,65 @@ +#ifndef slic3r_GUI_DragCanvas_hpp_ +#define slic3r_GUI_DragCanvas_hpp_ + +#include "wx/bitmap.h" +#include "wx/dragimag.h" + +namespace Slic3r { namespace GUI { + +class DragShape : public wxObject +{ +public: + DragShape(const wxBitmap& bitmap, int index); + ~DragShape() {} + + wxPoint GetPosition() const { return m_pos; } + void SetPosition(const wxPoint& pos) { m_pos = pos; } + + const wxBitmap& GetBitmap() const { return m_bitmap; } + void SetBitmap(const wxBitmap& bitmap) { m_bitmap = bitmap; } + + int get_index() { return m_index; } + + bool hit_test(const wxPoint& pt) const; + void paint(wxDC& dc, bool highlight = false); + +protected: + wxPoint m_pos; + wxBitmap m_bitmap; + int m_index; +}; + + +enum class DragMode { + NONE, + DRAGGING, +}; +class DragCanvas : public wxPanel +{ +public: + DragCanvas(wxWindow* parent, const std::vector& colors, const std::vector& order); + ~DragCanvas(); + void set_shape_list(const std::vector& colors, const std::vector& order); + std::vector get_shape_list_order(); + std::vector get_ordered_shape_list(); + +protected: + void on_paint(wxPaintEvent& event); + void on_erase(wxEraseEvent& event); + void on_mouse(wxMouseEvent& event); + DragShape* find_shape(const wxPoint& pt) const; + +private: + std::vector m_dragshape_list; + DragMode m_drag_mode; + DragShape* m_dragging_shape{ nullptr }; + DragShape* m_slot_shape{ nullptr }; // The shape that's being highlighted + wxDragImage* m_drag_image{ nullptr }; + wxPoint m_drag_start_pos; + wxBitmap m_arrow_bmp; + wxPoint m_max_shape_pos; +}; + + +}} +#endif diff --git a/src/slic3r/GUI/PartPlate.cpp b/src/slic3r/GUI/PartPlate.cpp index 0c3569602..b91dcb0c2 100644 --- a/src/slic3r/GUI/PartPlate.cpp +++ b/src/slic3r/GUI/PartPlate.cpp @@ -931,13 +931,13 @@ void PartPlate::render_icons(bool bottom, bool only_body, int hover_id) if (m_partplate_list->render_plate_settings) { if (hover_id == 5) { - if (get_bed_type() == BedType::btDefault && get_print_seq() == PrintSequence::ByDefault) + if (get_bed_type() == BedType::btDefault && get_print_seq() == PrintSequence::ByDefault && get_first_layer_print_sequence().empty()) render_icon_texture(position_id, tex_coords_id, m_plate_settings_icon, m_partplate_list->m_plate_settings_hovered_texture, m_plate_settings_vbo_id); else render_icon_texture(position_id, tex_coords_id, m_plate_settings_icon, m_partplate_list->m_plate_settings_changed_hovered_texture, m_plate_settings_vbo_id); } else { - if (get_bed_type() == BedType::btDefault && get_print_seq() == PrintSequence::ByDefault) + if (get_bed_type() == BedType::btDefault && get_print_seq() == PrintSequence::ByDefault && get_first_layer_print_sequence().empty()) render_icon_texture(position_id, tex_coords_id, m_plate_settings_icon, m_partplate_list->m_plate_settings_texture, m_plate_settings_vbo_id); else render_icon_texture(position_id, tex_coords_id, m_plate_settings_icon, m_partplate_list->m_plate_settings_changed_texture, m_plate_settings_vbo_id); diff --git a/src/slic3r/GUI/PlateSettingsDialog.cpp b/src/slic3r/GUI/PlateSettingsDialog.cpp index 51ce7e954..8f3a9445e 100644 --- a/src/slic3r/GUI/PlateSettingsDialog.cpp +++ b/src/slic3r/GUI/PlateSettingsDialog.cpp @@ -43,6 +43,37 @@ PlateSettingsDialog::PlateSettingsDialog(wxWindow* parent, wxWindowID id, const top_sizer->Add(m_print_seq_txt, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT |wxALL, FromDIP(5)); top_sizer->Add(m_print_seq_choice, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT |wxALL, FromDIP(5)); + m_first_layer_print_seq_choice = new ComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(FromDIP(240), -1), 0, NULL, wxCB_READONLY); + m_first_layer_print_seq_choice->Append(_L("Auto")); + m_first_layer_print_seq_choice->Append(_L("Customize")); + m_first_layer_print_seq_choice->SetSelection(0); + m_first_layer_print_seq_choice->Bind(wxEVT_COMBOBOX, [this](auto& e) { + if (e.GetSelection() == 0) { + m_drag_canvas->Hide(); + } + else if (e.GetSelection() == 1) { + m_drag_canvas->Show(); + } + Layout(); + Fit(); + }); + wxStaticText* first_layer_txt = new wxStaticText(this, wxID_ANY, _L("First Layer print sequence")); + first_layer_txt->SetFont(Label::Body_14); + top_sizer->Add(first_layer_txt, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT | wxALL, FromDIP(5)); + top_sizer->Add(m_first_layer_print_seq_choice, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, FromDIP(5)); + + const std::vector extruder_colours = wxGetApp().plater()->get_extruder_colors_from_plater_config(); + std::vector order; + if (order.empty()) { + for (int i = 1; i <= extruder_colours.size(); i++) { + order.push_back(i); + } + } + m_drag_canvas = new DragCanvas(this, extruder_colours, order); + m_drag_canvas->Hide(); + top_sizer->Add(0, 0, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT | wxALL, FromDIP(5)); + top_sizer->Add(m_drag_canvas, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT | wxALL, FromDIP(5)); + m_sizer_main->Add(top_sizer, 0, wxEXPAND | wxALL, FromDIP(30)); auto sizer_button = new wxBoxSizer(wxHORIZONTAL); @@ -119,6 +150,22 @@ void PlateSettingsDialog::sync_print_seq(int print_seq) } } +void PlateSettingsDialog::sync_first_layer_print_seq(int selection, const std::vector& seq) +{ + if (m_first_layer_print_seq_choice != nullptr) { + if (selection == 1) { + const std::vector extruder_colours = wxGetApp().plater()->get_extruder_colors_from_plater_config(); + m_drag_canvas->set_shape_list(extruder_colours, seq); + } + m_first_layer_print_seq_choice->SetSelection(selection); + + wxCommandEvent event(wxEVT_COMBOBOX); + event.SetInt(selection); + event.SetEventObject(m_first_layer_print_seq_choice); + wxPostEvent(m_first_layer_print_seq_choice, event); + } +} + wxString PlateSettingsDialog::to_bed_type_name(BedType bed_type) { switch (bed_type) { case btDefault: @@ -149,6 +196,11 @@ void PlateSettingsDialog::on_dpi_changed(const wxRect& suggested_rect) m_button_cancel->Rescale(); } +std::vector PlateSettingsDialog::get_first_layer_print_seq() +{ + return m_drag_canvas->get_shape_list_order(); +} + //PlateNameEditDialog PlateNameEditDialog::PlateNameEditDialog(wxWindow *parent, wxWindowID id, const wxString &title, const wxPoint &pos, const wxSize &size, long style) diff --git a/src/slic3r/GUI/PlateSettingsDialog.hpp b/src/slic3r/GUI/PlateSettingsDialog.hpp index 83310d7e6..724d54bbf 100644 --- a/src/slic3r/GUI/PlateSettingsDialog.hpp +++ b/src/slic3r/GUI/PlateSettingsDialog.hpp @@ -6,6 +6,7 @@ #include "Widgets/Button.hpp" #include "Widgets/RadioBox.hpp" #include "Widgets/ComboBox.hpp" +#include "DragCanvas.hpp" namespace Slic3r { namespace GUI { @@ -31,6 +32,7 @@ public: ~PlateSettingsDialog(); void sync_bed_type(BedType type); void sync_print_seq(int print_seq = 0); + void sync_first_layer_print_seq(int selection, const std::vector& seq = std::vector()); wxString to_bed_type_name(BedType bed_type); wxString to_print_sequence_name(PrintSequence print_seq); void on_dpi_changed(const wxRect& suggested_rect) override; @@ -49,7 +51,18 @@ public: return choice; }; + int get_first_layer_print_seq_choice() { + int choice = 0; + if (m_first_layer_print_seq_choice != nullptr) + choice = m_first_layer_print_seq_choice->GetSelection(); + return choice; + }; + + std::vector get_first_layer_print_seq(); + protected: + DragCanvas* m_drag_canvas; + ComboBox* m_first_layer_print_seq_choice { nullptr }; ComboBox* m_print_seq_choice { nullptr }; ComboBox* m_bed_type_choice { nullptr }; Button* m_button_ok; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 1934b75f2..8c8c87802 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -12115,6 +12115,11 @@ int Plater::select_plate_by_hover_id(int hover_id, bool right_click, bool isModi else dlg.sync_print_seq(0); + auto first_layer_print_seq = curr_plate->get_first_layer_print_sequence(); + if (first_layer_print_seq.empty()) + dlg.sync_first_layer_print_seq(0); + else + dlg.sync_first_layer_print_seq(1, curr_plate->get_first_layer_print_sequence()); dlg.Bind(EVT_SET_BED_TYPE_CONFIRM, [this, plate_index, &dlg](wxCommandEvent& e) { PartPlate *curr_plate = p->partplate_list.get_curr_plate(); @@ -12127,6 +12132,11 @@ int Plater::select_plate_by_hover_id(int hover_id, bool right_click, bool isModi } BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format("select bed type %1% for plate %2% at plate side")%bt_sel %plate_index; + if (dlg.get_first_layer_print_seq_choice() != 0) + curr_plate->set_first_layer_print_sequence(dlg.get_first_layer_print_seq()); + else + curr_plate->set_first_layer_print_sequence({}); + int ps_sel = dlg.get_print_seq_choice(); if (ps_sel != 0) curr_plate->set_print_seq(PrintSequence(ps_sel - 1));