diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index ec9392ec4..4d3ad00dd 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -717,6 +717,7 @@ void GCode::_do_export(Print &print, FILE *file) // Prepare the helper object for replacing placeholders in custom G-code and output filename. m_placeholder_parser = print.placeholder_parser(); m_placeholder_parser.update_timestamp(); + print.update_object_placeholders(m_placeholder_parser.config_writable()); // Get optimal tool ordering to minimize tool switches of a multi-exruder print. // For a print by objects, find the 1st printing object. diff --git a/src/libslic3r/GCode/CoolingBuffer.cpp b/src/libslic3r/GCode/CoolingBuffer.cpp index 09d211994..552fbf88c 100644 --- a/src/libslic3r/GCode/CoolingBuffer.cpp +++ b/src/libslic3r/GCode/CoolingBuffer.cpp @@ -252,8 +252,10 @@ float new_feedrate_to_reach_time_stretch( for (size_t i = 0; i < (*it)->n_lines_adjustable; ++i) { const CoolingLine &line = (*it)->lines[i]; if (line.feedrate > min_feedrate && line.feedrate < new_feedrate) - // Some of the line segments taken into account in the calculation of nomin / denom are now slower than new_feedrate. - // Re-run the calculation with a new min_feedrate limit. + // Some of the line segments taken into account in the calculation of nomin / denom are now slower than new_feedrate, + // which makes the new_feedrate lower than it should be. + // Re-run the calculation with a new min_feedrate limit, so that the segments with current feedrate lower than new_feedrate + // are not taken into account. goto not_finished_yet; } goto finished; diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 968a3e234..08eb8df81 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -547,13 +547,26 @@ void Model::reset_auto_extruder_id() s_auto_extruder_id = 1; } -std::string Model::propose_export_file_name() const +// Propose a filename including path derived from the ModelObject's input path. +// If object's name is filled in, use the object name, otherwise use the input name. +std::string Model::propose_export_file_name_and_path() const { std::string input_file; for (const ModelObject *model_object : this->objects) for (ModelInstance *model_instance : model_object->instances) if (model_instance->is_printable()) { - input_file = model_object->name.empty() ? model_object->input_file : model_object->name; + input_file = model_object->input_file; + if (! model_object->name.empty()) { + if (input_file.empty()) + // model_object->input_file was empty, just use model_object->name + input_file = model_object->name; + else { + // Replace file name in input_file with model_object->name, but keep the path and file extension. + input_file = (boost::filesystem::path(model_object->name).parent_path().empty()) ? + (boost::filesystem::path(input_file).parent_path() / model_object->name).make_preferred().string() : + model_object->name; + } + } if (! input_file.empty()) goto end; // Other instances will produce the same name, skip them. diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 8a19ba003..5d6f2a9db 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -387,6 +387,8 @@ public: const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); } + using ModelBase::set_new_unique_id; + protected: friend class Print; friend class SLAPrint; @@ -609,8 +611,8 @@ public: static std::string get_auto_extruder_id_as_string(unsigned int max_extruders); static void reset_auto_extruder_id(); - // Propose an output file name based on the first printable object's name. - std::string propose_export_file_name() const; + // Propose an output file name & path based on the first printable object's name and source input file's path. + std::string propose_export_file_name_and_path() const; private: MODELBASE_DERIVED_PRIVATE_COPY_MOVE(Model) diff --git a/src/libslic3r/PlaceholderParser.hpp b/src/libslic3r/PlaceholderParser.hpp index b5ed56fa1..22c790e6b 100644 --- a/src/libslic3r/PlaceholderParser.hpp +++ b/src/libslic3r/PlaceholderParser.hpp @@ -32,7 +32,8 @@ public: void set(const std::string &key, double value) { this->set(key, new ConfigOptionFloat(value)); } void set(const std::string &key, const std::vector &values) { this->set(key, new ConfigOptionStrings(values)); } void set(const std::string &key, ConfigOption *opt) { m_config.set_key_value(key, opt); } - const DynamicConfig& config() const { return m_config; } + DynamicConfig& config_writable() { return m_config; } + const DynamicConfig& config() const { return m_config; } const ConfigOption* option(const std::string &key) const { return m_config.option(key); } // Fill in the template using a macro processing language. diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 42fda92fe..bc692ca90 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -421,8 +421,6 @@ void Print::add_model_object(ModelObject* model_object, int idx) src_normalized.normalize(); object->config_apply(src_normalized, true); } - - this->update_object_placeholders(); } bool Print::apply_config(DynamicPrintConfig config) @@ -1096,9 +1094,6 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co } } - //FIXME there may be a race condition with the G-code export running at the background thread. - this->update_object_placeholders(); - #ifdef _DEBUG check_model_ids_equal(m_model, model); #endif /* _DEBUG */ @@ -1855,6 +1850,9 @@ int Print::get_extruder(const ExtrusionEntityCollection& fill, const PrintRegion std::max(region.config().perimeter_extruder.value - 1, 0); } +// Generate a recommended G-code output file name based on the format template, default extension, and template parameters +// (timestamps, object placeholders derived from the model, current placeholder prameters and print statistics. +// Use the final print statistics if available, or just keep the print statistics placeholders if not available yet (before G-code is finalized). std::string Print::output_filename() const { // Set the placeholders for the data know first after the G-code export is finished. diff --git a/src/libslic3r/PrintBase.cpp b/src/libslic3r/PrintBase.cpp index 48e991b8d..3fe9a2b4d 100644 --- a/src/libslic3r/PrintBase.cpp +++ b/src/libslic3r/PrintBase.cpp @@ -15,7 +15,7 @@ namespace Slic3r size_t PrintStateBase::g_last_timestamp = 0; // Update "scale", "input_filename", "input_filename_base" placeholders from the current m_objects. -void PrintBase::update_object_placeholders() +void PrintBase::update_object_placeholders(DynamicConfig &config) const { // get the first input file name std::string input_file; @@ -33,27 +33,29 @@ void PrintBase::update_object_placeholders() "% y:" + boost::lexical_cast(printable->get_scaling_factor(Y) * 100) + "% z:" + boost::lexical_cast(printable->get_scaling_factor(Z) * 100) + "%"); if (input_file.empty()) - input_file = model_object->input_file; + input_file = model_object->name.empty() ? model_object->input_file : model_object->name; } } - PlaceholderParser &pp = m_placeholder_parser; - pp.set("scale", v_scale); + config.set_key_value("year", new ConfigOptionStrings(v_scale)); if (! input_file.empty()) { // get basename with and without suffix const std::string input_basename = boost::filesystem::path(input_file).filename().string(); - pp.set("input_filename", input_basename); + config.set_key_value("input_filename", new ConfigOptionString(input_basename)); const std::string input_basename_base = input_basename.substr(0, input_basename.find_last_of(".")); - pp.set("input_filename_base", input_basename_base); + config.set_key_value("input_filename_base", new ConfigOptionString(input_basename_base)); } } +// Generate an output file name based on the format template, default extension, and template parameters +// (timestamps, object placeholders derived from the model, current placeholder prameters, print statistics - config_override) std::string PrintBase::output_filename(const std::string &format, const std::string &default_ext, const DynamicConfig *config_override) const { DynamicConfig cfg; if (config_override != nullptr) cfg = *config_override; PlaceholderParser::update_timestamp(cfg); + this->update_object_placeholders(cfg); try { boost::filesystem::path filename = this->placeholder_parser().process(format, 0, &cfg); if (filename.extension().empty()) @@ -69,7 +71,7 @@ std::string PrintBase::output_filepath(const std::string &path) const // if we were supplied no path, generate an automatic one based on our first object's input file if (path.empty()) // get the first input file name - return (boost::filesystem::path(m_model.propose_export_file_name()).parent_path() / this->output_filename()).make_preferred().string(); + return (boost::filesystem::path(m_model.propose_export_file_name_and_path()).parent_path() / this->output_filename()).make_preferred().string(); // if we were supplied a directory, use it and append our automatically generated filename boost::filesystem::path p(path); diff --git a/src/libslic3r/PrintBase.hpp b/src/libslic3r/PrintBase.hpp index 1a61921d6..84d04d26f 100644 --- a/src/libslic3r/PrintBase.hpp +++ b/src/libslic3r/PrintBase.hpp @@ -309,7 +309,7 @@ protected: // To be called by this->output_filename() with the format string pulled from the configuration layer. std::string output_filename(const std::string &format, const std::string &default_ext, const DynamicConfig *config_override = nullptr) const; // Update "scale", "input_filename", "input_filename_base" placeholders from the current printable ModelObjects. - void update_object_placeholders(); + void update_object_placeholders(DynamicConfig &config) const; Model m_model; diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 673625e85..a4e554d8e 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -387,8 +387,6 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf update_apply_status(false); } - this->update_object_placeholders(); - #ifdef _DEBUG check_model_ids_equal(m_model, model); #endif /* _DEBUG */ diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index ed12d0559..046745e6f 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -8,6 +8,8 @@ #include "libslic3r.h" +namespace boost { namespace filesystem { class directory_entry; }} + namespace Slic3r { extern void set_logging_level(unsigned int level); @@ -61,6 +63,12 @@ extern int rename_file(const std::string &from, const std::string &to); // Copy a file, adjust the access attributes, so that the target is writable. extern int copy_file(const std::string &from, const std::string &to); +// Ignore system and hidden files, which may be created by the DropBox synchronisation process. +// https://github.com/prusa3d/Slic3r/issues/1298 +extern bool is_plain_file(const boost::filesystem::directory_entry &path); +extern bool is_ini_file(const boost::filesystem::directory_entry &path); +extern bool is_idx_file(const boost::filesystem::directory_entry &path); + // File path / name / extension splitting utilities, working with UTF-8, // to be published to Perl. namespace PerlUtils { diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index f48abfd89..6727bb799 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -30,7 +30,13 @@ #include -#include +#if defined(__linux) || defined(__GNUC__ ) +#include +#endif /* __linux */ + +#ifdef _MSC_VER + #define strcasecmp _stricmp +#endif namespace Slic3r { @@ -248,6 +254,30 @@ int copy_file(const std::string &from, const std::string &to) return 0; } +// Ignore system and hidden files, which may be created by the DropBox synchronisation process. +// https://github.com/prusa3d/Slic3r/issues/1298 +bool is_plain_file(const boost::filesystem::directory_entry &dir_entry) +{ + if (! boost::filesystem::is_regular_file(dir_entry.status())) + return false; +#ifdef _MSC_VER + DWORD attributes = GetFileAttributesW(boost::nowide::widen(dir_entry.path().string()).c_str()); + return (attributes & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) == 0; +#else + return true; +#endif +} + +bool is_ini_file(const boost::filesystem::directory_entry &dir_entry) +{ + return is_plain_file(dir_entry) && strcasecmp(dir_entry.path().extension().string().c_str(), ".ini") == 0; +} + +bool is_idx_file(const boost::filesystem::directory_entry &dir_entry) +{ + return is_plain_file(dir_entry) && strcasecmp(dir_entry.path().extension().string().c_str(), ".idx") == 0; +} + } // namespace Slic3r #ifdef WIN32 diff --git a/src/slic3r.cpp b/src/slic3r.cpp index 0b7dada70..62b56f7ff 100644 --- a/src/slic3r.cpp +++ b/src/slic3r.cpp @@ -252,10 +252,6 @@ int main(int argc, char **argv) model.arrange_objects(fff_print.config().min_object_distance()); model.center_instances_around_point(cli_config.print_center); } - if (outfile.empty()) { - outfile = model.propose_export_file_name(); - outfile += (printer_technology == ptFFF) ? ".gcode" : ".zip"; - } if (printer_technology == ptFFF) { for (auto* mo : model.objects) fff_print.auto_assign_extruders(mo); @@ -265,6 +261,7 @@ int main(int argc, char **argv) std::string err = print->validate(); if (err.empty()) { if (printer_technology == ptFFF) { + // The outfile is processed by a PlaceholderParser. fff_print.export_gcode(outfile, nullptr); } else { assert(printer_technology == ptSLA); diff --git a/src/slic3r/Config/Snapshot.cpp b/src/slic3r/Config/Snapshot.cpp index 35bfde0b6..b208554b5 100644 --- a/src/slic3r/Config/Snapshot.cpp +++ b/src/slic3r/Config/Snapshot.cpp @@ -249,10 +249,10 @@ bool Snapshot::equal_to_active(const AppConfig &app_config) const boost::filesystem::path path2 = snapshot_dir / subdir; std::vector files1, files2; for (auto &dir_entry : boost::filesystem::directory_iterator(path1)) - if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini")) + if (Slic3r::is_ini_file(dir_entry)) files1.emplace_back(dir_entry.path().filename().string()); for (auto &dir_entry : boost::filesystem::directory_iterator(path2)) - if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini")) + if (Slic3r::is_ini_file(dir_entry)) files2.emplace_back(dir_entry.path().filename().string()); std::sort(files1.begin(), files1.end()); std::sort(files2.begin(), files2.end()); @@ -343,7 +343,7 @@ static void copy_config_dir_single_level(const boost::filesystem::path &path_src throw std::runtime_error(std::string("Slic3r was unable to create a directory at ") + path_dst.string()); for (auto &dir_entry : boost::filesystem::directory_iterator(path_src)) - if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini")) + if (Slic3r::is_ini_file(dir_entry)) boost::filesystem::copy_file(dir_entry.path(), path_dst / dir_entry.path().filename(), boost::filesystem::copy_option::overwrite_if_exists); } diff --git a/src/slic3r/Config/Version.cpp b/src/slic3r/Config/Version.cpp index 48ace7b60..70b12f23b 100644 --- a/src/slic3r/Config/Version.cpp +++ b/src/slic3r/Config/Version.cpp @@ -297,7 +297,7 @@ std::vector Index::load_db() std::vector index_db; std::string errors_cummulative; for (auto &dir_entry : boost::filesystem::directory_iterator(cache_dir)) - if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".idx")) { + if (Slic3r::is_idx_file(dir_entry)) { Index idx; try { idx.load(dir_entry.path()); diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 6cf45166d..3471395c2 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -635,35 +635,32 @@ void ConfigWizard::priv::load_vendors() const auto rsrc_vendor_dir = fs::path(resources_dir()) / "profiles"; // Load vendors from the "vendors" directory in datadir - for (fs::directory_iterator it(vendor_dir); it != fs::directory_iterator(); ++it) { - if (it->path().extension() == ".ini") { + for (auto &dir_entry : boost::filesystem::directory_iterator(vendor_dir)) + if (Slic3r::is_ini_file(dir_entry)) { try { - auto vp = VendorProfile::from_ini(it->path()); + auto vp = VendorProfile::from_ini(dir_entry.path()); vendors[vp.id] = std::move(vp); } catch (const std::exception& e) { - BOOST_LOG_TRIVIAL(error) << boost::format("Error loading vendor bundle %1%: %2%") % it->path() % e.what(); + BOOST_LOG_TRIVIAL(error) << boost::format("Error loading vendor bundle %1%: %2%") % dir_entry.path() % e.what(); } - } - } // Additionally load up vendors from the application resources directory, but only those not seen in the datadir - for (fs::directory_iterator it(rsrc_vendor_dir); it != fs::directory_iterator(); ++it) { - if (it->path().extension() == ".ini") { - const auto id = it->path().stem().string(); + for (auto &dir_entry : boost::filesystem::directory_iterator(rsrc_vendor_dir)) + if (Slic3r::is_ini_file(dir_entry)) { + const auto id = dir_entry.path().stem().string(); if (vendors.find(id) == vendors.end()) { try { - auto vp = VendorProfile::from_ini(it->path()); - vendors_rsrc[vp.id] = it->path().filename().string(); + auto vp = VendorProfile::from_ini(dir_entry.path()); + vendors_rsrc[vp.id] = dir_entry.path().filename().string(); vendors[vp.id] = std::move(vp); } catch (const std::exception& e) { - BOOST_LOG_TRIVIAL(error) << boost::format("Error loading vendor bundle %1%: %2%") % it->path() % e.what(); + BOOST_LOG_TRIVIAL(error) << boost::format("Error loading vendor bundle %1%: %2%") % dir_entry.path() % e.what(); } } } - } // Load up the set of vendors / models / variants the user has had enabled up till now const AppConfig *app_config = GUI::get_app_config(); @@ -672,14 +669,15 @@ void ConfigWizard::priv::load_vendors() } else { // In case of legacy datadir, try to guess the preference based on the printer preset files that are present const auto printer_dir = fs::path(Slic3r::data_dir()) / "printer"; - for (fs::directory_iterator it(printer_dir); it != fs::directory_iterator(); ++it) { - auto needle = legacy_preset_map.find(it->path().filename().string()); - if (needle == legacy_preset_map.end()) { continue; } + for (auto &dir_entry : boost::filesystem::directory_iterator(printer_dir)) + if (Slic3r::is_ini_file(dir_entry)) { + auto needle = legacy_preset_map.find(dir_entry.path().filename().string()); + if (needle == legacy_preset_map.end()) { continue; } - const auto &model = needle->second.first; - const auto &variant = needle->second.second; - appconfig_vendors.set_variant("PrusaResearch", model, variant, true); - } + const auto &model = needle->second.first; + const auto &variant = needle->second.second; + appconfig_vendors.set_variant("PrusaResearch", model, variant, true); + } } } diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 919c68662..2ee322ebe 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3987,6 +3987,7 @@ wxDEFINE_EVENT(EVT_GLCANVAS_VIEWPORT_CHANGED, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, Vec2dEvent); wxDEFINE_EVENT(EVT_GLCANVAS_REMOVE_OBJECT, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_ARRANGE, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_SELECT_ALL, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_QUESTION_MARK, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_INCREASE_INSTANCES, Event); wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_MOVED, SimpleEvent); @@ -4442,6 +4443,7 @@ void GLCanvas3D::update_toolbar_items_visibility() ConfigOptionMode mode = wxGetApp().get_mode(); m_toolbar.set_item_visible("more", mode != comSimple); m_toolbar.set_item_visible("fewer", mode != comSimple); + m_toolbar.set_item_visible("splitvolumes", mode != comSimple); m_dirty = true; } #endif // ENABLE_MODE_AWARE_TOOLBAR_ITEMS @@ -5068,7 +5070,6 @@ void GLCanvas3D::bind_event_handlers() m_canvas->Bind(wxEVT_MIDDLE_DCLICK, &GLCanvas3D::on_mouse, this); m_canvas->Bind(wxEVT_RIGHT_DCLICK, &GLCanvas3D::on_mouse, this); m_canvas->Bind(wxEVT_PAINT, &GLCanvas3D::on_paint, this); - m_canvas->Bind(wxEVT_KEY_DOWN, &GLCanvas3D::on_key_down, this); } } @@ -5082,7 +5083,7 @@ void GLCanvas3D::unbind_event_handlers() m_canvas->Unbind(wxEVT_MOUSEWHEEL, &GLCanvas3D::on_mouse_wheel, this); m_canvas->Unbind(wxEVT_TIMER, &GLCanvas3D::on_timer, this); m_canvas->Unbind(wxEVT_LEFT_DOWN, &GLCanvas3D::on_mouse, this); - m_canvas->Unbind(wxEVT_LEFT_UP, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_LEFT_UP, &GLCanvas3D::on_mouse, this); m_canvas->Unbind(wxEVT_MIDDLE_DOWN, &GLCanvas3D::on_mouse, this); m_canvas->Unbind(wxEVT_MIDDLE_UP, &GLCanvas3D::on_mouse, this); m_canvas->Unbind(wxEVT_RIGHT_DOWN, &GLCanvas3D::on_mouse, this); @@ -5094,7 +5095,6 @@ void GLCanvas3D::unbind_event_handlers() m_canvas->Unbind(wxEVT_MIDDLE_DCLICK, &GLCanvas3D::on_mouse, this); m_canvas->Unbind(wxEVT_RIGHT_DCLICK, &GLCanvas3D::on_mouse, this); m_canvas->Unbind(wxEVT_PAINT, &GLCanvas3D::on_paint, this); - m_canvas->Unbind(wxEVT_KEY_DOWN, &GLCanvas3D::on_key_down, this); } } @@ -5113,71 +5113,64 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt) void GLCanvas3D::on_char(wxKeyEvent& evt) { - if (evt.HasModifiers()) + // see include/wx/defs.h enum wxKeyCode + int keyCode = evt.GetKeyCode(); + int ctrlMask = wxMOD_CONTROL; +#ifdef __APPLE__ + ctrlMask |= wxMOD_RAW_CONTROL; +#endif /* __APPLE__ */ + if ((evt.GetModifiers() & ctrlMask) != 0) { + switch (keyCode) { + case WXK_CONTROL_A: post_event(SimpleEvent(EVT_GLCANVAS_SELECT_ALL)); break; +#ifdef __APPLE__ + case WXK_BACK: // the low cost Apple solutions are not equipped with a Delete key, use Backspace instead. +#endif /* __APPLE__ */ + case WXK_DELETE: post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); break; + default: evt.Skip(); + } + } else if (evt.HasModifiers()) { evt.Skip(); - else - { - int keyCode = evt.GetKeyCode(); - switch (keyCode - 48) + } else { + switch (keyCode) { - // numerical input - case 0: { select_view("iso"); break; } - case 1: { select_view("top"); break; } - case 2: { select_view("bottom"); break; } - case 3: { select_view("front"); break; } - case 4: { select_view("rear"); break; } - case 5: { select_view("left"); break; } - case 6: { select_view("right"); break; } + // key ESC + case WXK_ESCAPE: { m_gizmos.reset_all_states(); m_dirty = true; break; } +#ifdef __APPLE__ + case WXK_BACK: // the low cost Apple solutions are not equipped with a Delete key, use Backspace instead. +#endif /* __APPLE__ */ + case WXK_DELETE: post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE)); break; + case '0': { select_view("iso"); break; } + case '1': { select_view("top"); break; } + case '2': { select_view("bottom"); break; } + case '3': { select_view("front"); break; } + case '4': { select_view("rear"); break; } + case '5': { select_view("left"); break; } + case '6': { select_view("right"); break; } + case '+': { post_event(Event(EVT_GLCANVAS_INCREASE_INSTANCES, +1)); break; } + case '-': { post_event(Event(EVT_GLCANVAS_INCREASE_INSTANCES, -1)); break; } + case '?': { post_event(SimpleEvent(EVT_GLCANVAS_QUESTION_MARK)); break; } + case 'A': + case 'a': { post_event(SimpleEvent(EVT_GLCANVAS_ARRANGE)); break; } + case 'B': + case 'b': { zoom_to_bed(); break; } + case 'I': + case 'i': { set_camera_zoom(1.0f); break; } + case 'O': + case 'o': { set_camera_zoom(-1.0f); break; } + case 'Z': + case 'z': { m_selection.is_empty() ? zoom_to_volumes() : zoom_to_selection(); break; } default: + { + if (m_gizmos.handle_shortcut(keyCode, m_selection)) { - // text input - switch (keyCode) - { - // key ESC - case 27: { m_gizmos.reset_all_states(); m_dirty = true; break; } - // key + - case 43: { post_event(Event(EVT_GLCANVAS_INCREASE_INSTANCES, +1)); break; } - // key - - case 45: { post_event(Event(EVT_GLCANVAS_INCREASE_INSTANCES, -1)); break; } - // key ? - case 63: { post_event(SimpleEvent(EVT_GLCANVAS_QUESTION_MARK)); break; } - // key A/a - case 65: - case 97: { post_event(SimpleEvent(EVT_GLCANVAS_ARRANGE)); break; } - // key B/b - case 66: - case 98: { zoom_to_bed(); break; } - // key I/i - case 73: - case 105: { set_camera_zoom(1.0f); break; } - // key O/o - case 79: - case 111: { set_camera_zoom(-1.0f); break; } - // key Z/z - case 90: - case 122: - { - if (m_selection.is_empty()) - zoom_to_volumes(); - else - zoom_to_selection(); - - break; - } - default: - { - if (m_gizmos.handle_shortcut(keyCode, m_selection)) - { - _update_gizmos_data(); - m_dirty = true; - } - else - evt.Skip(); - - break; - } - } + _update_gizmos_data(); + m_dirty = true; } + else + evt.Skip(); + + break; + } } } } @@ -5692,24 +5685,6 @@ void GLCanvas3D::on_paint(wxPaintEvent& evt) this->render(); } -void GLCanvas3D::on_key_down(wxKeyEvent& evt) -{ - if (evt.HasModifiers()) - evt.Skip(); - else - { - int key = evt.GetKeyCode(); -#ifdef __WXOSX__ - if (key == WXK_BACK) -#else - if (key == WXK_DELETE) -#endif // __WXOSX__ - post_event(SimpleEvent(EVT_GLCANVAS_REMOVE_OBJECT)); - else - evt.Skip(); - } -} - Size GLCanvas3D::get_canvas_size() const { int w = 0; @@ -6087,7 +6062,7 @@ bool GLCanvas3D::_init_toolbar() GLToolbarItem::Data item; item.name = "add"; - item.tooltip = GUI::L_str("Add... [Ctrl+I]"); + item.tooltip = GUI::L_str("Add...") + " [" + GUI::shortkey_ctrl_prefix() + "I]"; item.sprite_id = 0; item.is_toggable = false; item.action_event = EVT_GLTOOLBAR_ADD; @@ -6095,7 +6070,7 @@ bool GLCanvas3D::_init_toolbar() return false; item.name = "delete"; - item.tooltip = GUI::L_str("Delete [Del]"); + item.tooltip = GUI::L_str("Delete") + " [Del]"; item.sprite_id = 1; item.is_toggable = false; item.action_event = EVT_GLTOOLBAR_DELETE; @@ -6103,7 +6078,7 @@ bool GLCanvas3D::_init_toolbar() return false; item.name = "deleteall"; - item.tooltip = GUI::L_str("Delete all [Ctrl+Del]"); + item.tooltip = GUI::L_str("Delete all") + " [" + GUI::shortkey_ctrl_prefix() + "Del]"; item.sprite_id = 2; item.is_toggable = false; item.action_event = EVT_GLTOOLBAR_DELETE_ALL; diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 34e3df2a7..84a162082 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -121,6 +121,7 @@ wxDECLARE_EVENT(EVT_GLCANVAS_VIEWPORT_CHANGED, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, Vec2dEvent); wxDECLARE_EVENT(EVT_GLCANVAS_REMOVE_OBJECT, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_ARRANGE, SimpleEvent); +wxDECLARE_EVENT(EVT_GLCANVAS_SELECT_ALL, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_QUESTION_MARK, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_INCREASE_INSTANCES, Event); // data: +1 => increase, -1 => decrease wxDECLARE_EVENT(EVT_GLCANVAS_INSTANCE_MOVED, SimpleEvent); @@ -1042,7 +1043,6 @@ public: void on_timer(wxTimerEvent& evt); void on_mouse(wxMouseEvent& evt); void on_paint(wxPaintEvent& evt); - void on_key_down(wxKeyEvent& evt); Size get_canvas_size() const; Point get_local_mouse_position() const; diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index 148285e86..8e80d64c3 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -75,6 +75,30 @@ void break_to_debugger() #endif /* _WIN32 */ } +const std::string& shortkey_ctrl_prefix() +{ + static const std::string str = +#ifdef __APPLE__ + "⌘" +#else + "Ctrl+" +#endif + ; + return str; +} + +const std::string& shortkey_alt_prefix() +{ + static const std::string str = +#ifdef __APPLE__ + "⌥" +#else + "Alt+" +#endif + ; + return str; +} + bool config_wizard_startup(bool app_config_exists) { if (!app_config_exists || wxGetApp().preset_bundle->printers.size() <= 1) { diff --git a/src/slic3r/GUI/GUI.hpp b/src/slic3r/GUI/GUI.hpp index e33be8f58..f066c82a8 100644 --- a/src/slic3r/GUI/GUI.hpp +++ b/src/slic3r/GUI/GUI.hpp @@ -27,7 +27,11 @@ void enable_screensaver(); bool debugged(); void break_to_debugger(); -AppConfig* get_app_config(); +// Platform specific Ctrl+/Alt+ (Windows, Linux) vs. ⌘/⌥ (OSX) prefixes +extern const std::string& shortkey_ctrl_prefix(); +extern const std::string& shortkey_alt_prefix(); + +extern AppConfig* get_app_config(); extern void add_menus(wxMenuBar *menu, int event_preferences_changed, int event_language_change); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 9991d98ea..056062cf4 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -561,7 +561,13 @@ void GUI_App::add_config_menu(wxMenuBar *menu) local_menu->Append(config_id_base + ConfigMenuTakeSnapshot, _(L("Take Configuration &Snapshot")), _(L("Capture a configuration snapshot"))); // local_menu->Append(config_id_base + ConfigMenuUpdate, _(L("Check for updates")), _(L("Check for configuration updates"))); local_menu->AppendSeparator(); - local_menu->Append(config_id_base + ConfigMenuPreferences, _(L("&Preferences")) + dots + "\tCtrl+P", _(L("Application preferences"))); + local_menu->Append(config_id_base + ConfigMenuPreferences, _(L("&Preferences")) + dots + +#ifdef __APPLE__ + "\tCtrl+,", +#else + "\tCtrl+P", +#endif + _(L("Application preferences"))); local_menu->AppendSeparator(); auto mode_menu = new wxMenu(); mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeSimple, _(L("Simple")), _(L("Simple View Mode"))); diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index 3aeda0348..d893d6f76 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -88,13 +88,8 @@ KBShortcutsDialog::KBShortcutsDialog() void KBShortcutsDialog::fill_shortcuts() { -#ifdef __WXOSX__ - const std::string ctrl = "⌘"; - const std::string alt = "⌥"; -#else - const std::string ctrl = "Ctrl+"; - const std::string alt = "Alt+"; -#endif // __WXOSX__ + const std::string &ctrl = GUI::shortkey_ctrl_prefix(); + const std::string &alt = GUI::shortkey_alt_prefix(); m_full_shortcuts.reserve(4); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 947dd59bd..78dffa1cb 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -324,7 +324,19 @@ void MainFrame::init_menubar() if (m_plater != nullptr) { editMenu = new wxMenu(); - wxMenuItem* item_select_all = append_menu_item(editMenu, wxID_ANY, _(L("&Select all")) + "\tCtrl+A", _(L("Selects all objects")), + // \xA0 is a non-breaking space. It is entered here to spoil the automatic accelerators, + // as the simple numeric accelerators spoil all numeric data entry. + wxMenuItem* item_select_all = append_menu_item(editMenu, wxID_ANY, _(L("&Select all")) + +#ifdef _MSC_VER + "\t\xA0" + "Ctrl+\xA0" + "A" +#else +#ifdef __APPLE__ + "\tCtrl+A" +#else + " - Ctrl+A" +#endif +#endif + , _(L("Selects all objects")), [this](wxCommandEvent&) { m_plater->select_all(); }, ""); editMenu->AppendSeparator(); wxMenuItem* item_delete_sel = append_menu_item(editMenu, wxID_ANY, _(L("&Delete selected")) + "\tDel", _(L("Deletes the current selection")), @@ -386,19 +398,23 @@ void MainFrame::init_menubar() // View menu wxMenu* viewMenu = nullptr; + wxString sep = +#ifdef _MSC_VER + "\t"; +#else + " - "; +#endif 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(). - wxMenuItem* item_iso = append_menu_item(viewMenu, wxID_ANY, _(L("&Iso")) + "\t\xA0" + "0", _(L("Iso View")), [this](wxCommandEvent&) { select_view("iso"); }); + wxMenuItem* item_iso = append_menu_item(viewMenu, wxID_ANY, _(L("Iso")) + sep + "&0", _(L("Iso View")), [this](wxCommandEvent&) { select_view("iso"); }); viewMenu->AppendSeparator(); - wxMenuItem* item_top = append_menu_item(viewMenu, wxID_ANY, _(L("&Top")) + "\t\xA0" + "1", _(L("Top View")), [this](wxCommandEvent&) { select_view("top"); }); - wxMenuItem* item_bottom = append_menu_item(viewMenu, wxID_ANY, _(L("&Bottom")) + "\t\xA0" + "2", _(L("Bottom View")), [this](wxCommandEvent&) { select_view("bottom"); }); - wxMenuItem* item_front = append_menu_item(viewMenu, wxID_ANY, _(L("&Front")) + "\t\xA0" + "3", _(L("Front View")), [this](wxCommandEvent&) { select_view("front"); }); - wxMenuItem* item_rear = append_menu_item(viewMenu, wxID_ANY, _(L("R&ear")) + "\t\xA0" + "4", _(L("Rear View")), [this](wxCommandEvent&) { select_view("rear"); }); - wxMenuItem* item_left = append_menu_item(viewMenu, wxID_ANY, _(L("&Left")) + "\t\xA0" + "5", _(L("Left View")), [this](wxCommandEvent&) { select_view("left"); }); - wxMenuItem* item_right = append_menu_item(viewMenu, wxID_ANY, _(L("&Right")) + "\t\xA0" + "6", _(L("Right View")), [this](wxCommandEvent&) { select_view("right"); }); + wxMenuItem* item_top = append_menu_item(viewMenu, wxID_ANY, _(L("Top")) + sep + "&1", _(L("Top View")), [this](wxCommandEvent&) { select_view("top"); }); + wxMenuItem* item_bottom = append_menu_item(viewMenu, wxID_ANY, _(L("Bottom")) + sep + "&2", _(L("Bottom View")), [this](wxCommandEvent&) { select_view("bottom"); }); + wxMenuItem* item_front = append_menu_item(viewMenu, wxID_ANY, _(L("Front")) + sep + "&3", _(L("Front View")), [this](wxCommandEvent&) { select_view("front"); }); + wxMenuItem* item_rear = append_menu_item(viewMenu, wxID_ANY, _(L("Rear")) + sep + "&4", _(L("Rear View")), [this](wxCommandEvent&) { select_view("rear"); }); + wxMenuItem* item_left = append_menu_item(viewMenu, wxID_ANY, _(L("Left")) + sep + "&5", _(L("Left View")), [this](wxCommandEvent&) { select_view("left"); }); + wxMenuItem* item_right = append_menu_item(viewMenu, wxID_ANY, _(L("Right")) + sep + "&6", _(L("Right View")), [this](wxCommandEvent&) { select_view("right"); }); Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_change_view()); }, item_iso->GetId()); Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_change_view()); }, item_top->GetId()); @@ -434,7 +450,7 @@ void MainFrame::init_menubar() append_menu_item(helpMenu, wxID_ANY, _(L("&About Slic3r")), _(L("Show about dialog")), [this](wxCommandEvent&) { Slic3r::GUI::about(); }); helpMenu->AppendSeparator(); - append_menu_item(helpMenu, wxID_ANY, _(L("&Keyboard Shortcuts")) + "\t\xA0?", _(L("Show the list of the keyboard shortcuts")), + append_menu_item(helpMenu, wxID_ANY, _(L("Keyboard Shortcuts")) + sep + "&?", _(L("Show the list of the keyboard shortcuts")), [this](wxCommandEvent&) { wxGetApp().keyboard_shortcuts(); }); } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index edc0fecd6..f9ad7a39a 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1028,7 +1028,8 @@ struct Plater::priv unsigned int update_background_process(bool force_validation = false); // Restart background processing thread based on a bitmask of UpdateBackgroundProcessReturnState. bool restart_background_process(unsigned int state); - void update_restart_background_process(bool force_scene_update, bool force_preview_update); + // returns bit mask of UpdateBackgroundProcessReturnState + unsigned int update_restart_background_process(bool force_scene_update, bool force_preview_update); void export_gcode(fs::path output_path, PrintHostJob upload_job); void reload_from_disk(); void fix_through_netfabb(const int obj_idx, const int vol_idx = -1); @@ -1170,6 +1171,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) view3D_canvas->Bind(EVT_GLCANVAS_RIGHT_CLICK, &priv::on_right_click, this); view3D_canvas->Bind(EVT_GLCANVAS_REMOVE_OBJECT, [q](SimpleEvent&) { q->remove_selected(); }); view3D_canvas->Bind(EVT_GLCANVAS_ARRANGE, [this](SimpleEvent&) { arrange(); }); + view3D_canvas->Bind(EVT_GLCANVAS_SELECT_ALL, [this](SimpleEvent&) { this->q->select_all(); }); view3D_canvas->Bind(EVT_GLCANVAS_QUESTION_MARK, [this](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); }); view3D_canvas->Bind(EVT_GLCANVAS_INCREASE_INSTANCES, [this](Event &evt) { if (evt.data == 1) this->q->increase_instances(); else if (this->can_decrease_instances()) this->q->decrease_instances(); }); @@ -1575,7 +1577,7 @@ std::unique_ptr Plater::priv::get_export_file(GUI::FileType // Update printbility state of each of the ModelInstances. this->update_print_volume_state(); // Find the file name of the first printable object. - fs::path output_file = this->model.propose_export_file_name(); + fs::path output_file = this->model.propose_export_file_name_and_path(); switch (file_type) { case FT_STL: output_file.replace_extension("stl"); break; @@ -1735,6 +1737,8 @@ void Plater::priv::arrange() // Guard the arrange process arranging.store(true); + wxBusyCursor wait; + // Disable the arrange button (to prevent reentrancies, we will call wxYied) view3D->enable_toolbar_item("arrange", can_arrange()); @@ -2045,7 +2049,7 @@ void Plater::priv::export_gcode(fs::path output_path, PrintHostJob upload_job) this->restart_background_process(priv::UPDATE_BACKGROUND_PROCESS_FORCE_EXPORT); } -void Plater::priv::update_restart_background_process(bool force_update_scene, bool force_update_preview) +unsigned int Plater::priv::update_restart_background_process(bool force_update_scene, bool force_update_preview) { // bitmask of UpdateBackgroundProcessReturnState unsigned int state = this->update_background_process(false); @@ -2055,6 +2059,7 @@ void Plater::priv::update_restart_background_process(bool force_update_scene, bo if (force_update_preview) this->preview->reload_print(); this->restart_background_process(state); + return state; } void Plater::priv::update_fff_scene() @@ -2108,31 +2113,10 @@ void Plater::priv::fix_through_netfabb(const int obj_idx, const int vol_idx/* = { if (obj_idx < 0) return; - - const auto model_object = model.objects[obj_idx]; - Model model_fixed;// = new Model(); - fix_model_by_win10_sdk_gui(*model_object, this->fff_print, model_fixed); - - auto new_obj_idxs = load_model_objects(model_fixed.objects); - if (new_obj_idxs.empty()) - return; - - for(auto new_obj_idx : new_obj_idxs) { - auto o = model.objects[new_obj_idx]; - o->clear_instances(); - for (auto instance: model_object->instances) - o->add_instance(*instance); - o->invalidate_bounding_box(); - - if (o->volumes.size() == model_object->volumes.size()) { - for (int i = 0; i < o->volumes.size(); i++) { - o->volumes[i]->config.apply(model_object->volumes[i]->config); - } - } - // FIXME restore volumes and their configs, layer_height_ranges, layer_height_profile - } - - remove(obj_idx); + fix_model_by_win10_sdk_gui(*model.objects[obj_idx], vol_idx); + this->object_list_changed(); + this->update(); + this->schedule_background_process(); } void Plater::priv::set_current_panel(wxPanel* panel) @@ -2553,7 +2537,7 @@ void Plater::priv::init_view_toolbar() GLToolbarItem::Data item; item.name = "3D"; - item.tooltip = GUI::L_str("3D editor view [Ctrl+5]"); + item.tooltip = GUI::L_str("3D editor view") + " [" + GUI::shortkey_ctrl_prefix() + "5]"; item.sprite_id = 0; item.action_event = EVT_GLVIEWTOOLBAR_3D; item.is_toggable = false; @@ -2561,7 +2545,7 @@ void Plater::priv::init_view_toolbar() return; item.name = "Preview"; - item.tooltip = GUI::L_str("Preview [Ctrl+6]"); + item.tooltip = GUI::L_str("Preview") + " [" + GUI::shortkey_ctrl_prefix() + "6]"; item.sprite_id = 1; item.action_event = EVT_GLVIEWTOOLBAR_PREVIEW; item.is_toggable = false; @@ -2846,51 +2830,43 @@ void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_uppe p->load_model_objects(new_objects); } -void Plater::export_gcode(fs::path output_path) +void Plater::export_gcode() { if (p->model.objects.empty()) return; - // select output file - if (output_path.empty()) { - // 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. - fs::path default_output_file; - try { - default_output_file = this->p->background_process.current_print()->output_filepath(output_path.string()); - } catch (const std::exception &ex) { - show_error(this, ex.what()); - return; - } - 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()); - - wxFileDialog dlg(this, (printer_technology() == ptFFF) ? _(L("Save G-code file as:")) : _(L("Save Zip file as:")), - start_dir, - from_path(default_output_file.filename()), - GUI::file_wildcards((printer_technology() == ptFFF) ? FT_GCODE : FT_PNGZIP, default_output_file.extension().string()), - wxFD_SAVE | wxFD_OVERWRITE_PROMPT - ); - - if (dlg.ShowModal() == wxID_OK) { - fs::path path = into_path(dlg.GetPath()); - wxGetApp().app_config->update_last_output_dir(path.parent_path().string()); - output_path = std::move(path); - } - } else { - try { - output_path = this->p->background_process.current_print()->output_filepath(output_path.string()); - } catch (const std::exception &ex) { - show_error(this, ex.what()); - return; - } + // If possible, remove accents from accented latin characters. + // This function is useful for generating file names to be processed by legacy firmwares. + fs::path default_output_file; + try { + // Update the background processing, so that the placeholder parser will get the correct values for the ouput file template. + // Also if there is something wrong with the current configuration, a pop-up dialog will be shown and the export will not be performed. + unsigned int state = this->p->update_restart_background_process(false, false); + if (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) + return; + default_output_file = this->p->background_process.current_print()->output_filepath(""); + } catch (const std::exception &ex) { + show_error(this, ex.what()); + return; } + 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 (! output_path.empty()) { + wxFileDialog dlg(this, (printer_technology() == ptFFF) ? _(L("Save G-code file as:")) : _(L("Save Zip file as:")), + start_dir, + from_path(default_output_file.filename()), + GUI::file_wildcards((printer_technology() == ptFFF) ? FT_GCODE : FT_PNGZIP, default_output_file.extension().string()), + wxFD_SAVE | wxFD_OVERWRITE_PROMPT + ); + + 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()); + output_path = std::move(path); + } + if (! output_path.empty()) p->export_gcode(std::move(output_path), PrintHostJob()); - } } void Plater::export_stl(bool selection_only) @@ -2991,7 +2967,12 @@ void Plater::send_gcode() // Obtain default output path fs::path default_output_file; try { - default_output_file = this->p->background_process.current_print()->output_filepath(""); + // Update the background processing, so that the placeholder parser will get the correct values for the ouput file template. + // Also if there is something wrong with the current configuration, a pop-up dialog will be shown and the export will not be performed. + unsigned int state = this->p->update_restart_background_process(false, false); + if (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) + return; + default_output_file = this->p->background_process.current_print()->output_filepath(""); } catch (const std::exception &ex) { show_error(this, ex.what()); return; diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 09b7348d5..e3601b65c 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -140,8 +140,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); - // Note: empty path means "use the default" - void export_gcode(boost::filesystem::path output_path = boost::filesystem::path()); + void export_gcode(); void export_stl(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/Preset.cpp b/src/slic3r/GUI/Preset.cpp index 53650481c..871cd0b73 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/slic3r/GUI/Preset.cpp @@ -517,16 +517,6 @@ void PresetCollection::add_default_preset(const std::vector &keys, ++ m_num_default_presets; } -bool is_file_plain(const std::string &path) -{ -#ifdef _MSC_VER - DWORD attributes = GetFileAttributesW(boost::nowide::widen(path).c_str()); - return (attributes & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) == 0; -#else - return true; -#endif -} - // Load all presets found in dir_path. // Throws an exception on error. void PresetCollection::load_presets(const std::string &dir_path, const std::string &subdir) @@ -538,10 +528,7 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri // (see the "Preset already present, not loading" message). std::deque presets_loaded; for (auto &dir_entry : boost::filesystem::directory_iterator(dir)) - if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini") && - // Ignore system and hidden files, which may be created by the DropBox synchronisation process. - // https://github.com/prusa3d/Slic3r/issues/1298 - is_file_plain(dir_entry.path().string())) { + if (Slic3r::is_ini_file(dir_entry)) { std::string name = dir_entry.path().filename().string(); // Remove the .ini suffix. name.erase(name.size() - 4); @@ -1169,6 +1156,18 @@ std::string PresetCollection::name() const } } +std::string PresetCollection::section_name() const +{ + switch (this->type()) { + case Preset::TYPE_PRINT: return "print"; + case Preset::TYPE_FILAMENT: return "filament"; + case Preset::TYPE_SLA_PRINT: return "sla_print"; + case Preset::TYPE_SLA_MATERIAL: return "sla_material"; + case Preset::TYPE_PRINTER: return "printer"; + default: return "invalid"; + } +} + std::vector PresetCollection::system_preset_names() const { size_t num = 0; diff --git a/src/slic3r/GUI/Preset.hpp b/src/slic3r/GUI/Preset.hpp index eae406bef..73a921cf7 100644 --- a/src/slic3r/GUI/Preset.hpp +++ b/src/slic3r/GUI/Preset.hpp @@ -230,7 +230,10 @@ public: void reset(bool delete_files); Preset::Type type() const { return m_type; } + // Name, to be used on the screen and in error messages. Not localized. std::string name() const; + // Name, to be used as a section name in config bundle, and as a folder name for presets. + std::string section_name() const; const std::deque& operator()() const { return m_presets; } // Add default preset at the start of the collection, increment the m_default_preset counter. diff --git a/src/slic3r/GUI/PresetBundle.cpp b/src/slic3r/GUI/PresetBundle.cpp index 4c6c52763..5f33fd00a 100644 --- a/src/slic3r/GUI/PresetBundle.cpp +++ b/src/slic3r/GUI/PresetBundle.cpp @@ -243,7 +243,7 @@ std::string PresetBundle::load_system_presets() std::string errors_cummulative; bool first = true; for (auto &dir_entry : boost::filesystem::directory_iterator(dir)) - if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini")) { + if (Slic3r::is_ini_file(dir_entry)) { std::string name = dir_entry.path().filename().string(); // Remove the .ini suffix. name.erase(name.size() - 4); @@ -1208,7 +1208,7 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla #else // Store the print/filament/printer presets at the same location as the upstream Slic3r. #endif - / presets->name() / file_name).make_preferred(); + / presets->section_name() / file_name).make_preferred(); // Load the preset into the list of presets, save it to disk. Preset &loaded = presets->load_preset(file_path.string(), preset_name, std::move(config), false); if (flags & LOAD_CFGBNDLE_SAVE) @@ -1365,7 +1365,7 @@ void PresetBundle::export_configbundle(const std::string &path, bool export_syst if (preset.is_default || preset.is_external || (preset.is_system && ! export_system_settings)) // Only export the common presets, not external files or the default preset. continue; - c << std::endl << "[" << presets->name() << ":" << preset.name << "]" << std::endl; + c << std::endl << "[" << presets->section_name() << ":" << preset.name << "]" << std::endl; for (const std::string &opt_key : preset.config.keys()) c << opt_key << " = " << preset.config.serialize(opt_key) << std::endl; } diff --git a/src/slic3r/Utils/FixModelByWin10.cpp b/src/slic3r/Utils/FixModelByWin10.cpp index 4b487588a..1daeaff26 100644 --- a/src/slic3r/Utils/FixModelByWin10.cpp +++ b/src/slic3r/Utils/FixModelByWin10.cpp @@ -216,7 +216,7 @@ void fix_model_by_win10_sdk(const std::string &path_src, const std::string &path HRESULT hr = (*s_RoInitialize)(RO_INIT_MULTITHREADED); { - on_progress(L("Exporting the source model"), 20); + on_progress(L("Exporting source model"), 20); Microsoft::WRL::ComPtr fileStream; hr = winrt_open_file_stream(boost::nowide::widen(path_src), ABI::Windows::Storage::FileAccessMode::FileAccessMode_Read, fileStream.GetAddressOf(), throw_on_cancel); @@ -239,7 +239,7 @@ void fix_model_by_win10_sdk(const std::string &path_src, const std::string &path unsigned num_meshes = 0; hr = meshes->get_Size(&num_meshes); - on_progress(L("Repairing the model by the Netfabb service"), 40); + on_progress(L("Repairing model by the Netfabb service"), 40); Microsoft::WRL::ComPtr repairAsync; hr = model->RepairAsync(repairAsync.GetAddressOf()); @@ -248,7 +248,7 @@ void fix_model_by_win10_sdk(const std::string &path_src, const std::string &path throw std::runtime_error(L("Mesh repair failed.")); repairAsync->GetResults(); - on_progress(L("Loading the repaired model"), 60); + on_progress(L("Loading repaired model"), 60); // Verify the number of meshes returned after the repair action. meshes.Reset(); @@ -316,7 +316,7 @@ public: const char* what() const throw() { return "Model repair has been canceled"; } }; -void fix_model_by_win10_sdk_gui(const ModelObject &model_object, const Print &print, Model &result) +void fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx) { std::mutex mutex; std::condition_variable condition; @@ -329,6 +329,12 @@ void fix_model_by_win10_sdk_gui(const ModelObject &model_object, const Print &pr std::atomic canceled = false; std::atomic finished = false; + std::vector volumes; + if (volume_idx == -1) + volumes = model_object.volumes; + else + volumes.emplace_back(model_object.volumes[volume_idx]); + // Open a progress dialog. wxProgressDialog progress_dialog( _(L("Model fixing")), @@ -336,43 +342,64 @@ void fix_model_by_win10_sdk_gui(const ModelObject &model_object, const Print &pr 100, nullptr, wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT); // Executing the calculation in a background thread, so that the COM context could be created with its own threading model. // (It seems like wxWidgets initialize the COM contex as single threaded and we need a multi-threaded context). - bool success = false; - auto on_progress = [&mutex, &condition, &progress](const char *msg, unsigned prcnt) { + bool success = false; + size_t ivolume = 0; + auto on_progress = [&mutex, &condition, &ivolume, &volumes, &progress](const char *msg, unsigned prcnt) { std::lock_guard lk(mutex); progress.message = msg; - progress.percent = prcnt; + progress.percent = (int)floor((float(prcnt) + float(ivolume) * 100.f) / float(volumes.size())); progress.updated = true; condition.notify_all(); }; - auto worker_thread = boost::thread([&model_object, &print, &result, on_progress, &success, &canceled, &finished]() { + auto worker_thread = boost::thread([&model_object, &volumes, &ivolume, on_progress, &success, &canceled, &finished]() { try { - on_progress(L("Exporting the source model"), 0); - boost::filesystem::path path_src = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path(); - path_src += ".3mf"; - Model model; - DynamicPrintConfig config; - model.add_object(model_object); - if (! Slic3r::store_3mf(path_src.string().c_str(), &model, &config)) { + std::vector meshes_repaired; + meshes_repaired.reserve(volumes.size()); + for (; ivolume < volumes.size(); ++ ivolume) { + on_progress(L("Exporting source model"), 0); + boost::filesystem::path path_src = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path(); + path_src += ".3mf"; + Model model; + ModelObject *model_object = model.add_object(); + model_object->add_volume(*volumes[ivolume]); + model_object->add_instance(); + if (! Slic3r::store_3mf(path_src.string().c_str(), &model, nullptr)) { + boost::filesystem::remove(path_src); + throw std::runtime_error(L("Export of a temporary 3mf file failed")); + } + model.clear_objects(); + model.clear_materials(); + boost::filesystem::path path_dst = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path(); + path_dst += ".3mf"; + fix_model_by_win10_sdk(path_src.string().c_str(), path_dst.string(), on_progress, + [&canceled]() { if (canceled) throw RepairCanceledException(); }); boost::filesystem::remove(path_src); - throw std::runtime_error(L("Export of a temporary 3mf file failed")); + // PresetBundle bundle; + on_progress(L("Loading repaired model"), 80); + DynamicPrintConfig config; + bool loaded = Slic3r::load_3mf(path_dst.string().c_str(), &config, &model); + boost::filesystem::remove(path_dst); + if (! loaded) + throw std::runtime_error(L("Import of the repaired 3mf file failed")); + if (model.objects.size() == 0) + throw std::runtime_error(L("Repaired 3MF file does not contain any object")); + if (model.objects.size() > 1) + throw std::runtime_error(L("Repaired 3MF file contains more than one object")); + if (model.objects.front()->volumes.size() == 0) + throw std::runtime_error(L("Repaired 3MF file does not contain any volume")); + if (model.objects.front()->volumes.size() > 1) + throw std::runtime_error(L("Repaired 3MF file contains more than one volume")); + meshes_repaired.emplace_back(std::move(model.objects.front()->volumes.front()->mesh)); } - model.clear_objects(); - model.clear_materials(); - boost::filesystem::path path_dst = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path(); - path_dst += ".3mf"; - fix_model_by_win10_sdk(path_src.string().c_str(), path_dst.string(), on_progress, - [&canceled]() { if (canceled) throw RepairCanceledException(); }); - boost::filesystem::remove(path_src); - // PresetBundle bundle; - on_progress(L("Loading the repaired model"), 80); - bool loaded = Slic3r::load_3mf(path_dst.string().c_str(), &config, &result); - result.objects[0]->name = boost::filesystem::path(model_object.name).filename().stem().string() + "_fixed"; - boost::filesystem::remove(path_dst); - if (! loaded) - throw std::runtime_error(L("Import of the repaired 3mf file failed")); + for (size_t i = 0; i < volumes.size(); ++ i) { + volumes[i]->mesh = std::move(meshes_repaired[i]); + volumes[i]->set_new_unique_id(); + } + model_object.invalidate_bounding_box(); + -- ivolume; + on_progress(L("Model repair finished"), 100); success = true; finished = true; - on_progress(L("Model repair finished"), 100); } catch (RepairCanceledException & /* ex */) { canceled = true; finished = true; diff --git a/src/slic3r/Utils/FixModelByWin10.hpp b/src/slic3r/Utils/FixModelByWin10.hpp index c148a6970..8e4766467 100644 --- a/src/slic3r/Utils/FixModelByWin10.hpp +++ b/src/slic3r/Utils/FixModelByWin10.hpp @@ -12,12 +12,12 @@ class Print; #ifdef HAS_WIN10SDK extern bool is_windows10(); -extern void fix_model_by_win10_sdk_gui(const ModelObject &model_object, const Print &print, Model &result); +extern void fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx); #else /* HAS_WIN10SDK */ inline bool is_windows10() { return false; } -inline void fix_model_by_win10_sdk_gui(const ModelObject &, const Print &, Model &) {} +inline void fix_model_by_win10_sdk_gui(ModelObject &, int) {} #endif /* HAS_WIN10SDK */ diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index bfa3af3e6..a22d463fb 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -180,12 +180,11 @@ bool PresetUpdater::priv::get_file(const std::string &url, const fs::path &targe // Remove leftover paritally downloaded files, if any. void PresetUpdater::priv::prune_tmps() const { - for (fs::directory_iterator it(cache_path); it != fs::directory_iterator(); ++it) { - if (it->path().extension() == TMP_EXTENSION) { - BOOST_LOG_TRIVIAL(debug) << "Cache prune: " << it->path().string(); - fs::remove(it->path()); + for (auto &dir_entry : boost::filesystem::directory_iterator(cache_path)) + if (is_plain_file(dir_entry) && dir_entry.path().extension() == TMP_EXTENSION) { + BOOST_LOG_TRIVIAL(debug) << "Cache prune: " << dir_entry.path().string(); + fs::remove(dir_entry.path()); } - } } // Get Slic3rPE version available online, save in AppConfig. @@ -299,9 +298,9 @@ void PresetUpdater::priv::check_install_indices() const { BOOST_LOG_TRIVIAL(info) << "Checking if indices need to be installed from resources..."; - for (fs::directory_iterator it(rsrc_path); it != fs::directory_iterator(); ++it) { - const auto &path = it->path(); - if (path.extension() == ".idx") { + for (auto &dir_entry : boost::filesystem::directory_iterator(rsrc_path)) + if (is_idx_file(dir_entry)) { + const auto &path = dir_entry.path(); const auto path_in_cache = cache_path / path.filename(); if (! fs::exists(path_in_cache)) { @@ -318,7 +317,6 @@ void PresetUpdater::priv::check_install_indices() const } } } - } } // Generates a list of bundle updates that are to be performed