From e59a10e0c28f3daeb7793a75a23c416d7112d05a Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 1 Feb 2019 17:15:41 +0100 Subject: [PATCH 01/12] Fix of a cooling slow down logic. fixes "Min print speed" to "Estimated Print Time" Inconsistencies #1488 --- src/libslic3r/GCode/CoolingBuffer.cpp | 95 +++++++++++++++++++++------ src/libslic3r/GCode/CoolingBuffer.hpp | 2 +- 2 files changed, 76 insertions(+), 21 deletions(-) diff --git a/src/libslic3r/GCode/CoolingBuffer.cpp b/src/libslic3r/GCode/CoolingBuffer.cpp index 40ccc7b09f..09d211994a 100644 --- a/src/libslic3r/GCode/CoolingBuffer.cpp +++ b/src/libslic3r/GCode/CoolingBuffer.cpp @@ -83,7 +83,7 @@ struct CoolingLine struct PerExtruderAdjustments { // Calculate the total elapsed time per this extruder, adjusted for the slowdown. - float elapsed_time_total() { + float elapsed_time_total() const { float time_total = 0.f; for (const CoolingLine &line : lines) time_total += line.time; @@ -91,7 +91,7 @@ struct PerExtruderAdjustments } // Calculate the total elapsed time when slowing down // to the minimum extrusion feed rate defined for the current material. - float maximum_time_after_slowdown(bool slowdown_external_perimeters) { + float maximum_time_after_slowdown(bool slowdown_external_perimeters) const { float time_total = 0.f; for (const CoolingLine &line : lines) if (line.adjustable(slowdown_external_perimeters)) { @@ -104,7 +104,7 @@ struct PerExtruderAdjustments return time_total; } // Calculate the adjustable part of the total time. - float adjustable_time(bool slowdown_external_perimeters) { + float adjustable_time(bool slowdown_external_perimeters) const { float time_total = 0.f; for (const CoolingLine &line : lines) if (line.adjustable(slowdown_external_perimeters)) @@ -112,7 +112,7 @@ struct PerExtruderAdjustments return time_total; } // Calculate the non-adjustable part of the total time. - float non_adjustable_time(bool slowdown_external_perimeters) { + float non_adjustable_time(bool slowdown_external_perimeters) const { float time_total = 0.f; for (const CoolingLine &line : lines) if (! line.adjustable(slowdown_external_perimeters)) @@ -169,7 +169,7 @@ struct PerExtruderAdjustments // Calculate the maximum time stretch when slowing down to min_feedrate. // Slowdown to min_feedrate shall be allowed for this extruder's material. // Used by non-proportional slow down. - float time_stretch_when_slowing_down_to_feedrate(float min_feedrate) { + float time_stretch_when_slowing_down_to_feedrate(float min_feedrate) const { float time_stretch = 0.f; assert(this->min_print_speed < min_feedrate + EPSILON); for (size_t i = 0; i < n_lines_adjustable; ++ i) { @@ -221,6 +221,61 @@ struct PerExtruderAdjustments size_t idx_line_end = 0; }; +// Calculate a new feedrate when slowing down by time_stretch for segments faster than min_feedrate. +// Used by non-proportional slow down. +float new_feedrate_to_reach_time_stretch( + std::vector::const_iterator it_begin, std::vector::const_iterator it_end, + float min_feedrate, float time_stretch, size_t max_iter = 20) +{ + float new_feedrate = min_feedrate; + for (size_t iter = 0; iter < max_iter; ++ iter) { + float nomin = 0; + float denom = time_stretch; + for (auto it = it_begin; it != it_end; ++ it) { + assert((*it)->min_print_speed < min_feedrate + EPSILON); + for (size_t i = 0; i < (*it)->n_lines_adjustable; ++i) { + const CoolingLine &line = (*it)->lines[i]; + if (line.feedrate > min_feedrate) { + nomin += line.time * line.feedrate; + denom += line.time; + } + } + } + assert(denom > 0); + if (denom < 0) + return min_feedrate; + new_feedrate = nomin / denom; + assert(new_feedrate > min_feedrate - EPSILON); + if (new_feedrate < min_feedrate + EPSILON) + goto finished; + for (auto it = it_begin; it != it_end; ++ it) + 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. + goto not_finished_yet; + } + goto finished; +not_finished_yet: + min_feedrate = new_feedrate; + } + // Failed to find the new feedrate for the time_stretch. + +finished: + // Test whether the time_stretch was achieved. +#ifndef NDEBUG + { + float time_stretch_final = 0.f; + for (auto it = it_begin; it != it_end; ++ it) + time_stretch_final += (*it)->time_stretch_when_slowing_down_to_feedrate(new_feedrate); + assert(std::abs(time_stretch - time_stretch_final) < EPSILON); + } +#endif /* NDEBUG */ + + return new_feedrate; +} + std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_id) { std::vector per_extruder_adjustments = this->parse_layer_gcode(gcode, m_current_pos); @@ -241,12 +296,12 @@ std::vector CoolingBuffer::parse_layer_gcode(const std:: std::vector per_extruder_adjustments(extruders.size()); std::vector map_extruder_to_per_extruder_adjustment(num_extruders, 0); for (size_t i = 0; i < extruders.size(); ++ i) { - PerExtruderAdjustments &adj = per_extruder_adjustments[i]; - unsigned int extruder_id = extruders[i].id(); - adj.extruder_id = extruder_id; - adj.cooling_slow_down_enabled = config.cooling.get_at(extruder_id); - adj.slowdown_below_layer_time = config.slowdown_below_layer_time.get_at(extruder_id); - adj.min_print_speed = config.min_print_speed.get_at(extruder_id); + PerExtruderAdjustments &adj = per_extruder_adjustments[i]; + unsigned int extruder_id = extruders[i].id(); + adj.extruder_id = extruder_id; + adj.cooling_slow_down_enabled = config.cooling.get_at(extruder_id); + adj.slowdown_below_layer_time = config.slowdown_below_layer_time.get_at(extruder_id); + adj.min_print_speed = config.min_print_speed.get_at(extruder_id); map_extruder_to_per_extruder_adjustment[extruder_id] = i; } @@ -452,14 +507,14 @@ static inline void extruder_range_slow_down_non_proportional( std::vector by_min_print_speed(it_begin, it_end); // Find the next highest adjustable feedrate among the extruders. float feedrate = 0; - for (PerExtruderAdjustments *adj : by_min_print_speed) { - adj->idx_line_begin = 0; - adj->idx_line_end = 0; - assert(adj->idx_line_begin < adj->n_lines_adjustable); - if (adj->lines[adj->idx_line_begin].feedrate > feedrate) - feedrate = adj->lines[adj->idx_line_begin].feedrate; - } - assert(feedrate > 0.f); + for (PerExtruderAdjustments *adj : by_min_print_speed) { + adj->idx_line_begin = 0; + adj->idx_line_end = 0; + assert(adj->idx_line_begin < adj->n_lines_adjustable); + if (adj->lines[adj->idx_line_begin].feedrate > feedrate) + feedrate = adj->lines[adj->idx_line_begin].feedrate; + } + assert(feedrate > 0.f); // Sort by min_print_speed, maximum speed first. std::sort(by_min_print_speed.begin(), by_min_print_speed.end(), [](const PerExtruderAdjustments *p1, const PerExtruderAdjustments *p2){ return p1->min_print_speed > p2->min_print_speed; }); @@ -496,7 +551,7 @@ static inline void extruder_range_slow_down_non_proportional( for (auto it = adj; it != by_min_print_speed.end(); ++ it) time_stretch_max += (*it)->time_stretch_when_slowing_down_to_feedrate(feedrate_limit); if (time_stretch_max >= time_stretch) { - feedrate_limit = feedrate - (feedrate - feedrate_limit) * time_stretch / time_stretch_max; + feedrate_limit = new_feedrate_to_reach_time_stretch(adj, by_min_print_speed.end(), feedrate_limit, time_stretch, 20); done = true; } else time_stretch -= time_stretch_max; diff --git a/src/libslic3r/GCode/CoolingBuffer.hpp b/src/libslic3r/GCode/CoolingBuffer.hpp index bf4b082e25..511089ad0c 100644 --- a/src/libslic3r/GCode/CoolingBuffer.hpp +++ b/src/libslic3r/GCode/CoolingBuffer.hpp @@ -9,7 +9,7 @@ namespace Slic3r { class GCode; class Layer; -class PerExtruderAdjustments; +struct PerExtruderAdjustments; // A standalone G-code filter, to control cooling of the print. // The G-code is processed per layer. Once a layer is collected, fan start / stop commands are edited From a56f7d60e58f05624ccb45573e4d6344c71a9692 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Sun, 3 Feb 2019 10:41:14 +0100 Subject: [PATCH 02/12] Fixed an issue, where the output G-code file name was not always updated from the current Model/ModelObjects. Fixed a possible race condition in updating Print::m_placeholder_parser with the proposed filename / filename base. Improved documentation (source code comments). --- src/libslic3r/GCode.cpp | 1 + src/libslic3r/GCode/CoolingBuffer.cpp | 6 +- src/libslic3r/Model.cpp | 17 +++++- src/libslic3r/Model.hpp | 4 +- src/libslic3r/PlaceholderParser.hpp | 3 +- src/libslic3r/Print.cpp | 8 +-- src/libslic3r/PrintBase.cpp | 16 +++--- src/libslic3r/PrintBase.hpp | 2 +- src/libslic3r/SLAPrint.cpp | 2 - src/slic3r.cpp | 5 +- src/slic3r/GUI/Plater.cpp | 83 +++++++++++++-------------- src/slic3r/GUI/Plater.hpp | 3 +- 12 files changed, 80 insertions(+), 70 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index ec9392ec45..4d3ad00dd0 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 09d211994a..552fbf88c8 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 968a3e2348..08eb8df818 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 ba109246a5..732cacaf08 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -607,8 +607,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 b5ed56fa1e..22c790e6b5 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 42fda92fe5..bc692ca90d 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 48e991b8da..3fe9a2b4d3 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 1a61921d60..84d04d26fe 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 dc55b196bb..3cc6895589 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/slic3r.cpp b/src/slic3r.cpp index 0b7dada70f..62b56f7ffa 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/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index edc0fecd67..2fe032f9eb 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); @@ -1575,7 +1576,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; @@ -2045,7 +2046,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 +2056,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() @@ -2846,51 +2848,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 +2985,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 09b7348d5d..e3601b65c3 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()); From f050d912395523162d1234a61158d58558635f2f Mon Sep 17 00:00:00 2001 From: bubnikv Date: Sun, 3 Feb 2019 11:10:25 +0100 Subject: [PATCH 03/12] OSX specific: The Command short keys over the 3D scene toolbars are now shown with the OSX "Command" symbols, not as "Ctrl+" --- src/slic3r/GUI/GLCanvas3D.cpp | 6 +++--- src/slic3r/GUI/GUI.cpp | 24 ++++++++++++++++++++++++ src/slic3r/GUI/GUI.hpp | 6 +++++- src/slic3r/GUI/KBShortcutsDialog.cpp | 9 ++------- src/slic3r/GUI/Plater.cpp | 4 ++-- 5 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index e09ecd2bf4..bfcf796c7e 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -6073,7 +6073,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; @@ -6081,7 +6081,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; @@ -6089,7 +6089,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/GUI.cpp b/src/slic3r/GUI/GUI.cpp index 148285e86e..8e80d64c30 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 e33be8f58c..f066c82a80 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/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index 3aeda03482..d893d6f765 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/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 2fe032f9eb..ef8e84ca8d 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2555,7 +2555,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; @@ -2563,7 +2563,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; From 5deb8fcc6582f3b19e7eb0b3cf17116aeee289db Mon Sep 17 00:00:00 2001 From: bubnikv Date: Sun, 3 Feb 2019 11:15:26 +0100 Subject: [PATCH 04/12] Suppressed the "Split to volumes" button in simple mode. --- src/slic3r/GUI/GLCanvas3D.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index bfcf796c7e..0e474670c8 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -4428,6 +4428,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 From ecdf550e65a6d40ec6499fb1002b9a4b90972dd0 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Sun, 3 Feb 2019 12:12:26 +0100 Subject: [PATCH 05/12] OSX specific: Changed the "Preferences dialog" short cut to the platform default "Control-," fixes #1748 --- src/slic3r/GUI/GUI_App.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 9991d98ea4..056062cf4e 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"))); From 0c1f750cba3686ab0db19779acb8782ed047de31 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Sun, 3 Feb 2019 14:06:13 +0100 Subject: [PATCH 06/12] The accelerators Ctrl+A, Ctrl+Del and Del were incorrectly captured globally by being defined in the Edit menu. These accelerators are now suppressed in the menu (shown on Windows but inactive, not shown on OSX / Linux), and they are now captured by the 3D scene widget instead. Fix of ctrl-A doesn't work well #1753 --- src/slic3r/GUI/GLCanvas3D.cpp | 110 ++++++++++++++++------------------ src/slic3r/GUI/GLCanvas3D.hpp | 1 + src/slic3r/GUI/MainFrame.cpp | 10 ++-- src/slic3r/GUI/Plater.cpp | 3 + 4 files changed, 60 insertions(+), 64 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 0e474670c8..f524033218 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3995,6 +3995,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); @@ -5100,71 +5101,60 @@ 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(); + if (evt.GetModifiers() == wxMOD_CONTROL) { + 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; + } } } } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index ec980dd926..21ae80606a 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); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 947dd59bdf..4ff01c0d34 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -324,12 +324,14 @@ 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")) + "\t\xA0" + "Ctrl+\xA0" + "A", _(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")), + wxMenuItem* item_delete_sel = append_menu_item(editMenu, wxID_ANY, _(L("&Delete selected")) + "\t\xA0" + "Del", _(L("Deletes the current selection")), [this](wxCommandEvent&) { m_plater->remove_selected(); }, ""); - wxMenuItem* item_delete_all = append_menu_item(editMenu, wxID_ANY, _(L("Delete &all")) + "\tCtrl+Del", _(L("Deletes all objects")), + wxMenuItem* item_delete_all = append_menu_item(editMenu, wxID_ANY, _(L("Delete &all")) + "\t\xA0" + "Ctrl+\xA0" + "Del", _(L("Deletes all objects")), [this](wxCommandEvent&) { m_plater->reset(); }, ""); Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_select()); }, item_select_all->GetId()); @@ -388,7 +390,7 @@ void MainFrame::init_menubar() wxMenu* viewMenu = nullptr; if (m_plater) { viewMenu = new wxMenu(); - // \xA0 is a non-breaing space. It is entered here to spoil the automatic accelerators, + // \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. // 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"); }); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index ef8e84ca8d..59148c0a36 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1171,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(); }); @@ -1736,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()); From 14a623f50ee7051045a999d912bd6290d18fc640 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Sun, 3 Feb 2019 14:11:09 +0100 Subject: [PATCH 07/12] Removed GLCanvas3D::on_key_down() handler, as it is replaced by the on_char() handler. --- src/slic3r/GUI/GLCanvas3D.cpp | 22 +--------------------- src/slic3r/GUI/GLCanvas3D.hpp | 1 - 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index f524033218..610a5008c7 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -5056,7 +5056,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); } } @@ -5070,7 +5069,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); @@ -5082,7 +5081,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); } } @@ -5669,24 +5667,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; diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 21ae80606a..351a3d9497 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -1043,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; From f9743d17e9b700e5d6413de2d0eb497fc283c4f4 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Sun, 3 Feb 2019 15:30:37 +0100 Subject: [PATCH 08/12] On Windows, system and hidden files are now ignored in all file enumeration loops. Should fix "desktop.ini still displaying error" #1761 --- src/libslic3r/Utils.hpp | 8 +++++++ src/libslic3r/utils.cpp | 32 ++++++++++++++++++++++++- src/slic3r/Config/Snapshot.cpp | 6 ++--- src/slic3r/Config/Version.cpp | 2 +- src/slic3r/GUI/ConfigWizard.cpp | 38 ++++++++++++++---------------- src/slic3r/GUI/Preset.cpp | 15 +----------- src/slic3r/GUI/PresetBundle.cpp | 2 +- src/slic3r/Utils/PresetUpdater.cpp | 16 ++++++------- 8 files changed, 70 insertions(+), 49 deletions(-) diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index ed12d05594..046745e6f5 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 f48abfd89e..6727bb799a 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/Config/Snapshot.cpp b/src/slic3r/Config/Snapshot.cpp index 35bfde0b6b..b208554b50 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 48ace7b609..70b12f23b6 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 6cf45166db..3471395c29 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/Preset.cpp b/src/slic3r/GUI/Preset.cpp index 53650481c3..66cc5ca4c5 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); diff --git a/src/slic3r/GUI/PresetBundle.cpp b/src/slic3r/GUI/PresetBundle.cpp index 4c6c52763c..183c912348 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); diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index bfa3af3e69..a22d463fb7 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 From d0b1b3b3dec04b529c11ac60a239b8aacb6ce8bb Mon Sep 17 00:00:00 2001 From: bubnikv Date: Sun, 3 Feb 2019 17:57:55 +0100 Subject: [PATCH 09/12] Trying to convince OSX that we want the Control key to behave as Command key when accessing OSX machine over VNC from a PC. --- src/slic3r/GUI/GLCanvas3D.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 610a5008c7..cca4b893f1 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -5101,7 +5101,11 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) { // see include/wx/defs.h enum wxKeyCode int keyCode = evt.GetKeyCode(); - if (evt.GetModifiers() == wxMOD_CONTROL) { + 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__ From 1905d49ade517277b7b75200b1d7afaa9b686e6a Mon Sep 17 00:00:00 2001 From: bubnikv Date: Sun, 3 Feb 2019 19:10:20 +0100 Subject: [PATCH 10/12] Trying to find a reasonable workaround for the single key menu accelerators. --- src/slic3r/GUI/MainFrame.cpp | 40 ++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 4ff01c0d34..78dffa1cba 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -326,12 +326,22 @@ void MainFrame::init_menubar() editMenu = new wxMenu(); // \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")) + "\t\xA0" + "Ctrl+\xA0" + "A", _(L("Selects all objects")), + 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")) + "\t\xA0" + "Del", _(L("Deletes the current selection")), + wxMenuItem* item_delete_sel = append_menu_item(editMenu, wxID_ANY, _(L("&Delete selected")) + "\tDel", _(L("Deletes the current selection")), [this](wxCommandEvent&) { m_plater->remove_selected(); }, ""); - wxMenuItem* item_delete_all = append_menu_item(editMenu, wxID_ANY, _(L("Delete &all")) + "\t\xA0" + "Ctrl+\xA0" + "Del", _(L("Deletes all objects")), + wxMenuItem* item_delete_all = append_menu_item(editMenu, wxID_ANY, _(L("Delete &all")) + "\tCtrl+Del", _(L("Deletes all objects")), [this](wxCommandEvent&) { m_plater->reset(); }, ""); Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_select()); }, item_select_all->GetId()); @@ -388,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-breaking 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()); @@ -436,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(); }); } From bcab373a544b956071ba8185ce5aaea6beb97930 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Sun, 3 Feb 2019 20:17:55 +0100 Subject: [PATCH 11/12] Fixed an error when importing / exporting Config Bundles with at least one SLA print or SLA material defined. --- src/slic3r/GUI/Preset.cpp | 12 ++++++++++++ src/slic3r/GUI/Preset.hpp | 3 +++ src/slic3r/GUI/PresetBundle.cpp | 4 ++-- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp index 66cc5ca4c5..871cd0b736 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/slic3r/GUI/Preset.cpp @@ -1156,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 eae406bef2..73a921cf74 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 183c912348..5f33fd00ab 100644 --- a/src/slic3r/GUI/PresetBundle.cpp +++ b/src/slic3r/GUI/PresetBundle.cpp @@ -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; } From cd838561eee28a2733a9b6b61f9ba41ef36ccd23 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Sun, 3 Feb 2019 22:14:34 +0100 Subject: [PATCH 12/12] Model fixing through Netfabb service (Windows only): Model volumes are now fixed one by one, instances & parameters are maintained, it is now possible to fix just a single volume of a multi-part object. --- src/libslic3r/Model.hpp | 2 + src/slic3r/GUI/Plater.cpp | 29 ++------- src/slic3r/Utils/FixModelByWin10.cpp | 89 ++++++++++++++++++---------- src/slic3r/Utils/FixModelByWin10.hpp | 4 +- 4 files changed, 66 insertions(+), 58 deletions(-) diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 732cacaf08..b998fbb7db 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -385,6 +385,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; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 59148c0a36..f9ad7a39a4 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2113,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) diff --git a/src/slic3r/Utils/FixModelByWin10.cpp b/src/slic3r/Utils/FixModelByWin10.cpp index 4b487588a0..1daeaff269 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 c148a6970d..8e47664678 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 */