diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 87cdc3777..100178108 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -56,10 +56,10 @@ inline Vec2i64 to_2d(const Vec3i64 &pt3) { return Vec2i64(pt3(0), pt3(1)); } inline Vec2f to_2d(const Vec3f &pt3) { return Vec2f (pt3(0), pt3(1)); } inline Vec2d to_2d(const Vec3d &pt3) { return Vec2d (pt3(0), pt3(1)); } -// inline Vec3crd to_3d(const Vec2crd &pt2, coord_t z) { return Vec3crd(pt2(0), pt2(1), z); } -// inline Vec3i64 to_3d(const Vec2i64 &pt2, int64_t z) { return Vec3i64(pt2(0), pt2(1), z); } -// inline Vec3f to_3d(const Vec2f &pt2, float z) { return Vec3f (pt2(0), pt2(1), z); } -// inline Vec3d to_3d(const Vec2d &pt2, double z) { return Vec3d (pt2(0), pt2(1), z); } +inline Vec3d to_3d(const Vec2d &v, double z) { return Vec3d(v(0), v(1), z); } +inline Vec3f to_3d(const Vec2f &v, float z) { return Vec3f(v(0), v(1), z); } +inline Vec3i64 to_3d(const Vec2i64 &v, float z) { return Vec3i64(v(0), v(1), z); } +inline Vec3crd to_3d(const Vec3crd &p, coord_t z) { return Vec3crd(p(0), p(1), z); } inline Vec2d unscale(coord_t x, coord_t y) { return Vec2d(unscale(x), unscale(y)); } inline Vec2d unscale(const Vec2crd &pt) { return Vec2d(unscale(pt(0)), unscale(pt(1))); } diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 7f88110bc..a646ab8fd 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -419,6 +419,7 @@ public: const PrintObject* get_object(int idx) const { return m_objects[idx]; } const PrintRegionPtrs& regions() const { return m_regions; } const PlaceholderParser& placeholder_parser() const { return m_placeholder_parser; } + PlaceholderParser& placeholder_parser() { return m_placeholder_parser; } // Returns extruder this eec should be printed with, according to PrintRegion config: static int get_extruder(const ExtrusionEntityCollection& fill, const PrintRegion ®ion); diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 54c90c20e..4e9edfbdc 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -39,6 +39,8 @@ add_library(libslic3r_gui STATIC ${LIBDIR}/slic3r/GUI/GUI_PreviewIface.hpp ${LIBDIR}/slic3r/GUI/GUI_App.cpp ${LIBDIR}/slic3r/GUI/GUI_App.hpp + ${LIBDIR}/slic3r/GUI/GUI_Utils.cpp + ${LIBDIR}/slic3r/GUI/GUI_Utils.hpp ${LIBDIR}/slic3r/GUI/MainFrame.cpp ${LIBDIR}/slic3r/GUI/MainFrame.hpp ${LIBDIR}/slic3r/GUI/Plater.cpp diff --git a/src/slic3r/GUI/BedShapeDialog.cpp b/src/slic3r/GUI/BedShapeDialog.cpp index 7c019599a..407da7ae3 100644 --- a/src/slic3r/GUI/BedShapeDialog.cpp +++ b/src/slic3r/GUI/BedShapeDialog.cpp @@ -292,7 +292,7 @@ void BedShapePanel::update_shape() void BedShapePanel::load_stl() { auto dialog = new wxFileDialog(this, _(L("Choose a file to import bed shape from (STL/OBJ/AMF/3MF/PRUSA):")), "", "", - MODEL_WILDCARD, wxFD_OPEN | wxFD_FILE_MUST_EXIST); + file_wildcards[FT_MODEL], wxFD_OPEN | wxFD_FILE_MUST_EXIST); if (dialog->ShowModal() != wxID_OK) { dialog->Destroy(); return; diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 02fc9b8b6..5dbc65877 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -611,8 +611,9 @@ void ColourPicker::BUILD() if (m_opt.width >= 0) size.SetWidth(m_opt.width); wxString clr(static_cast(m_opt.default_value)->get_at(m_opt_idx)); + // FIXME: verify clr is valid, otherwise this causes an assert auto temp = new wxColourPickerCtrl(m_parent, wxID_ANY, clr, wxDefaultPosition, size); - + // // recast as a wxWindow to fit the calling convention window = dynamic_cast(temp); diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index e06f6b721..0e23585f7 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -347,53 +347,6 @@ std::string into_u8(const wxString &str) return std::string(buffer_utf8.data()); } -wxWindow* export_option_creator(wxWindow* parent) -{ - wxPanel* panel = new wxPanel(parent, -1); - wxSizer* sizer = new wxBoxSizer(wxHORIZONTAL); - wxCheckBox* cbox = new wxCheckBox(panel, wxID_HIGHEST + 1, L("Export print config")); - cbox->SetValue(true); - sizer->AddSpacer(5); - sizer->Add(cbox, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, 5); - panel->SetSizer(sizer); - sizer->SetSizeHints(panel); - return panel; -} - -void add_export_option(wxFileDialog* dlg, const std::string& format) -{ - if ((dlg != nullptr) && (format == "AMF") || (format == "3MF")) - { - if (dlg->SupportsExtraControl()) - dlg->SetExtraControlCreator(export_option_creator); - } -} - -int get_export_option(wxFileDialog* dlg) -{ - if (dlg != nullptr) - { - wxWindow* wnd = dlg->GetExtraControl(); - if (wnd != nullptr) - { - wxPanel* panel = dynamic_cast(wnd); - if (panel != nullptr) - { - wxWindow* child = panel->FindWindow(wxID_HIGHEST + 1); - if (child != nullptr) - { - wxCheckBox* cbox = dynamic_cast(child); - if (cbox != nullptr) - return cbox->IsChecked() ? 1 : 0; - } - } - } - } - - return 0; - -} - bool get_current_screen_size(wxWindow *window, unsigned &width, unsigned &height) { const auto idx = wxDisplay::GetFromWindow(window); diff --git a/src/slic3r/GUI/GUI.hpp b/src/slic3r/GUI/GUI.hpp index 1f2107d59..3203fd8b2 100644 --- a/src/slic3r/GUI/GUI.hpp +++ b/src/slic3r/GUI/GUI.hpp @@ -102,9 +102,6 @@ std::string into_u8(const wxString &str); // Callback to trigger a configuration update timer on the Plater. static PerlCallback g_on_request_update_callback; - -void add_export_option(wxFileDialog* dlg, const std::string& format); -int get_export_option(wxFileDialog* dlg); // Returns the dimensions of the screen on which the main frame is displayed bool get_current_screen_size(wxWindow *window, unsigned &width, unsigned &height); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 1862bab8a..b619b1899 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -34,6 +34,21 @@ namespace Slic3r { namespace GUI { + +const wxString file_wildcards[FT_SIZE] = { + /* FT_STL */ "STL files (*.stl)|*.stl;*.STL", + /* FT_OBJ */ "OBJ files (*.obj)|*.obj;*.OBJ", + /* FT_AMF */ "AMF files (*.amf)|*.zip.amf;*.amf;*.AMF;*.xml;*.XML", + /* FT_3MF */ "3MF files (*.3mf)|*.3mf;*.3MF;", + /* FT_PRUSA */ "Prusa Control files (*.prusa)|*.prusa;*.PRUSA", + /* FT_GCODE */ "G-code files (*.gcode, *.gco, *.g, *.ngc)|*.gcode;*.GCODE;*.gco;*.GCO;*.g;*.G;*.ngc;*.NGC", + /* FT_MODEL */ "Known files (*.stl, *.obj, *.amf, *.xml, *.3mf, *.prusa)|*.stl;*.STL;*.obj;*.OBJ;*.amf;*.AMF;*.xml;*.XML;*.3mf;*.3MF;*.prusa;*.PRUSA", + + /* FT_INI */ "INI files *.ini|*.ini;*.INI", + /* FT_SVG */ "SVG files *.svg|*.svg;*.SVG", +}; + + static std::string libslic3r_translate_callback(const char *s) { return wxGetTranslation(wxString(s, wxConvUTF8)).utf8_str().data(); } IMPLEMENT_APP(GUI_App) @@ -291,7 +306,7 @@ void GUI_App::open_model(wxWindow *parent, wxArrayString& input_files) auto dialog = new wxFileDialog(parent ? parent : GetTopWindow(), _(L("Choose one or more files (STL/OBJ/AMF/3MF/PRUSA):")), app_config->get_last_dir(), "", - MODEL_WILDCARD, wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST); + file_wildcards[FT_MODEL], wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST); if (dialog->ShowModal() != wxID_OK) { dialog->Destroy(); return; diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index e5ea77a38..848a5a370 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -25,26 +25,24 @@ class ModelObject; namespace GUI { -// Map from an file_type name to full file wildcard name. -const std::map FILE_WILDCARDS{ - std::make_pair("known", "Known files (*.stl, *.obj, *.amf, *.xml, *.prusa)|*.stl;*.STL;*.obj;*.OBJ;*.amf;*.AMF;*.xml;*.XML;*.prusa;*.PRUSA"), - std::make_pair("stl", "STL files (*.stl)|*.stl;*.STL"), - std::make_pair("obj", "OBJ files (*.obj)|*.obj;*.OBJ"), - std::make_pair("amf", "AMF files (*.amf)|*.zip.amf;*.amf;*.AMF;*.xml;*.XML"), - std::make_pair("3mf", "3MF files (*.3mf)|*.3mf;*.3MF;"), - std::make_pair("prusa", "Prusa Control files (*.prusa)|*.prusa;*.PRUSA"), - std::make_pair("ini", "INI files *.ini|*.ini;*.INI"), - std::make_pair("gcode", "G-code files (*.gcode, *.gco, *.g, *.ngc)|*.gcode;*.GCODE;*.gco;*.GCO;*.g;*.G;*.ngc;*.NGC"), - std::make_pair("svg", "SVG files *.svg|*.svg;*.SVG") + +enum FileType +{ + FT_STL, + FT_OBJ, + FT_AMF, + FT_3MF, + FT_PRUSA, + FT_GCODE, + FT_MODEL, + + FT_INI, + FT_SVG, + + FT_SIZE, }; -const std::string MODEL_WILDCARD{ FILE_WILDCARDS.at("known") + std::string("|") + - FILE_WILDCARDS.at("stl") + std::string("|") + - FILE_WILDCARDS.at("obj") + std::string("|") + - FILE_WILDCARDS.at("amf") + std::string("|") + - FILE_WILDCARDS.at("3mf") + std::string("|") + - FILE_WILDCARDS.at("prusa") }; - +extern const wxString file_wildcards[FT_SIZE]; enum ConfigMenuIDs { ConfigMenuWizard, diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 0ea56710c..6451b50c5 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -511,7 +511,7 @@ void ObjectList::menu_item_add_generic(wxMenuItem* &menu, int id) { sub_menu->Append(new wxMenuItem(sub_menu, ++id, _(item))); #ifndef __WXMSW__ - sub_menu->Bind(wxEVT_MENU, [sub_menu](wxEvent &event) { + sub_menu->Bind(wxEVT_MENU, [this, sub_menu](wxEvent &event) { load_lambda(sub_menu->GetLabel(event.GetId()).ToStdString()); }); #endif //no __WXMSW__ @@ -633,7 +633,7 @@ wxMenu* ObjectList::create_add_settings_popupmenu(bool is_part) menu->Append(menu_item); } #ifndef __WXMSW__ - menu->Bind(wxEVT_MENU, [menu, is_part](wxEvent &event) { + menu->Bind(wxEVT_MENU, [this, menu, is_part](wxEvent &event) { get_settings_choice(menu, event.GetId(), is_part); }); #endif //no __WXMSW__ diff --git a/src/slic3r/GUI/GUI_Utils.cpp b/src/slic3r/GUI/GUI_Utils.cpp new file mode 100644 index 000000000..b8cd60944 --- /dev/null +++ b/src/slic3r/GUI/GUI_Utils.cpp @@ -0,0 +1,54 @@ +#include "GUI_Utils.hpp" + +#include +#include +#include + + +namespace Slic3r { +namespace GUI { + + +CheckboxFileDialog::CheckboxFileDialog(wxWindow *parent, + const wxString &checkbox_label, + bool checkbox_value, + const wxString &message, + const wxString &default_dir, + const wxString &default_file, + const wxString &wildcard, + long style, + const wxPoint &pos, + const wxSize &size, + const wxString &name +) + : wxFileDialog(parent, message, default_dir, default_file, wildcard, style, pos, size, name) + , cbox(nullptr) +{ + if (checkbox_label.IsEmpty()) { + return; + } + + extra_control_creator = [this, checkbox_label](wxWindow *parent) -> wxWindow* { + wxPanel* panel = new wxPanel(parent, -1); + wxSizer* sizer = new wxBoxSizer(wxHORIZONTAL); + this->cbox = new wxCheckBox(panel, wxID_HIGHEST + 1, checkbox_label); + this->cbox->SetValue(true); + sizer->AddSpacer(5); + sizer->Add(this->cbox, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, 5); + panel->SetSizer(sizer); + sizer->SetSizeHints(panel); + + return panel; + }; + + SetExtraControlCreator(*extra_control_creator.target()); +} + +bool CheckboxFileDialog::get_checkbox_value() const +{ + return this->cbox != nullptr ? cbox->IsChecked() : false; +} + + +} +} diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp new file mode 100644 index 000000000..bf2ee961f --- /dev/null +++ b/src/slic3r/GUI/GUI_Utils.hpp @@ -0,0 +1,41 @@ +#ifndef slic3r_GUI_Utils_hpp_ +#define slic3r_GUI_Utils_hpp_ + +#include + +#include + +class wxCheckBox; + + +namespace Slic3r { +namespace GUI { + + +class CheckboxFileDialog : public wxFileDialog +{ +public: + CheckboxFileDialog(wxWindow *parent, + const wxString &checkbox_label, + bool checkbox_value, + const wxString &message = wxFileSelectorPromptStr, + const wxString &default_dir = wxEmptyString, + const wxString &default_file = wxEmptyString, + const wxString &wildcard = wxFileSelectorDefaultWildcardStr, + long style = wxFD_DEFAULT_STYLE, + const wxPoint &pos = wxDefaultPosition, + const wxSize &size = wxDefaultSize, + const wxString &name = wxFileDialogNameStr + ); + + bool get_checkbox_value() const; + +private: + std::function extra_control_creator; + wxCheckBox *cbox; +}; + + +}} + +#endif diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index e2a4f9d54..a8282e6c4 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -460,7 +460,7 @@ void MainFrame::quick_slice(const int qs){ if (!(qs & qsReslice)) { auto dlg = new wxFileDialog(this, _(L("Choose a file to slice (STL/OBJ/AMF/3MF/PRUSA):")), wxGetApp().app_config->get_last_dir(), "", - MODEL_WILDCARD, wxFD_OPEN | wxFD_FILE_MUST_EXIST); + file_wildcards[FT_MODEL], wxFD_OPEN | wxFD_FILE_MUST_EXIST); if (dlg->ShowModal() != wxID_OK) { dlg->Destroy(); return; @@ -519,7 +519,7 @@ void MainFrame::quick_slice(const int qs){ // output_file = ~s / \.[gG][cC][oO][dD][eE]$ / .svg /; auto dlg = new wxFileDialog(this, _(L("Save ")) + (qs & qsExportSVG ? _(L("SVG")) : _(L("G-code"))) + _(L(" file as:")), wxGetApp().app_config->get_last_output_dir(get_dir_name(output_file)), get_base_name(input_file), - qs & qsExportSVG ? FILE_WILDCARDS.at("svg") : FILE_WILDCARDS.at("gcode"), + qs & qsExportSVG ? file_wildcards[FT_SVG] : file_wildcards[FT_GCODE], wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if (dlg->ShowModal() != wxID_OK) { dlg->Destroy(); @@ -587,7 +587,7 @@ void MainFrame::repair_stl() { auto dlg = new wxFileDialog(this, _(L("Select the STL file to repair:")), wxGetApp().app_config->get_last_dir(), "", - FILE_WILDCARDS.at("stl"), wxFD_OPEN | wxFD_FILE_MUST_EXIST); + file_wildcards[FT_STL], wxFD_OPEN | wxFD_FILE_MUST_EXIST); if (dlg->ShowModal() != wxID_OK) { dlg->Destroy(); return; @@ -601,7 +601,7 @@ void MainFrame::repair_stl() // output_file = ~s / \.[sS][tT][lL]$ / _fixed.obj / ; auto dlg = new wxFileDialog( this, L("Save OBJ file (less prone to coordinate errors than STL) as:"), get_dir_name(output_file), get_base_name(output_file), - FILE_WILDCARDS.at("obj"), wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + file_wildcards[FT_OBJ], wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if (dlg->ShowModal() != wxID_OK) { dlg->Destroy(); return /*undef*/; @@ -631,7 +631,7 @@ void MainFrame::export_config() auto dlg = new wxFileDialog(this, _(L("Save configuration as:")), !m_last_config.IsEmpty() ? get_dir_name(m_last_config) : wxGetApp().app_config->get_last_dir(), !m_last_config.IsEmpty() ? get_base_name(m_last_config) : "config.ini", - FILE_WILDCARDS.at("ini"), wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + file_wildcards[FT_INI], wxFD_SAVE | wxFD_OVERWRITE_PROMPT); wxString file; if (dlg->ShowModal() == wxID_OK) file = dlg->GetPath(); @@ -683,7 +683,7 @@ void MainFrame::export_configbundle() auto dlg = new wxFileDialog(this, _(L("Save presets bundle as:")), !m_last_config.IsEmpty() ? get_dir_name(m_last_config) : wxGetApp().app_config->get_last_dir(), "Slic3r_config_bundle.ini", - FILE_WILDCARDS.at("ini"), wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + file_wildcards[FT_INI], wxFD_SAVE | wxFD_OVERWRITE_PROMPT); wxString file; if (dlg->ShowModal() == wxID_OK) file = dlg->GetPath(); @@ -707,7 +707,7 @@ void MainFrame::load_configbundle(wxString file/* = wxEmptyString, const bool re if (file.IsEmpty()) { auto dlg = new wxFileDialog(this, _(L("Select configuration to load:")), !m_last_config.IsEmpty() ? get_dir_name(m_last_config) : wxGetApp().app_config->get_last_dir(), - "config.ini", FILE_WILDCARDS.at("ini"), wxFD_OPEN | wxFD_FILE_MUST_EXIST); + "config.ini", file_wildcards[FT_INI], wxFD_OPEN | wxFD_FILE_MUST_EXIST); if (dlg->ShowModal() != wxID_OK) return; file = dlg->GetPath(); @@ -774,7 +774,7 @@ void MainFrame::on_presets_changed(SimpleEvent &event) // Update preset combo boxes(Print settings, Filament, Material, Printer) from their respective tabs. auto presets = tab->get_presets(); - if (presets) { + if (m_plater != nullptr && presets != nullptr) { auto reload_dependent_tabs = tab->get_dependent_tabs(); // FIXME: The preset type really should be a property of Tab instead diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index cd5a1fc12..b4b0cb726 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -25,10 +26,12 @@ #include "libslic3r/GCode/PreviewData.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/Polygon.hpp" +#include "libslic3r/Format/STL.hpp" #include "GUI.hpp" #include "GUI_App.hpp" #include "GUI_ObjectList.hpp" #include "GUI_ObjectManipulation.hpp" +#include "GUI_Utils.hpp" #include "MainFrame.hpp" #include "3DScene.hpp" #include "GLCanvas3D.hpp" @@ -43,6 +46,7 @@ #include // Needs to be last because reasons :-/ #include "WipeTowerDialog.hpp" +using boost::optional; namespace fs = boost::filesystem; using Slic3r::_3DScene; using Slic3r::Preset; @@ -340,7 +344,7 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) : struct Sidebar::priv { - // Sidebar *q; // PIMPL back pointer ("Q-Pointer") + Plater *plater; wxScrolledWindow *scrolled; @@ -362,13 +366,15 @@ struct Sidebar::priv wxButton *btn_reslice; // wxButton *btn_print; // XXX: remove wxButton *btn_send_gcode; + + priv(Plater *plater) : plater(plater) {} }; // Sidebar / public -Sidebar::Sidebar(wxWindow *parent) - : wxPanel(parent), p(new priv) +Sidebar::Sidebar(Plater *parent) + : wxPanel(parent), p(new priv(parent)) { p->scrolled = new wxScrolledWindow(this); @@ -457,6 +463,11 @@ Sidebar::Sidebar(wxWindow *parent) sizer->Add(p->scrolled, 1, wxEXPAND | wxTOP, 5); sizer->Add(btns_sizer, 0, wxEXPAND | wxLEFT, 20); SetSizer(sizer); + + // Events + p->btn_export_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->export_gcode(); }); + p->btn_reslice->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->reslice(); }); + p->btn_send_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->send_gcode(); }); } Sidebar::~Sidebar() {} @@ -600,8 +611,8 @@ struct Plater::priv Slic3r::GCodePreviewData gcode_preview_data; std::vector objects; - std::string export_gcode_output_file; - std::string send_gcode_file; + fs::path export_gcode_output_file; + fs::path send_gcode_file; // GUI elements wxNotebook *notebook; @@ -610,7 +621,6 @@ struct Plater::priv Preview *preview; BackgroundSlicingProcess background_process; - static const int gl_attrs[]; static const std::regex pattern_bundle; static const std::regex pattern_3mf; static const std::regex pattern_zip_amf; @@ -628,19 +638,33 @@ struct Plater::priv BoundingBox scaled_bed_shape_bb() const; std::vector load_files(const std::vector &input_files); std::vector load_model_objects(const ModelObjectPtrs &model_objects); + std::unique_ptr get_export_file(GUI::FileType file_type); + + void select_object(optional obj_idx); + optional selected_object() const; + void selection_changed(); + + void reset(); void on_notebook_changed(wxBookCtrlEvent &); void on_select_preset(wxCommandEvent &); void on_update_print_preview(wxCommandEvent &); void on_process_completed(wxCommandEvent &); void on_layer_editing_toggled(bool enable); + void on_action_add(SimpleEvent&); + void on_action_arrange(SimpleEvent&); + void on_action_more(SimpleEvent&); + void on_action_fewer(SimpleEvent&); + void on_action_split(SimpleEvent&); + void on_action_cut(SimpleEvent&); + void on_action_settings(SimpleEvent&); + void on_action_layersediting(SimpleEvent&); + void on_action_selectbyparts(SimpleEvent&); void on_viewport_changed(SimpleEvent& evt); }; -// TODO: multisample, see 3DScene.pm -const int Plater::priv::gl_attrs[] = {WX_GL_RGBA, WX_GL_DOUBLEBUFFER, WX_GL_DEPTH_SIZE, 24, 0}; const std::regex Plater::priv::pattern_bundle("[.](amf|amf[.]xml|zip[.]amf|3mf|prusa)$", std::regex::icase); const std::regex Plater::priv::pattern_3mf("[.]3mf$", std::regex::icase); const std::regex Plater::priv::pattern_zip_amf("[.]zip[.]amf$", std::regex::icase); @@ -716,15 +740,26 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) : sidebar->Bind(wxEVT_COMBOBOX, &priv::on_select_preset, this); // Sidebar button events - sidebar->p->btn_export_gcode->Bind(wxEVT_BUTTON, [q](wxCommandEvent&) { q->export_gcode(""); }); - sidebar->p->btn_reslice->Bind(wxEVT_BUTTON, [q](wxCommandEvent&) { q->reslice(); }); - sidebar->p->btn_send_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { - this->send_gcode_file = this->q->export_gcode(""); - }); + // sidebar->p->btn_export_gcode->Bind(wxEVT_BUTTON, [q](wxCommandEvent&) { q->export_gcode(); }); + // sidebar->p->btn_reslice->Bind(wxEVT_BUTTON, [q](wxCommandEvent&) { q->reslice(); }); + // sidebar->p->btn_send_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { + // this->send_gcode_file = this->q->export_gcode(); + // }); // 3DScene events: // TODO: more canvas3D->Bind(EVT_GLTOOLBAR_ADD, &priv::on_action_add, this); + canvas3D->Bind(EVT_GLTOOLBAR_DELETE, [q](SimpleEvent&) { q->remove_selected(); } ); + canvas3D->Bind(EVT_GLTOOLBAR_DELETE_ALL, [this](SimpleEvent&) { reset(); }); + canvas3D->Bind(EVT_GLTOOLBAR_ARRANGE, &priv::on_action_arrange, this); + canvas3D->Bind(EVT_GLTOOLBAR_MORE, &priv::on_action_more, this); + canvas3D->Bind(EVT_GLTOOLBAR_FEWER, &priv::on_action_fewer, this); + canvas3D->Bind(EVT_GLTOOLBAR_SPLIT, &priv::on_action_split, this); + canvas3D->Bind(EVT_GLTOOLBAR_CUT, &priv::on_action_cut, this); + canvas3D->Bind(EVT_GLTOOLBAR_SETTINGS, &priv::on_action_settings, this); + canvas3D->Bind(EVT_GLTOOLBAR_LAYERSEDITING, &priv::on_action_layersediting, this); + canvas3D->Bind(EVT_GLTOOLBAR_SELECTBYPARTS, &priv::on_action_selectbyparts, this); + canvas3D->Bind(EVT_GLCANVAS_VIEWPORT_CHANGED, &priv::on_viewport_changed, this); preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_VIEWPORT_CHANGED, &priv::on_viewport_changed, this); @@ -810,7 +845,7 @@ BoundingBox Plater::priv::scaled_bed_shape_bb() const std::vector Plater::priv::load_files(const std::vector &input_files) { - if (input_files.size() < 1) { return std::vector(); } + if (input_files.empty()) { return std::vector(); } auto *nozzle_dmrs = config->opt("nozzle_diameter"); @@ -883,9 +918,6 @@ std::vector Plater::priv::load_files(const std::vector &input_ } if (one_by_one) { - // TODO: - // push @obj_idx, $self->load_model_objects(@{$model->objects}); - // obj_idx.push_back(load_model_objects(model.objects); auto loaded_idxs = load_model_objects(model.objects); obj_idxs.insert(obj_idxs.end(), loaded_idxs.begin(), loaded_idxs.end()); } else { @@ -906,9 +938,6 @@ std::vector Plater::priv::load_files(const std::vector &input_ new_model->convert_multipart_object(nozzle_dmrs->values.size()); } - // TODO: - // push @obj_idx, $self->load_model_objects(@{$new_model->objects}); - // obj_idx.push_back(load_model_objects(new_model->objects); auto loaded_idxs = load_model_objects(model.objects); obj_idxs.insert(obj_idxs.end(), loaded_idxs.begin(), loaded_idxs.end()); } @@ -919,18 +948,11 @@ std::vector Plater::priv::load_files(const std::vector &input_ return obj_idxs; } - -// TODO: move to Point.hpp -Vec3d to_3d(const Vec2d &v, double z) { return Vec3d(v(0), v(1), z); } -Vec3f to_3d(const Vec2f &v, float z) { return Vec3f(v(0), v(1), z); } -Vec3i64 to_3d(const Vec2i64 &v, float z) { return Vec3i64(v(0), v(1), z); } -Vec3crd to_3d(const Point &p, coord_t z) { return Vec3crd(p(0), p(1), z); } - -std::vector Plater::priv::load_model_objects(const ModelObjectPtrs &model_objects) +std::vector Plater::priv::load_model_objects(const ModelObjectPtrs &model_objects) { const BoundingBoxf bed_shape = bed_shape_bb(); - const Vec3d bed_center = to_3d(bed_shape.center().cast(), 0.0); - const Vec3d bed_size = to_3d(bed_shape.size().cast(), 1.0); + const Vec3d bed_center = Slic3r::to_3d(bed_shape.center().cast(), 0.0); + const Vec3d bed_size = Slic3r::to_3d(bed_shape.size().cast(), 1.0); bool need_arrange = false; bool scaled_down = false; @@ -942,7 +964,7 @@ std::vector Plater::priv::load_model_objects(const ModelObjectPtrs &mod objects.emplace_back(std::move(object_name)); obj_idxs.push_back(objects.size() - 1); - if (model_object->instances.size() == 0) { + if (model_object->instances.empty()) { // if object has no defined position(s) we need to rearrange everything after loading need_arrange = true; @@ -1002,6 +1024,186 @@ std::vector Plater::priv::load_model_objects(const ModelObjectPtrs &mod return obj_idxs; } +std::unique_ptr Plater::priv::get_export_file(GUI::FileType file_type) +{ + wxString wildcard; + switch (file_type) { + case FT_STL: + case FT_AMF: + case FT_3MF: + wildcard = file_wildcards[FT_STL]; + break; + + default: + wildcard = file_wildcards[FT_MODEL]; + break; + } + + fs::path output_file(print.output_filepath(std::string())); + + switch (file_type) { + case FT_STL: output_file.replace_extension("stl"); break; + case FT_AMF: output_file.replace_extension("zip.amf"); break; // XXX: Problem on OS X with double extension? + case FT_3MF: output_file.replace_extension("3mf"); break; + default: break; + } + + wxGetApp().preset_bundle->export_selections(print.placeholder_parser()); + + auto dlg = Slic3r::make_unique(q, + _(L("Export print config")), + true, + _(L("Save file as:")), + output_file.parent_path().string(), + output_file.filename().string(), + wildcard, + wxFD_SAVE | wxFD_OVERWRITE_PROMPT + ); + + if (dlg->ShowModal() != wxID_OK) { + return nullptr; + } + + fs::path path(dlg->GetPath()); + wxGetApp().app_config->update_last_output_dir(path.parent_path().string()); + export_gcode_output_file = path; + + return dlg; +} + +void Plater::priv::select_object(optional obj_idx) +{ + for (auto &obj : objects) { + obj.selected = false; + } + + if (obj_idx) { + objects[*obj_idx].selected = true; + } + + // TODO: + // $self->selection_changed(1); +} + +void Plater::priv::selection_changed() +{ + // TODO + + const auto obj_idx = selected_object(); + const bool have_sel = !!obj_idx; + const bool layers_height_allowed = config->opt("variable_layer_height")->value; + + wxWindowUpdateLocker freeze_guard(sidebar); + + _3DScene::enable_toolbar_item(canvas3D, "delete", have_sel); + _3DScene::enable_toolbar_item(canvas3D, "more", have_sel); + _3DScene::enable_toolbar_item(canvas3D, "fewer", have_sel); + _3DScene::enable_toolbar_item(canvas3D, "split", have_sel); + _3DScene::enable_toolbar_item(canvas3D, "cut", have_sel); + _3DScene::enable_toolbar_item(canvas3D, "settings", have_sel); + + _3DScene::enable_toolbar_item(canvas3D, "layersediting", layers_height_allowed); + + bool can_select_by_parts = false; + + if (have_sel) { + // my $model_object = $self->{model}->objects->[$obj_idx]; + const auto *model_object = model.objects[*obj_idx]; + // $can_select_by_parts = ($obj_idx >= 0) && ($obj_idx < 1000) && ($model_object->volumes_count > 1); + // XXX: ? + can_select_by_parts = *obj_idx < 1000 && model_object->volumes.size() > 1; + // Slic3r::GUI::_3DScene::enable_toolbar_item($self->{canvas3D}, "fewer", $model_object->instances_count > 1); + _3DScene::enable_toolbar_item(canvas3D, "fewer", model_object->instances.size() > 1); + } + + if (can_select_by_parts) { + // first disable to let the item in the toolbar to switch to the unpressed state // XXX: ? + _3DScene::enable_toolbar_item(canvas3D, "selectbyparts", false); + _3DScene::enable_toolbar_item(canvas3D, "selectbyparts", true); + } else { + _3DScene::enable_toolbar_item(canvas3D, "selectbyparts", false); + _3DScene::set_select_by(canvas3D, "object"); + } + + if (have_sel) { + const auto *model_object = model.objects[*obj_idx]; + // FIXME print_info runs model fixing in two rounds, it is very slow, it should not be performed here! + // # $model_object->print_info; + + // my $model_instance = $model_object->instances->[0]; + const auto *model_instance = model_object->instances[0]; + // TODO + // $self->{object_info_size}->SetLabel(sprintf("%.2f x %.2f x %.2f", @{$model_object->instance_bounding_box(0)->size})); + // $self->{object_info_materials}->SetLabel($model_object->materials_count); + + // if (my $stats = $model_object->mesh_stats) { + // $self->{object_info_volume}->SetLabel(sprintf('%.2f', $stats->{volume} * ($model_instance->scaling_factor**3))); + // $self->{object_info_facets}->SetLabel(sprintf(L('%d (%d shells)'), $model_object->facets_count, $stats->{number_of_parts})); + // if (my $errors = sum(@$stats{qw(degenerate_facets edges_fixed facets_removed facets_added facets_reversed backwards_edges)})) { + // $self->{object_info_manifold}->SetLabel(sprintf(L("Auto-repaired (%d errors)"), $errors)); + // #$self->{object_info_manifold_warning_icon}->Show; + // $self->{"object_info_manifold_warning_icon_show"}->(1); + + // # we don't show normals_fixed because we never provide normals + // # to admesh, so it generates normals for all facets + // my $message = sprintf L('%d degenerate facets, %d edges fixed, %d facets removed, %d facets added, %d facets reversed, %d backwards edges'), + // @$stats{qw(degenerate_facets edges_fixed facets_removed facets_added facets_reversed backwards_edges)}; + // $self->{object_info_manifold}->SetToolTipString($message); + // $self->{object_info_manifold_warning_icon}->SetToolTipString($message); + // } else { + // $self->{object_info_manifold}->SetLabel(L("Yes")); + // #$self->{object_info_manifold_warning_icon}->Hide; + // $self->{"object_info_manifold_warning_icon_show"}->(0); + // $self->{object_info_manifold}->SetToolTipString(""); + // $self->{object_info_manifold_warning_icon}->SetToolTipString(""); + // } + // } else { + // $self->{object_info_facets}->SetLabel($object->facets); + // } + } else { + // $self->{"object_info_$_"}->SetLabel("") for qw(size volume facets materials manifold); + // $self->{"object_info_manifold_warning_icon_show"}->(0); + // $self->{object_info_manifold}->SetToolTipString(""); + // $self->{object_info_manifold_warning_icon}->SetToolTipString(""); + } + + q->Layout(); +} + +optional Plater::priv::selected_object() const +{ + for (size_t i = 0; i < objects.size(); i++) { + if (objects[i].selected) { return i; } + } + + return boost::none; +} + +void Plater::priv::reset() +{ + // TODO + + // $self->stop_background_process; + + // Prevent toolpaths preview from rendering while we modify the Print object + preview->set_enabled(false); + + objects.clear(); + model.clear_objects(); + print.clear_objects(); + + // Delete all objects from list on c++ side + // TODO + // Slic3r::GUI::delete_all_objects_from_list(); + sidebar->obj_list()->delete_all_objects_from_list(); + // $self->object_list_changed; + + // TODO + // $self->select_object(undef); + update(); +} + + void Plater::priv::on_notebook_changed(wxBookCtrlEvent&) { const auto current_id = notebook->GetCurrentPage()->GetId(); @@ -1082,6 +1284,47 @@ void Plater::priv::on_action_add(SimpleEvent&) load_files(input_paths); } +void Plater::priv::on_action_arrange(SimpleEvent&) +{ + // TODO +} + +void Plater::priv::on_action_more(SimpleEvent&) +{ + // TODO +} + +void Plater::priv::on_action_fewer(SimpleEvent&) +{ + // TODO +} + +void Plater::priv::on_action_split(SimpleEvent&) +{ + // TODO +} + +void Plater::priv::on_action_cut(SimpleEvent&) +{ + // TODO +} + +void Plater::priv::on_action_settings(SimpleEvent&) +{ + // TODO +} + +void Plater::priv::on_action_layersediting(SimpleEvent&) +{ + // TODO +} + +void Plater::priv::on_action_selectbyparts(SimpleEvent&) +{ + // TODO +} + + void Plater::priv::on_viewport_changed(SimpleEvent& evt) { wxObject* o = evt.GetEventObject(); @@ -1107,63 +1350,115 @@ Plater::~Plater() Sidebar& Plater::sidebar() { return *p->sidebar; } Model& Plater::model() { return p->model; } -std::string Plater::export_gcode(const std::string &output_path) +void Plater::update(bool force_autocenter) { - if (p->objects.size() == 0) { return ""; } + p->update(force_autocenter); +} + +void Plater::remove(size_t obj_idx) +{ + // TODO +} + +void Plater::remove_selected() +{ + const auto selected = p->selected_object(); + if (selected) { + remove(*selected); + } +} + + +fs::path Plater::export_gcode(const fs::path &output_path) +{ + if (p->objects.empty()) { return ""; } if (! p->export_gcode_output_file.empty()) { GUI::show_error(this, _(L("Another export job is currently running."))); return ""; } - // wxTheApp->{preset_bundle}->full_config->validate; // FIXME - const std::string err = p->print.validate(); + std::string err = wxGetApp().preset_bundle->full_config().validate(); + if (err.empty()) { + err = p->print.validate(); + } if (! err.empty()) { // The config is not valid GUI::show_error(this, _(err)); - return ""; + return fs::path(); } // Copy the names of active presets into the placeholder parser. - // wxTheApp->{preset_bundle}->export_selections_pp($self->{print}->placeholder_parser); // FIXME + wxGetApp().preset_bundle->export_selections(p->print.placeholder_parser()); // select output file if (! output_path.empty()) { - p->export_gcode_output_file = p->print.output_filepath(output_path); + p->export_gcode_output_file = fs::path(p->print.output_filepath(output_path.string())); // FIXME: ^ errors to handle? } else { - // FIXME: - std::string default_output_file; // FIXME: tmp - // my $default_output_file = eval { $self->{print}->output_filepath($main::opt{output} // '') }; - // Slic3r::GUI::catch_error($self) and return; + + // XXX: take output path from CLI opts? Ancient Slic3r versions used to do that... // If possible, remove accents from accented latin characters. // This function is useful for generating file names to be processed by legacy firmwares. - default_output_file = Slic3r::fold_utf8_to_ascii(default_output_file); + auto default_output_file = fs::path(Slic3r::fold_utf8_to_ascii( + p->print.output_filepath(output_path.string()) + // FIXME: ^ errors to handle? + )); + auto start_dir = wxGetApp().app_config->get_last_output_dir(default_output_file.parent_path().string()); wxFileDialog dlg(this, _(L("Save G-code file as:")), - wxEmptyString, - wxEmptyString, - Slic3r::GUI::FILE_WILDCARDS.at("gcode"), + start_dir, + default_output_file.filename().string(), + GUI::file_wildcards[FT_GCODE], wxFD_SAVE | wxFD_OVERWRITE_PROMPT ); - // FIXME: ^ defaultDir: - // wxTheApp->{app_config}->get_last_output_dir(dirname($default_output_file)), - // FIXME: ^ defaultFile: - // basename($default_output_file), &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if (dlg.ShowModal() != wxID_OK) { return ""; } - auto path = dlg.GetPath(); - // wxTheApp->{app_config}->update_last_output_dir(dirname($path)); // FIXME + fs::path path(dlg.GetPath()); + wxGetApp().app_config->update_last_output_dir(path.parent_path().string()); p->export_gcode_output_file = path; } return p->export_gcode_output_file; } +void Plater::export_stl() +{ + if (p->objects.empty()) { return; } + + auto dialog = p->get_export_file(FT_STL); + if (! dialog) { return; } + + // Store a binary STL + wxString path = dialog->GetPath(); + auto path_cstr = path.c_str(); + auto mesh = p->model.mesh(); + Slic3r::store_stl(path_cstr, &mesh, true); + p->statusbar()->set_status_text(wxString::Format(_(L("STL file exported to %s")), path)); +} + +void Plater::export_amf() +{ + // TODO + throw 0; +} + +void Plater::export_3mf() +{ + // TODO + throw 0; +} + + void Plater::reslice() { // TODO } +void Plater::send_gcode() +{ + p->send_gcode_file = export_gcode(); +} + }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index a060eda9c..f449cc75f 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -2,11 +2,15 @@ #define slic3r_Plater_hpp_ #include +#include #include #include "Preset.hpp" +class wxButton; + + namespace Slic3r { class Model; @@ -20,10 +24,12 @@ class ObjectList; using t_optgroups = std::vector >; +class Plater; + class Sidebar : public wxPanel { public: - Sidebar(wxWindow *parent); + Sidebar(Plater *parent); Sidebar(Sidebar &&) = delete; Sidebar(const Sidebar &) = delete; Sidebar &operator=(Sidebar &&) = delete; @@ -45,8 +51,6 @@ public: private: struct priv; std::unique_ptr p; - - friend class Plater; // XXX: better encapsulation? }; @@ -63,10 +67,17 @@ public: Sidebar& sidebar(); Model& model(); - // TODO: use fs::path - // Note: empty string means request default path - std::string export_gcode(const std::string &output_path); + void update(bool force_autocenter = false); + void remove(size_t obj_idx); + void remove_selected(); + + // Note: empty path means "use the default" + boost::filesystem::path export_gcode(const boost::filesystem::path &output_path = boost::filesystem::path()); + void export_stl(); + void export_amf(); + void export_3mf(); void reslice(); + void send_gcode(); private: struct priv; std::unique_ptr p;