1 #include "Config.hpp"
2 #include "format.hpp"
3 #include "Utils.hpp"
4 #include <assert.h>
5 #include <fstream>
6 #include <iostream>
7 #include <iomanip>
8 #include <boost/algorithm/string.hpp>
9 #include <boost/algorithm/string/classification.hpp>
10 #include <boost/algorithm/string/erase.hpp>
11 #include <boost/algorithm/string/predicate.hpp>
12 #include <boost/algorithm/string/replace.hpp>
13 #include <boost/algorithm/string/split.hpp>
14 #include <boost/config.hpp>
15 #include <boost/foreach.hpp>
16 #include <boost/lexical_cast.hpp>
17 #include <boost/nowide/cenv.hpp>
18 #include <boost/nowide/iostream.hpp>
19 #include <boost/nowide/fstream.hpp>
20 #include <boost/property_tree/ini_parser.hpp>
21 #include <boost/format.hpp>
22 #include <string.h>
23
24 //FIXME for GCodeFlavor and gcfMarlin (for forward-compatibility conversion)
25 // This is not nice, likely it would be better to pass the ConfigSubstitutionContext to handle_legacy().
26 #include "PrintConfig.hpp"
27
28 namespace Slic3r {
29
30 // Escape \n, \r and backslash
escape_string_cstyle(const std::string & str)31 std::string escape_string_cstyle(const std::string &str)
32 {
33 // Allocate a buffer twice the input string length,
34 // so the output will fit even if all input characters get escaped.
35 std::vector<char> out(str.size() * 2, 0);
36 char *outptr = out.data();
37 for (size_t i = 0; i < str.size(); ++ i) {
38 char c = str[i];
39 if (c == '\r') {
40 (*outptr ++) = '\\';
41 (*outptr ++) = 'r';
42 } else if (c == '\n') {
43 (*outptr ++) = '\\';
44 (*outptr ++) = 'n';
45 } else if (c == '\\') {
46 (*outptr ++) = '\\';
47 (*outptr ++) = '\\';
48 } else
49 (*outptr ++) = c;
50 }
51 return std::string(out.data(), outptr - out.data());
52 }
53
escape_strings_cstyle(const std::vector<std::string> & strs)54 std::string escape_strings_cstyle(const std::vector<std::string> &strs)
55 {
56 // 1) Estimate the output buffer size to avoid buffer reallocation.
57 size_t outbuflen = 0;
58 for (size_t i = 0; i < strs.size(); ++ i)
59 // Reserve space for every character escaped + quotes + semicolon.
60 outbuflen += strs[i].size() * 2 + 3;
61 // 2) Fill in the buffer.
62 std::vector<char> out(outbuflen, 0);
63 char *outptr = out.data();
64 for (size_t j = 0; j < strs.size(); ++ j) {
65 if (j > 0)
66 // Separate the strings.
67 (*outptr ++) = ';';
68 const std::string &str = strs[j];
69 // Is the string simple or complex? Complex string contains spaces, tabs, new lines and other
70 // escapable characters. Empty string shall be quoted as well, if it is the only string in strs.
71 bool should_quote = strs.size() == 1 && str.empty();
72 for (size_t i = 0; i < str.size(); ++ i) {
73 char c = str[i];
74 if (c == ' ' || c == '\t' || c == '\\' || c == '"' || c == '\r' || c == '\n') {
75 should_quote = true;
76 break;
77 }
78 }
79 if (should_quote) {
80 (*outptr ++) = '"';
81 for (size_t i = 0; i < str.size(); ++ i) {
82 char c = str[i];
83 if (c == '\\' || c == '"') {
84 (*outptr ++) = '\\';
85 (*outptr ++) = c;
86 } else if (c == '\r') {
87 (*outptr ++) = '\\';
88 (*outptr ++) = 'r';
89 } else if (c == '\n') {
90 (*outptr ++) = '\\';
91 (*outptr ++) = 'n';
92 } else
93 (*outptr ++) = c;
94 }
95 (*outptr ++) = '"';
96 } else {
97 memcpy(outptr, str.data(), str.size());
98 outptr += str.size();
99 }
100 }
101 return std::string(out.data(), outptr - out.data());
102 }
103
104 // Unescape \n, \r and backslash
unescape_string_cstyle(const std::string & str,std::string & str_out)105 bool unescape_string_cstyle(const std::string &str, std::string &str_out)
106 {
107 std::vector<char> out(str.size(), 0);
108 char *outptr = out.data();
109 for (size_t i = 0; i < str.size(); ++ i) {
110 char c = str[i];
111 if (c == '\\') {
112 if (++ i == str.size())
113 return false;
114 c = str[i];
115 if (c == 'r')
116 (*outptr ++) = '\r';
117 else if (c == 'n')
118 (*outptr ++) = '\n';
119 else
120 (*outptr ++) = c;
121 } else
122 (*outptr ++) = c;
123 }
124 str_out.assign(out.data(), outptr - out.data());
125 return true;
126 }
127
unescape_strings_cstyle(const std::string & str,std::vector<std::string> & out)128 bool unescape_strings_cstyle(const std::string &str, std::vector<std::string> &out)
129 {
130 if (str.empty())
131 return true;
132
133 size_t i = 0;
134 for (;;) {
135 // Skip white spaces.
136 char c = str[i];
137 while (c == ' ' || c == '\t') {
138 if (++ i == str.size())
139 return true;
140 c = str[i];
141 }
142 // Start of a word.
143 std::vector<char> buf;
144 buf.reserve(16);
145 // Is it enclosed in quotes?
146 c = str[i];
147 if (c == '"') {
148 // Complex case, string is enclosed in quotes.
149 for (++ i; i < str.size(); ++ i) {
150 c = str[i];
151 if (c == '"') {
152 // End of string.
153 break;
154 }
155 if (c == '\\') {
156 if (++ i == str.size())
157 return false;
158 c = str[i];
159 if (c == 'r')
160 c = '\r';
161 else if (c == 'n')
162 c = '\n';
163 }
164 buf.push_back(c);
165 }
166 if (i == str.size())
167 return false;
168 ++ i;
169 } else {
170 for (; i < str.size(); ++ i) {
171 c = str[i];
172 if (c == ';')
173 break;
174 buf.push_back(c);
175 }
176 }
177 // Store the string into the output vector.
178 out.push_back(std::string(buf.data(), buf.size()));
179 if (i == str.size())
180 return true;
181 // Skip white spaces.
182 c = str[i];
183 while (c == ' ' || c == '\t') {
184 if (++ i == str.size())
185 // End of string. This is correct.
186 return true;
187 c = str[i];
188 }
189 if (c != ';')
190 return false;
191 if (++ i == str.size()) {
192 // Emit one additional empty string.
193 out.push_back(std::string());
194 return true;
195 }
196 }
197 }
198
escape_ampersand(const std::string & str)199 std::string escape_ampersand(const std::string& str)
200 {
201 // Allocate a buffer 2 times the input string length,
202 // so the output will fit even if all input characters get escaped.
203 std::vector<char> out(str.size() * 6, 0);
204 char* outptr = out.data();
205 for (size_t i = 0; i < str.size(); ++i) {
206 char c = str[i];
207 if (c == '&') {
208 (*outptr++) = '&';
209 (*outptr++) = '&';
210 } else
211 (*outptr++) = c;
212 }
213 return std::string(out.data(), outptr - out.data());
214 }
215
operator ()(ConfigOption * p)216 void ConfigOptionDeleter::operator()(ConfigOption* p) {
217 delete p;
218 }
219
cli_args(const std::string & key) const220 std::vector<std::string> ConfigOptionDef::cli_args(const std::string &key) const
221 {
222 std::vector<std::string> args;
223 if (this->cli != ConfigOptionDef::nocli) {
224 std::string cli = this->cli.substr(0, this->cli.find("="));
225 boost::trim_right_if(cli, boost::is_any_of("!"));
226 if (cli.empty()) {
227 // Add the key
228 std::string opt = key;
229 boost::replace_all(opt, "_", "-");
230 args.emplace_back(std::move(opt));
231 } else
232 boost::split(args, cli, boost::is_any_of("|"));
233 }
234 return args;
235 }
236
create_empty_option() const237 ConfigOption* ConfigOptionDef::create_empty_option() const
238 {
239 if (this->nullable) {
240 switch (this->type) {
241 case coFloats: return new ConfigOptionFloatsNullable();
242 case coInts: return new ConfigOptionIntsNullable();
243 case coPercents: return new ConfigOptionPercentsNullable();
244 case coFloatsOrPercents: return new ConfigOptionFloatsOrPercentsNullable();
245 case coBools: return new ConfigOptionBoolsNullable();
246 default: throw ConfigurationError(std::string("Unknown option type for nullable option ") + this->label);
247 }
248 } else {
249 switch (this->type) {
250 case coFloat: return new ConfigOptionFloat();
251 case coFloats: return new ConfigOptionFloats();
252 case coInt: return new ConfigOptionInt();
253 case coInts: return new ConfigOptionInts();
254 case coString: return new ConfigOptionString();
255 case coStrings: return new ConfigOptionStrings();
256 case coPercent: return new ConfigOptionPercent();
257 case coPercents: return new ConfigOptionPercents();
258 case coFloatOrPercent: return new ConfigOptionFloatOrPercent();
259 case coFloatsOrPercents: return new ConfigOptionFloatsOrPercents();
260 case coPoint: return new ConfigOptionPoint();
261 case coPoints: return new ConfigOptionPoints();
262 case coPoint3: return new ConfigOptionPoint3();
263 // case coPoint3s: return new ConfigOptionPoint3s();
264 case coBool: return new ConfigOptionBool();
265 case coBools: return new ConfigOptionBools();
266 case coEnum: return new ConfigOptionEnumGeneric(this->enum_keys_map);
267 default: throw ConfigurationError(std::string("Unknown option type for option ") + this->label);
268 }
269 }
270 }
271
create_default_option() const272 ConfigOption* ConfigOptionDef::create_default_option() const
273 {
274 if (this->default_value)
275 return (this->default_value->type() == coEnum) ?
276 // Special case: For a DynamicConfig, convert a templated enum to a generic enum.
277 new ConfigOptionEnumGeneric(this->enum_keys_map, this->default_value->getInt()) :
278 this->default_value->clone();
279 return this->create_empty_option();
280 }
281
282 // Assignment of the serialization IDs is not thread safe. The Defs shall be initialized from the main thread!
add(const t_config_option_key & opt_key,ConfigOptionType type)283 ConfigOptionDef* ConfigDef::add(const t_config_option_key &opt_key, ConfigOptionType type)
284 {
285 static size_t serialization_key_ordinal_last = 0;
286 ConfigOptionDef *opt = &this->options[opt_key];
287 opt->opt_key = opt_key;
288 opt->type = type;
289 opt->serialization_key_ordinal = ++ serialization_key_ordinal_last;
290 this->by_serialization_key_ordinal[opt->serialization_key_ordinal] = opt;
291 return opt;
292 }
293
add_nullable(const t_config_option_key & opt_key,ConfigOptionType type)294 ConfigOptionDef* ConfigDef::add_nullable(const t_config_option_key &opt_key, ConfigOptionType type)
295 {
296 ConfigOptionDef *def = this->add(opt_key, type);
297 def->nullable = true;
298 return def;
299 }
300
print_cli_help(std::ostream & out,bool show_defaults,std::function<bool (const ConfigOptionDef &)> filter) const301 std::ostream& ConfigDef::print_cli_help(std::ostream& out, bool show_defaults, std::function<bool(const ConfigOptionDef &)> filter) const
302 {
303 // prepare a function for wrapping text
304 auto wrap = [](std::string text, size_t line_length) -> std::string {
305 std::istringstream words(text);
306 std::ostringstream wrapped;
307 std::string word;
308
309 if (words >> word) {
310 wrapped << word;
311 size_t space_left = line_length - word.length();
312 while (words >> word) {
313 if (space_left < word.length() + 1) {
314 wrapped << '\n' << word;
315 space_left = line_length - word.length();
316 } else {
317 wrapped << ' ' << word;
318 space_left -= word.length() + 1;
319 }
320 }
321 }
322 return wrapped.str();
323 };
324
325 // get the unique categories
326 std::set<std::string> categories;
327 for (const auto& opt : this->options) {
328 const ConfigOptionDef& def = opt.second;
329 if (filter(def))
330 categories.insert(def.category);
331 }
332
333 for (auto category : categories) {
334 if (category != "") {
335 out << category << ":" << std::endl;
336 } else if (categories.size() > 1) {
337 out << "Misc options:" << std::endl;
338 }
339
340 for (const auto& opt : this->options) {
341 const ConfigOptionDef& def = opt.second;
342 if (def.category != category || def.cli == ConfigOptionDef::nocli || !filter(def))
343 continue;
344
345 // get all possible variations: --foo, --foobar, -f...
346 std::vector<std::string> cli_args = def.cli_args(opt.first);
347 if (cli_args.empty())
348 continue;
349
350 for (auto& arg : cli_args) {
351 arg.insert(0, (arg.size() == 1) ? "-" : "--");
352 if (def.type == coFloat || def.type == coInt || def.type == coFloatOrPercent
353 || def.type == coFloats || def.type == coInts) {
354 arg += " N";
355 } else if (def.type == coPoint) {
356 arg += " X,Y";
357 } else if (def.type == coPoint3) {
358 arg += " X,Y,Z";
359 } else if (def.type == coString || def.type == coStrings) {
360 arg += " ABCD";
361 }
362 }
363
364 // left: command line options
365 const std::string cli = boost::algorithm::join(cli_args, ", ");
366 out << " " << std::left << std::setw(20) << cli;
367
368 // right: option description
369 std::string descr = def.tooltip;
370 bool show_defaults_this = show_defaults || def.opt_key == "config_compatibility";
371 if (show_defaults_this && def.default_value && def.type != coBool
372 && (def.type != coString || !def.default_value->serialize().empty())) {
373 descr += " (";
374 if (!def.sidetext.empty()) {
375 descr += def.sidetext + ", ";
376 } else if (!def.enum_values.empty()) {
377 descr += boost::algorithm::join(def.enum_values, ", ") + "; ";
378 }
379 descr += "default: " + def.default_value->serialize() + ")";
380 }
381
382 // wrap lines of description
383 descr = wrap(descr, 80);
384 std::vector<std::string> lines;
385 boost::split(lines, descr, boost::is_any_of("\n"));
386
387 // if command line options are too long, print description in new line
388 for (size_t i = 0; i < lines.size(); ++i) {
389 if (i == 0 && cli.size() > 19)
390 out << std::endl;
391 if (i > 0 || cli.size() > 19)
392 out << std::string(21, ' ');
393 out << lines[i] << std::endl;
394 }
395 }
396 }
397 return out;
398 }
399
apply_only(const ConfigBase & other,const t_config_option_keys & keys,bool ignore_nonexistent)400 void ConfigBase::apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent)
401 {
402 // loop through options and apply them
403 for (const t_config_option_key &opt_key : keys) {
404 // Create a new option with default value for the key.
405 // If the key is not in the parameter definition, or this ConfigBase is a static type and it does not support the parameter,
406 // an exception is thrown if not ignore_nonexistent.
407 ConfigOption *my_opt = this->option(opt_key, true);
408 if (my_opt == nullptr) {
409 // opt_key does not exist in this ConfigBase and it cannot be created, because it is not defined by this->def().
410 // This is only possible if other is of DynamicConfig type.
411 if (ignore_nonexistent)
412 continue;
413 throw UnknownOptionException(opt_key);
414 }
415 const ConfigOption *other_opt = other.option(opt_key);
416 if (other_opt == nullptr) {
417 // The key was not found in the source config, therefore it will not be initialized!
418 // printf("Not found, therefore not initialized: %s\n", opt_key.c_str());
419 } else
420 my_opt->set(other_opt);
421 }
422 }
423
424 // this will *ignore* options not present in both configs
diff(const ConfigBase & other) const425 t_config_option_keys ConfigBase::diff(const ConfigBase &other) const
426 {
427 t_config_option_keys diff;
428 for (const t_config_option_key &opt_key : this->keys()) {
429 const ConfigOption *this_opt = this->option(opt_key);
430 const ConfigOption *other_opt = other.option(opt_key);
431 if (this_opt != nullptr && other_opt != nullptr && *this_opt != *other_opt)
432 diff.emplace_back(opt_key);
433 }
434 return diff;
435 }
436
equal(const ConfigBase & other) const437 t_config_option_keys ConfigBase::equal(const ConfigBase &other) const
438 {
439 t_config_option_keys equal;
440 for (const t_config_option_key &opt_key : this->keys()) {
441 const ConfigOption *this_opt = this->option(opt_key);
442 const ConfigOption *other_opt = other.option(opt_key);
443 if (this_opt != nullptr && other_opt != nullptr && *this_opt == *other_opt)
444 equal.emplace_back(opt_key);
445 }
446 return equal;
447 }
448
opt_serialize(const t_config_option_key & opt_key) const449 std::string ConfigBase::opt_serialize(const t_config_option_key &opt_key) const
450 {
451 const ConfigOption* opt = this->option(opt_key);
452 assert(opt != nullptr);
453 return opt->serialize();
454 }
455
set(const std::string & opt_key,int value,bool create)456 void ConfigBase::set(const std::string &opt_key, int value, bool create)
457 {
458 ConfigOption *opt = this->option_throw(opt_key, create);
459 switch (opt->type()) {
460 case coInt: static_cast<ConfigOptionInt*>(opt)->value = value; break;
461 case coFloat: static_cast<ConfigOptionFloat*>(opt)->value = value; break;
462 case coFloatOrPercent: static_cast<ConfigOptionFloatOrPercent*>(opt)->value = value; static_cast<ConfigOptionFloatOrPercent*>(opt)->percent = false; break;
463 case coString: static_cast<ConfigOptionString*>(opt)->value = std::to_string(value); break;
464 default: throw BadOptionTypeException("Configbase::set() - conversion from int not possible");
465 }
466 }
467
set(const std::string & opt_key,double value,bool create)468 void ConfigBase::set(const std::string &opt_key, double value, bool create)
469 {
470 ConfigOption *opt = this->option_throw(opt_key, create);
471 switch (opt->type()) {
472 case coFloat: static_cast<ConfigOptionFloat*>(opt)->value = value; break;
473 case coFloatOrPercent: static_cast<ConfigOptionFloatOrPercent*>(opt)->value = value; static_cast<ConfigOptionFloatOrPercent*>(opt)->percent = false; break;
474 case coString: static_cast<ConfigOptionString*>(opt)->value = std::to_string(value); break;
475 default: throw BadOptionTypeException("Configbase::set() - conversion from float not possible");
476 }
477 }
478
set_deserialize_nothrow(const t_config_option_key & opt_key_src,const std::string & value_src,ConfigSubstitutionContext & substitutions_ctxt,bool append)479 bool ConfigBase::set_deserialize_nothrow(const t_config_option_key &opt_key_src, const std::string &value_src, ConfigSubstitutionContext& substitutions_ctxt, bool append)
480 {
481 t_config_option_key opt_key = opt_key_src;
482 std::string value = value_src;
483 // Both opt_key and value may be modified by _handle_legacy().
484 // If the opt_key is no more valid in this version of Slic3r, opt_key is cleared by _handle_legacy().
485 this->handle_legacy(opt_key, value);
486 if (opt_key.empty())
487 // Ignore the option.
488 return true;
489 return this->set_deserialize_raw(opt_key, value, substitutions_ctxt, append);
490 }
491
set_deserialize(const t_config_option_key & opt_key_src,const std::string & value_src,ConfigSubstitutionContext & substitutions_ctxt,bool append)492 void ConfigBase::set_deserialize(const t_config_option_key &opt_key_src, const std::string &value_src, ConfigSubstitutionContext& substitutions_ctxt, bool append)
493 {
494 if (! this->set_deserialize_nothrow(opt_key_src, value_src, substitutions_ctxt, append))
495 throw BadOptionValueException(format("Invalid value provided for parameter %1%: %2%", opt_key_src, value_src));
496 }
497
set_deserialize(std::initializer_list<SetDeserializeItem> items,ConfigSubstitutionContext & substitutions_ctxt)498 void ConfigBase::set_deserialize(std::initializer_list<SetDeserializeItem> items, ConfigSubstitutionContext& substitutions_ctxt)
499 {
500 for (const SetDeserializeItem &item : items)
501 this->set_deserialize(item.opt_key, item.opt_value, substitutions_ctxt, item.append);
502 }
503
set_deserialize_raw(const t_config_option_key & opt_key_src,const std::string & value,ConfigSubstitutionContext & substitutions_ctxt,bool append)504 bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, const std::string &value, ConfigSubstitutionContext& substitutions_ctxt, bool append)
505 {
506 t_config_option_key opt_key = opt_key_src;
507 // Try to deserialize the option by its name.
508 const ConfigDef *def = this->def();
509 if (def == nullptr)
510 throw NoDefinitionException(opt_key);
511 const ConfigOptionDef *optdef = def->get(opt_key);
512 if (optdef == nullptr) {
513 // If we didn't find an option, look for any other option having this as an alias.
514 for (const auto &opt : def->options) {
515 for (const t_config_option_key &opt_key2 : opt.second.aliases) {
516 if (opt_key2 == opt_key) {
517 opt_key = opt.first;
518 optdef = &opt.second;
519 break;
520 }
521 }
522 if (optdef != nullptr)
523 break;
524 }
525 if (optdef == nullptr)
526 throw UnknownOptionException(opt_key);
527 }
528
529 if (! optdef->shortcut.empty()) {
530 // Aliasing for example "solid_layers" to "top_solid_layers" and "bottom_solid_layers".
531 for (const t_config_option_key &shortcut : optdef->shortcut)
532 // Recursive call.
533 if (! this->set_deserialize_raw(shortcut, value, substitutions_ctxt, append))
534 return false;
535 return true;
536 }
537
538 ConfigOption *opt = this->option(opt_key, true);
539 assert(opt != nullptr);
540 bool success = false;
541 bool substituted = false;
542 if (optdef->type == coBools && substitutions_ctxt.rule != ForwardCompatibilitySubstitutionRule::Disable) {
543 //FIXME Special handling of vectors of bools, quick and not so dirty solution before PrusaSlicer 2.3.2 release.
544 bool nullable = opt->nullable();
545 ConfigHelpers::DeserializationSubstitution default_value = ConfigHelpers::DeserializationSubstitution::DefaultsToFalse;
546 if (optdef->default_value) {
547 // Default value for vectors of booleans used in a "per extruder" context, thus the default contains just a single value.
548 assert(dynamic_cast<const ConfigOptionVector<unsigned char>*>(optdef->default_value.get()));
549 auto &values = static_cast<const ConfigOptionVector<unsigned char>*>(optdef->default_value.get())->values;
550 if (values.size() == 1 && values.front() == 1)
551 default_value = ConfigHelpers::DeserializationSubstitution::DefaultsToTrue;
552 }
553 auto result = nullable ?
554 static_cast<ConfigOptionBoolsNullable*>(opt)->deserialize_with_substitutions(value, append, default_value) :
555 static_cast<ConfigOptionBools*>(opt)->deserialize_with_substitutions(value, append, default_value);
556 success = result != ConfigHelpers::DeserializationResult::Failed;
557 substituted = result == ConfigHelpers::DeserializationResult::Substituted;
558 } else {
559 success = opt->deserialize(value, append);
560 if (! success && substitutions_ctxt.rule != ForwardCompatibilitySubstitutionRule::Disable &&
561 // Only allow substitutions of an enum value by another enum value or a boolean value with an enum value.
562 // That means, we expect enum values being added in the future and possibly booleans being converted to enums.
563 (optdef->type == coEnum || optdef->type == coBool) && ConfigHelpers::looks_like_enum_value(value)) {
564 // Deserialize failed, try to substitute with a default value.
565 assert(substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::Enable || substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::EnableSilent);
566 if (optdef->type == coEnum && opt_key == "gcode_flavor" && (value == "marlin2" || value == "marlinfirmware"))
567 static_cast<ConfigOptionEnum<GCodeFlavor>*>(opt)->value = gcfMarlin;
568 else if (optdef->type == coBool)
569 static_cast<ConfigOptionBool*>(opt)->value = ConfigHelpers::enum_looks_like_true_value(value);
570 else
571 // Just use the default of the option.
572 opt->set(optdef->default_value.get());
573 success = true;
574 substituted = true;
575 }
576 }
577
578 if (substituted && (substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::Enable ||
579 substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::EnableSystemSilent)) {
580 // Log the substitution.
581 ConfigSubstitution config_substitution;
582 config_substitution.opt_def = optdef;
583 config_substitution.old_value = value;
584 config_substitution.new_value = ConfigOptionUniquePtr(opt->clone());
585 substitutions_ctxt.substitutions.emplace_back(std::move(config_substitution));
586 }
587 return success;
588 }
589
590 // Return an absolute value of a possibly relative config variable.
591 // For example, return absolute infill extrusion width, either from an absolute value, or relative to the layer height.
get_abs_value(const t_config_option_key & opt_key) const592 double ConfigBase::get_abs_value(const t_config_option_key &opt_key) const
593 {
594 // Get stored option value.
595 const ConfigOption *raw_opt = this->option(opt_key);
596 assert(raw_opt != nullptr);
597 if (raw_opt->type() == coFloat)
598 return static_cast<const ConfigOptionFloat*>(raw_opt)->value;
599 if (raw_opt->type() == coFloatOrPercent) {
600 // Get option definition.
601 const ConfigDef *def = this->def();
602 if (def == nullptr)
603 throw NoDefinitionException(opt_key);
604 const ConfigOptionDef *opt_def = def->get(opt_key);
605 assert(opt_def != nullptr);
606 // Compute absolute value over the absolute value of the base option.
607 //FIXME there are some ratio_over chains, which end with empty ratio_with.
608 // For example, XXX_extrusion_width parameters are not handled by get_abs_value correctly.
609 return opt_def->ratio_over.empty() ? 0. :
610 static_cast<const ConfigOptionFloatOrPercent*>(raw_opt)->get_abs_value(this->get_abs_value(opt_def->ratio_over));
611 }
612 throw ConfigurationError("ConfigBase::get_abs_value(): Not a valid option type for get_abs_value()");
613 }
614
615 // Return an absolute value of a possibly relative config variable.
616 // For example, return absolute infill extrusion width, either from an absolute value, or relative to a provided value.
get_abs_value(const t_config_option_key & opt_key,double ratio_over) const617 double ConfigBase::get_abs_value(const t_config_option_key &opt_key, double ratio_over) const
618 {
619 // Get stored option value.
620 const ConfigOption *raw_opt = this->option(opt_key);
621 assert(raw_opt != nullptr);
622 if (raw_opt->type() != coFloatOrPercent)
623 throw ConfigurationError("ConfigBase::get_abs_value(): opt_key is not of coFloatOrPercent");
624 // Compute absolute value.
625 return static_cast<const ConfigOptionFloatOrPercent*>(raw_opt)->get_abs_value(ratio_over);
626 }
627
setenv_() const628 void ConfigBase::setenv_() const
629 {
630 t_config_option_keys opt_keys = this->keys();
631 for (t_config_option_keys::const_iterator it = opt_keys.begin(); it != opt_keys.end(); ++it) {
632 // prepend the SLIC3R_ prefix
633 std::ostringstream ss;
634 ss << "SLIC3R_";
635 ss << *it;
636 std::string envname = ss.str();
637
638 // capitalize environment variable name
639 for (size_t i = 0; i < envname.size(); ++i)
640 envname[i] = (envname[i] <= 'z' && envname[i] >= 'a') ? envname[i]-('a'-'A') : envname[i];
641
642 boost::nowide::setenv(envname.c_str(), this->opt_serialize(*it).c_str(), 1);
643 }
644 }
645
load(const std::string & file,ForwardCompatibilitySubstitutionRule compatibility_rule)646 ConfigSubstitutions ConfigBase::load(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule)
647 {
648 return is_gcode_file(file) ?
649 this->load_from_gcode_file(file, compatibility_rule) :
650 this->load_from_ini(file, compatibility_rule);
651 }
652
load_from_ini(const std::string & file,ForwardCompatibilitySubstitutionRule compatibility_rule)653 ConfigSubstitutions ConfigBase::load_from_ini(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule)
654 {
655 try {
656 boost::property_tree::ptree tree;
657 boost::nowide::ifstream ifs(file);
658 boost::property_tree::read_ini(ifs, tree);
659 return this->load(tree, compatibility_rule);
660 } catch (const ConfigurationError &e) {
661 throw ConfigurationError(format("Failed loading configuration file \"%1%\": %2%", file, e.what()));
662 }
663 }
664
load(const boost::property_tree::ptree & tree,ForwardCompatibilitySubstitutionRule compatibility_rule)665 ConfigSubstitutions ConfigBase::load(const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule)
666 {
667 ConfigSubstitutionContext substitutions_ctxt(compatibility_rule);
668 for (const boost::property_tree::ptree::value_type &v : tree) {
669 try {
670 t_config_option_key opt_key = v.first;
671 this->set_deserialize(opt_key, v.second.get_value<std::string>(), substitutions_ctxt);
672 } catch (UnknownOptionException & /* e */) {
673 // ignore
674 }
675 }
676 return std::move(substitutions_ctxt.substitutions);
677 }
678
679 // Load the config keys from the tail of a G-code file.
load_from_gcode_file(const std::string & file,ForwardCompatibilitySubstitutionRule compatibility_rule)680 ConfigSubstitutions ConfigBase::load_from_gcode_file(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule)
681 {
682 try {
683 // Read a 64k block from the end of the G-code.
684 boost::nowide::ifstream ifs(file);
685 {
686 const char slic3r_gcode_header[] = "; generated by Slic3r ";
687 const char prusaslicer_gcode_header[] = "; generated by PrusaSlicer ";
688 std::string firstline;
689 std::getline(ifs, firstline);
690 if (strncmp(slic3r_gcode_header, firstline.c_str(), strlen(slic3r_gcode_header)) != 0 &&
691 strncmp(prusaslicer_gcode_header, firstline.c_str(), strlen(prusaslicer_gcode_header)) != 0)
692 throw ConfigurationError("Not a PrusaSlicer / Slic3r PE generated g-code.");
693 }
694 ifs.seekg(0, ifs.end);
695 auto file_length = ifs.tellg();
696 auto data_length = std::min<std::fstream::pos_type>(65535, file_length);
697 ifs.seekg(file_length - data_length, ifs.beg);
698 std::vector<char> data(size_t(data_length) + 1, 0);
699 ifs.read(data.data(), data_length);
700 ifs.close();
701
702 ConfigSubstitutionContext substitutions_ctxt(compatibility_rule);
703 size_t key_value_pairs = load_from_gcode_string(data.data(), substitutions_ctxt);
704 if (key_value_pairs < 80)
705 throw ConfigurationError(format("Suspiciously low number of configuration values extracted from %1%: %2%", file, key_value_pairs));
706 return std::move(substitutions_ctxt.substitutions);
707 } catch (const ConfigurationError &e) {
708 throw ConfigurationError(format("Failed loading configuration from G-code \"%1%\": %2%", file, e.what()));
709 }
710 }
711
712 // Load the config keys from the given string.
load_from_gcode_string(const char * str,ConfigSubstitutionContext & substitutions)713 size_t ConfigBase::load_from_gcode_string(const char* str, ConfigSubstitutionContext& substitutions)
714 {
715 if (str == nullptr)
716 return 0;
717
718 // Walk line by line in reverse until a non-configuration key appears.
719 const char *data_start = str;
720 // boost::nowide::ifstream seems to cook the text data somehow, so less then the 64k of characters may be retrieved.
721 const char *end = data_start + strlen(str);
722 size_t num_key_value_pairs = 0;
723 for (;;) {
724 // Extract next line.
725 for (--end; end > data_start && (*end == '\r' || *end == '\n'); --end);
726 if (end == data_start)
727 break;
728 const char *start = end ++;
729 for (; start > data_start && *start != '\r' && *start != '\n'; --start);
730 if (start == data_start)
731 break;
732 // Extracted a line from start to end. Extract the key = value pair.
733 if (end - (++ start) < 10 || start[0] != ';' || start[1] != ' ')
734 break;
735 const char *key = start + 2;
736 if (!(*key >= 'a' && *key <= 'z') || (*key >= 'A' && *key <= 'Z'))
737 // A key must start with a letter.
738 break;
739 const char *sep = key;
740 for (; sep != end && *sep != '='; ++ sep) ;
741 if (sep == end || sep[-1] != ' ' || sep[1] != ' ')
742 break;
743 const char *value = sep + 2;
744 if (value > end)
745 break;
746 const char *key_end = sep - 1;
747 if (key_end - key < 3)
748 break;
749 // The key may contain letters, digits and underscores.
750 for (const char *c = key; c != key_end; ++ c)
751 if (!((*c >= 'a' && *c <= 'z') || (*c >= 'A' && *c <= 'Z') || (*c >= '0' && *c <= '9') || *c == '_')) {
752 key = nullptr;
753 break;
754 }
755 if (key == nullptr)
756 break;
757 try {
758 this->set_deserialize(std::string(key, key_end), std::string(value, end), substitutions);
759 ++num_key_value_pairs;
760 }
761 catch (UnknownOptionException & /* e */) {
762 // ignore
763 }
764 end = start;
765 }
766
767 return num_key_value_pairs;
768 }
769
save(const std::string & file) const770 void ConfigBase::save(const std::string &file) const
771 {
772 boost::nowide::ofstream c;
773 c.open(file, std::ios::out | std::ios::trunc);
774 c << "# " << Slic3r::header_slic3r_generated() << std::endl;
775 for (const std::string &opt_key : this->keys())
776 c << opt_key << " = " << this->opt_serialize(opt_key) << std::endl;
777 c.close();
778 }
779
780 // Set all the nullable values to nils.
null_nullables()781 void ConfigBase::null_nullables()
782 {
783 for (const std::string &opt_key : this->keys()) {
784 ConfigOption *opt = this->optptr(opt_key, false);
785 assert(opt != nullptr);
786 if (opt->nullable())
787 opt->deserialize("nil", ForwardCompatibilitySubstitutionRule::Disable);
788 }
789 }
790
DynamicConfig(const ConfigBase & rhs,const t_config_option_keys & keys)791 DynamicConfig::DynamicConfig(const ConfigBase& rhs, const t_config_option_keys& keys)
792 {
793 for (const t_config_option_key& opt_key : keys)
794 this->options[opt_key] = std::unique_ptr<ConfigOption>(rhs.option(opt_key)->clone());
795 }
796
operator ==(const DynamicConfig & rhs) const797 bool DynamicConfig::operator==(const DynamicConfig &rhs) const
798 {
799 auto it1 = this->options.begin();
800 auto it1_end = this->options.end();
801 auto it2 = rhs.options.begin();
802 auto it2_end = rhs.options.end();
803 for (; it1 != it1_end && it2 != it2_end; ++ it1, ++ it2)
804 if (it1->first != it2->first || *it1->second != *it2->second)
805 // key or value differ
806 return false;
807 return it1 == it1_end && it2 == it2_end;
808 }
809
810 // Remove options with all nil values, those are optional and it does not help to hold them.
remove_nil_options()811 size_t DynamicConfig::remove_nil_options()
812 {
813 size_t cnt_removed = 0;
814 for (auto it = options.begin(); it != options.end();)
815 if (it->second->is_nil()) {
816 it = options.erase(it);
817 ++ cnt_removed;
818 } else
819 ++ it;
820 return cnt_removed;
821 }
822
optptr(const t_config_option_key & opt_key,bool create)823 ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool create)
824 {
825 auto it = options.find(opt_key);
826 if (it != options.end())
827 // Option was found.
828 return it->second.get();
829 if (! create)
830 // Option was not found and a new option shall not be created.
831 return nullptr;
832 // Try to create a new ConfigOption.
833 const ConfigDef *def = this->def();
834 if (def == nullptr)
835 throw NoDefinitionException(opt_key);
836 const ConfigOptionDef *optdef = def->get(opt_key);
837 if (optdef == nullptr)
838 // throw ConfigurationError(std::string("Invalid option name: ") + opt_key);
839 // Let the parent decide what to do if the opt_key is not defined by this->def().
840 return nullptr;
841 ConfigOption *opt = optdef->create_default_option();
842 this->options.emplace_hint(it, opt_key, std::unique_ptr<ConfigOption>(opt));
843 return opt;
844 }
845
optptr(const t_config_option_key & opt_key) const846 const ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key) const
847 {
848 auto it = options.find(opt_key);
849 return (it == options.end()) ? nullptr : it->second.get();
850 }
851
read_cli(const std::vector<std::string> & tokens,t_config_option_keys * extra,t_config_option_keys * keys)852 void DynamicConfig::read_cli(const std::vector<std::string> &tokens, t_config_option_keys* extra, t_config_option_keys* keys)
853 {
854 std::vector<const char*> args;
855 // push a bogus executable name (argv[0])
856 args.emplace_back("");
857 for (size_t i = 0; i < tokens.size(); ++ i)
858 args.emplace_back(tokens[i].c_str());
859 this->read_cli(int(args.size()), args.data(), extra, keys);
860 }
861
read_cli(int argc,const char * const argv[],t_config_option_keys * extra,t_config_option_keys * keys)862 bool DynamicConfig::read_cli(int argc, const char* const argv[], t_config_option_keys* extra, t_config_option_keys* keys)
863 {
864 // cache the CLI option => opt_key mapping
865 std::map<std::string,std::string> opts;
866 for (const auto &oit : this->def()->options)
867 for (auto t : oit.second.cli_args(oit.first))
868 opts[t] = oit.first;
869
870 bool parse_options = true;
871 for (int i = 1; i < argc; ++ i) {
872 std::string token = argv[i];
873 // Store non-option arguments in the provided vector.
874 if (! parse_options || ! boost::starts_with(token, "-")) {
875 extra->push_back(token);
876 continue;
877 }
878 #ifdef __APPLE__
879 if (boost::starts_with(token, "-psn_"))
880 // OSX launcher may add a "process serial number", for example "-psn_0_989382" to the command line.
881 // While it is supposed to be dropped since OSX 10.9, we will rather ignore it.
882 continue;
883 #endif /* __APPLE__ */
884 // Stop parsing tokens as options when -- is supplied.
885 if (token == "--") {
886 parse_options = false;
887 continue;
888 }
889 // Remove leading dashes
890 boost::trim_left_if(token, boost::is_any_of("-"));
891 // Remove the "no-" prefix used to negate boolean options.
892 bool no = false;
893 if (boost::starts_with(token, "no-")) {
894 no = true;
895 boost::replace_first(token, "no-", "");
896 }
897 // Read value when supplied in the --key=value form.
898 std::string value;
899 {
900 size_t equals_pos = token.find("=");
901 if (equals_pos != std::string::npos) {
902 value = token.substr(equals_pos+1);
903 token.erase(equals_pos);
904 }
905 }
906 // Look for the cli -> option mapping.
907 const auto it = opts.find(token);
908 if (it == opts.end()) {
909 boost::nowide::cerr << "Unknown option --" << token.c_str() << std::endl;
910 return false;
911 }
912 const t_config_option_key opt_key = it->second;
913 const ConfigOptionDef &optdef = this->def()->options.at(opt_key);
914 // If the option type expects a value and it was not already provided,
915 // look for it in the next token.
916 if (optdef.type != coBool && optdef.type != coBools && value.empty()) {
917 if (i == (argc-1)) {
918 boost::nowide::cerr << "No value supplied for --" << token.c_str() << std::endl;
919 return false;
920 }
921 value = argv[++ i];
922 }
923 // Store the option value.
924 const bool existing = this->has(opt_key);
925 if (keys != nullptr && ! existing) {
926 // Save the order of detected keys.
927 keys->push_back(opt_key);
928 }
929 ConfigOption *opt_base = this->option(opt_key, true);
930 ConfigOptionVectorBase *opt_vector = opt_base->is_vector() ? static_cast<ConfigOptionVectorBase*>(opt_base) : nullptr;
931 if (opt_vector) {
932 if (! existing)
933 // remove the default values
934 opt_vector->clear();
935 // Vector values will be chained. Repeated use of a parameter will append the parameter or parameters
936 // to the end of the value.
937 if (opt_base->type() == coBools)
938 static_cast<ConfigOptionBools*>(opt_base)->values.push_back(!no);
939 else
940 // Deserialize any other vector value (ConfigOptionInts, Floats, Percents, Points) the same way
941 // they get deserialized from an .ini file. For ConfigOptionStrings, that means that the C-style unescape
942 // will be applied for values enclosed in quotes, while values non-enclosed in quotes are left to be
943 // unescaped by the calling shell.
944 opt_vector->deserialize(value, true);
945 } else if (opt_base->type() == coBool) {
946 static_cast<ConfigOptionBool*>(opt_base)->value = !no;
947 } else if (opt_base->type() == coString) {
948 // Do not unescape single string values, the unescaping is left to the calling shell.
949 static_cast<ConfigOptionString*>(opt_base)->value = value;
950 } else {
951 // Just bail out if the configuration value is not understood.
952 ConfigSubstitutionContext context(ForwardCompatibilitySubstitutionRule::Disable);
953 // Any scalar value of a type different from Bool and String.
954 if (! this->set_deserialize_nothrow(opt_key, value, context, false)) {
955 boost::nowide::cerr << "Invalid value supplied for --" << token.c_str() << std::endl;
956 return false;
957 }
958 }
959 }
960 return true;
961 }
962
keys() const963 t_config_option_keys DynamicConfig::keys() const
964 {
965 t_config_option_keys keys;
966 keys.reserve(this->options.size());
967 for (const auto &opt : this->options)
968 keys.emplace_back(opt.first);
969 return keys;
970 }
971
set_defaults()972 void StaticConfig::set_defaults()
973 {
974 // use defaults from definition
975 auto *defs = this->def();
976 if (defs != nullptr) {
977 for (const std::string &key : this->keys()) {
978 const ConfigOptionDef *def = defs->get(key);
979 ConfigOption *opt = this->option(key);
980 if (def != nullptr && opt != nullptr && def->default_value)
981 opt->set(def->default_value.get());
982 }
983 }
984 }
985
keys() const986 t_config_option_keys StaticConfig::keys() const
987 {
988 t_config_option_keys keys;
989 assert(this->def() != nullptr);
990 for (const auto &opt_def : this->def()->options)
991 if (this->option(opt_def.first) != nullptr)
992 keys.push_back(opt_def.first);
993 return keys;
994 }
995
996 }
997
998 #include <cereal/types/polymorphic.hpp>
999 CEREAL_REGISTER_TYPE(Slic3r::ConfigOption)
1000 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle<double>)
1001 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle<int>)
1002 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle<std::string>)
1003 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle<Slic3r::Vec2d>)
1004 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle<Slic3r::Vec3d>)
1005 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle<bool>)
1006 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVectorBase)
1007 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector<double>)
1008 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector<int>)
1009 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector<std::string>)
1010 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector<Slic3r::Vec2d>)
1011 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector<unsigned char>)
1012 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloat)
1013 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloats)
1014 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloatsNullable)
1015 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionInt)
1016 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionInts)
1017 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionIntsNullable)
1018 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionString)
1019 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionStrings)
1020 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPercent)
1021 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPercents)
1022 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPercentsNullable)
1023 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloatOrPercent)
1024 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloatsOrPercents)
1025 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloatsOrPercentsNullable)
1026 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPoint)
1027 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPoints)
1028 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPoint3)
1029 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionBool)
1030 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionBools)
1031 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionBoolsNullable)
1032 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionEnumGeneric)
1033 CEREAL_REGISTER_TYPE(Slic3r::ConfigBase)
1034 CEREAL_REGISTER_TYPE(Slic3r::DynamicConfig)
1035
1036 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingle<double>)
1037 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingle<int>)
1038 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingle<std::string>)
1039 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingle<Slic3r::Vec2d>)
1040 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingle<Slic3r::Vec3d>)
1041 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingle<bool>)
1042 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionVectorBase)
1043 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector<double>)
1044 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector<int>)
1045 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector<std::string>)
1046 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector<Slic3r::Vec2d>)
1047 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector<unsigned char>)
1048 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle<double>, Slic3r::ConfigOptionFloat)
1049 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector<double>, Slic3r::ConfigOptionFloats)
1050 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector<double>, Slic3r::ConfigOptionFloatsNullable)
1051 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle<int>, Slic3r::ConfigOptionInt)
1052 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector<int>, Slic3r::ConfigOptionInts)
1053 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector<int>, Slic3r::ConfigOptionIntsNullable)
1054 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle<std::string>, Slic3r::ConfigOptionString)
1055 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector<std::string>, Slic3r::ConfigOptionStrings)
1056 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionFloat, Slic3r::ConfigOptionPercent)
1057 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionFloats, Slic3r::ConfigOptionPercents)
1058 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionFloats, Slic3r::ConfigOptionPercentsNullable)
1059 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionPercent, Slic3r::ConfigOptionFloatOrPercent)
1060 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector<Slic3r::FloatOrPercent>, Slic3r::ConfigOptionFloatsOrPercents)
1061 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector<Slic3r::FloatOrPercent>, Slic3r::ConfigOptionFloatsOrPercentsNullable)
1062 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle<Slic3r::Vec2d>, Slic3r::ConfigOptionPoint)
1063 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector<Slic3r::Vec2d>, Slic3r::ConfigOptionPoints)
1064 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle<Slic3r::Vec3d>, Slic3r::ConfigOptionPoint3)
1065 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle<bool>, Slic3r::ConfigOptionBool)
1066 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector<unsigned char>, Slic3r::ConfigOptionBools)
1067 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector<unsigned char>, Slic3r::ConfigOptionBoolsNullable)
1068 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionInt, Slic3r::ConfigOptionEnumGeneric)
1069 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigBase, Slic3r::DynamicConfig)
1070