diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index c2781d2f1..9e2edccd8 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -101,8 +101,14 @@ sub load { my $class = shift; my ($file) = @_; - my $ini = __PACKAGE__->read_ini($file); - return $class->load_ini_hash($ini->{_}); + if ($file =~ /\.gcode$/i || $file =~ /\.g$/i) { + my $config = $class->new; + $config->_load_from_gcode($file); + return $config; + } else { + my $ini = __PACKAGE__->read_ini($file); + return $class->load_ini_hash($ini->{_}); + } } # Deserialize a perl hash into the underlying C++ Slic3r::DynamicConfig class, diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index 970593235..2a6c4d5e8 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -543,7 +543,7 @@ sub load_config_file { return unless $self->check_unsaved_changes; my $dir = $last_config ? dirname($last_config) : $Slic3r::GUI::Settings->{recent}{config_directory} || $Slic3r::GUI::Settings->{recent}{skein_directory} || ''; my $dlg = Wx::FileDialog->new($self, 'Select configuration to load:', $dir, "config.ini", - &Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_OPEN | wxFD_FILE_MUST_EXIST); + 'INI files (*.ini, *.gcode)|*.ini;*.INI;*.gcode;*.g', wxFD_OPEN | wxFD_FILE_MUST_EXIST); return unless $dlg->ShowModal == wxID_OK; $file = Slic3r::decode_path($dlg->GetPaths); $dlg->Destroy; diff --git a/xs/src/libslic3r/Config.cpp b/xs/src/libslic3r/Config.cpp index 301baf1f2..f3316ff20 100644 --- a/xs/src/libslic3r/Config.cpp +++ b/xs/src/libslic3r/Config.cpp @@ -1,6 +1,22 @@ #include "Config.hpp" -#include // for setenv() #include +#include +#include +#include +#include // std::runtime_error +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #if defined(_WIN32) && !defined(setenv) && defined(_putenv_s) @@ -94,7 +110,6 @@ bool unescape_string_cstyle(const std::string &str, std::string &str_out) bool unescape_strings_cstyle(const std::string &str, std::vector &out) { - out.clear(); if (str.empty()) return true; @@ -195,19 +210,36 @@ std::string ConfigBase::serialize(const t_config_option_key &opt_key) const return opt->serialize(); } -bool ConfigBase::set_deserialize(const t_config_option_key &opt_key, std::string str) +bool ConfigBase::set_deserialize(t_config_option_key opt_key, const std::string &str, bool append) { const ConfigOptionDef* optdef = this->def->get(opt_key); - if (optdef == NULL) throw "Calling set_deserialize() on unknown option"; - if (!optdef->shortcut.empty()) { - for (std::vector::const_iterator it = optdef->shortcut.begin(); it != optdef->shortcut.end(); ++it) { - if (!this->set_deserialize(*it, str)) return false; + if (optdef == nullptr) { + // If we didn't find an option, look for any other option having this as an alias. + for (const auto &opt : this->def->options) { + for (const t_config_option_key &opt_key2 : opt.second.aliases) { + if (opt_key2 == opt_key) { + opt_key = opt_key2; + optdef = &opt.second; + break; + } + } + if (optdef != nullptr) + break; } + if (optdef == nullptr) + throw UnknownOptionException(); + } + + if (! optdef->shortcut.empty()) { + for (const t_config_option_key &shortcut : optdef->shortcut) + if (! this->set_deserialize(shortcut, str)) + return false; return true; } - ConfigOption* opt = this->option(opt_key, true); + + ConfigOption *opt = this->option(opt_key, true); assert(opt != nullptr); - return opt->deserialize(str); + return opt->deserialize(str, append); } // Return an absolute value of a possibly relative config variable. @@ -259,6 +291,90 @@ void ConfigBase::setenv_() #endif } +void ConfigBase::load(const std::string &file) +{ + namespace pt = boost::property_tree; + pt::ptree tree; + boost::nowide::ifstream ifs(file); + pt::read_ini(ifs, tree); + for (const pt::ptree::value_type &v : tree) { + try { + t_config_option_key opt_key = v.first; + std::string value = v.second.get_value(); + this->set_deserialize(opt_key, value); + } catch (UnknownOptionException & /* e */) { + // ignore + } + } +} + +// Load the config keys from the tail of a G-code. +void ConfigBase::load_from_gcode(const std::string &file) +{ + // 1) Read a 64k block from the end of the G-code. + boost::nowide::ifstream ifs(file); + ifs.seekg(0, ifs.end); + auto length = std::min(65535, ifs.tellg()); + ifs.seekg(std::min(length, length), ifs.end); + std::vector data(size_t(length) + 1, 0); + ifs.read(data.data(), length); + ifs.close(); + + // 2) Walk line by line in reverse until a non-configuration key appears. + char *data_start = data.data(); + char *end = data_start + length; + for (;;) { + // Extract next line. + for (-- end; end > data_start && (*end == '\r' || *end == '\n'); -- end); + if (end == data_start) + break; + char *start = end; + *(++ end) = 0; + for (-- start; start > data_start && *start != '\r' && *start != '\n'; -- start); + if (start == data_start) + break; + // Extracted a line from start to end. Extract the key = value pair. + if (end - start < 10 || start[0] != ';' || start[1] != ' ' || (start[2] == ' ' || start[2] == '\t')) + break; + char *key = start + 2; + char *sep = strchr(key, '='); + if (sep == nullptr) + break; + char *value = sep + 2; + if (value >= end) + break; + char *key_end = sep - 1; + if (key_end - key < 3) + break; + *key_end = 0; + try { + this->set_deserialize(key, value); + } catch (UnknownOptionException & /* e */) { + // ignore + } + } +} + +void ConfigBase::save(const std::string &file) const +{ + using namespace std; + boost::nowide::ofstream c; + c.open(file, ios::out | ios::trunc); + + { + time_t now; + time(&now); + char buf[sizeof "0000-00-00 00:00:00"]; + strftime(buf, sizeof buf, "%F %T", gmtime(&now)); + c << "# generated by Slic3r " << SLIC3R_VERSION << " on " << buf << endl; + } + + t_config_option_keys my_keys = this->keys(); + for (t_config_option_keys::const_iterator opt_key = my_keys.begin(); opt_key != my_keys.end(); ++opt_key) + c << *opt_key << " = " << this->serialize(*opt_key) << endl; + c.close(); +} + ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool create) { t_options_map::iterator it = options.find(opt_key); if (it == options.end()) { diff --git a/xs/src/libslic3r/Config.hpp b/xs/src/libslic3r/Config.hpp index 06f37a78d..03a6dd859 100644 --- a/xs/src/libslic3r/Config.hpp +++ b/xs/src/libslic3r/Config.hpp @@ -29,7 +29,7 @@ class ConfigOption { public: virtual ~ConfigOption() {}; virtual std::string serialize() const = 0; - virtual bool deserialize(std::string str) = 0; + virtual bool deserialize(const std::string &str, bool append = false) = 0; virtual void set(const ConfigOption &option) = 0; virtual int getInt() const { return 0; }; virtual double getFloat() const { return 0; }; @@ -94,7 +94,7 @@ public: return ss.str(); }; - bool deserialize(std::string str) { + bool deserialize(const std::string &str, bool append = false) { std::istringstream iss(str); iss >> this->value; return !iss.fail(); @@ -124,8 +124,9 @@ public: return vv; }; - bool deserialize(std::string str) { - this->values.clear(); + bool deserialize(const std::string &str, bool append = false) { + if (! append) + this->values.clear(); std::istringstream is(str); std::string item_str; while (std::getline(is, item_str, ',')) { @@ -153,7 +154,7 @@ public: return ss.str(); }; - bool deserialize(std::string str) { + bool deserialize(const std::string &str, bool append = false) { std::istringstream iss(str); iss >> this->value; return !iss.fail(); @@ -183,8 +184,9 @@ public: return vv; }; - bool deserialize(std::string str) { - this->values.clear(); + bool deserialize(const std::string &str, bool append = false) { + if (! append) + this->values.clear(); std::istringstream is(str); std::string item_str; while (std::getline(is, item_str, ',')) { @@ -207,7 +209,7 @@ public: return escape_string_cstyle(this->value); } - bool deserialize(std::string str) { + bool deserialize(const std::string &str, bool append = false) { return unescape_string_cstyle(str, this->value); }; }; @@ -224,7 +226,9 @@ public: return this->values; }; - bool deserialize(std::string str) { + bool deserialize(const std::string &str, bool append = false) { + if (! append) + this->values.clear(); return unescape_strings_cstyle(str, this->values); }; }; @@ -247,7 +251,7 @@ public: return s; }; - bool deserialize(std::string str) { + bool deserialize(const std::string &str, bool append = false) { // don't try to parse the trailing % since it's optional std::istringstream iss(str); iss >> this->value; @@ -280,8 +284,9 @@ public: return vv; }; - bool deserialize(std::string str) { - this->values.clear(); + bool deserialize(const std::string &str, bool append = false) { + if (! append) + this->values.clear(); std::istringstream is(str); std::string item_str; while (std::getline(is, item_str, ',')) { @@ -327,7 +332,7 @@ public: return s; }; - bool deserialize(std::string str) { + bool deserialize(const std::string &str, bool append = false) { this->percent = str.find_first_of("%") != std::string::npos; std::istringstream iss(str); iss >> this->value; @@ -349,7 +354,7 @@ public: return ss.str(); }; - bool deserialize(std::string str) { + bool deserialize(const std::string &str, bool append = false) { std::istringstream iss(str); iss >> this->value.x; iss.ignore(std::numeric_limits::max(), ','); @@ -383,8 +388,9 @@ public: return vv; }; - bool deserialize(std::string str) { - this->values.clear(); + bool deserialize(const std::string &str, bool append = false) { + if (! append) + this->values.clear(); std::istringstream is(str); std::string point_str; while (std::getline(is, point_str, ',')) { @@ -415,7 +421,7 @@ public: return std::string(this->value ? "1" : "0"); }; - bool deserialize(std::string str) { + bool deserialize(const std::string &str, bool append = false) { this->value = (str.compare("1") == 0); return true; }; @@ -443,8 +449,9 @@ public: return vv; }; - bool deserialize(std::string str) { - this->values.clear(); + bool deserialize(const std::string &str, bool append = false) { + if (! append) + this->values.clear(); std::istringstream is(str); std::string item_str; while (std::getline(is, item_str, ',')) { @@ -473,7 +480,7 @@ public: return ""; }; - bool deserialize(std::string str) { + bool deserialize(const std::string &str, bool append = false) { t_config_enum_values enum_keys_map = ConfigOptionEnum::get_enum_values(); if (enum_keys_map.count(str) == 0) return false; this->value = static_cast(enum_keys_map[str]); @@ -500,7 +507,7 @@ public: return ""; }; - bool deserialize(std::string str) { + bool deserialize(const std::string &str, bool append = false) { if (this->keys_map->count(str) == 0) return false; this->value = (*const_cast(this->keys_map))[str]; return true; @@ -660,11 +667,14 @@ public: bool equals(const ConfigBase &other) const { return this->diff(other).empty(); } t_config_option_keys diff(const ConfigBase &other) const; std::string serialize(const t_config_option_key &opt_key) const; - bool set_deserialize(const t_config_option_key &opt_key, std::string str); + bool set_deserialize(t_config_option_key opt_key, const std::string &str, bool append = false); double get_abs_value(const t_config_option_key &opt_key) const; double get_abs_value(const t_config_option_key &opt_key, double ratio_over) const; void setenv_(); + void load(const std::string &file); + void load_from_gcode(const std::string &file); + void save(const std::string &file) const; }; // Configuration store with dynamic number of configuration values. @@ -703,6 +713,9 @@ public: // virtual ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false) = 0; }; +/// Specialization of std::exception to indicate that an unknown config option has been encountered. +class UnknownOptionException : public std::exception {}; + } #endif diff --git a/xs/t/15_config.t b/xs/t/15_config.t index 1f99513cd..946297358 100644 --- a/xs/t/15_config.t +++ b/xs/t/15_config.t @@ -4,7 +4,7 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 146; +use Test::More tests => 147; foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintConfig) { $config->set('layer_height', 0.3); @@ -241,4 +241,13 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo is_deeply $config->get('retract_layer_change'), [0,0], 'retract_layer_change is disabled with spiral_vase'; } +{ + use Cwd qw(abs_path); + use File::Basename qw(dirname); + my $class = Slic3r::Config->new; + my $path = abs_path($0); + my $config = $class->_load(dirname($path)."/inc/22_config_bad_config_options.ini"); + ok 1, 'did not crash on reading invalid items in config'; +} + __END__ diff --git a/xs/t/inc/22_config_bad_config_options.ini b/xs/t/inc/22_config_bad_config_options.ini new file mode 100644 index 000000000..b28c62479 --- /dev/null +++ b/xs/t/inc/22_config_bad_config_options.ini @@ -0,0 +1,7 @@ +# generated by Slic3r 1.1.7 on Tue Aug 19 21:49:50 2014 +avoid_crossing_perimeters = 1 +bed_size = 200,180 +g0 = 0 +perimeter_acceleration = 0 +support_material_extruder = 1 +support_material_extrusion_width = 0 diff --git a/xs/xsp/Config.xsp b/xs/xsp/Config.xsp index 0447703ea..5a3980d31 100644 --- a/xs/xsp/Config.xsp +++ b/xs/xsp/Config.xsp @@ -38,6 +38,9 @@ void normalize(); %name{setenv} void setenv_(); double min_object_distance(); + %name{_load} void load(std::string file); + %name{_load_from_gcode} void load_from_gcode(std::string file); + %name{_save} void save(std::string file); }; %name{Slic3r::Config::Static} class StaticPrintConfig { @@ -84,6 +87,9 @@ %}; %name{setenv} void setenv_(); double min_object_distance(); + %name{_load} void load(std::string file); + %name{_load_from_gcode} void load_from_gcode(std::string file); + %name{_save} void save(std::string file); }; %package{Slic3r::Config};