diff --git a/resources/icons/export_to_sd.svg b/resources/icons/export_to_sd.svg new file mode 100644 index 000000000..c836b00a1 --- /dev/null +++ b/resources/icons/export_to_sd.svg @@ -0,0 +1,145 @@ + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/slic3r/GUI/AppConfig.cpp b/src/slic3r/GUI/AppConfig.cpp index 12302a5dc..a410f3ad8 100644 --- a/src/slic3r/GUI/AppConfig.cpp +++ b/src/slic3r/GUI/AppConfig.cpp @@ -78,6 +78,9 @@ void AppConfig::set_defaults() if (get("remember_output_path").empty()) set("remember_output_path", "1"); + if (get("remember_output_path_removable").empty()) + set("remember_output_path_removable", "1"); + if (get("use_custom_toolbar_size").empty()) set("use_custom_toolbar_size", "0"); @@ -388,7 +391,7 @@ void AppConfig::update_skein_dir(const std::string &dir) { this->set("recent", "skein_directory", dir); } - +/* std::string AppConfig::get_last_output_dir(const std::string &alt) const { @@ -406,6 +409,26 @@ void AppConfig::update_last_output_dir(const std::string &dir) { this->set("", "last_output_path", dir); } +*/ +std::string AppConfig::get_last_output_dir(const std::string& alt, const bool removable) const +{ + std::string s1 = (removable ? "last_output_path_removable" : "last_output_path"); + std::string s2 = (removable ? "remember_output_path_removable" : "remember_output_path"); + const auto it = m_storage.find(""); + if (it != m_storage.end()) { + const auto it2 = it->second.find(s1); + const auto it3 = it->second.find(s2); + if (it2 != it->second.end() && it3 != it->second.end() && !it2->second.empty() && it3->second == "1") + return it2->second; + } + return alt; +} + +void AppConfig::update_last_output_dir(const std::string& dir, const bool removable) +{ + this->set("", (removable ? "last_output_path_removable" : "last_output_path"), dir); +} + void AppConfig::reset_selections() { diff --git a/src/slic3r/GUI/AppConfig.hpp b/src/slic3r/GUI/AppConfig.hpp index b432367b6..32f1c32c8 100644 --- a/src/slic3r/GUI/AppConfig.hpp +++ b/src/slic3r/GUI/AppConfig.hpp @@ -102,8 +102,10 @@ public: void update_config_dir(const std::string &dir); void update_skein_dir(const std::string &dir); - std::string get_last_output_dir(const std::string &alt) const; - void update_last_output_dir(const std::string &dir); + //std::string get_last_output_dir(const std::string &alt) const; + //void update_last_output_dir(const std::string &dir); + std::string get_last_output_dir(const std::string& alt, const bool removable = false) const; + void update_last_output_dir(const std::string &dir, const bool removable = false); // reset the current print / filament / printer selections, so that // the PresetBundle::load_selections(const AppConfig &config) call will select diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index b388ad1d2..15820c68a 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -701,6 +701,7 @@ struct Sidebar::priv wxButton *btn_reslice; ScalableButton *btn_send_gcode; ScalableButton *btn_remove_device; + ScalableButton* btn_export_gcode_removable; //exports to removable drives (appears only if removable drive is connected) priv(Plater *plater) : plater(plater) {} ~priv(); @@ -866,6 +867,7 @@ Sidebar::Sidebar(Plater *parent) init_scalable_btn(&p->btn_send_gcode , "export_gcode", _(L("Send to printer"))); init_scalable_btn(&p->btn_remove_device, "cross" , _(L("Remove device"))); + init_scalable_btn(&p->btn_export_gcode_removable, "export_to_sd", _(L("Export to SD card/ USB thumb drive"))); // regular buttons "Slice now" and "Export G-code" @@ -886,7 +888,9 @@ Sidebar::Sidebar(Plater *parent) auto* complect_btns_sizer = new wxBoxSizer(wxHORIZONTAL); complect_btns_sizer->Add(p->btn_export_gcode, 1, wxEXPAND); complect_btns_sizer->Add(p->btn_send_gcode); + complect_btns_sizer->Add(p->btn_export_gcode_removable); complect_btns_sizer->Add(p->btn_remove_device); + btns_sizer->Add(p->btn_reslice, 0, wxEXPAND | wxTOP, margin_5); btns_sizer->Add(complect_btns_sizer, 0, wxEXPAND | wxTOP, margin_5); @@ -897,7 +901,7 @@ Sidebar::Sidebar(Plater *parent) SetSizer(sizer); // Events - p->btn_export_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->export_gcode(); }); + p->btn_export_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->export_gcode(false); }); p->btn_reslice->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { const bool export_gcode_after_slicing = wxGetKeyState(WXK_SHIFT); @@ -909,6 +913,7 @@ Sidebar::Sidebar(Plater *parent) }); p->btn_send_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->send_gcode(); }); p->btn_remove_device->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->eject_drive(); }); + p->btn_export_gcode_removable->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->export_gcode(true); }); } Sidebar::~Sidebar() {} @@ -1056,6 +1061,7 @@ void Sidebar::msw_rescale() p->btn_send_gcode->msw_rescale(); p->btn_remove_device->msw_rescale(); + p->btn_export_gcode_removable->msw_rescale(); const int scaled_height = p->btn_remove_device->GetBitmap().GetHeight() + 4; p->btn_export_gcode->SetMinSize(wxSize(-1, scaled_height)); p->btn_reslice ->SetMinSize(wxSize(-1, scaled_height)); @@ -1289,12 +1295,14 @@ void Sidebar::enable_buttons(bool enable) p->btn_export_gcode->Enable(enable); p->btn_send_gcode->Enable(enable); p->btn_remove_device->Enable(enable); + p->btn_export_gcode_removable->Enable(enable); } -bool Sidebar::show_reslice(bool show) const { return p->btn_reslice->Show(show); } -bool Sidebar::show_export(bool show) const { return p->btn_export_gcode->Show(show); } -bool Sidebar::show_send(bool show) const { return p->btn_send_gcode->Show(show); } -bool Sidebar::show_disconnect(bool show)const { return p->btn_remove_device->Show(show); } +bool Sidebar::show_reslice(bool show) const { return p->btn_reslice->Show(show); } +bool Sidebar::show_export(bool show) const { return p->btn_export_gcode->Show(show); } +bool Sidebar::show_send(bool show) const { return p->btn_send_gcode->Show(show); } +bool Sidebar::show_disconnect(bool show) const { return p->btn_remove_device->Show(show); } +bool Sidebar::show_export_removable(bool show)const { return p->btn_export_gcode_removable->Show(show); } bool Sidebar::is_multifilament() { @@ -2192,6 +2200,9 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) // Initialize the Undo / Redo stack with a first snapshot. this->take_snapshot(_(L("New Project"))); + + //void Plater::priv::show_action_buttons(const bool is_ready_to_slice) const + RemovableDriveManager::get_instance().set_drive_count_changed_callback(std::bind(&Plater::priv::show_action_buttons, this, std::placeholders::_1)); } Plater::priv::~priv() @@ -3596,12 +3607,8 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt) if(!canceled && RemovableDriveManager::get_instance().get_is_writing()) { - //if (!RemovableDriveManager::get_instance().is_last_drive_removed()) - //{ - RemovableDriveManager::get_instance().set_is_writing(false); - show_action_buttons(false); - //} - + RemovableDriveManager::get_instance().set_is_writing(false); + show_action_buttons(false); } } @@ -4156,18 +4163,21 @@ void Plater::priv::update_object_menu() void Plater::priv::show_action_buttons(const bool is_ready_to_slice) const { + RemovableDriveManager::get_instance().set_plater_ready_to_slice(is_ready_to_slice); wxWindowUpdateLocker noUpdater(sidebar); const auto prin_host_opt = config->option("print_host"); const bool send_gcode_shown = prin_host_opt != nullptr && !prin_host_opt->value.empty(); - bool disconnect_shown = !RemovableDriveManager::get_instance().is_last_drive_removed() ; // #dk_FIXME + bool disconnect_shown = !RemovableDriveManager::get_instance().is_last_drive_removed(); + bool export_removable_shown = RemovableDriveManager::get_instance().get_drives_count() > 0; // when a background processing is ON, export_btn and/or send_btn are showing if (wxGetApp().app_config->get("background_processing") == "1") { - if (sidebar->show_reslice(false) | - sidebar->show_export(true) | - sidebar->show_send(send_gcode_shown) | - sidebar->show_disconnect(disconnect_shown)) + if (sidebar->show_reslice(false) | + sidebar->show_export(true) | + sidebar->show_send(send_gcode_shown) | + sidebar->show_export_removable(export_removable_shown) | + sidebar->show_disconnect(disconnect_shown)) sidebar->Layout(); } else @@ -4175,6 +4185,7 @@ void Plater::priv::show_action_buttons(const bool is_ready_to_slice) const if (sidebar->show_reslice(is_ready_to_slice) | sidebar->show_export(!is_ready_to_slice) | sidebar->show_send(send_gcode_shown && !is_ready_to_slice) | + sidebar->show_export_removable(export_removable_shown && !is_ready_to_slice) | sidebar->show_disconnect(disconnect_shown && !is_ready_to_slice)) sidebar->Layout(); } @@ -4684,7 +4695,7 @@ void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_uppe } } -void Plater::export_gcode() +void Plater::export_gcode(bool prefer_removable) { if (p->model.objects.empty()) return; @@ -4706,11 +4717,19 @@ void Plater::export_gcode() } default_output_file = fs::path(Slic3r::fold_utf8_to_ascii(default_output_file.string())); auto start_dir = wxGetApp().app_config->get_last_output_dir(default_output_file.parent_path().string()); - if (GUI::RemovableDriveManager::get_instance().update()) + bool removable_drives_connected = GUI::RemovableDriveManager::get_instance().update(); + if(prefer_removable) { - if (!RemovableDriveManager::get_instance().is_path_on_removable_drive(start_dir)) + if(removable_drives_connected) { - start_dir = RemovableDriveManager::get_instance().get_drive_path(); + auto start_dir_removable = wxGetApp().app_config->get_last_output_dir(default_output_file.parent_path().string(), true); + if (RemovableDriveManager::get_instance().is_path_on_removable_drive(start_dir_removable)) + { + start_dir = start_dir_removable; + }else + { + start_dir = RemovableDriveManager::get_instance().get_drive_path(); + } } } wxFileDialog dlg(this, (printer_technology() == ptFFF) ? _(L("Save G-code file as:")) : _(L("Save SL1 file as:")), @@ -4723,7 +4742,7 @@ void Plater::export_gcode() fs::path output_path; if (dlg.ShowModal() == wxID_OK) { fs::path path = into_path(dlg.GetPath()); - wxGetApp().app_config->update_last_output_dir(path.parent_path().string()); + wxGetApp().app_config->update_last_output_dir(path.parent_path().string(), RemovableDriveManager::get_instance().is_path_on_removable_drive(path.parent_path().string())); output_path = std::move(path); } if (! output_path.empty()) @@ -4739,7 +4758,7 @@ void Plater::export_gcode() { RemovableDriveManager::get_instance().set_is_writing(true); RemovableDriveManager::get_instance().erase_callbacks(); - RemovableDriveManager::get_instance().add_callback(std::bind(&Plater::drive_ejected_callback, this)); + RemovableDriveManager::get_instance().add_remove_callback(std::bind(&Plater::drive_ejected_callback, this)); } } @@ -5041,8 +5060,8 @@ void Plater::send_gcode() void Plater::eject_drive() { RemovableDriveManager::get_instance().update(0, true); - //RemovableDriveManager::get_instance().erase_callbacks(); - //RemovableDriveManager::get_instance().add_callback(std::bind(&Plater::drive_ejected_callback, this)); + RemovableDriveManager::get_instance().erase_callbacks(); + RemovableDriveManager::get_instance().add_remove_callback(std::bind(&Plater::drive_ejected_callback, this)); RemovableDriveManager::get_instance().eject_drive(RemovableDriveManager::get_instance().get_last_save_path()); } @@ -5051,7 +5070,7 @@ void Plater::drive_ejected_callback() if (RemovableDriveManager::get_instance().get_did_eject()) { RemovableDriveManager::get_instance().set_did_eject(false); - wxString message = "Unmounting succesesful. The device " + RemovableDriveManager::get_instance().get_last_save_name() + "(" + RemovableDriveManager::get_instance().get_last_save_path() + ")" + " can now be safely removed from the computer."; + wxString message = "Unmounting successful. The device " + RemovableDriveManager::get_instance().get_ejected_name() + "(" + RemovableDriveManager::get_instance().get_ejected_path() + ")" + " can now be safely removed from the computer."; wxMessageBox(message); } p->show_action_buttons(false); diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 1bea07795..f9891a252 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -120,6 +120,7 @@ public: bool show_export(bool show) const; bool show_send(bool show) const; bool show_disconnect(bool show)const; + bool show_export_removable(bool show) const; bool is_multifilament(); void update_mode(); @@ -186,7 +187,7 @@ public: void cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_upper = true, bool keep_lower = true, bool rotate_lower = false); - void export_gcode(); + void export_gcode(bool prefer_removable = true); void export_stl(bool extended = false, bool selection_only = false); void export_amf(); void export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path()); diff --git a/src/slic3r/GUI/RemovableDriveManager.cpp b/src/slic3r/GUI/RemovableDriveManager.cpp index 5041e2120..4ed6d36a4 100644 --- a/src/slic3r/GUI/RemovableDriveManager.cpp +++ b/src/slic3r/GUI/RemovableDriveManager.cpp @@ -109,6 +109,8 @@ void RemovableDriveManager::eject_drive(const std::string &path) CloseHandle(handle); m_did_eject = true; m_current_drives.erase(it); + m_ejected_path = m_last_save_path; + m_ejected_name = m_last_save_name; break; } } @@ -239,35 +241,10 @@ void RemovableDriveManager::search_for_drives() //search /media/* folder search_path("/media/*", "/media"); - //search /Volumes/* folder (OSX) //search_path("/Volumes/*", "/Volumes"); std::string path(std::getenv("USER")); std::string pp(path); - //std::cout << "user: "<< path << "\n"; - //if program is run with sudo, we have to search for all users - // but do we want that? - /* - if(path == "root"){ - while (true) { - passwd* entry = getpwent(); - if (!entry) { - break; - } - path = entry->pw_name; - pp = path; - //search /media/USERNAME/* folder - pp = "/media/"+pp; - path = "/media/" + path + "/*"; - search_path(path, pp); - //search /run/media/USERNAME/* folder - path = "/run" + path; - pp = "/run"+pp; - search_path(path, pp); - } - endpwent(); - }else - */ { //search /media/USERNAME/* folder pp = "/media/"+pp; @@ -310,7 +287,6 @@ void RemovableDriveManager::inspect_file(const std::string &path, const std::str { //free space boost::filesystem::space_info si = boost::filesystem::space(path); - //std::cout << "Free space: " << fs_si.free << "Available space: " << fs_si.available << " " << path << '\n'; if(si.available != 0) { //user id @@ -353,7 +329,7 @@ void RemovableDriveManager::eject_drive(const std::string &path) i++; } } - std::cout<<"Ejecting "<<(*it).name<<" from "<< correct_path<<"\n"; + //std::cout<<"Ejecting "<<(*it).name<<" from "<< correct_path<<"\n"; // there is no usable command in c++ so terminal command is used instead // but neither triggers "succesful safe removal messege" std::string command = ""; @@ -373,7 +349,8 @@ void RemovableDriveManager::eject_drive(const std::string &path) m_did_eject = true; m_current_drives.erase(it); - + m_ejected_path = m_last_save_path; + m_ejected_name = m_last_save_name; break; } @@ -385,7 +362,7 @@ bool RemovableDriveManager::is_path_on_removable_drive(const std::string &path) if (m_current_drives.empty()) return false; std::size_t found = path.find_last_of("/"); - std::string new_path = path.substr(0,found); + std::string new_path = found == path.size() - 1 ? path.substr(0, found) : path; for (auto it = m_current_drives.begin(); it != m_current_drives.end(); ++it) { if(compare_filesystem_id(new_path, (*it).path)) @@ -396,7 +373,7 @@ bool RemovableDriveManager::is_path_on_removable_drive(const std::string &path) std::string RemovableDriveManager::get_drive_from_path(const std::string& path) { std::size_t found = path.find_last_of("/"); - std::string new_path = path.substr(0, found); + std::string new_path = found == path.size() - 1 ? path.substr(0, found) : path; //check if same filesystem for (auto it = m_current_drives.begin(); it != m_current_drives.end(); ++it) { @@ -414,7 +391,10 @@ RemovableDriveManager::RemovableDriveManager(): m_last_save_name(""), m_last_save_path_verified(false), m_is_writing(false), - m_did_eject(false) + m_did_eject(false), + m_plater_ready_to_slice(true), + m_ejected_path(""), + m_ejected_name("") #if __APPLE__ , m_rdmmm(new RDMMMWrapper()) #endif @@ -451,7 +431,10 @@ bool RemovableDriveManager::update(const long time,const bool check) search_for_drives(); if (m_drives_count != m_current_drives.size()) { - if (check)check_and_notify(); + if (check) + { + check_and_notify(); + } m_drives_count = m_current_drives.size(); } return !m_current_drives.empty(); @@ -495,7 +478,11 @@ std::vector RemovableDriveManager::get_all_drives() } void RemovableDriveManager::check_and_notify() { - if(m_callbacks.size() != 0 && m_drives_count > m_current_drives.size() && m_last_save_path_verified && !is_drive_mounted(m_last_save_path)) + if(m_drive_count_changed_callback) + { + m_drive_count_changed_callback(m_plater_ready_to_slice); + } + if(m_callbacks.size() != 0 && m_drives_count > m_current_drives.size() /*&& m_last_save_path_verified */&& !is_drive_mounted(m_last_save_path)) { for (auto it = m_callbacks.begin(); it != m_callbacks.end(); ++it) { @@ -503,18 +490,35 @@ void RemovableDriveManager::check_and_notify() } } } -void RemovableDriveManager::add_callback(std::function callback) +void RemovableDriveManager::add_remove_callback(std::function callback) { m_callbacks.push_back(callback); } -void RemovableDriveManager::erase_callbacks() +void RemovableDriveManager::erase_callbacks() { m_callbacks.clear(); } +void RemovableDriveManager::set_drive_count_changed_callback(std::function callback) +{ + m_drive_count_changed_callback = callback; +} +void RemovableDriveManager::set_plater_ready_to_slice(bool b) +{ + m_plater_ready_to_slice = b; +} void RemovableDriveManager::set_last_save_path(const std::string& path) { - m_last_save_path_verified = false; - m_last_save_path = path; + if(m_last_save_path_verified)// if old path is on drive + { + if(get_drive_from_path(path) != "") //and new is too, rewrite the path + { + m_last_save_path_verified = false; + m_last_save_path = path; + }//else do nothing + }else + { + m_last_save_path = path; + } } void RemovableDriveManager::verify_last_save_path() { @@ -544,16 +548,15 @@ std::string RemovableDriveManager::get_drive_name(const std::string& path) } bool RemovableDriveManager::is_last_drive_removed() { - //std::cout<<"is last: "< get_all_drives(); bool is_path_on_removable_drive(const std::string &path); // callback will notify only if device with last save path was removed - void add_callback(std::function callback); - // erases all callbacks added by add_callback() + void add_remove_callback(std::function callback); + // erases all remove callbacks added by add_remove_callback() void erase_callbacks(); + //drive_count_changed callback is called on every added or removed device + void set_drive_count_changed_callback(std::function callback); + //thi serves to set correct value for drive_count_changed callback + void set_plater_ready_to_slice(bool b); // marks one of the eveices in vector as last used void set_last_save_path(const std::string &path); void verify_last_save_path(); @@ -59,6 +63,9 @@ public: bool get_did_eject(); void set_did_eject(const bool b); std::string get_drive_name(const std::string& path); + size_t get_drives_count(); + std::string get_ejected_path(); + std::string get_ejected_name(); private: RemovableDriveManager(); void search_for_drives(); @@ -70,6 +77,7 @@ private: std::vector m_current_drives; std::vector> m_callbacks; + std::function m_drive_count_changed_callback; size_t m_drives_count; long m_last_update; std::string m_last_save_path; @@ -77,6 +85,9 @@ private: std::string m_last_save_name; bool m_is_writing;//on device bool m_did_eject; + bool m_plater_ready_to_slice; + std::string m_ejected_path; + std::string m_ejected_name; #if _WIN32 //registers for notifications by creating invisible window void register_window();