diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 5a44ae48e1..8df87d6f85 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -25,6 +25,8 @@ #define ENABLE_MODELVOLUME_TRANSFORM (1 && ENABLE_1_42_0) // Gizmos always rendered on top of objects #define ENABLE_GIZMOS_ON_TOP (1 && ENABLE_1_42_0) +// New menu layout (open/save/save as project + import/export) +#define ENABLE_NEW_MENU_LAYOUT (1 && ENABLE_1_42_0) #endif // _technologies_h_ diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index c201d4018b..33fdb87793 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -304,20 +304,32 @@ void GUI_App::update_ui_from_settings() mainframe->update_ui_from_settings(); } - -void GUI_App::open_model(wxWindow *parent, wxArrayString& input_files) +#if ENABLE_NEW_MENU_LAYOUT +void GUI_App::load_project(wxWindow *parent, wxString& input_file) { - auto dialog = new wxFileDialog(parent ? parent : GetTopWindow(), + input_file.Clear(); + wxFileDialog dialog(parent ? parent : GetTopWindow(), + _(L("Choose one file (3MF):")), + app_config->get_last_dir(), "", + file_wildcards[FT_3MF], wxFD_OPEN | wxFD_FILE_MUST_EXIST); + + if (dialog.ShowModal() == wxID_OK) + input_file = dialog.GetPath(); +} + +void GUI_App::import_model(wxWindow *parent, wxArrayString& input_files) +#else +void GUI_App::open_model(wxWindow *parent, wxArrayString& input_files) +#endif // ENABLE_NEW_MENU_LAYOUT +{ + input_files.Clear(); + wxFileDialog dialog(parent ? parent : GetTopWindow(), _(L("Choose one or more files (STL/OBJ/AMF/3MF/PRUSA):")), app_config->get_last_dir(), "", file_wildcards[FT_MODEL], wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST); - if (dialog->ShowModal() != wxID_OK) { - dialog->Destroy(); - return; - } - dialog->GetPaths(input_files); - dialog->Destroy(); + if (dialog.ShowModal() == wxID_OK) + dialog.GetPaths(input_files); } void GUI_App::CallAfter(std::function cb) diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index ee74d6293f..dcd9b7ad37 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -102,7 +102,12 @@ public: void recreate_GUI(); void system_info(); +#if ENABLE_NEW_MENU_LAYOUT + void load_project(wxWindow *parent, wxString& input_file); + void import_model(wxWindow *parent, wxArrayString& input_files); +#else void open_model(wxWindow *parent, wxArrayString& input_files); +#endif // ENABLE_NEW_MENU_LAYOUT static bool catch_error(std::function cb, // wxMessageDialog* message_dialog, const std::string& err); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 01815cfd65..25aaf16f25 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -727,7 +727,11 @@ void ObjectList::load_part( ModelObject* model_object, m_parts_changed = false; wxArrayString input_files; +#if ENABLE_NEW_MENU_LAYOUT + wxGetApp().import_model(parent, input_files); +#else wxGetApp().open_model(parent, input_files); +#endif // ENABLE_NEW_MENU_LAYOUT for (int i = 0; i < input_files.size(); ++i) { std::string input_file = input_files.Item(i).ToStdString(); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index b67d19af2d..bfdc95ba09 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -217,11 +217,76 @@ void MainFrame::add_created_tab(Tab* panel) m_tabpanel->AddPage(panel, panel->title()); } +#if ENABLE_NEW_MENU_LAYOUT +bool MainFrame::can_save() const +{ + return (m_plater != nullptr) ? !m_plater->model().objects.empty() : false; +} + +bool MainFrame::can_export_model() const +{ + return (m_plater != nullptr) ? !m_plater->model().objects.empty() : false; +} + +bool MainFrame::can_export_gcode() const +{ + if (m_plater == nullptr) + return false; + + if (m_plater->model().objects.empty()) + return false; + + if (m_plater->is_export_gcode_scheduled()) + return false; + + // TODO:: add other filters + + return true; +} +#endif // ENABLE_NEW_MENU_LAYOUT + void MainFrame::init_menubar() { // File menu wxMenu* fileMenu = new wxMenu; { +#if ENABLE_NEW_MENU_LAYOUT + append_menu_item(fileMenu, wxID_ANY, _(L("Open…\tCtrl+O")), _(L("Open a project file")), + [this](wxCommandEvent&) { if (m_plater) m_plater->load_project(); }, "brick_add.png"); + wxMenuItem* item_save = append_menu_item(fileMenu, wxID_ANY, _(L("Save\tCtrl+S")), _(L("Save current project file")), + [this](wxCommandEvent&) { if (m_plater) m_plater->export_3mf(m_plater->get_project_filename().wx_str()); }, "disk.png"); + wxMenuItem* item_save_as = append_menu_item(fileMenu, wxID_ANY, _(L("Save as…\tCtrl+Alt+S")), _(L("Save current project file as")), + [this](wxCommandEvent&) { if (m_plater) m_plater->export_3mf(); }, "disk.png"); + + fileMenu->AppendSeparator(); + + wxMenu* import_menu = new wxMenu(); + append_menu_item(import_menu, wxID_ANY, _(L("Import STL/OBJ/AMF/3MF…\tCtrl+I")), _(L("Load a model")), + [this](wxCommandEvent&) { if (m_plater) m_plater->add_model(); }, "brick_add.png"); + import_menu->AppendSeparator(); + append_menu_item(import_menu, wxID_ANY, _(L("Import Config…\tCtrl+L")), _(L("Load exported configuration file")), + [this](wxCommandEvent&) { load_config_file(); }, "plugin_add.png"); + append_menu_item(import_menu, wxID_ANY, _(L("Import Config Bundle…")), _(L("Load presets from a bundle")), + [this](wxCommandEvent&) { load_configbundle(); }, "lorry_add.png"); + append_submenu(fileMenu, import_menu, wxID_ANY, _(L("Import")), _(L(""))); + + wxMenu* export_menu = new wxMenu(); + wxMenuItem* item_export_gcode = append_menu_item(export_menu, wxID_ANY, _(L("Export G-code…")), _(L("Export current plate as G-code")), + [this](wxCommandEvent&) { if (m_plater) m_plater->export_gcode(); }, "cog_go.png"); + export_menu->AppendSeparator(); + wxMenuItem* item_export_stl = append_menu_item(export_menu, wxID_ANY, _(L("Export plate as STL…")), _(L("Export current plate as STL")), + [this](wxCommandEvent&) { if (m_plater) m_plater->export_stl(); }, "brick_go.png"); + wxMenuItem* item_export_amf = append_menu_item(export_menu, wxID_ANY, _(L("Export plate as AMF…")), _(L("Export current plate as AMF")), + [this](wxCommandEvent&) { if (m_plater) m_plater->export_amf(); }, "brick_go.png"); + export_menu->AppendSeparator(); + append_menu_item(export_menu, wxID_ANY, _(L("Export Config…\tCtrl+E")), _(L("Export current configuration to file")), + [this](wxCommandEvent&) { export_config(); }, "plugin_go.png"); + append_menu_item(export_menu, wxID_ANY, _(L("Export Config Bundle…")), _(L("Export all presets to file")), + [this](wxCommandEvent&) { export_configbundle(); }, "lorry_go.png"); + append_submenu(fileMenu, export_menu, wxID_ANY, _(L("Export")), _(L(""))); + + fileMenu->AppendSeparator(); +#else append_menu_item(fileMenu, wxID_ANY, _(L("Open STL/OBJ/AMF/3MF…\tCtrl+O")), _(L("Open a model")), [this](wxCommandEvent&) { if (m_plater) m_plater->add(); }, "brick_add.png"); append_menu_item(fileMenu, wxID_ANY, _(L("&Load Config…\tCtrl+L")), _(L("Load exported configuration file")), @@ -233,6 +298,8 @@ void MainFrame::init_menubar() append_menu_item(fileMenu, wxID_ANY, _(L("&Export Config Bundle…")), _(L("Export all presets to file")), [this](wxCommandEvent&) { export_configbundle(); }, "lorry_go.png"); fileMenu->AppendSeparator(); +#endif // ENABLE_NEW_MENU_LAYOUT + m_menu_item_repeat = nullptr; append_menu_item(fileMenu, wxID_ANY, _(L("Q&uick Slice…\tCtrl+U")), _(L("Slice a file into a G-code")), [this](wxCommandEvent&) { @@ -263,8 +330,17 @@ void MainFrame::init_menubar() fileMenu->AppendSeparator(); append_menu_item(fileMenu, wxID_EXIT, _(L("&Quit")), _(L("Quit Slic3r")), [this](wxCommandEvent&) { Close(false); } ); + +#if ENABLE_NEW_MENU_LAYOUT + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_save()); }, item_save->GetId()); + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_save()); }, item_save_as->GetId()); + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_export_gcode()); }, item_export_gcode->GetId()); + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_export_model()); }, item_export_stl->GetId()); + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_export_model()); }, item_export_amf->GetId()); +#endif // ENABLE_NEW_MENU_LAYOUT } +#if !ENABLE_NEW_MENU_LAYOUT // Plater menu if (m_plater) { m_plater_menu = new wxMenu(); @@ -277,6 +353,7 @@ void MainFrame::init_menubar() append_menu_item(m_plater_menu, wxID_ANY, _(L("Export plate as 3MF...")), _(L("Export current plate as 3MF")), [this](wxCommandEvent&) { m_plater->export_3mf(); }, "brick_go.png"); } +#endif // !ENABLE_NEW_MENU_LAYOUT // Window menu auto windowMenu = new wxMenu(); @@ -299,6 +376,23 @@ void MainFrame::init_menubar() } // View menu +#if ENABLE_NEW_MENU_LAYOUT + wxMenu* viewMenu = nullptr; + if (m_plater) { + viewMenu = new wxMenu(); + // \xA0 is a non-breaing space. It is entered here to spoil the automatic accelerators, + // as the simple numeric accelerators spoil all numeric data entry. + // The camera control accelerators are captured by GLCanvas3D::on_char(). + append_menu_item(viewMenu, wxID_ANY, _(L("Iso")) + "\t\xA0" + "0", _(L("Iso View")), [this](wxCommandEvent&) { select_view("iso"); }); + viewMenu->AppendSeparator(); + append_menu_item(viewMenu, wxID_ANY, _(L("Top")) + "\t\xA0" + "1", _(L("Top View")), [this](wxCommandEvent&) { select_view("top"); }); + append_menu_item(viewMenu, wxID_ANY, _(L("Bottom")) + "\t\xA0" + "2", _(L("Bottom View")), [this](wxCommandEvent&) { select_view("bottom"); }); + append_menu_item(viewMenu, wxID_ANY, _(L("Front")) + "\t\xA0" + "3", _(L("Front View")), [this](wxCommandEvent&) { select_view("front"); }); + append_menu_item(viewMenu, wxID_ANY, _(L("Rear")) + "\t\xA0" + "4", _(L("Rear View")), [this](wxCommandEvent&) { select_view("rear"); }); + append_menu_item(viewMenu, wxID_ANY, _(L("Left")) + "\t\xA0" + "5", _(L("Left View")), [this](wxCommandEvent&) { select_view("left"); }); + append_menu_item(viewMenu, wxID_ANY, _(L("Right")) + "\t\xA0" + "6", _(L("Right View")), [this](wxCommandEvent&) { select_view("right"); }); + } +#else if (m_plater) { m_viewMenu = new wxMenu(); // \xA0 is a non-breaing space. It is entered here to spoil the automatic accelerators, @@ -313,6 +407,7 @@ void MainFrame::init_menubar() append_menu_item(m_viewMenu, wxID_ANY, _(L("Left")) + "\t\xA0" + "5", _(L("Left View")), [this](wxCommandEvent&) { select_view("left"); }); append_menu_item(m_viewMenu, wxID_ANY, _(L("Right")) + "\t\xA0" + "6", _(L("Right View")), [this](wxCommandEvent&) { select_view("right"); }); } +#endif // ENABLE_NEW_MENU_LAYOUT // Help menu auto helpMenu = new wxMenu(); @@ -346,9 +441,15 @@ void MainFrame::init_menubar() { auto menubar = new wxMenuBar(); menubar->Append(fileMenu, L("&File")); - if (m_plater_menu) menubar->Append(m_plater_menu, L("&Plater")) ; +#if !ENABLE_NEW_MENU_LAYOUT + if (m_plater_menu) menubar->Append(m_plater_menu, L("&Plater")); +#endif // !ENABLE_NEW_MENU_LAYOUT menubar->Append(windowMenu, L("&Window")); +#if ENABLE_NEW_MENU_LAYOUT + if (viewMenu) menubar->Append(viewMenu, L("&View")); +#else if (m_viewMenu) menubar->Append(m_viewMenu, L("&View")); +#endif // !ENABLE_NEW_MENU_LAYOUT // Add additional menus from C++ wxGetApp().add_config_menu(menubar); menubar->Append(helpMenu, L("&Help")); diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index b6cd7916ac..bb3b5776a0 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -57,8 +57,10 @@ class MainFrame : public wxFrame wxMenuItem* m_menu_item_repeat { nullptr }; wxMenuItem* m_menu_item_reslice_now { nullptr }; - wxMenu* m_plater_menu { nullptr }; +#if !ENABLE_NEW_MENU_LAYOUT + wxMenu* m_plater_menu{ nullptr }; wxMenu* m_viewMenu{ nullptr }; +#endif // !ENABLE_NEW_MENU_LAYOUT std::string get_base_name(const wxString full_name) const ; std::string get_dir_name(const wxString full_name) const ; @@ -67,6 +69,12 @@ class MainFrame : public wxFrame void on_value_changed(wxCommandEvent&); Tab* get_tab(const std::string& name); +#if ENABLE_NEW_MENU_LAYOUT + bool can_save() const; + bool can_export_model() const; + bool can_export_gcode() const; +#endif // ENABLE_NEW_MENU_LAYOUT + public: MainFrame() {} MainFrame(const bool no_plater, const bool loaded); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 3840b248c1..a3f8637a6b 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -886,6 +886,11 @@ struct Plater::priv Sidebar *sidebar; wxGLCanvas *canvas3D; // TODO: Use GLCanvas3D when we can Preview *preview; + +#if ENABLE_NEW_MENU_LAYOUT + wxString project_filename; +#endif // ENABLE_NEW_MENU_LAYOUT + BackgroundSlicingProcess background_process; std::atomic arranging; @@ -995,6 +1000,9 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) : notebook(new wxNotebook(q, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_BOTTOM)), sidebar(new Sidebar(q)), canvas3D(GLCanvas3DManager::create_wxglcanvas(notebook)) +#if ENABLE_NEW_MENU_LAYOUT + , project_filename(wxEmptyString) +#endif // ENABLE_NEW_MENU_LAYOUT { arranging.store(false); background_process.set_fff_print(&print); @@ -1478,6 +1486,10 @@ void Plater::priv::delete_object_from_model(size_t obj_idx) void Plater::priv::reset() { +#if ENABLE_NEW_MENU_LAYOUT + project_filename.Clear(); +#endif // ENABLE_NEW_MENU_LAYOUT + // Prevent toolpaths preview from rendering while we modify the Print object preview->set_enabled(false); @@ -1906,7 +1918,11 @@ void Plater::priv::on_schedule_background_process(SimpleEvent&) void Plater::priv::on_action_add(SimpleEvent&) { if (q != nullptr) +#if ENABLE_NEW_MENU_LAYOUT + q->add_model(); +#else q->add(); +#endif // ENABLE_NEW_MENU_LAYOUT } void Plater::priv::on_action_split_objects(SimpleEvent&) @@ -2107,10 +2123,38 @@ Sidebar& Plater::sidebar() { return *p->sidebar; } Model& Plater::model() { return p->model; } Print& Plater::print() { return p->print; } +#if ENABLE_NEW_MENU_LAYOUT +void Plater::load_project() +{ + wxString input_file; + wxGetApp().load_project(this, input_file); + + if (input_file.empty()) + return; + + p->reset(); + p->project_filename = input_file; + + std::vector input_paths; + input_paths.push_back(input_file.wx_str()); + load_files(input_paths); +} + +void Plater::add_model() +#else void Plater::add() +#endif // ENABLE_NEW_MENU_LAYOUT { wxArrayString input_files; +#if ENABLE_NEW_MENU_LAYOUT + wxGetApp().import_model(this, input_files); +#else wxGetApp().open_model(this, input_files); +#endif // ENABLE_NEW_MENU_LAYOUT +#if ENABLE_NEW_MENU_LAYOUT + if (input_files.empty()) + return; +#endif // ENABLE_NEW_MENU_LAYOUT std::vector input_paths; for (const auto &file : input_files) { @@ -2321,18 +2365,42 @@ void Plater::export_amf() } } +#if ENABLE_NEW_MENU_LAYOUT +void Plater::export_3mf(const boost::filesystem::path& output_path) +#else void Plater::export_3mf() +#endif // ENABLE_NEW_MENU_LAYOUT { if (p->model.objects.empty()) { return; } - auto dialog = p->get_export_file(FT_3MF); - if (! dialog) { return; } +#if ENABLE_NEW_MENU_LAYOUT + wxString path; + bool export_config = true; + if (output_path.empty()) + { +#endif // ENABLE_NEW_MENU_LAYOUT + auto dialog = p->get_export_file(FT_3MF); + if (!dialog) { return; } +#if ENABLE_NEW_MENU_LAYOUT + path = dialog->GetPath(); + export_config = dialog->get_checkbox_value(); + } + else + path = output_path.string(); - wxString path = dialog->GetPath(); - auto path_cstr = path.c_str(); + if (!path.Lower().EndsWith(".3mf")) + return; +#else + wxString path = dialog->GetPath(); + auto path_cstr = path.c_str(); +#endif // ENABLE_NEW_MENU_LAYOUT DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config(); - if (Slic3r::store_3mf(path_cstr, &p->model, dialog->get_checkbox_value() ? &cfg : nullptr)) { +#if ENABLE_NEW_MENU_LAYOUT + if (Slic3r::store_3mf(path.c_str(), &p->model, export_config ? &cfg : nullptr)) { +#else + if (Slic3r::store_3mf(path_cstr, &p->model, dialog->get_checkbox_value() ? &cfg : nullptr)) { +#endif // ENABLE_NEW_MENU_LAYOUT // Success p->statusbar()->set_status_text(wxString::Format(_(L("3MF file exported to %s")), path)); } else { @@ -2449,6 +2517,18 @@ void Plater::on_config_change(const DynamicPrintConfig &config) this->p->schedule_background_process(); } +#if ENABLE_NEW_MENU_LAYOUT +const wxString& Plater::get_project_filename() const +{ + return p->project_filename; +} + +bool Plater::is_export_gcode_scheduled() const +{ + return p->background_process.is_export_scheduled(); +} +#endif // ENABLE_NEW_MENU_LAYOUT + int Plater::get_selected_object_idx() { return p->get_selected_object_idx(); diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 1b2b530402..7982d504fe 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -111,7 +111,12 @@ public: Model& model(); Print& print(); +#if ENABLE_NEW_MENU_LAYOUT + void load_project(); + void add_model(); +#else void add(); +#endif // ENABLE_NEW_MENU_LAYOUT void load_files(const std::vector &input_files); @@ -129,7 +134,11 @@ public: void export_gcode(boost::filesystem::path output_path = boost::filesystem::path()); void export_stl(); void export_amf(); +#if ENABLE_NEW_MENU_LAYOUT + void export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path()); +#else void export_3mf(); +#endif // ENABLE_NEW_MENU_LAYOUT void reslice(); void changed_object(int obj_idx); void fix_through_netfabb(const int obj_idx); @@ -138,6 +147,11 @@ public: void on_extruders_change(int extruders_count); void on_config_change(const DynamicPrintConfig &config); +#if ENABLE_NEW_MENU_LAYOUT + const wxString& get_project_filename() const; + bool is_export_gcode_scheduled() const; +#endif // ENABLE_NEW_MENU_LAYOUT + int get_selected_object_idx(); bool is_single_full_object_selection() const; wxGLCanvas* canvas3D();