mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-07-22 22:24:01 -06:00
Support for forward compatibility of configurations, user and system
config bundles, project files (3MFs, AMFs). When loading these files, the caller may decide whether to substitute some of the configuration values the current PrusaSlicer version does not understand with some reasonable default value, and whether to report it. If substitution is disabled, an exception is being thrown as before this commit. If substitution is enabled, list of substitutions is returned by the API to be presented to the user. This allows us to introduce for example new firmware flavor key in PrusaSlicer 2.4 while letting PrusaSlicer 2.3.2 to fall back to some default and to report it to the user. When slicing from command line, substutions are performed by default and reported into the console, however substitutions may be either disabled or made silent with the new "config-compatibility" command line option. Substitute enums and bools only. Allow booleans to be parsed as true: "1", "enabled", "on" case insensitive false: "0", "disabled", "off" case insensitive This will allow us in the future for example to switch the draft_shield boolean to an enum with the following values: "disabled" / "enabled" / "limited". Added "enum_bitmask.hpp" - support for type safe sets of options. See for example PresetBundle::load_configbundle(... LoadConfigBundleAttributes flags) for an example of intended usage. WIP: GUI for reporting the list of config substitutions needs to be implemented by @YuSanka.
This commit is contained in:
parent
ad336e2cc0
commit
0f3cabb5d9
47 changed files with 643 additions and 314 deletions
|
@ -33,6 +33,7 @@ add_library(libslic3r STATIC
|
|||
EdgeGrid.hpp
|
||||
ElephantFootCompensation.cpp
|
||||
ElephantFootCompensation.hpp
|
||||
enum_bitmask.hpp
|
||||
ExPolygon.cpp
|
||||
ExPolygon.hpp
|
||||
ExPolygonCollection.cpp
|
||||
|
|
|
@ -23,6 +23,10 @@
|
|||
#include <boost/format.hpp>
|
||||
#include <string.h>
|
||||
|
||||
//FIXME for GCodeFlavor and gcfMarlin (for forward-compatibility conversion)
|
||||
// This is not nice, likely it would be better to pass the ConfigSubstitutionContext to handle_legacy().
|
||||
#include "PrintConfig.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Escape \n, \r and backslash
|
||||
|
@ -211,6 +215,10 @@ std::string escape_ampersand(const std::string& str)
|
|||
return std::string(out.data(), outptr - out.data());
|
||||
}
|
||||
|
||||
void ConfigOptionDeleter::operator()(ConfigOption* p) {
|
||||
delete p;
|
||||
}
|
||||
|
||||
std::vector<std::string> ConfigOptionDef::cli_args(const std::string &key) const
|
||||
{
|
||||
std::vector<std::string> args;
|
||||
|
@ -361,7 +369,8 @@ std::ostream& ConfigDef::print_cli_help(std::ostream& out, bool show_defaults, s
|
|||
|
||||
// right: option description
|
||||
std::string descr = def.tooltip;
|
||||
if (show_defaults && def.default_value && def.type != coBool
|
||||
bool show_defaults_this = show_defaults || def.opt_key == "config_compatibility";
|
||||
if (show_defaults_this && def.default_value && def.type != coBool
|
||||
&& (def.type != coString || !def.default_value->serialize().empty())) {
|
||||
descr += " (";
|
||||
if (!def.sidetext.empty()) {
|
||||
|
@ -469,7 +478,7 @@ void ConfigBase::set(const std::string &opt_key, double value, bool create)
|
|||
}
|
||||
}
|
||||
|
||||
bool ConfigBase::set_deserialize_nothrow(const t_config_option_key &opt_key_src, const std::string &value_src, bool append)
|
||||
bool ConfigBase::set_deserialize_nothrow(const t_config_option_key &opt_key_src, const std::string &value_src, ConfigSubstitutionContext& substitutions_ctxt, bool append)
|
||||
{
|
||||
t_config_option_key opt_key = opt_key_src;
|
||||
std::string value = value_src;
|
||||
|
@ -479,29 +488,29 @@ bool ConfigBase::set_deserialize_nothrow(const t_config_option_key &opt_key_src,
|
|||
if (opt_key.empty())
|
||||
// Ignore the option.
|
||||
return true;
|
||||
return this->set_deserialize_raw(opt_key, value, append);
|
||||
return this->set_deserialize_raw(opt_key, value, substitutions_ctxt, append);
|
||||
}
|
||||
|
||||
void ConfigBase::set_deserialize(const t_config_option_key &opt_key_src, const std::string &value_src, bool append)
|
||||
void ConfigBase::set_deserialize(const t_config_option_key &opt_key_src, const std::string &value_src, ConfigSubstitutionContext& substitutions_ctxt, bool append)
|
||||
{
|
||||
if (! this->set_deserialize_nothrow(opt_key_src, value_src, append))
|
||||
if (! this->set_deserialize_nothrow(opt_key_src, value_src, substitutions_ctxt, append))
|
||||
throw BadOptionTypeException(format("ConfigBase::set_deserialize() failed for parameter \"%1%\", value \"%2%\"", opt_key_src, value_src));
|
||||
}
|
||||
|
||||
void ConfigBase::set_deserialize(std::initializer_list<SetDeserializeItem> items)
|
||||
void ConfigBase::set_deserialize(std::initializer_list<SetDeserializeItem> items, ConfigSubstitutionContext& substitutions_ctxt)
|
||||
{
|
||||
for (const SetDeserializeItem &item : items)
|
||||
this->set_deserialize(item.opt_key, item.opt_value, item.append);
|
||||
this->set_deserialize(item.opt_key, item.opt_value, substitutions_ctxt, item.append);
|
||||
}
|
||||
|
||||
bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, const std::string &value, bool append)
|
||||
bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, const std::string &value, ConfigSubstitutionContext& substitutions_ctxt, bool append)
|
||||
{
|
||||
t_config_option_key opt_key = opt_key_src;
|
||||
t_config_option_key opt_key = opt_key_src;
|
||||
// Try to deserialize the option by its name.
|
||||
const ConfigDef *def = this->def();
|
||||
const ConfigDef *def = this->def();
|
||||
if (def == nullptr)
|
||||
throw NoDefinitionException(opt_key);
|
||||
const ConfigOptionDef *optdef = def->get(opt_key);
|
||||
const ConfigOptionDef *optdef = def->get(opt_key);
|
||||
if (optdef == nullptr) {
|
||||
// If we didn't find an option, look for any other option having this as an alias.
|
||||
for (const auto &opt : def->options) {
|
||||
|
@ -523,14 +532,35 @@ bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, con
|
|||
// Aliasing for example "solid_layers" to "top_solid_layers" and "bottom_solid_layers".
|
||||
for (const t_config_option_key &shortcut : optdef->shortcut)
|
||||
// Recursive call.
|
||||
if (! this->set_deserialize_raw(shortcut, value, append))
|
||||
if (! this->set_deserialize_raw(shortcut, value, substitutions_ctxt, append))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
ConfigOption *opt = this->option(opt_key, true);
|
||||
assert(opt != nullptr);
|
||||
return opt->deserialize(value, append);
|
||||
bool success = opt->deserialize(value, append);
|
||||
if (! success && substitutions_ctxt.rule != ForwardCompatibilitySubstitutionRule::Disable &&
|
||||
// Only allow substitutions of an enum value by another enum value or a boolean value with an enum value.
|
||||
// That means, we expect enum values being added in the future and possibly booleans being converted to enums.
|
||||
(optdef->type == coEnum || optdef->type == coBool))
|
||||
{
|
||||
// Deserialize failed, try to substitute with a default value.
|
||||
assert(substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::Enable || substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::EnableSilent);
|
||||
|
||||
opt->set(optdef->default_value.get());
|
||||
|
||||
if (substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::Enable) {
|
||||
// Log the substitution.
|
||||
ConfigSubstitution config_substitution;
|
||||
config_substitution.opt_def = optdef;
|
||||
config_substitution.old_value = value;//std::unique_ptr<ConfigOption>(opt);
|
||||
config_substitution.new_value = ConfigOptionUniquePtr(this->option(opt_key, true)->clone());
|
||||
substitutions_ctxt.substitutions.emplace_back(std::move(config_substitution));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
// Return an absolute value of a possibly relative config variable.
|
||||
|
@ -589,36 +619,37 @@ void ConfigBase::setenv_() const
|
|||
}
|
||||
}
|
||||
|
||||
void ConfigBase::load(const std::string &file)
|
||||
ConfigSubstitutions ConfigBase::load(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule)
|
||||
{
|
||||
if (is_gcode_file(file))
|
||||
this->load_from_gcode_file(file);
|
||||
else
|
||||
this->load_from_ini(file);
|
||||
return is_gcode_file(file) ?
|
||||
this->load_from_gcode_file(file, true /* check header */, compatibility_rule) :
|
||||
this->load_from_ini(file, compatibility_rule);
|
||||
}
|
||||
|
||||
void ConfigBase::load_from_ini(const std::string &file)
|
||||
ConfigSubstitutions ConfigBase::load_from_ini(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule)
|
||||
{
|
||||
boost::property_tree::ptree tree;
|
||||
boost::nowide::ifstream ifs(file);
|
||||
boost::property_tree::read_ini(ifs, tree);
|
||||
this->load(tree);
|
||||
return this->load(tree, compatibility_rule);
|
||||
}
|
||||
|
||||
void ConfigBase::load(const boost::property_tree::ptree &tree)
|
||||
ConfigSubstitutions ConfigBase::load(const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule)
|
||||
{
|
||||
ConfigSubstitutionContext substitutions_ctxt(compatibility_rule);
|
||||
for (const boost::property_tree::ptree::value_type &v : tree) {
|
||||
try {
|
||||
t_config_option_key opt_key = v.first;
|
||||
this->set_deserialize(opt_key, v.second.get_value<std::string>());
|
||||
this->set_deserialize(opt_key, v.second.get_value<std::string>(), substitutions_ctxt);
|
||||
} catch (UnknownOptionException & /* e */) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return std::move(substitutions_ctxt.substitutions);
|
||||
}
|
||||
|
||||
// Load the config keys from the tail of a G-code file.
|
||||
void ConfigBase::load_from_gcode_file(const std::string& file, bool check_header)
|
||||
ConfigSubstitutions ConfigBase::load_from_gcode_file(const std::string &file, bool check_header, ForwardCompatibilitySubstitutionRule compatibility_rule)
|
||||
{
|
||||
// Read a 64k block from the end of the G-code.
|
||||
boost::nowide::ifstream ifs(file);
|
||||
|
@ -639,13 +670,15 @@ void ConfigBase::load_from_gcode_file(const std::string& file, bool check_header
|
|||
ifs.read(data.data(), data_length);
|
||||
ifs.close();
|
||||
|
||||
size_t key_value_pairs = load_from_gcode_string(data.data());
|
||||
ConfigSubstitutionContext substitutions_ctxt(compatibility_rule);
|
||||
size_t key_value_pairs = load_from_gcode_string(data.data(), substitutions_ctxt);
|
||||
if (key_value_pairs < 80)
|
||||
throw Slic3r::RuntimeError(format("Suspiciously low number of configuration values extracted from %1%: %2%", file, key_value_pairs));
|
||||
return std::move(substitutions_ctxt.substitutions);
|
||||
}
|
||||
|
||||
// Load the config keys from the given string.
|
||||
size_t ConfigBase::load_from_gcode_string(const char* str)
|
||||
size_t ConfigBase::load_from_gcode_string(const char* str, ConfigSubstitutionContext& substitutions)
|
||||
{
|
||||
if (str == nullptr)
|
||||
return 0;
|
||||
|
@ -690,7 +723,7 @@ size_t ConfigBase::load_from_gcode_string(const char* str)
|
|||
if (key == nullptr)
|
||||
break;
|
||||
try {
|
||||
this->set_deserialize(std::string(key, key_end), std::string(value, end));
|
||||
this->set_deserialize(std::string(key, key_end), std::string(value, end), substitutions);
|
||||
++num_key_value_pairs;
|
||||
}
|
||||
catch (UnknownOptionException & /* e */) {
|
||||
|
@ -719,7 +752,7 @@ void ConfigBase::null_nullables()
|
|||
ConfigOption *opt = this->optptr(opt_key, false);
|
||||
assert(opt != nullptr);
|
||||
if (opt->nullable())
|
||||
opt->deserialize("nil");
|
||||
opt->deserialize("nil", ForwardCompatibilitySubstitutionRule::Disable);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -883,8 +916,10 @@ bool DynamicConfig::read_cli(int argc, const char* const argv[], t_config_option
|
|||
// Do not unescape single string values, the unescaping is left to the calling shell.
|
||||
static_cast<ConfigOptionString*>(opt_base)->value = value;
|
||||
} else {
|
||||
// Just bail out if the configuration value is not understood.
|
||||
ConfigSubstitutionContext context(ForwardCompatibilitySubstitutionRule::Disable);
|
||||
// Any scalar value of a type different from Bool and String.
|
||||
if (! this->set_deserialize_nothrow(opt_key, value, false)) {
|
||||
if (! this->set_deserialize_nothrow(opt_key, value, context, false)) {
|
||||
boost::nowide::cerr << "Invalid value supplied for --" << token.c_str() << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "Exception.hpp"
|
||||
#include "Point.hpp"
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/algorithm/string/trim.hpp>
|
||||
#include <boost/format/format_fwd.hpp>
|
||||
#include <boost/functional/hash.hpp>
|
||||
|
@ -163,6 +164,41 @@ enum PrinterTechnology : unsigned char
|
|||
ptAny
|
||||
};
|
||||
|
||||
enum ForwardCompatibilitySubstitutionRule
|
||||
{
|
||||
Disable,
|
||||
Enable,
|
||||
EnableSilent,
|
||||
};
|
||||
|
||||
class ConfigOption;
|
||||
class ConfigOptionDef;
|
||||
// For forward definition of ConfigOption in ConfigOptionUniquePtr, we have to define a custom deleter.
|
||||
struct ConfigOptionDeleter { void operator()(ConfigOption* p); };
|
||||
using ConfigOptionUniquePtr = std::unique_ptr<ConfigOption, ConfigOptionDeleter>;
|
||||
|
||||
// When parsing a configuration value, if the old_value is not understood by this PrusaSlicer version,
|
||||
// it is being substituted with some default value that this PrusaSlicer could work with.
|
||||
// This structure serves to inform the user about the substitutions having been done during file import.
|
||||
struct ConfigSubstitution {
|
||||
const ConfigOptionDef *opt_def { nullptr };
|
||||
std::string old_value;
|
||||
ConfigOptionUniquePtr new_value;
|
||||
};
|
||||
|
||||
using ConfigSubstitutions = std::vector<ConfigSubstitution>;
|
||||
|
||||
// Filled in by ConfigBase::set_deserialize_raw(), which based on "rule" either bails out
|
||||
// or performs substitutions when encountering an unknown configuration value.
|
||||
struct ConfigSubstitutionContext
|
||||
{
|
||||
ConfigSubstitutionContext(ForwardCompatibilitySubstitutionRule rl) : rule(rl) {}
|
||||
bool empty() const throw() { return substitutions.empty(); }
|
||||
|
||||
ForwardCompatibilitySubstitutionRule rule;
|
||||
ConfigSubstitutions substitutions;
|
||||
};
|
||||
|
||||
// A generic value of a configuration option.
|
||||
class ConfigOption {
|
||||
public:
|
||||
|
@ -768,7 +804,7 @@ public:
|
|||
return escape_string_cstyle(this->value);
|
||||
}
|
||||
|
||||
bool deserialize(const std::string &str, bool append = false) override
|
||||
bool deserialize(const std::string &str, bool append = false) override
|
||||
{
|
||||
UNUSED(append);
|
||||
return unescape_string_cstyle(str, this->value);
|
||||
|
@ -1272,8 +1308,15 @@ public:
|
|||
bool deserialize(const std::string &str, bool append = false) override
|
||||
{
|
||||
UNUSED(append);
|
||||
this->value = (str.compare("1") == 0);
|
||||
return true;
|
||||
if (str == "1" || boost::iequals(str, "enabled") || boost::iequals(str, "on")) {
|
||||
this->value = true;
|
||||
return true;
|
||||
}
|
||||
if (str == "0" || boost::iequals(str, "disabled") || boost::iequals(str, "off")) {
|
||||
this->value = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -1687,6 +1730,14 @@ public:
|
|||
static const constexpr char *nocli = "~~~noCLI";
|
||||
};
|
||||
|
||||
inline bool operator<(const ConfigSubstitution &lhs, const ConfigSubstitution &rhs) throw() {
|
||||
return lhs.opt_def->opt_key < rhs.opt_def->opt_key ||
|
||||
(lhs.opt_def->opt_key == rhs.opt_def->opt_key && lhs.old_value < rhs.old_value);
|
||||
}
|
||||
inline bool operator==(const ConfigSubstitution &lhs, const ConfigSubstitution &rhs) throw() {
|
||||
return lhs.opt_def == rhs.opt_def && lhs.old_value == rhs.old_value;
|
||||
}
|
||||
|
||||
// Map from a config option name to its definition.
|
||||
// The definition does not carry an actual value of the config option, only its constant default value.
|
||||
// t_config_option_key is std::string
|
||||
|
@ -1765,6 +1816,8 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
// An abstract configuration store.
|
||||
class ConfigBase : public ConfigOptionResolver
|
||||
{
|
||||
|
@ -1853,9 +1906,11 @@ public:
|
|||
|
||||
// Set a configuration value from a string, it will call an overridable handle_legacy()
|
||||
// to resolve renamed and removed configuration keys.
|
||||
bool set_deserialize_nothrow(const t_config_option_key &opt_key_src, const std::string &value_src, bool append = false);
|
||||
bool set_deserialize_nothrow(const t_config_option_key &opt_key_src, const std::string &value_src, ConfigSubstitutionContext& substitutions, bool append = false);
|
||||
// May throw BadOptionTypeException() if the operation fails.
|
||||
void set_deserialize(const t_config_option_key &opt_key, const std::string &str, bool append = false);
|
||||
void set_deserialize(const t_config_option_key &opt_key, const std::string &str, ConfigSubstitutionContext& config_substitutions, bool append = false);
|
||||
void set_deserialize_strict(const t_config_option_key &opt_key, const std::string &str, bool append = false)
|
||||
{ ConfigSubstitutionContext ctxt{ ForwardCompatibilitySubstitutionRule::Disable }; this->set_deserialize(opt_key, str, ctxt, append); }
|
||||
struct SetDeserializeItem {
|
||||
SetDeserializeItem(const char *opt_key, const char *opt_value, bool append = false) : opt_key(opt_key), opt_value(opt_value), append(append) {}
|
||||
SetDeserializeItem(const std::string &opt_key, const std::string &opt_value, bool append = false) : opt_key(opt_key), opt_value(opt_value), append(append) {}
|
||||
|
@ -1870,17 +1925,19 @@ public:
|
|||
std::string opt_key; std::string opt_value; bool append = false;
|
||||
};
|
||||
// May throw BadOptionTypeException() if the operation fails.
|
||||
void set_deserialize(std::initializer_list<SetDeserializeItem> items);
|
||||
void set_deserialize(std::initializer_list<SetDeserializeItem> items, ConfigSubstitutionContext& substitutions);
|
||||
void set_deserialize_strict(std::initializer_list<SetDeserializeItem> items)
|
||||
{ ConfigSubstitutionContext ctxt{ ForwardCompatibilitySubstitutionRule::Disable }; this->set_deserialize(items, ctxt); }
|
||||
|
||||
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_() const;
|
||||
void load(const std::string &file);
|
||||
void load_from_ini(const std::string &file);
|
||||
void load_from_gcode_file(const std::string& file, bool check_header = true);
|
||||
ConfigSubstitutions load(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule);
|
||||
ConfigSubstitutions load_from_ini(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule);
|
||||
ConfigSubstitutions load_from_gcode_file(const std::string &file, bool check_header /* = true */, ForwardCompatibilitySubstitutionRule compatibility_rule);
|
||||
// Returns number of key/value pairs extracted.
|
||||
size_t load_from_gcode_string(const char* str);
|
||||
void load(const boost::property_tree::ptree &tree);
|
||||
size_t load_from_gcode_string(const char* str, ConfigSubstitutionContext& substitutions);
|
||||
ConfigSubstitutions load(const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule);
|
||||
void save(const std::string &file) const;
|
||||
|
||||
// Set all the nullable values to nils.
|
||||
|
@ -1888,7 +1945,7 @@ public:
|
|||
|
||||
private:
|
||||
// Set a configuration value from a string.
|
||||
bool set_deserialize_raw(const t_config_option_key &opt_key_src, const std::string &str, bool append);
|
||||
bool set_deserialize_raw(const t_config_option_key& opt_key_src, const std::string& value, ConfigSubstitutionContext& substitutions, bool append);
|
||||
};
|
||||
|
||||
// Configuration store with dynamic number of configuration values.
|
||||
|
|
|
@ -419,7 +419,7 @@ namespace Slic3r {
|
|||
_3MF_Importer();
|
||||
~_3MF_Importer();
|
||||
|
||||
bool load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, bool check_version);
|
||||
bool load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, bool check_version);
|
||||
|
||||
private:
|
||||
void _destroy_xml_parser();
|
||||
|
@ -434,16 +434,16 @@ namespace Slic3r {
|
|||
XML_ErrorString(XML_GetErrorCode(m_xml_parser));
|
||||
}
|
||||
|
||||
bool _load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config);
|
||||
bool _load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions);
|
||||
bool _extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
||||
void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
||||
void _extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
||||
void _extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions);
|
||||
void _extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
||||
void _extract_sla_drain_holes_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
||||
|
||||
void _extract_custom_gcode_per_print_z_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
||||
|
||||
void _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, const std::string& archive_filename);
|
||||
void _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, ConfigSubstitutionContext& subs_context, const std::string& archive_filename);
|
||||
bool _extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model);
|
||||
|
||||
// handlers to parse the .model file
|
||||
|
@ -510,7 +510,7 @@ namespace Slic3r {
|
|||
bool _handle_start_config_metadata(const char** attributes, unsigned int num_attributes);
|
||||
bool _handle_end_config_metadata();
|
||||
|
||||
bool _generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes);
|
||||
bool _generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes, ConfigSubstitutionContext& config_substitutions);
|
||||
|
||||
// callbacks to parse the .model file
|
||||
static void XMLCALL _handle_start_model_xml_element(void* userData, const char* name, const char** attributes);
|
||||
|
@ -539,7 +539,7 @@ namespace Slic3r {
|
|||
_destroy_xml_parser();
|
||||
}
|
||||
|
||||
bool _3MF_Importer::load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, bool check_version)
|
||||
bool _3MF_Importer::load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, bool check_version)
|
||||
{
|
||||
m_version = 0;
|
||||
m_check_version = check_version;
|
||||
|
@ -560,7 +560,7 @@ namespace Slic3r {
|
|||
m_curr_characters.clear();
|
||||
clear_errors();
|
||||
|
||||
return _load_model_from_file(filename, model, config);
|
||||
return _load_model_from_file(filename, model, config, config_substitutions);
|
||||
}
|
||||
|
||||
void _3MF_Importer::_destroy_xml_parser()
|
||||
|
@ -581,7 +581,7 @@ namespace Slic3r {
|
|||
XML_StopParser(m_xml_parser, false);
|
||||
}
|
||||
|
||||
bool _3MF_Importer::_load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config)
|
||||
bool _3MF_Importer::_load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions)
|
||||
{
|
||||
mz_zip_archive archive;
|
||||
mz_zip_zero_struct(&archive);
|
||||
|
@ -635,7 +635,7 @@ namespace Slic3r {
|
|||
}
|
||||
else if (boost::algorithm::iequals(name, LAYER_CONFIG_RANGES_FILE)) {
|
||||
// extract slic3r layer config ranges file
|
||||
_extract_layer_config_ranges_from_archive(archive, stat);
|
||||
_extract_layer_config_ranges_from_archive(archive, stat, config_substitutions);
|
||||
}
|
||||
else if (boost::algorithm::iequals(name, SLA_SUPPORT_POINTS_FILE)) {
|
||||
// extract sla support points file
|
||||
|
@ -647,7 +647,7 @@ namespace Slic3r {
|
|||
}
|
||||
else if (boost::algorithm::iequals(name, PRINT_CONFIG_FILE)) {
|
||||
// extract slic3r print config file
|
||||
_extract_print_config_from_archive(archive, stat, config, filename);
|
||||
_extract_print_config_from_archive(archive, stat, config, config_substitutions, filename);
|
||||
}
|
||||
else if (boost::algorithm::iequals(name, CUSTOM_GCODE_PER_PRINT_Z_FILE)) {
|
||||
// extract slic3r layer config ranges file
|
||||
|
@ -704,7 +704,7 @@ namespace Slic3r {
|
|||
new_model_object->clear_instances();
|
||||
new_model_object->add_instance(*model_object->instances.back());
|
||||
model_object->delete_last_instance();
|
||||
if (!_generate_volumes(*new_model_object, *geometry, volumes))
|
||||
if (!_generate_volumes(*new_model_object, *geometry, volumes, config_substitutions))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -759,7 +759,7 @@ namespace Slic3r {
|
|||
if (metadata.key == "name")
|
||||
model_object->name = metadata.value;
|
||||
else
|
||||
model_object->config.set_deserialize(metadata.key, metadata.value);
|
||||
model_object->config.set_deserialize(metadata.key, metadata.value, config_substitutions);
|
||||
}
|
||||
|
||||
// select object's detected volumes
|
||||
|
@ -775,7 +775,7 @@ namespace Slic3r {
|
|||
volumes_ptr = &volumes;
|
||||
}
|
||||
|
||||
if (!_generate_volumes(*model_object, obj_geometry->second, *volumes_ptr))
|
||||
if (!_generate_volumes(*model_object, obj_geometry->second, *volumes_ptr, config_substitutions))
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -867,7 +867,10 @@ namespace Slic3r {
|
|||
return true;
|
||||
}
|
||||
|
||||
void _3MF_Importer::_extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, const std::string& archive_filename)
|
||||
void _3MF_Importer::_extract_print_config_from_archive(
|
||||
mz_zip_archive& archive, const mz_zip_archive_file_stat& stat,
|
||||
DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions,
|
||||
const std::string& archive_filename)
|
||||
{
|
||||
if (stat.m_uncomp_size > 0) {
|
||||
std::string buffer((size_t)stat.m_uncomp_size, 0);
|
||||
|
@ -876,7 +879,7 @@ namespace Slic3r {
|
|||
add_error("Error while reading config data to buffer");
|
||||
return;
|
||||
}
|
||||
config.load_from_gcode_string(buffer.data());
|
||||
config.load_from_gcode_string(buffer.data(), config_substitutions);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -942,7 +945,7 @@ namespace Slic3r {
|
|||
}
|
||||
}
|
||||
|
||||
void _3MF_Importer::_extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat)
|
||||
void _3MF_Importer::_extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions)
|
||||
{
|
||||
if (stat.m_uncomp_size > 0) {
|
||||
std::string buffer((size_t)stat.m_uncomp_size, 0);
|
||||
|
@ -987,8 +990,7 @@ namespace Slic3r {
|
|||
continue;
|
||||
std::string opt_key = option.second.get<std::string>("<xmlattr>.opt_key");
|
||||
std::string value = option.second.data();
|
||||
|
||||
config.set_deserialize(opt_key, value);
|
||||
config.set_deserialize(opt_key, value, config_substitutions);
|
||||
}
|
||||
|
||||
config_ranges[{ min_z, max_z }].assign_config(std::move(config));
|
||||
|
@ -1827,7 +1829,7 @@ namespace Slic3r {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool _3MF_Importer::_generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes)
|
||||
bool _3MF_Importer::_generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes, ConfigSubstitutionContext& config_substitutions)
|
||||
{
|
||||
if (!object.volumes.empty()) {
|
||||
add_error("Found invalid volumes count");
|
||||
|
@ -1943,7 +1945,7 @@ namespace Slic3r {
|
|||
else if (metadata.key == SOURCE_IN_METERS)
|
||||
volume->source.is_converted_from_meters = metadata.value == "1";
|
||||
else
|
||||
volume->config.set_deserialize(metadata.key, metadata.value);
|
||||
volume->config.set_deserialize(metadata.key, metadata.value, config_substitutions);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2953,16 +2955,15 @@ bool _3MF_Exporter::_add_custom_gcode_per_print_z_file_to_archive( mz_zip_archiv
|
|||
return true;
|
||||
}
|
||||
|
||||
bool load_3mf(const char* path, DynamicPrintConfig* config, Model* model, bool check_version)
|
||||
bool load_3mf(const char* path, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, Model* model, bool check_version)
|
||||
{
|
||||
if (path == nullptr || config == nullptr || model == nullptr)
|
||||
if (path == nullptr || model == nullptr)
|
||||
return false;
|
||||
|
||||
// All import should use "C" locales for number formatting.
|
||||
CNumericLocalesSetter locales_setter;
|
||||
|
||||
_3MF_Importer importer;
|
||||
bool res = importer.load_model_from_file(path, *model, *config, check_version);
|
||||
_3MF_Importer importer;
|
||||
bool res = importer.load_model_from_file(path, *model, config, config_substitutions, check_version);
|
||||
importer.log_errors();
|
||||
return res;
|
||||
}
|
||||
|
|
|
@ -25,11 +25,12 @@ namespace Slic3r {
|
|||
};
|
||||
|
||||
class Model;
|
||||
struct ConfigSubstitutionContext;
|
||||
class DynamicPrintConfig;
|
||||
struct ThumbnailData;
|
||||
|
||||
// Load the content of a 3mf file into the given model and preset bundle.
|
||||
extern bool load_3mf(const char* path, DynamicPrintConfig* config, Model* model, bool check_version);
|
||||
extern bool load_3mf(const char* path, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, Model* model, bool check_version);
|
||||
|
||||
// Save the given model and the config data contained in the given Print into a 3mf file.
|
||||
// The model could be modified during the export process if meshes are not repaired or have no shared vertices
|
||||
|
|
|
@ -64,10 +64,11 @@ namespace Slic3r
|
|||
|
||||
struct AMFParserContext
|
||||
{
|
||||
AMFParserContext(XML_Parser parser, DynamicPrintConfig* config, Model* model) :
|
||||
AMFParserContext(XML_Parser parser, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model) :
|
||||
m_parser(parser),
|
||||
m_model(*model),
|
||||
m_config(config)
|
||||
m_config(config),
|
||||
m_config_substitutions(config_substitutions)
|
||||
{
|
||||
m_path.reserve(12);
|
||||
}
|
||||
|
@ -258,6 +259,8 @@ struct AMFParserContext
|
|||
std::string m_value[5];
|
||||
// Pointer to config to update if config data are stored inside the amf file
|
||||
DynamicPrintConfig *m_config { nullptr };
|
||||
// Config substitution rules and collected config substitution log.
|
||||
ConfigSubstitutionContext *m_config_substitutions { nullptr };
|
||||
|
||||
private:
|
||||
AMFParserContext& operator=(AMFParserContext&);
|
||||
|
@ -702,8 +705,9 @@ void AMFParserContext::endElement(const char * /* name */)
|
|||
}
|
||||
|
||||
case NODE_TYPE_METADATA:
|
||||
if ((m_config != nullptr) && strncmp(m_value[0].c_str(), SLIC3R_CONFIG_TYPE, strlen(SLIC3R_CONFIG_TYPE)) == 0)
|
||||
m_config->load_from_gcode_string(m_value[1].c_str());
|
||||
if ((m_config != nullptr) && strncmp(m_value[0].c_str(), SLIC3R_CONFIG_TYPE, strlen(SLIC3R_CONFIG_TYPE)) == 0) {
|
||||
m_config->load_from_gcode_string(m_value[1].c_str(), *m_config_substitutions);
|
||||
}
|
||||
else if (strncmp(m_value[0].c_str(), "slic3r.", 7) == 0) {
|
||||
const char *opt_key = m_value[0].c_str() + 7;
|
||||
if (print_config_def.options.find(opt_key) != print_config_def.options.end()) {
|
||||
|
@ -721,7 +725,7 @@ void AMFParserContext::endElement(const char * /* name */)
|
|||
config = &it->second;
|
||||
}
|
||||
if (config)
|
||||
config->set_deserialize(opt_key, m_value[1]);
|
||||
config->set_deserialize(opt_key, m_value[1], *m_config_substitutions);
|
||||
} else if (m_path.size() == 3 && m_path[1] == NODE_TYPE_OBJECT && m_object && strcmp(opt_key, "layer_height_profile") == 0) {
|
||||
// Parse object's layer height profile, a semicolon separated list of floats.
|
||||
char *p = m_value[1].data();
|
||||
|
@ -849,7 +853,7 @@ void AMFParserContext::endDocument()
|
|||
}
|
||||
|
||||
// Load an AMF file into a provided model.
|
||||
bool load_amf_file(const char *path, DynamicPrintConfig *config, Model *model)
|
||||
bool load_amf_file(const char *path, DynamicPrintConfig *config, ConfigSubstitutionContext *config_substitutions, Model *model)
|
||||
{
|
||||
if ((path == nullptr) || (model == nullptr))
|
||||
return false;
|
||||
|
@ -866,7 +870,7 @@ bool load_amf_file(const char *path, DynamicPrintConfig *config, Model *model)
|
|||
return false;
|
||||
}
|
||||
|
||||
AMFParserContext ctx(parser, config, model);
|
||||
AMFParserContext ctx(parser, config, config_substitutions, model);
|
||||
XML_SetUserData(parser, (void*)&ctx);
|
||||
XML_SetElementHandler(parser, AMFParserContext::startElement, AMFParserContext::endElement);
|
||||
XML_SetCharacterDataHandler(parser, AMFParserContext::characters);
|
||||
|
@ -908,7 +912,7 @@ bool load_amf_file(const char *path, DynamicPrintConfig *config, Model *model)
|
|||
return result;
|
||||
}
|
||||
|
||||
bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig* config, Model* model, bool check_version)
|
||||
bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model, bool check_version)
|
||||
{
|
||||
if (stat.m_uncomp_size == 0)
|
||||
{
|
||||
|
@ -924,7 +928,7 @@ bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_fi
|
|||
return false;
|
||||
}
|
||||
|
||||
AMFParserContext ctx(parser, config, model);
|
||||
AMFParserContext ctx(parser, config, config_substitutions, model);
|
||||
XML_SetUserData(parser, (void*)&ctx);
|
||||
XML_SetElementHandler(parser, AMFParserContext::startElement, AMFParserContext::endElement);
|
||||
XML_SetCharacterDataHandler(parser, AMFParserContext::characters);
|
||||
|
@ -984,7 +988,7 @@ bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_fi
|
|||
}
|
||||
|
||||
// Load an AMF archive into a provided model.
|
||||
bool load_amf_archive(const char* path, DynamicPrintConfig* config, Model* model, bool check_version)
|
||||
bool load_amf_archive(const char* path, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model, bool check_version)
|
||||
{
|
||||
if ((path == nullptr) || (model == nullptr))
|
||||
return false;
|
||||
|
@ -1010,7 +1014,7 @@ bool load_amf_archive(const char* path, DynamicPrintConfig* config, Model* model
|
|||
{
|
||||
try
|
||||
{
|
||||
if (!extract_model_from_archive(archive, stat, config, model, check_version))
|
||||
if (!extract_model_from_archive(archive, stat, config, config_substitutions, model, check_version))
|
||||
{
|
||||
close_zip_reader(&archive);
|
||||
BOOST_LOG_TRIVIAL(error) << "Archive does not contain a valid model";
|
||||
|
@ -1052,13 +1056,13 @@ bool load_amf_archive(const char* path, DynamicPrintConfig* config, Model* model
|
|||
|
||||
// Load an AMF file into a provided model.
|
||||
// If config is not a null pointer, updates it if the amf file/archive contains config data
|
||||
bool load_amf(const char* path, DynamicPrintConfig* config, Model* model, bool check_version)
|
||||
bool load_amf(const char* path, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model, bool check_version)
|
||||
{
|
||||
CNumericLocalesSetter locales_setter; // use "C" locales and point as a decimal separator
|
||||
|
||||
if (boost::iends_with(path, ".amf.xml"))
|
||||
// backward compatibility with older slic3r output
|
||||
return load_amf_file(path, config, model);
|
||||
return load_amf_file(path, config, config_substitutions, model);
|
||||
else if (boost::iends_with(path, ".amf"))
|
||||
{
|
||||
boost::nowide::ifstream file(path, boost::nowide::ifstream::binary);
|
||||
|
@ -1069,7 +1073,7 @@ bool load_amf(const char* path, DynamicPrintConfig* config, Model* model, bool c
|
|||
file.read(zip_mask.data(), 2);
|
||||
file.close();
|
||||
|
||||
return (zip_mask == "PK") ? load_amf_archive(path, config, model, check_version) : load_amf_file(path, config, model);
|
||||
return (zip_mask == "PK") ? load_amf_archive(path, config, config_substitutions, model, check_version) : load_amf_file(path, config, config_substitutions, model);
|
||||
}
|
||||
else
|
||||
return false;
|
||||
|
|
|
@ -7,7 +7,7 @@ class Model;
|
|||
class DynamicPrintConfig;
|
||||
|
||||
// Load the content of an amf file into the given model and configuration.
|
||||
extern bool load_amf(const char* path, DynamicPrintConfig* config, Model* model, bool check_version);
|
||||
extern bool load_amf(const char* path, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model, bool check_version);
|
||||
|
||||
// Save the given model and the config data into an amf file.
|
||||
// The model could be modified during the export process if meshes are not repaired or have no shared vertices
|
||||
|
|
|
@ -284,11 +284,8 @@ static void extract_model_from_archive(
|
|||
volume->name = name;
|
||||
}
|
||||
// Set the extruder to the volume.
|
||||
if (extruder_id != (unsigned int)-1) {
|
||||
char str_extruder[64];
|
||||
sprintf(str_extruder, "%ud", extruder_id);
|
||||
volume->config.set_deserialize("extruder", str_extruder);
|
||||
}
|
||||
if (extruder_id != (unsigned int)-1)
|
||||
volume->config.set("extruder", int(extruder_id));
|
||||
}
|
||||
|
||||
// Load a PrusaControl project file into a provided model.
|
||||
|
|
|
@ -287,13 +287,13 @@ std::vector<ExPolygons> extract_slices_from_sla_archive(
|
|||
|
||||
} // namespace
|
||||
|
||||
void import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out)
|
||||
ConfigSubstitutions import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out)
|
||||
{
|
||||
ArchiveData arch = extract_sla_archive(zipfname, "png");
|
||||
out.load(arch.profile);
|
||||
return out.load(arch.profile, ForwardCompatibilitySubstitutionRule::Enable);
|
||||
}
|
||||
|
||||
void import_sla_archive(
|
||||
ConfigSubstitutions import_sla_archive(
|
||||
const std::string & zipfname,
|
||||
Vec2i windowsize,
|
||||
indexed_triangle_set & out,
|
||||
|
@ -305,7 +305,7 @@ void import_sla_archive(
|
|||
windowsize.y() = std::max(2, windowsize.y());
|
||||
|
||||
ArchiveData arch = extract_sla_archive(zipfname, "thumbnail");
|
||||
profile.load(arch.profile);
|
||||
ConfigSubstitutions config_substitutions = profile.load(arch.profile, ForwardCompatibilitySubstitutionRule::Enable);
|
||||
|
||||
RasterParams rstp = get_raster_params(profile);
|
||||
rstp.win = {windowsize.y(), windowsize.x()};
|
||||
|
@ -317,6 +317,8 @@ void import_sla_archive(
|
|||
|
||||
if (!slices.empty())
|
||||
out = slices_to_mesh(slices, 0, slicp.layerh, slicp.initial_layerh);
|
||||
|
||||
return config_substitutions;
|
||||
}
|
||||
|
||||
using ConfMap = std::map<std::string, std::string>;
|
||||
|
|
|
@ -38,23 +38,23 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
void import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out);
|
||||
ConfigSubstitutions import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out);
|
||||
|
||||
void import_sla_archive(
|
||||
ConfigSubstitutions import_sla_archive(
|
||||
const std::string & zipfname,
|
||||
Vec2i windowsize,
|
||||
indexed_triangle_set & out,
|
||||
DynamicPrintConfig & profile,
|
||||
std::function<bool(int)> progr = [](int) { return true; });
|
||||
|
||||
inline void import_sla_archive(
|
||||
inline ConfigSubstitutions import_sla_archive(
|
||||
const std::string & zipfname,
|
||||
Vec2i windowsize,
|
||||
indexed_triangle_set & out,
|
||||
std::function<bool(int)> progr = [](int) { return true; })
|
||||
{
|
||||
DynamicPrintConfig profile;
|
||||
import_sla_archive(zipfname, windowsize, out, profile, progr);
|
||||
return import_sla_archive(zipfname, windowsize, out, profile, progr);
|
||||
}
|
||||
|
||||
} // namespace Slic3r::sla
|
||||
|
|
|
@ -1285,7 +1285,10 @@ void GCodeProcessor::process_file(const std::string& filename, bool apply_postpr
|
|||
if (m_producer == EProducer::PrusaSlicer || m_producer == EProducer::Slic3rPE || m_producer == EProducer::Slic3r) {
|
||||
DynamicPrintConfig config;
|
||||
config.apply(FullPrintConfig::defaults());
|
||||
config.load_from_gcode_file(filename, false);
|
||||
// Silently substitute unknown values by new ones for loading configurations from PrusaSlicer's own G-code.
|
||||
// Showing substitution log or errors may make sense, but we are not really reading many values from the G-code config,
|
||||
// thus a probability of incorrect substitution is low and the G-code viewer is a consumer-only anyways.
|
||||
config.load_from_gcode_file(filename, false, ForwardCompatibilitySubstitutionRule::EnableSilent);
|
||||
apply_config(config);
|
||||
}
|
||||
else if (m_producer == EProducer::Simplify3D)
|
||||
|
|
|
@ -96,13 +96,17 @@ void Model::update_links_bottom_up_recursive()
|
|||
}
|
||||
}
|
||||
|
||||
Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* config, bool add_default_instances, bool check_version)
|
||||
// Loading model from a file, it may be a simple geometry file as STL or OBJ, however it may be a project file as well.
|
||||
Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, LoadAttributes options)
|
||||
{
|
||||
Model model;
|
||||
|
||||
DynamicPrintConfig temp_config;
|
||||
ConfigSubstitutionContext temp_config_substitutions_context(ForwardCompatibilitySubstitutionRule::EnableSilent);
|
||||
if (config == nullptr)
|
||||
config = &temp_config;
|
||||
if (config_substitutions == nullptr)
|
||||
config_substitutions = &temp_config_substitutions_context;
|
||||
|
||||
bool result = false;
|
||||
if (boost::algorithm::iends_with(input_file, ".stl"))
|
||||
|
@ -110,9 +114,10 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c
|
|||
else if (boost::algorithm::iends_with(input_file, ".obj"))
|
||||
result = load_obj(input_file.c_str(), &model);
|
||||
else if (boost::algorithm::iends_with(input_file, ".amf") || boost::algorithm::iends_with(input_file, ".amf.xml"))
|
||||
result = load_amf(input_file.c_str(), config, &model, check_version);
|
||||
result = load_amf(input_file.c_str(), config, config_substitutions, &model, options & LoadAttribute::CheckVersion);
|
||||
else if (boost::algorithm::iends_with(input_file, ".3mf"))
|
||||
result = load_3mf(input_file.c_str(), config, &model, false);
|
||||
//FIXME options & LoadAttribute::CheckVersion ?
|
||||
result = load_3mf(input_file.c_str(), *config, *config_substitutions, &model, false);
|
||||
else if (boost::algorithm::iends_with(input_file, ".prusa"))
|
||||
result = load_prus(input_file.c_str(), &model);
|
||||
else
|
||||
|
@ -127,24 +132,29 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c
|
|||
for (ModelObject *o : model.objects)
|
||||
o->input_file = input_file;
|
||||
|
||||
if (add_default_instances)
|
||||
if (options & LoadAttribute::AddDefaultInstances)
|
||||
model.add_default_instances();
|
||||
|
||||
CustomGCode::update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z, config);
|
||||
CustomGCode::check_mode_for_custom_gcode_per_print_z(model.custom_gcode_per_print_z);
|
||||
|
||||
sort_remove_duplicates(config_substitutions->substitutions);
|
||||
return model;
|
||||
}
|
||||
|
||||
Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig* config, bool add_default_instances, bool check_version)
|
||||
// Loading model from a file (3MF or AMF), not from a simple geometry file (STL or OBJ).
|
||||
Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, LoadAttributes options)
|
||||
{
|
||||
assert(config != nullptr);
|
||||
assert(config_substitutions != nullptr);
|
||||
|
||||
Model model;
|
||||
|
||||
bool result = false;
|
||||
if (boost::algorithm::iends_with(input_file, ".3mf"))
|
||||
result = load_3mf(input_file.c_str(), config, &model, check_version);
|
||||
result = load_3mf(input_file.c_str(), *config, *config_substitutions, &model, options & LoadAttribute::CheckVersion);
|
||||
else if (boost::algorithm::iends_with(input_file, ".zip.amf"))
|
||||
result = load_amf(input_file.c_str(), config, &model, check_version);
|
||||
result = load_amf(input_file.c_str(), config, config_substitutions, &model, options & LoadAttribute::CheckVersion);
|
||||
else
|
||||
throw Slic3r::RuntimeError("Unknown file format. Input file must have .3mf or .zip.amf extension.");
|
||||
|
||||
|
@ -165,7 +175,7 @@ Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig
|
|||
o->input_file = input_file;
|
||||
}
|
||||
|
||||
if (add_default_instances)
|
||||
if (options & LoadAttribute::AddDefaultInstances)
|
||||
model.add_default_instances();
|
||||
|
||||
CustomGCode::update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z, config);
|
||||
|
@ -398,13 +408,12 @@ bool Model::looks_like_multipart_object() const
|
|||
}
|
||||
|
||||
// Generate next extruder ID string, in the range of (1, max_extruders).
|
||||
static inline std::string auto_extruder_id(unsigned int max_extruders, unsigned int &cntr)
|
||||
static inline int auto_extruder_id(unsigned int max_extruders, unsigned int &cntr)
|
||||
{
|
||||
char str_extruder[64];
|
||||
sprintf(str_extruder, "%ud", cntr + 1);
|
||||
if (++ cntr == max_extruders)
|
||||
int out = ++ cntr;
|
||||
if (cntr == max_extruders)
|
||||
cntr = 0;
|
||||
return str_extruder;
|
||||
return out;
|
||||
}
|
||||
|
||||
void Model::convert_multipart_object(unsigned int max_extruders)
|
||||
|
@ -431,7 +440,7 @@ void Model::convert_multipart_object(unsigned int max_extruders)
|
|||
auto copy_volume = [o, max_extruders, &counter, &extruder_counter](ModelVolume *new_v) {
|
||||
assert(new_v != nullptr);
|
||||
new_v->name = o->name + "_" + std::to_string(counter++);
|
||||
new_v->config.set_deserialize("extruder", auto_extruder_id(max_extruders, extruder_counter));
|
||||
new_v->config.set("extruder", auto_extruder_id(max_extruders, extruder_counter));
|
||||
return new_v;
|
||||
};
|
||||
if (o->instances.empty()) {
|
||||
|
@ -1738,7 +1747,7 @@ size_t ModelVolume::split(unsigned int max_extruders)
|
|||
this->object->volumes[ivolume]->center_geometry_after_creation();
|
||||
this->object->volumes[ivolume]->translate(offset);
|
||||
this->object->volumes[ivolume]->name = name + "_" + std::to_string(idx + 1);
|
||||
this->object->volumes[ivolume]->config.set_deserialize("extruder", auto_extruder_id(max_extruders, extruder_counter));
|
||||
this->object->volumes[ivolume]->config.set("extruder", auto_extruder_id(max_extruders, extruder_counter));
|
||||
this->object->volumes[ivolume]->m_is_splittable = 0;
|
||||
delete mesh;
|
||||
++ idx;
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include "TriangleMesh.hpp"
|
||||
#include "Arrange.hpp"
|
||||
#include "CustomGCode.hpp"
|
||||
#include "enum_bitmask.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
@ -1031,8 +1032,20 @@ public:
|
|||
|
||||
OBJECTBASE_DERIVED_COPY_MOVE_CLONE(Model)
|
||||
|
||||
static Model read_from_file(const std::string& input_file, DynamicPrintConfig* config = nullptr, bool add_default_instances = true, bool check_version = false);
|
||||
static Model read_from_archive(const std::string& input_file, DynamicPrintConfig* config, bool add_default_instances = true, bool check_version = false);
|
||||
enum class LoadAttribute : int {
|
||||
AddDefaultInstances,
|
||||
CheckVersion
|
||||
};
|
||||
using LoadAttributes = enum_bitmask<LoadAttribute>;
|
||||
|
||||
static Model read_from_file(
|
||||
const std::string& input_file,
|
||||
DynamicPrintConfig* config = nullptr, ConfigSubstitutionContext* config_substitutions = nullptr,
|
||||
LoadAttributes options = LoadAttribute::AddDefaultInstances);
|
||||
static Model read_from_archive(
|
||||
const std::string& input_file,
|
||||
DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions,
|
||||
LoadAttributes options = LoadAttribute::AddDefaultInstances);
|
||||
|
||||
// Add a new ModelObject to this Model, generate a new ID for this ModelObject.
|
||||
ModelObject* add_object();
|
||||
|
@ -1097,6 +1110,8 @@ private:
|
|||
}
|
||||
};
|
||||
|
||||
ENABLE_ENUM_BITMASK_OPERATORS(Model::LoadAttribute)
|
||||
|
||||
#undef OBJECTBASE_DERIVED_COPY_MOVE_CLONE
|
||||
#undef OBJECTBASE_DERIVED_PRIVATE_COPY_MOVE
|
||||
|
||||
|
|
|
@ -666,7 +666,9 @@ void PresetCollection::add_default_preset(const std::vector<std::string> &keys,
|
|||
|
||||
// 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)
|
||||
void PresetCollection::load_presets(
|
||||
const std::string &dir_path, const std::string &subdir,
|
||||
PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule substitution_rule)
|
||||
{
|
||||
// Don't use boost::filesystem::canonical() on Windows, it is broken in regard to reparse points,
|
||||
// see https://github.com/prusa3d/PrusaSlicer/issues/732
|
||||
|
@ -693,7 +695,9 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri
|
|||
// Load the preset file, apply preset values on top of defaults.
|
||||
try {
|
||||
DynamicPrintConfig config;
|
||||
config.load_from_ini(preset.file);
|
||||
ConfigSubstitutions config_substitutions = config.load_from_ini(preset.file, substitution_rule);
|
||||
if (! config_substitutions.empty())
|
||||
substitutions.push_back({ preset.name, m_type, PresetConfigSubstitutions::Source::UserFile, preset.file, std::move(config_substitutions) });
|
||||
// Find a default preset for the config. The PrintPresetCollection provides different default preset based on the "printer_technology" field.
|
||||
const Preset &default_preset = this->default_preset_for(config);
|
||||
preset.config = default_preset.config;
|
||||
|
@ -1580,7 +1584,9 @@ PhysicalPrinterCollection::PhysicalPrinterCollection( const std::vector<std::str
|
|||
|
||||
// Load all printers found in dir_path.
|
||||
// Throws an exception on error.
|
||||
void PhysicalPrinterCollection::load_printers(const std::string& dir_path, const std::string& subdir)
|
||||
void PhysicalPrinterCollection::load_printers(
|
||||
const std::string& dir_path, const std::string& subdir,
|
||||
PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule substitution_rule)
|
||||
{
|
||||
// Don't use boost::filesystem::canonical() on Windows, it is broken in regard to reparse points,
|
||||
// see https://github.com/prusa3d/PrusaSlicer/issues/732
|
||||
|
@ -1606,7 +1612,9 @@ void PhysicalPrinterCollection::load_printers(const std::string& dir_path, const
|
|||
// Load the preset file, apply preset values on top of defaults.
|
||||
try {
|
||||
DynamicPrintConfig config;
|
||||
config.load_from_ini(printer.file);
|
||||
ConfigSubstitutions config_substitutions = config.load_from_ini(printer.file, substitution_rule);
|
||||
if (! config_substitutions.empty())
|
||||
substitutions.push_back({ name, Preset::TYPE_PHYSICAL_PRINTER, PresetConfigSubstitutions::Source::UserFile, printer.file, std::move(config_substitutions) });
|
||||
printer.update_from_config(config);
|
||||
printer.loaded = true;
|
||||
}
|
||||
|
|
|
@ -113,6 +113,9 @@ public:
|
|||
TYPE_SLA_MATERIAL,
|
||||
TYPE_PRINTER,
|
||||
TYPE_COUNT,
|
||||
// This type is here to support PresetConfigSubstitutions for physical printers, however it does not belong to the Preset class,
|
||||
// PhysicalPrinter class is used instead.
|
||||
TYPE_PHYSICAL_PRINTER,
|
||||
};
|
||||
|
||||
Type type = TYPE_INVALID;
|
||||
|
@ -251,6 +254,27 @@ enum class PresetSelectCompatibleType {
|
|||
Always
|
||||
};
|
||||
|
||||
// Substitutions having been performed during parsing a single configuration file.
|
||||
struct PresetConfigSubstitutions {
|
||||
// User readable preset name.
|
||||
std::string preset_name;
|
||||
// Type of the preset (Print / Filament / Printer ...)
|
||||
Preset::Type preset_type;
|
||||
enum class Source {
|
||||
UserFile,
|
||||
ConfigBundle,
|
||||
};
|
||||
Source preset_source;
|
||||
// Source of the preset. It may be empty in case of a ConfigBundle being loaded.
|
||||
std::string preset_file;
|
||||
// What config value has been substituted with what.
|
||||
ConfigSubstitutions substitutions;
|
||||
};
|
||||
|
||||
// Substitutions having been performed during parsing a set of configuration files, for example when starting up
|
||||
// PrusaSlicer and reading the user Print / Filament / Printer profiles.
|
||||
using PresetsConfigSubstitutions = std::vector<PresetConfigSubstitutions>;
|
||||
|
||||
// Collections of presets of the same type (one of the Print, Filament or Printer type).
|
||||
class PresetCollection
|
||||
{
|
||||
|
@ -280,7 +304,7 @@ public:
|
|||
void add_default_preset(const std::vector<std::string> &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &preset_name);
|
||||
|
||||
// Load ini files of the particular type from the provided directory path.
|
||||
void load_presets(const std::string &dir_path, const std::string &subdir);
|
||||
void load_presets(const std::string &dir_path, const std::string &subdir, PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule rule);
|
||||
|
||||
// Load a preset from an already parsed config file, insert it into the sorted sequence of presets
|
||||
// and select it, losing previous modifications.
|
||||
|
@ -692,7 +716,7 @@ public:
|
|||
const std::deque<PhysicalPrinter>& operator()() const { return m_printers; }
|
||||
|
||||
// Load ini files of the particular type from the provided directory path.
|
||||
void load_printers(const std::string& dir_path, const std::string& subdir);
|
||||
void load_printers(const std::string& dir_path, const std::string& subdir, PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule rule);
|
||||
void load_printers_from_presets(PrinterPresetCollection &printer_presets);
|
||||
// Load printer from the loaded configuration
|
||||
void load_printer(const std::string& path, const std::string& name, DynamicPrintConfig&& config, bool select, bool save=false);
|
||||
|
|
|
@ -187,7 +187,7 @@ void PresetBundle::setup_directories()
|
|||
}
|
||||
}
|
||||
|
||||
void PresetBundle::load_presets(AppConfig &config, const std::string &preferred_model_id)
|
||||
PresetsConfigSubstitutions PresetBundle::load_presets(AppConfig &config, ForwardCompatibilitySubstitutionRule substitution_rule, const std::string &preferred_model_id)
|
||||
{
|
||||
// First load the vendor specific system presets.
|
||||
std::string errors_cummulative = this->load_system_presets();
|
||||
|
@ -200,33 +200,35 @@ void PresetBundle::load_presets(AppConfig &config, const std::string &preferred_
|
|||
// Store the print/filament/printer presets at the same location as the upstream Slic3r.
|
||||
#endif
|
||||
;
|
||||
|
||||
PresetsConfigSubstitutions substitutions;
|
||||
try {
|
||||
this->prints.load_presets(dir_user_presets, "print");
|
||||
this->prints.load_presets(dir_user_presets, "print", substitutions, substitution_rule);
|
||||
} catch (const std::runtime_error &err) {
|
||||
errors_cummulative += err.what();
|
||||
}
|
||||
try {
|
||||
this->sla_prints.load_presets(dir_user_presets, "sla_print");
|
||||
this->sla_prints.load_presets(dir_user_presets, "sla_print", substitutions, substitution_rule);
|
||||
} catch (const std::runtime_error &err) {
|
||||
errors_cummulative += err.what();
|
||||
}
|
||||
try {
|
||||
this->filaments.load_presets(dir_user_presets, "filament");
|
||||
this->filaments.load_presets(dir_user_presets, "filament", substitutions, substitution_rule);
|
||||
} catch (const std::runtime_error &err) {
|
||||
errors_cummulative += err.what();
|
||||
}
|
||||
try {
|
||||
this->sla_materials.load_presets(dir_user_presets, "sla_material");
|
||||
this->sla_materials.load_presets(dir_user_presets, "sla_material", substitutions, substitution_rule);
|
||||
} catch (const std::runtime_error &err) {
|
||||
errors_cummulative += err.what();
|
||||
}
|
||||
try {
|
||||
this->printers.load_presets(dir_user_presets, "printer");
|
||||
this->printers.load_presets(dir_user_presets, "printer", substitutions, substitution_rule);
|
||||
} catch (const std::runtime_error &err) {
|
||||
errors_cummulative += err.what();
|
||||
}
|
||||
try {
|
||||
this->physical_printers.load_printers(dir_user_presets, "physical_printer");
|
||||
this->physical_printers.load_printers(dir_user_presets, "physical_printer", substitutions, substitution_rule);
|
||||
} catch (const std::runtime_error &err) {
|
||||
errors_cummulative += err.what();
|
||||
}
|
||||
|
@ -236,6 +238,8 @@ void PresetBundle::load_presets(AppConfig &config, const std::string &preferred_
|
|||
throw Slic3r::RuntimeError(errors_cummulative);
|
||||
|
||||
this->load_selections(config, preferred_model_id);
|
||||
|
||||
return substitutions;
|
||||
}
|
||||
|
||||
// Load system presets into this PresetBundle.
|
||||
|
@ -255,13 +259,13 @@ std::string PresetBundle::load_system_presets()
|
|||
// Load the config bundle, flatten it.
|
||||
if (first) {
|
||||
// Reset this PresetBundle and load the first vendor config.
|
||||
this->load_configbundle(dir_entry.path().string(), LOAD_CFGBNDLE_SYSTEM);
|
||||
this->load_configbundle(dir_entry.path().string(), PresetBundle::LoadSystem);
|
||||
first = false;
|
||||
} else {
|
||||
// Load the other vendor configs, merge them with this PresetBundle.
|
||||
// Report duplicate profiles.
|
||||
PresetBundle other;
|
||||
other.load_configbundle(dir_entry.path().string(), LOAD_CFGBNDLE_SYSTEM);
|
||||
other.load_configbundle(dir_entry.path().string(), PresetBundle::LoadSystem);
|
||||
std::vector<std::string> duplicates = this->merge_presets(std::move(other));
|
||||
if (! duplicates.empty()) {
|
||||
errors_cummulative += "Vendor configuration file " + name + " contains the following presets with names used by other vendors: ";
|
||||
|
@ -690,15 +694,15 @@ DynamicPrintConfig PresetBundle::full_sla_config() const
|
|||
// Instead of a config file, a G-code may be loaded containing the full set of parameters.
|
||||
// In the future the configuration will likely be read from an AMF file as well.
|
||||
// If the file is loaded successfully, its print / filament / printer profiles will be activated.
|
||||
void PresetBundle::load_config_file(const std::string &path)
|
||||
ConfigSubstitutions PresetBundle::load_config_file(const std::string &path, ForwardCompatibilitySubstitutionRule compatibility_rule)
|
||||
{
|
||||
if (is_gcode_file(path)) {
|
||||
DynamicPrintConfig config;
|
||||
config.apply(FullPrintConfig::defaults());
|
||||
config.load_from_gcode_file(path);
|
||||
ConfigSubstitutions config_substitutions = config.load_from_gcode_file(path, true /* check_header */, compatibility_rule);
|
||||
Preset::normalize(config);
|
||||
load_config_file_config(path, true, std::move(config));
|
||||
return;
|
||||
return config_substitutions;
|
||||
}
|
||||
|
||||
// 1) Try to load the config file into a boost property tree.
|
||||
|
@ -717,6 +721,7 @@ void PresetBundle::load_config_file(const std::string &path)
|
|||
|
||||
// 2) Continue based on the type of the configuration file.
|
||||
ConfigFileType config_file_type = guess_config_file_type(tree);
|
||||
ConfigSubstitutions config_substitutions;
|
||||
switch (config_file_type) {
|
||||
case CONFIG_FILE_TYPE_UNKNOWN:
|
||||
throw Slic3r::RuntimeError(std::string("Unknown configuration file type: ") + path);
|
||||
|
@ -727,15 +732,18 @@ void PresetBundle::load_config_file(const std::string &path)
|
|||
// Initialize a config from full defaults.
|
||||
DynamicPrintConfig config;
|
||||
config.apply(FullPrintConfig::defaults());
|
||||
config.load(tree);
|
||||
config_substitutions = config.load(tree, compatibility_rule);
|
||||
Preset::normalize(config);
|
||||
load_config_file_config(path, true, std::move(config));
|
||||
break;
|
||||
}
|
||||
case CONFIG_FILE_TYPE_CONFIG_BUNDLE:
|
||||
load_config_file_config_bundle(path, tree);
|
||||
break;
|
||||
return config_substitutions;
|
||||
}
|
||||
case CONFIG_FILE_TYPE_CONFIG_BUNDLE:
|
||||
return load_config_file_config_bundle(path, tree);
|
||||
}
|
||||
|
||||
// This shall never happen. Suppres compiler warnings.
|
||||
assert(false);
|
||||
return ConfigSubstitutions{};
|
||||
}
|
||||
|
||||
// Load a config file from a boost property_tree. This is a private method called from load_config_file.
|
||||
|
@ -907,16 +915,24 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
|
|||
}
|
||||
|
||||
// Load the active configuration of a config bundle from a boost property_tree. This is a private method called from load_config_file.
|
||||
void PresetBundle::load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree)
|
||||
ConfigSubstitutions PresetBundle::load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree)
|
||||
{
|
||||
// 1) Load the config bundle into a temp data.
|
||||
PresetBundle tmp_bundle;
|
||||
// Load the config bundle, don't save the loaded presets to user profile directory.
|
||||
tmp_bundle.load_configbundle(path, 0);
|
||||
// Load the config bundle, but don't save the loaded presets to user profile directory, as only the presets marked as active in the loaded preset bundle
|
||||
// will be loaded into the master PresetBundle and activated.
|
||||
auto [presets_substitutions, presets_imported] = tmp_bundle.load_configbundle(path, {});
|
||||
|
||||
std::string bundle_name = std::string(" - ") + boost::filesystem::path(path).filename().string();
|
||||
|
||||
// 2) Extract active configs from the config bundle, copy them and activate them in this bundle.
|
||||
auto load_one = [&path, &bundle_name](PresetCollection &collection_dst, PresetCollection &collection_src, const std::string &preset_name_src, bool activate) -> std::string {
|
||||
ConfigSubstitutions config_substitutions;
|
||||
auto load_one = [this, &path, &bundle_name, &presets_substitutions = presets_substitutions, &config_substitutions](
|
||||
PresetCollection &collection_dst, PresetCollection &collection_src, const std::string &preset_name_src, bool activate) -> std::string {
|
||||
// If there are substitutions reported for this preset, move them to config_substitutions.
|
||||
if (auto it = std::find_if(presets_substitutions.begin(), presets_substitutions.end(), [&preset_name_src](const PresetConfigSubstitutions& subs){ return subs.preset_name == preset_name_src; });
|
||||
it != presets_substitutions.end() && ! it->substitutions.empty())
|
||||
append(config_substitutions, std::move(it->substitutions));
|
||||
Preset *preset_src = collection_src.find_preset(preset_name_src, false);
|
||||
Preset *preset_dst = collection_dst.find_preset(preset_name_src, false);
|
||||
assert(preset_src != nullptr);
|
||||
|
@ -970,6 +986,9 @@ void PresetBundle::load_config_file_config_bundle(const std::string &path, const
|
|||
this->filament_presets[i] = load_one(this->filaments, tmp_bundle.filaments, tmp_bundle.filament_presets[i], false);
|
||||
|
||||
this->update_compatible(PresetSelectCompatibleType::Never);
|
||||
|
||||
sort_remove_duplicates(config_substitutions);
|
||||
return std::move(config_substitutions);
|
||||
}
|
||||
|
||||
// Process the Config Bundle loaded as a Boost property tree.
|
||||
|
@ -1114,11 +1133,20 @@ static void flatten_configbundle_hierarchy(boost::property_tree::ptree &tree, co
|
|||
|
||||
// Load a config bundle file, into presets and store the loaded presets into separate files
|
||||
// of the local configuration directory.
|
||||
size_t PresetBundle::load_configbundle(const std::string &path, unsigned int flags)
|
||||
std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_configbundle(const std::string &path, LoadConfigBundleAttributes flags)
|
||||
{
|
||||
if (flags & (LOAD_CFGBNDLE_RESET_USER_PROFILE | LOAD_CFGBNDLE_SYSTEM))
|
||||
// Reset this bundle, delete user profile files if LOAD_CFGBNDLE_SAVE.
|
||||
this->reset(flags & LOAD_CFGBNDLE_SAVE);
|
||||
// Enable substitutions for user config bundle, throw an exception when loading a system profile.
|
||||
ConfigSubstitutionContext substitution_context {
|
||||
flags.has(LoadConfigBundleAttribute::LoadSystem) ?
|
||||
ForwardCompatibilitySubstitutionRule::Disable :
|
||||
ForwardCompatibilitySubstitutionRule::Enable
|
||||
};
|
||||
|
||||
PresetsConfigSubstitutions substitutions;
|
||||
|
||||
if (flags.has(LoadConfigBundleAttribute::ResetUserProfile) || flags.has(LoadConfigBundleAttribute::LoadSystem))
|
||||
// Reset this bundle, delete user profile files if SaveImported.
|
||||
this->reset(flags.has(LoadConfigBundleAttribute::SaveImported));
|
||||
|
||||
// 1) Read the complete config file into a boost::property_tree.
|
||||
namespace pt = boost::property_tree;
|
||||
|
@ -1131,25 +1159,24 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
|
|||
}
|
||||
|
||||
const VendorProfile *vendor_profile = nullptr;
|
||||
if (flags & (LOAD_CFGBNDLE_SYSTEM | LOAD_CFGBUNDLE_VENDOR_ONLY)) {
|
||||
if (flags.has(LoadConfigBundleAttribute::LoadSystem) || flags.has(LoadConfigBundleAttribute::LoadVendorOnly)) {
|
||||
auto vp = VendorProfile::from_ini(tree, path);
|
||||
if (vp.models.size() == 0) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No printer model defined.") % path;
|
||||
return 0;
|
||||
return std::make_pair(PresetsConfigSubstitutions{}, 0);
|
||||
} else if (vp.num_variants() == 0) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No printer variant defined") % path;
|
||||
return 0;
|
||||
return std::make_pair(PresetsConfigSubstitutions{}, 0);
|
||||
}
|
||||
vendor_profile = &this->vendors.insert({vp.id, vp}).first->second;
|
||||
}
|
||||
|
||||
if (flags & LOAD_CFGBUNDLE_VENDOR_ONLY) {
|
||||
return 0;
|
||||
}
|
||||
if (flags.has(LoadConfigBundleAttribute::LoadVendorOnly))
|
||||
return std::make_pair(PresetsConfigSubstitutions{}, 0);
|
||||
|
||||
// 1.5) Flatten the config bundle by applying the inheritance rules. Internal profiles (with names starting with '*') are removed.
|
||||
// If loading a user config bundle, do not flatten with the system profiles, but keep the "inherits" flag intact.
|
||||
flatten_configbundle_hierarchy(tree, ((flags & LOAD_CFGBNDLE_SYSTEM) == 0) ? this : nullptr);
|
||||
flatten_configbundle_hierarchy(tree, flags.has(LoadConfigBundleAttribute::LoadSystem) ? nullptr : this);
|
||||
|
||||
// 2) Parse the property_tree, extract the active preset names and the profiles, save them into local config files.
|
||||
// Parse the obsolete preset names, to be deleted when upgrading from the old configuration structure.
|
||||
|
@ -1246,7 +1273,8 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
|
|||
DynamicPrintConfig config;
|
||||
std::string alias_name;
|
||||
std::vector<std::string> renamed_from;
|
||||
auto parse_config_section = [§ion, &alias_name, &renamed_from, &path](DynamicPrintConfig &config) {
|
||||
auto parse_config_section = [§ion, &alias_name, &renamed_from, &substitution_context, &path](DynamicPrintConfig &config) {
|
||||
substitution_context.substitutions.clear();
|
||||
for (auto &kvp : section.second) {
|
||||
if (kvp.first == "alias")
|
||||
alias_name = kvp.second.data();
|
||||
|
@ -1256,7 +1284,8 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
|
|||
section.first << "\" contains invalid \"renamed_from\" key, which is being ignored.";
|
||||
}
|
||||
}
|
||||
config.set_deserialize(kvp.first, kvp.second.data());
|
||||
// Throws on parsing error. For system presets, no substituion is being done, but an exception is thrown.
|
||||
config.set_deserialize(kvp.first, kvp.second.data(), substitution_context);
|
||||
}
|
||||
};
|
||||
if (presets == &this->printers) {
|
||||
|
@ -1277,7 +1306,7 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
|
|||
if (! incorrect_keys.empty())
|
||||
BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The printer preset \"" <<
|
||||
section.first << "\" contains the following incorrect keys: " << incorrect_keys << ", which were removed";
|
||||
if ((flags & LOAD_CFGBNDLE_SYSTEM) && presets == &printers) {
|
||||
if (flags.has(LoadConfigBundleAttribute::LoadSystem) && presets == &printers) {
|
||||
// Filter out printer presets, which are not mentioned in the vendor profile.
|
||||
// These presets are considered not installed.
|
||||
auto printer_model = config.opt_string("printer_model");
|
||||
|
@ -1312,7 +1341,7 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
|
|||
section.first << "\" has already been loaded from another Confing Bundle.";
|
||||
continue;
|
||||
}
|
||||
} else if ((flags & LOAD_CFGBNDLE_SYSTEM) == 0) {
|
||||
} else if (! flags.has(LoadConfigBundleAttribute::LoadSystem)) {
|
||||
// This is a user config bundle.
|
||||
const Preset *existing = presets->find_preset(preset_name, false);
|
||||
if (existing != nullptr) {
|
||||
|
@ -1341,9 +1370,9 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
|
|||
/ 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)
|
||||
if (flags.has(LoadConfigBundleAttribute::SaveImported))
|
||||
loaded.save();
|
||||
if (flags & LOAD_CFGBNDLE_SYSTEM) {
|
||||
if (flags.has(LoadConfigBundleAttribute::LoadSystem)) {
|
||||
loaded.is_system = true;
|
||||
loaded.vendor = vendor_profile;
|
||||
}
|
||||
|
@ -1364,7 +1393,10 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
|
|||
else
|
||||
loaded.alias = std::move(alias_name);
|
||||
loaded.renamed_from = std::move(renamed_from);
|
||||
|
||||
if (! substitution_context.empty())
|
||||
substitutions.push_back({
|
||||
preset_name, presets->type(), PresetConfigSubstitutions::Source::ConfigBundle,
|
||||
std::string(), std::move(substitution_context.substitutions) });
|
||||
++ presets_loaded;
|
||||
}
|
||||
|
||||
|
@ -1373,8 +1405,9 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
|
|||
const DynamicPrintConfig& default_config = ph_printers->default_config();
|
||||
DynamicPrintConfig config = default_config;
|
||||
|
||||
substitution_context.substitutions.clear();
|
||||
for (auto& kvp : section.second)
|
||||
config.set_deserialize(kvp.first, kvp.second.data());
|
||||
config.set_deserialize(kvp.first, kvp.second.data(), substitution_context);
|
||||
|
||||
// Report configuration fields, which are misplaced into a wrong group.
|
||||
std::string incorrect_keys = Preset::remove_invalid_keys(config, default_config);
|
||||
|
@ -1400,14 +1433,17 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
|
|||
#endif
|
||||
/ "physical_printer" / file_name).make_preferred();
|
||||
// Load the preset into the list of presets, save it to disk.
|
||||
ph_printers->load_printer(file_path.string(), ph_printer_name, std::move(config), false, flags & LOAD_CFGBNDLE_SAVE);
|
||||
|
||||
++ph_printers_loaded;
|
||||
ph_printers->load_printer(file_path.string(), ph_printer_name, std::move(config), false, flags.has(LoadConfigBundleAttribute::SaveImported));
|
||||
if (! substitution_context.empty())
|
||||
substitutions.push_back({
|
||||
ph_printer_name, Preset::TYPE_PHYSICAL_PRINTER, PresetConfigSubstitutions::Source::ConfigBundle,
|
||||
std::string(), std::move(substitution_context.substitutions) });
|
||||
++ ph_printers_loaded;
|
||||
}
|
||||
}
|
||||
|
||||
// 3) Activate the presets and physical printer if any exists.
|
||||
if ((flags & LOAD_CFGBNDLE_SYSTEM) == 0) {
|
||||
if (! flags.has(LoadConfigBundleAttribute::LoadSystem)) {
|
||||
if (! active_print.empty())
|
||||
prints.select_preset_by_name(active_print, true);
|
||||
if (! active_sla_print.empty())
|
||||
|
@ -1427,7 +1463,7 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
|
|||
this->update_compatible(PresetSelectCompatibleType::Never);
|
||||
}
|
||||
|
||||
return presets_loaded + ph_printers_loaded;
|
||||
return std::make_pair(std::move(substitutions), presets_loaded + ph_printers_loaded);
|
||||
}
|
||||
|
||||
void PresetBundle::update_multi_material_filament_presets()
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "Preset.hpp"
|
||||
#include "AppConfig.hpp"
|
||||
#include "enum_bitmask.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
@ -26,7 +27,7 @@ public:
|
|||
|
||||
// Load ini files of all types (print, filament, printer) from Slic3r::data_dir() / presets.
|
||||
// Load selections (current print, current filaments, current printer) from config.ini
|
||||
void load_presets(AppConfig &config, const std::string &preferred_model_id = std::string());
|
||||
PresetsConfigSubstitutions load_presets(AppConfig &config, ForwardCompatibilitySubstitutionRule rule, const std::string &preferred_model_id = std::string());
|
||||
|
||||
// Export selections (current print, current filaments, current printer) into config.ini
|
||||
void export_selections(AppConfig &config);
|
||||
|
@ -82,24 +83,26 @@ public:
|
|||
// Instead of a config file, a G-code may be loaded containing the full set of parameters.
|
||||
// In the future the configuration will likely be read from an AMF file as well.
|
||||
// If the file is loaded successfully, its print / filament / printer profiles will be activated.
|
||||
void load_config_file(const std::string &path);
|
||||
ConfigSubstitutions load_config_file(const std::string &path, ForwardCompatibilitySubstitutionRule compatibility_rule);
|
||||
|
||||
// Load a config bundle file, into presets and store the loaded presets into separate files
|
||||
// of the local configuration directory.
|
||||
// Load settings into the provided settings instance.
|
||||
// Activate the presets stored in the config bundle.
|
||||
// Returns the number of presets loaded successfully.
|
||||
enum {
|
||||
enum LoadConfigBundleAttribute {
|
||||
// Save the profiles, which have been loaded.
|
||||
LOAD_CFGBNDLE_SAVE = 1,
|
||||
SaveImported,
|
||||
// Delete all old config profiles before loading.
|
||||
LOAD_CFGBNDLE_RESET_USER_PROFILE = 2,
|
||||
ResetUserProfile,
|
||||
// Load a system config bundle.
|
||||
LOAD_CFGBNDLE_SYSTEM = 4,
|
||||
LOAD_CFGBUNDLE_VENDOR_ONLY = 8,
|
||||
LoadSystem,
|
||||
LoadVendorOnly,
|
||||
};
|
||||
// Load the config bundle, store it to the user profile directory by default.
|
||||
size_t load_configbundle(const std::string &path, unsigned int flags = LOAD_CFGBNDLE_SAVE);
|
||||
using LoadConfigBundleAttributes = enum_bitmask<LoadConfigBundleAttribute>;
|
||||
// Load the config bundle based on the flags.
|
||||
// Don't do any config substitutions when loading a system profile, perform and report substitutions otherwise.
|
||||
std::pair<PresetsConfigSubstitutions, size_t> load_configbundle(const std::string &path, LoadConfigBundleAttributes flags);
|
||||
|
||||
// Export a config bundle file containing all the presets and the names of the active presets.
|
||||
void export_configbundle(const std::string &path, bool export_system_settings = false, bool export_physical_printers = false);
|
||||
|
@ -155,12 +158,14 @@ private:
|
|||
// and the external config is just referenced, not stored into user profile directory.
|
||||
// If it is not an external config, then the config will be stored into the user profile directory.
|
||||
void load_config_file_config(const std::string &name_or_path, bool is_external, DynamicPrintConfig &&config);
|
||||
void load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree);
|
||||
ConfigSubstitutions load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree);
|
||||
|
||||
DynamicPrintConfig full_fff_config() const;
|
||||
DynamicPrintConfig full_sla_config() const;
|
||||
};
|
||||
|
||||
ENABLE_ENUM_BITMASK_OPERATORS(PresetBundle::LoadConfigBundleAttribute)
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_PresetBundle_hpp_ */
|
||||
|
|
|
@ -172,6 +172,13 @@ static const t_config_enum_values s_keys_map_BrimType = {
|
|||
};
|
||||
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BrimType)
|
||||
|
||||
static const t_config_enum_values s_keys_map_ForwardCompatibilitySubstitutionRule = {
|
||||
{ "disable", ForwardCompatibilitySubstitutionRule::Disable },
|
||||
{ "enable", ForwardCompatibilitySubstitutionRule::Enable },
|
||||
{ "enable_silent", ForwardCompatibilitySubstitutionRule::EnableSilent }
|
||||
};
|
||||
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(ForwardCompatibilitySubstitutionRule)
|
||||
|
||||
static void assign_printer_technology_to_unknown(t_optiondef_map &options, PrinterTechnology printer_technology)
|
||||
{
|
||||
for (std::pair<const t_config_option_key, ConfigOptionDef> &kvp : options)
|
||||
|
@ -3622,7 +3629,7 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
|
|||
} else if (opt_key == "bed_size" && !value.empty()) {
|
||||
opt_key = "bed_shape";
|
||||
ConfigOptionPoint p;
|
||||
p.deserialize(value);
|
||||
p.deserialize(value, ForwardCompatibilitySubstitutionRule::Disable);
|
||||
std::ostringstream oss;
|
||||
oss << "0x0," << p.value(0) << "x0," << p.value(0) << "x" << p.value(1) << ",0x" << p.value(1);
|
||||
value = oss.str();
|
||||
|
@ -4171,6 +4178,20 @@ CLIMiscConfigDef::CLIMiscConfigDef()
|
|||
def->label = L("Ignore non-existent config files");
|
||||
def->tooltip = L("Do not fail if a file supplied to --load does not exist.");
|
||||
|
||||
def = this->add("config_compatibility", coEnum);
|
||||
def->label = L("Forward-compatibility rule when loading configurations from config files and project files (3MF, AMF).");
|
||||
def->tooltip = L("This version of PrusaSlicer may not understand configurations produced by newest PrusaSlicer versions. "
|
||||
"For example, newer PrusaSlicer may extend the list of supported firmware flavors. One may decide to "
|
||||
"bail out or to substitute an unknown value with a default silently or verbosely.");
|
||||
def->enum_keys_map = &ConfigOptionEnum<ForwardCompatibilitySubstitutionRule>::get_enum_values();
|
||||
def->enum_values.push_back("disable");
|
||||
def->enum_values.push_back("enable");
|
||||
def->enum_values.push_back("enable_silent");
|
||||
def->enum_labels.push_back(L("Bail out on unknown configuration values"));
|
||||
def->enum_labels.push_back(L("Enable reading unknown configuration values by verbosely substituting them with defaults."));
|
||||
def->enum_labels.push_back(L("Enable reading unknown configuration values by silently substituting them with defaults."));
|
||||
def->set_default_value(new ConfigOptionEnum<ForwardCompatibilitySubstitutionRule>(ForwardCompatibilitySubstitutionRule::Enable));
|
||||
|
||||
def = this->add("load", coStrings);
|
||||
def->label = L("Load config file");
|
||||
def->tooltip = L("Load configuration from the specified file. It can be used more than once to load options from multiple files.");
|
||||
|
|
|
@ -141,6 +141,7 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SeamPosition)
|
|||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SLADisplayOrientation)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SLAPillarConnectionMode)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BrimType)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ForwardCompatibilitySubstitutionRule)
|
||||
|
||||
#undef CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS
|
||||
|
||||
|
@ -1086,8 +1087,8 @@ public:
|
|||
bool set_key_value(const std::string &opt_key, ConfigOption *opt) { bool out = m_data.set_key_value(opt_key, opt); this->touch(); return out; }
|
||||
template<typename T>
|
||||
void set(const std::string &opt_key, T value) { m_data.set(opt_key, value, true); this->touch(); }
|
||||
void set_deserialize(const t_config_option_key &opt_key, const std::string &str, bool append = false)
|
||||
{ m_data.set_deserialize(opt_key, str, append); this->touch(); }
|
||||
void set_deserialize(const t_config_option_key &opt_key, const std::string &str, ConfigSubstitutionContext &substitution_context, bool append = false)
|
||||
{ m_data.set_deserialize(opt_key, str, substitution_context, append); this->touch(); }
|
||||
bool erase(const t_config_option_key &opt_key) { bool out = m_data.erase(opt_key); if (out) this->touch(); return out; }
|
||||
|
||||
// Getters are thread safe.
|
||||
|
|
80
src/libslic3r/enum_bitmask.hpp
Normal file
80
src/libslic3r/enum_bitmask.hpp
Normal file
|
@ -0,0 +1,80 @@
|
|||
#ifndef slic3r_enum_bitmask_hpp_
|
||||
#define slic3r_enum_bitmask_hpp_
|
||||
|
||||
// enum_bitmask for passing a set of attributes to a function in a type safe way.
|
||||
// Adapted from https://gpfault.net/posts/typesafe-bitmasks.txt.html
|
||||
// with hints from https://www.strikerx3.dev/cpp/2019/02/27/typesafe-enum-class-bitmasks-in-cpp.html
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// enum_bitmasks can only be used with enums.
|
||||
template<class option_type, typename = typename std::enable_if<std::is_enum<option_type>::value>::type>
|
||||
class enum_bitmask {
|
||||
// The type we'll use for storing the value of our bitmask should be the same as the enum's underlying type.
|
||||
using underlying_type = typename std::underlying_type<option_type>::type;
|
||||
|
||||
// This method helps us avoid having to explicitly set enum values to powers of two.
|
||||
static constexpr underlying_type mask_value(option_type o) { return 1 << static_cast<underlying_type>(o); }
|
||||
|
||||
// Private ctor to be used internally.
|
||||
explicit constexpr enum_bitmask(underlying_type o) : m_bits(o) {}
|
||||
|
||||
public:
|
||||
// Default ctor creates a bitmask with no options selected.
|
||||
constexpr enum_bitmask() : m_bits(0) {}
|
||||
|
||||
// Creates a enum_bitmask with just one bit set.
|
||||
// This ctor is intentionally non-explicit, to allow passing an options to a function:
|
||||
// FunctionExpectingBitmask(Options::Opt1)
|
||||
constexpr enum_bitmask(option_type o) : m_bits(mask_value(o)) {}
|
||||
|
||||
// Set the bit corresponding to the given option.
|
||||
constexpr enum_bitmask operator|(option_type t) { return enum_bitmask(m_bits | mask_value(t)); }
|
||||
|
||||
// Combine with another enum_bitmask of the same type.
|
||||
constexpr enum_bitmask operator|(enum_bitmask<option_type> t) { return enum_bitmask(m_bits | t.m_bits); }
|
||||
|
||||
// Get the value of the bit corresponding to the given option.
|
||||
constexpr bool operator&(option_type t) { return m_bits & mask_value(t); }
|
||||
constexpr bool has(option_type t) { return m_bits & mask_value(t); }
|
||||
|
||||
private:
|
||||
underlying_type m_bits = 0;
|
||||
};
|
||||
|
||||
// For enabling free functions producing enum_bitmask<> type from bit operations on enums.
|
||||
template<typename Enum> struct is_enum_bitmask_type { static const bool enable = false; };
|
||||
#define ENABLE_ENUM_BITMASK_OPERATORS(x) template<> struct is_enum_bitmask_type<x> { static const bool enable = true; };
|
||||
template<class Enum> inline constexpr bool is_enum_bitmask_type_v = is_enum_bitmask_type<Enum>::enable;
|
||||
|
||||
// Creates an enum_bitmask from two options, convenient for passing of options to a function:
|
||||
// FunctionExpectingBitmask(Options::Opt1 | Options::Opt2 | Options::Opt3)
|
||||
template <class option_type>
|
||||
constexpr std::enable_if_t<is_enum_bitmask_type_v<option_type>, enum_bitmask<option_type>> operator|(option_type lhs, option_type rhs) {
|
||||
static_assert(std::is_enum_v<option_type>);
|
||||
return enum_bitmask<option_type>{lhs} | rhs;
|
||||
}
|
||||
|
||||
template <class option_type>
|
||||
constexpr std::enable_if_t<is_enum_bitmask_type_v<option_type>, enum_bitmask<option_type>> operator|(option_type lhs, enum_bitmask<option_type> rhs) {
|
||||
static_assert(std::is_enum_v<option_type>);
|
||||
return enum_bitmask<option_type>{lhs} | rhs;
|
||||
}
|
||||
|
||||
template <class option_type>
|
||||
constexpr std::enable_if_t<is_enum_bitmask_type_v<option_type>, enum_bitmask<option_type>> only_if(bool condition, option_type opt) {
|
||||
static_assert(std::is_enum_v<option_type>);
|
||||
return condition ? enum_bitmask<option_type>{opt} : enum_bitmask<option_type>{};
|
||||
}
|
||||
|
||||
template <class option_type>
|
||||
constexpr std::enable_if_t<is_enum_bitmask_type_v<option_type>, enum_bitmask<option_type>> only_if(bool condition, enum_bitmask<option_type> opt) {
|
||||
static_assert(std::is_enum_v<option_type>);
|
||||
return condition ? opt : enum_bitmask<option_type>{};
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_enum_bitmask_hpp_
|
|
@ -115,6 +115,7 @@
|
|||
#include "BoundingBox.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "Config.hpp"
|
||||
#include "enum_bitmask.hpp"
|
||||
#include "format.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "MultiPoint.hpp"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue