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));