diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 7d451a4cb..d6104d98c 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -51,6 +51,11 @@ void PrintConfigDef::init_common_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionPoints{ Vec2d(0, 0), Vec2d(200, 0), Vec2d(200, 200), Vec2d(0, 200) }); + def = this->add("bed_custom_texture", coString); + def->label = L("Bed custom texture"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionString("")); + def = this->add("layer_height", coFloat); def->label = L("Layer height"); def->category = L("Layers and Perimeters"); diff --git a/src/slic3r/GUI/2DBed.cpp b/src/slic3r/GUI/2DBed.cpp index a339f3e66..d2075f673 100644 --- a/src/slic3r/GUI/2DBed.cpp +++ b/src/slic3r/GUI/2DBed.cpp @@ -18,10 +18,9 @@ wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(25 * wxGetApp().em_unit(), - #ifdef __APPLE__ m_user_drawn_background = false; #endif /*__APPLE__*/ - Bind(wxEVT_PAINT, ([this](wxPaintEvent &/* e */) { repaint(); })); - Bind(wxEVT_SIZE, ([this](wxSizeEvent & /* e */) { Refresh(); })); } -void Bed_2D::repaint() + +void Bed_2D::repaint(const std::vector& shape) { wxAutoBufferedPaintDC dc(this); auto cw = GetSize().GetWidth(); @@ -41,7 +40,7 @@ void Bed_2D::repaint() dc.DrawRectangle(rect.GetLeft(), rect.GetTop(), rect.GetWidth(), rect.GetHeight()); } - if (m_bed_shape.empty()) + if (shape.empty()) return; // reduce size to have some space around the drawn shape @@ -52,10 +51,9 @@ void Bed_2D::repaint() auto ccenter = cbb.center(); // get bounding box of bed shape in G - code coordinates - auto bed_shape = m_bed_shape; - auto bed_polygon = Polygon::new_scale(m_bed_shape); - auto bb = BoundingBoxf(m_bed_shape); - bb.merge(Vec2d(0, 0)); // origin needs to be in the visible area + auto bed_polygon = Polygon::new_scale(shape); + auto bb = BoundingBoxf(shape); + bb.merge(Vec2d(0, 0)); // origin needs to be in the visible area auto bw = bb.size()(0); auto bh = bb.size()(1); auto bcenter = bb.center(); @@ -73,8 +71,8 @@ void Bed_2D::repaint() // draw bed fill dc.SetBrush(wxBrush(wxColour(255, 255, 255), wxBRUSHSTYLE_SOLID)); wxPointList pt_list; - for (auto pt: m_bed_shape) - { + for (auto pt : shape) + { Point pt_pix = to_pixels(pt, ch); pt_list.push_back(new wxPoint(pt_pix(0), pt_pix(1))); } @@ -155,13 +153,13 @@ void Bed_2D::repaint() // convert G - code coordinates into pixels -Point Bed_2D::to_pixels(Vec2d point, int height) +Point Bed_2D::to_pixels(const Vec2d& point, int height) { auto p = point * m_scale_factor + m_shift; return Point(p(0) + Border, height - p(1) + Border); } -void Bed_2D::set_pos(Vec2d pos) +void Bed_2D::set_pos(const Vec2d& pos) { m_pos = pos; Refresh(); diff --git a/src/slic3r/GUI/2DBed.hpp b/src/slic3r/GUI/2DBed.hpp index a61fb313d..80926bea7 100644 --- a/src/slic3r/GUI/2DBed.hpp +++ b/src/slic3r/GUI/2DBed.hpp @@ -17,16 +17,13 @@ class Bed_2D : public wxPanel Vec2d m_shift = Vec2d::Zero(); Vec2d m_pos = Vec2d::Zero(); - Point to_pixels(Vec2d point, int height); - void repaint(); - void set_pos(Vec2d pos); + Point to_pixels(const Vec2d& point, int height); + void set_pos(const Vec2d& pos); public: - Bed_2D(wxWindow* parent); - ~Bed_2D() {} + explicit Bed_2D(wxWindow* parent); - std::vector m_bed_shape; - + void repaint(const std::vector& shape); }; diff --git a/src/slic3r/GUI/BedShapeDialog.cpp b/src/slic3r/GUI/BedShapeDialog.cpp index 98a5178df..1beda280b 100644 --- a/src/slic3r/GUI/BedShapeDialog.cpp +++ b/src/slic3r/GUI/BedShapeDialog.cpp @@ -10,17 +10,18 @@ #include "libslic3r/Polygon.hpp" #include "boost/nowide/iostream.hpp" +#include #include namespace Slic3r { namespace GUI { -void BedShapeDialog::build_dialog(const ConfigOptionPoints& default_pt) +void BedShapeDialog::build_dialog(const ConfigOptionPoints& default_pt, const ConfigOptionString& custom_texture) { SetFont(wxGetApp().normal_font()); m_panel = new BedShapePanel(this); - m_panel->build_panel(default_pt); + m_panel->build_panel(default_pt, custom_texture); auto main_sizer = new wxBoxSizer(wxVERTICAL); main_sizer->Add(m_panel, 1, wxEXPAND); @@ -51,14 +52,19 @@ void BedShapeDialog::on_dpi_changed(const wxRect &suggested_rect) Refresh(); } -void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt) +const std::string BedShapePanel::NONE = "None"; +const std::string BedShapePanel::EMPTY_STRING = ""; + +void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt, const ConfigOptionString& custom_texture) { + m_shape = default_pt.values; + m_custom_texture = custom_texture.value.empty() ? NONE : custom_texture.value; + auto sbsizer = new wxStaticBoxSizer(wxVERTICAL, this, _(L("Shape"))); sbsizer->GetStaticBox()->SetFont(wxGetApp().bold_font()); // shape options - m_shape_options_book = new wxChoicebook(this, wxID_ANY, wxDefaultPosition, - wxSize(25*wxGetApp().em_unit(), -1), wxCHB_TOP); + m_shape_options_book = new wxChoicebook(this, wxID_ANY, wxDefaultPosition, wxSize(25*wxGetApp().em_unit(), -1), wxCHB_TOP); sbsizer->Add(m_shape_options_book); auto optgroup = init_shape_options_page(_(L("Rectangular"))); @@ -106,6 +112,8 @@ void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt) }; optgroup->append_line(line); + wxPanel* texture_panel = init_texture_panel(); + Bind(wxEVT_CHOICEBOOK_PAGE_CHANGED, ([this](wxCommandEvent& e) { update_shape(); @@ -113,13 +121,20 @@ void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt) // right pane with preview canvas m_canvas = new Bed_2D(this); - m_canvas->m_bed_shape = default_pt.values; - // main sizer - auto top_sizer = new wxBoxSizer(wxHORIZONTAL); - top_sizer->Add(sbsizer, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 10); - if (m_canvas) - top_sizer->Add(m_canvas, 1, wxEXPAND | wxALL, 10) ; + if (m_canvas != nullptr) + { + m_canvas->Bind(wxEVT_PAINT, [this](wxPaintEvent& e) { m_canvas->repaint(m_shape); }); + m_canvas->Bind(wxEVT_SIZE, [this](wxSizeEvent& e) { m_canvas->Refresh(); }); + } + + wxSizer* left_sizer = new wxBoxSizer(wxVERTICAL); + left_sizer->Add(sbsizer, 0, wxEXPAND); + left_sizer->Add(texture_panel, 1, wxEXPAND); + + wxSizer* top_sizer = new wxBoxSizer(wxHORIZONTAL); + top_sizer->Add(left_sizer, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 10); + top_sizer->Add(m_canvas, 1, wxEXPAND | wxALL, 10); SetSizerAndFit(top_sizer); @@ -150,6 +165,66 @@ ConfigOptionsGroupShp BedShapePanel::init_shape_options_page(const wxString& tit return optgroup; } +wxPanel* BedShapePanel::init_texture_panel() +{ + wxPanel* panel = new wxPanel(this); + ConfigOptionsGroupShp optgroup = std::make_shared(panel, _(L("Texture"))); + + optgroup->label_width = 10; + optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + update_shape(); + }; + + Line line{ "", "" }; + line.full_width = 1; + line.widget = [this](wxWindow* parent) { + wxButton* load_btn = new wxButton(parent, wxID_ANY, _(L("Load..."))); + wxSizer* load_sizer = new wxBoxSizer(wxHORIZONTAL); + load_sizer->Add(load_btn, 1, wxEXPAND); + + wxStaticText* filename_lbl = new wxStaticText(parent, wxID_ANY, _(NONE)); + wxSizer* filename_sizer = new wxBoxSizer(wxHORIZONTAL); + filename_sizer->Add(filename_lbl, 1, wxEXPAND); + + wxButton* remove_btn = new wxButton(parent, wxID_ANY, _(L("Remove"))); + wxSizer* remove_sizer = new wxBoxSizer(wxHORIZONTAL); + remove_sizer->Add(remove_btn, 1, wxEXPAND); + + wxSizer* sizer = new wxBoxSizer(wxVERTICAL); + sizer->Add(filename_sizer, 1, wxEXPAND); + sizer->Add(load_sizer, 1, wxEXPAND); + sizer->Add(remove_sizer, 1, wxEXPAND); + + load_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) + { + load_texture(); + })); + + remove_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) + { + m_custom_texture = NONE; + update_shape(); + })); + + filename_lbl->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e) + { + e.SetText(_(boost::filesystem::path(m_custom_texture).filename().string())); + })); + + remove_btn->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e) + { + e.Enable(m_custom_texture != NONE); + })); + + return sizer; + }; + optgroup->append_line(line); + + panel->SetSizerAndFit(optgroup->sizer); + + return panel; +} + // Called from the constructor. // Set the initial bed shape from a list of points. // Deduce the bed shape type(rect, circle, custom) @@ -232,7 +307,7 @@ void BedShapePanel::set_shape(const ConfigOptionPoints& points) // This is a custom bed shape, use the polygon provided. m_shape_options_book->SetSelection(SHAPE_CUSTOM); // Copy the polygon to the canvas, make a copy of the array. - m_loaded_bed_shape = points.values; + m_loaded_shape = points.values; update_shape(); } @@ -277,11 +352,11 @@ void BedShapePanel::update_shape() x1 -= dx; y0 -= dy; y1 -= dy; - m_canvas->m_bed_shape = { Vec2d(x0, y0), - Vec2d(x1, y0), - Vec2d(x1, y1), - Vec2d(x0, y1)}; - } + m_shape = { Vec2d(x0, y0), + Vec2d(x1, y0), + Vec2d(x1, y1), + Vec2d(x0, y1) }; + } else if(page_idx == SHAPE_CIRCULAR) { double diameter; try{ @@ -293,16 +368,16 @@ void BedShapePanel::update_shape() if (diameter == 0.0) return ; auto r = diameter / 2; auto twopi = 2 * PI; - auto edges = 60; - std::vector points; - for (size_t i = 1; i <= 60; ++i) { - auto angle = i * twopi / edges; + auto edges = 72; + std::vector points; + for (size_t i = 1; i <= edges; ++i) { + auto angle = i * twopi / edges; points.push_back(Vec2d(r*cos(angle), r*sin(angle))); } - m_canvas->m_bed_shape = points; - } + m_shape = points; + } else if (page_idx == SHAPE_CUSTOM) - m_canvas->m_bed_shape = m_loaded_bed_shape; + m_shape = m_loaded_shape; update_preview(); } @@ -310,19 +385,23 @@ void BedShapePanel::update_shape() // Loads an stl file, projects it to the XY plane and calculates a polygon. void BedShapePanel::load_stl() { - wxFileDialog dialog(this, _(L("Choose a file to import bed shape from (STL/OBJ/AMF/3MF/PRUSA):")), "", "", - file_wildcards(FT_MODEL), wxFD_OPEN | wxFD_FILE_MUST_EXIST); + wxFileDialog dialog(this, _(L("Choose an STL file to import bed shape from:")), "", "", file_wildcards(FT_STL), wxFD_OPEN | wxFD_FILE_MUST_EXIST); if (dialog.ShowModal() != wxID_OK) return; - wxArrayString input_file; - dialog.GetPaths(input_file); + std::string file_name = dialog.GetPath().ToUTF8().data(); - std::string file_name = input_file[0].ToUTF8().data(); + if (!boost::iequals(boost::filesystem::path(file_name).extension().string().c_str(), ".stl")) + { + show_error(this, _(L("Invalid file format."))); + return; + } + + wxBusyCursor wait; Model model; try { - model = Model::read_from_file(file_name); + model = Model::read_from_file(file_name); } catch (std::exception &) { show_error(this, _(L("Error! Invalid model"))); @@ -346,7 +425,32 @@ void BedShapePanel::load_stl() for (auto pt : polygon.points) points.push_back(unscale(pt)); - m_loaded_bed_shape = points; + m_loaded_shape = points; + update_shape(); +} + +void BedShapePanel::load_texture() +{ + wxFileDialog dialog(this, _(L("Choose a file to import bed texture from (PNG/SVG):")), "", "", + file_wildcards(FT_TEX), wxFD_OPEN | wxFD_FILE_MUST_EXIST); + + if (dialog.ShowModal() != wxID_OK) + return; + + m_custom_texture = NONE; + + std::string file_name = dialog.GetPath().ToUTF8().data(); + std::string file_ext = boost::filesystem::path(file_name).extension().string(); + + if (!boost::iequals(file_ext.c_str(), ".png") && !boost::iequals(file_ext.c_str(), ".svg")) + { + show_error(this, _(L("Invalid file format."))); + return; + } + + wxBusyCursor wait; + + m_custom_texture = file_name; update_shape(); } diff --git a/src/slic3r/GUI/BedShapeDialog.hpp b/src/slic3r/GUI/BedShapeDialog.hpp index 81d47320d..3a8d1c82b 100644 --- a/src/slic3r/GUI/BedShapeDialog.hpp +++ b/src/slic3r/GUI/BedShapeDialog.hpp @@ -16,25 +16,32 @@ namespace GUI { using ConfigOptionsGroupShp = std::shared_ptr; class BedShapePanel : public wxPanel { + static const std::string NONE; + static const std::string EMPTY_STRING; + Bed_2D* m_canvas; - std::vector m_loaded_bed_shape; + std::vector m_shape; + std::vector m_loaded_shape; + std::string m_custom_texture; public: - BedShapePanel(wxWindow* parent) : wxPanel(parent, wxID_ANY) {} - ~BedShapePanel() {} - - void build_panel(const ConfigOptionPoints& default_pt); + BedShapePanel(wxWindow* parent) : wxPanel(parent, wxID_ANY), m_custom_texture(NONE) {} + ~BedShapePanel() {} + void build_panel(const ConfigOptionPoints& default_pt, const ConfigOptionString& custom_texture); // Returns the resulting bed shape polygon. This value will be stored to the ini file. - std::vector get_bed_shape() { return m_canvas->m_bed_shape; } + const std::vector& get_shape() const { return m_shape; } + const std::string& get_custom_texture() const { return (m_custom_texture != NONE) ? m_custom_texture : EMPTY_STRING; } private: ConfigOptionsGroupShp init_shape_options_page(const wxString& title); + wxPanel* init_texture_panel(); void set_shape(const ConfigOptionPoints& points); void update_preview(); void update_shape(); void load_stl(); - + void load_texture(); + wxChoicebook* m_shape_options_book; std::vector m_optgroups; @@ -49,8 +56,9 @@ public: wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) {} ~BedShapeDialog() {} - void build_dialog(const ConfigOptionPoints& default_pt); - std::vector get_bed_shape() { return m_panel->get_bed_shape(); } + void build_dialog(const ConfigOptionPoints& default_pt, const ConfigOptionString& custom_texture); + const std::vector& get_shape() const { return m_panel->get_shape(); } + const std::string& get_custom_texture() const { return m_panel->get_custom_texture(); } protected: void on_dpi_changed(const wxRect &suggested_rect) override; diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 59ba93677..bdd487931 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -532,14 +532,18 @@ PageBedShape::PageBedShape(ConfigWizard *parent) { append_text(_(L("Set the shape of your printer's bed."))); - shape_panel->build_panel(*wizard_p()->custom_config->option("bed_shape")); + shape_panel->build_panel(*wizard_p()->custom_config->option("bed_shape"), + *wizard_p()->custom_config->option("bed_custom_texture")); + append(shape_panel); } void PageBedShape::apply_custom_config(DynamicPrintConfig &config) { - const auto points(shape_panel->get_bed_shape()); + const std::vector& points = shape_panel->get_shape(); + const std::string& custom_texture = shape_panel->get_custom_texture(); config.set_key_value("bed_shape", new ConfigOptionPoints(points)); + config.set_key_value("bed_custom_texture", new ConfigOptionString(custom_texture)); } PageDiameters::PageDiameters(ConfigWizard *parent) @@ -1085,7 +1089,7 @@ ConfigWizard::ConfigWizard(wxWindow *parent, RunReason reason) p->load_vendors(); p->custom_config.reset(DynamicPrintConfig::new_from_defaults_keys({ - "gcode_flavor", "bed_shape", "nozzle_diameter", "filament_diameter", "temperature", "bed_temperature", + "gcode_flavor", "bed_shape", "bed_custom_texture", "nozzle_diameter", "filament_diameter", "temperature", "bed_temperature", })); p->index = new ConfigWizardIndex(this); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 8a376c3a3..e43181dc0 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -67,9 +67,12 @@ wxString file_wildcards(FileType file_type, const std::string &custom_extension) /* FT_MODEL */ "Known files (*.stl, *.obj, *.amf, *.xml, *.3mf, *.prusa)|*.stl;*.STL;*.obj;*.OBJ;*.amf;*.AMF;*.xml;*.XML;*.3mf;*.3MF;*.prusa;*.PRUSA", /* FT_PROJECT */ "Project files (*.3mf, *.amf)|*.3mf;*.3MF;*.amf;*.AMF", - /* FT_INI */ "INI files (*.ini)|*.ini;*.INI", - /* FT_SVG */ "SVG files (*.svg)|*.svg;*.SVG", - /* FT_PNGZIP */"Masked SLA files (*.sl1)|*.sl1;*.SL1", + /* FT_INI */ "INI files (*.ini)|*.ini;*.INI", + /* FT_SVG */ "SVG files (*.svg)|*.svg;*.SVG", + + /* FT_TEX */ "Texture (*.png, *.svg)|*.png;*.PNG;*.svg;*.SVG", + + /* FT_PNGZIP */ "Masked SLA files (*.sl1)|*.sl1;*.SL1", }; std::string out = defaults[file_type]; diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index e69503ff8..9c06ce8ca 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -44,6 +44,9 @@ enum FileType FT_INI, FT_SVG, + + FT_TEX, + FT_PNGZIP, FT_SIZE, diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp index 3d467d7f8..12fed23a0 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/slic3r/GUI/Preset.cpp @@ -411,7 +411,7 @@ const std::vector& Preset::printer_options() if (s_opts.empty()) { s_opts = { "printer_technology", - "bed_shape", "z_offset", "gcode_flavor", "use_relative_e_distances", "serial_port", "serial_speed", + "bed_shape", "bed_custom_texture", "z_offset", "gcode_flavor", "use_relative_e_distances", "serial_port", "serial_speed", "use_firmware_retraction", "use_volumetric_e", "variable_layer_height", "host_type", "print_host", "printhost_apikey", "printhost_cafile", "single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode", diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index a30736220..20936ba3d 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1856,12 +1856,15 @@ void TabPrinter::build_fff() btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { BedShapeDialog dlg(this); - dlg.build_dialog(*m_config->option("bed_shape")); + dlg.build_dialog(*m_config->option("bed_shape"), + *m_config->option("bed_custom_texture")); if (dlg.ShowModal() == wxID_OK) { - std::vector shape = dlg.get_bed_shape(); + const std::vector& shape = dlg.get_shape(); + const std::string& custom_texture = dlg.get_custom_texture(); if (!shape.empty()) { load_key_value("bed_shape", shape); + load_key_value("bed_custom_texture", custom_texture); update_changed_ui(); } } @@ -2062,12 +2065,15 @@ void TabPrinter::build_sla() btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { BedShapeDialog dlg(this); - dlg.build_dialog(*m_config->option("bed_shape")); + dlg.build_dialog(*m_config->option("bed_shape"), + *m_config->option("bed_custom_texture")); if (dlg.ShowModal() == wxID_OK) { - std::vector shape = dlg.get_bed_shape(); + const std::vector& shape = dlg.get_shape(); + const std::string& custom_texture = dlg.get_custom_texture(); if (!shape.empty()) { load_key_value("bed_shape", shape); + load_key_value("bed_custom_texture", custom_texture); update_changed_ui(); } }