mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-10-22 16:21:24 -06:00
Fixed conflicts after merge with master
This commit is contained in:
commit
39ec1a6318
192 changed files with 7204 additions and 2296 deletions
|
@ -167,9 +167,6 @@ void AppConfig::set_defaults()
|
|||
if (get("show_splash_screen").empty())
|
||||
set("show_splash_screen", "1");
|
||||
|
||||
if (get("last_hint").empty())
|
||||
set("last_hint", "0");
|
||||
|
||||
if (get("show_hints").empty())
|
||||
set("show_hints", "1");
|
||||
|
||||
|
|
|
@ -45,14 +45,40 @@ static float max_brim_width(const ConstPrintObjectPtrsAdaptor &objects)
|
|||
}));
|
||||
}
|
||||
|
||||
static ConstPrintObjectPtrs get_top_level_objects_with_brim(const Print &print)
|
||||
// Returns ExPolygons of the bottom layer of the print object after elephant foot compensation.
|
||||
static ExPolygons get_print_object_bottom_layer_expolygons(const PrintObject &print_object)
|
||||
{
|
||||
ExPolygons ex_polygons;
|
||||
for (LayerRegion *region : print_object.layers().front()->regions())
|
||||
Slic3r::append(ex_polygons, offset_ex(offset_ex(region->slices.surfaces, float(SCALED_EPSILON)), -float(SCALED_EPSILON)));
|
||||
return ex_polygons;
|
||||
}
|
||||
|
||||
// Returns ExPolygons of bottom layer for every print object in Print after elephant foot compensation.
|
||||
static std::vector<ExPolygons> get_print_bottom_layers_expolygons(const Print &print)
|
||||
{
|
||||
std::vector<ExPolygons> bottom_layers_expolygons;
|
||||
bottom_layers_expolygons.reserve(print.objects().size());
|
||||
for (const PrintObject *object : print.objects())
|
||||
bottom_layers_expolygons.emplace_back(get_print_object_bottom_layer_expolygons(*object));
|
||||
|
||||
return bottom_layers_expolygons;
|
||||
}
|
||||
|
||||
static ConstPrintObjectPtrs get_top_level_objects_with_brim(const Print &print, const std::vector<ExPolygons> &bottom_layers_expolygons)
|
||||
{
|
||||
assert(print.objects().size() == bottom_layers_expolygons.size());
|
||||
Polygons islands;
|
||||
ConstPrintObjectPtrs island_to_object;
|
||||
for (const PrintObject *object : print.objects()) {
|
||||
for(size_t print_object_idx = 0; print_object_idx < print.objects().size(); ++print_object_idx) {
|
||||
const PrintObject *object = print.objects()[print_object_idx];
|
||||
|
||||
if (! object->has_brim())
|
||||
continue;
|
||||
|
||||
Polygons islands_object;
|
||||
islands_object.reserve(object->layers().front()->lslices.size());
|
||||
for (const ExPolygon &ex_poly : object->layers().front()->lslices)
|
||||
islands_object.reserve(bottom_layers_expolygons[print_object_idx].size());
|
||||
for (const ExPolygon &ex_poly : bottom_layers_expolygons[print_object_idx])
|
||||
islands_object.emplace_back(ex_poly.contour);
|
||||
|
||||
islands.reserve(islands.size() + object->instances().size() * islands_object.size());
|
||||
|
@ -110,7 +136,7 @@ static Polygons top_level_outer_brim_islands(const ConstPrintObjectPtrs &top_lev
|
|||
//FIXME how about the brim type?
|
||||
auto brim_offset = float(scale_(object->config().brim_offset.value));
|
||||
Polygons islands_object;
|
||||
for (const ExPolygon &ex_poly : object->layers().front()->lslices) {
|
||||
for (const ExPolygon &ex_poly : get_print_object_bottom_layer_expolygons(*object)) {
|
||||
Polygons contour_offset = offset(ex_poly.contour, brim_offset);
|
||||
for (Polygon &poly : contour_offset)
|
||||
poly.douglas_peucker(SCALED_RESOLUTION);
|
||||
|
@ -124,8 +150,12 @@ static Polygons top_level_outer_brim_islands(const ConstPrintObjectPtrs &top_lev
|
|||
return islands;
|
||||
}
|
||||
|
||||
static ExPolygons top_level_outer_brim_area(const Print &print, const ConstPrintObjectPtrs &top_level_objects_with_brim, const float no_brim_offset)
|
||||
static ExPolygons top_level_outer_brim_area(const Print &print,
|
||||
const ConstPrintObjectPtrs &top_level_objects_with_brim,
|
||||
const std::vector<ExPolygons> &bottom_layers_expolygons,
|
||||
const float no_brim_offset)
|
||||
{
|
||||
assert(print.objects().size() == bottom_layers_expolygons.size());
|
||||
std::unordered_set<size_t> top_level_objects_idx;
|
||||
top_level_objects_idx.reserve(top_level_objects_with_brim.size());
|
||||
for (const PrintObject *object : top_level_objects_with_brim)
|
||||
|
@ -133,15 +163,16 @@ static ExPolygons top_level_outer_brim_area(const Print &print, const ConstPrint
|
|||
|
||||
ExPolygons brim_area;
|
||||
ExPolygons no_brim_area;
|
||||
for (const PrintObject *object : print.objects()) {
|
||||
const BrimType brim_type = object->config().brim_type.value;
|
||||
const float brim_offset = scale_(object->config().brim_offset.value);
|
||||
const float brim_width = scale_(object->config().brim_width.value);
|
||||
const bool is_top_outer_brim = top_level_objects_idx.find(object->id().id) != top_level_objects_idx.end();
|
||||
for(size_t print_object_idx = 0; print_object_idx < print.objects().size(); ++print_object_idx) {
|
||||
const PrintObject *object = print.objects()[print_object_idx];
|
||||
const BrimType brim_type = object->config().brim_type.value;
|
||||
const float brim_offset = scale_(object->config().brim_offset.value);
|
||||
const float brim_width = scale_(object->config().brim_width.value);
|
||||
const bool is_top_outer_brim = top_level_objects_idx.find(object->id().id) != top_level_objects_idx.end();
|
||||
|
||||
ExPolygons brim_area_object;
|
||||
ExPolygons no_brim_area_object;
|
||||
for (const ExPolygon &ex_poly : object->layers().front()->lslices) {
|
||||
for (const ExPolygon &ex_poly : bottom_layers_expolygons[print_object_idx]) {
|
||||
if ((brim_type == BrimType::btOuterOnly || brim_type == BrimType::btOuterAndInner) && is_top_outer_brim)
|
||||
append(brim_area_object, diff_ex(offset(ex_poly.contour, brim_width + brim_offset), offset(ex_poly.contour, brim_offset)));
|
||||
|
||||
|
@ -166,8 +197,12 @@ static ExPolygons top_level_outer_brim_area(const Print &print, const ConstPrint
|
|||
return diff_ex(brim_area, no_brim_area);
|
||||
}
|
||||
|
||||
static ExPolygons inner_brim_area(const Print &print, const ConstPrintObjectPtrs &top_level_objects_with_brim, const float no_brim_offset)
|
||||
static ExPolygons inner_brim_area(const Print &print,
|
||||
const ConstPrintObjectPtrs &top_level_objects_with_brim,
|
||||
const std::vector<ExPolygons> &bottom_layers_expolygons,
|
||||
const float no_brim_offset)
|
||||
{
|
||||
assert(print.objects().size() == bottom_layers_expolygons.size());
|
||||
std::unordered_set<size_t> top_level_objects_idx;
|
||||
top_level_objects_idx.reserve(top_level_objects_with_brim.size());
|
||||
for (const PrintObject *object : top_level_objects_with_brim)
|
||||
|
@ -176,16 +211,17 @@ static ExPolygons inner_brim_area(const Print &print, const ConstPrintObjectPtrs
|
|||
ExPolygons brim_area;
|
||||
ExPolygons no_brim_area;
|
||||
Polygons holes;
|
||||
for (const PrintObject *object : print.objects()) {
|
||||
const BrimType brim_type = object->config().brim_type.value;
|
||||
const float brim_offset = scale_(object->config().brim_offset.value);
|
||||
const float brim_width = scale_(object->config().brim_width.value);
|
||||
const bool top_outer_brim = top_level_objects_idx.find(object->id().id) != top_level_objects_idx.end();
|
||||
for(size_t print_object_idx = 0; print_object_idx < print.objects().size(); ++print_object_idx) {
|
||||
const PrintObject *object = print.objects()[print_object_idx];
|
||||
const BrimType brim_type = object->config().brim_type.value;
|
||||
const float brim_offset = scale_(object->config().brim_offset.value);
|
||||
const float brim_width = scale_(object->config().brim_width.value);
|
||||
const bool top_outer_brim = top_level_objects_idx.find(object->id().id) != top_level_objects_idx.end();
|
||||
|
||||
ExPolygons brim_area_object;
|
||||
ExPolygons no_brim_area_object;
|
||||
Polygons holes_object;
|
||||
for (const ExPolygon &ex_poly : object->layers().front()->lslices) {
|
||||
for (const ExPolygon &ex_poly : bottom_layers_expolygons[print_object_idx]) {
|
||||
if (brim_type == BrimType::btOuterOnly || brim_type == BrimType::btOuterAndInner) {
|
||||
if (top_outer_brim)
|
||||
no_brim_area_object.emplace_back(ex_poly);
|
||||
|
@ -204,7 +240,7 @@ static ExPolygons inner_brim_area(const Print &print, const ConstPrintObjectPtrs
|
|||
|
||||
append(holes_object, ex_poly.holes);
|
||||
}
|
||||
append(no_brim_area_object, offset_ex(object->layers().front()->lslices, brim_offset));
|
||||
append(no_brim_area_object, offset_ex(bottom_layers_expolygons[print_object_idx], brim_offset));
|
||||
|
||||
for (const PrintInstance &instance : object->instances()) {
|
||||
append_and_translate(brim_area, brim_area_object, instance);
|
||||
|
@ -236,7 +272,7 @@ static void optimize_polylines_by_reversing(Polylines *polylines)
|
|||
static Polylines connect_brim_lines(Polylines &&polylines, const Polygons &brim_area, float max_connection_length)
|
||||
{
|
||||
if (polylines.empty())
|
||||
return Polylines();
|
||||
return {};
|
||||
|
||||
BoundingBox bbox = get_extents(polylines);
|
||||
bbox.merge(get_extents(brim_area));
|
||||
|
@ -305,16 +341,20 @@ static Polylines connect_brim_lines(Polylines &&polylines, const Polygons &brim_
|
|||
}
|
||||
}
|
||||
if (end < polylines.size())
|
||||
polylines.erase(polylines.begin() + end, polylines.end());
|
||||
polylines.erase(polylines.begin() + int(end), polylines.end());
|
||||
}
|
||||
|
||||
return std::move(polylines);
|
||||
}
|
||||
|
||||
static void make_inner_brim(const Print &print, const ConstPrintObjectPtrs &top_level_objects_with_brim, ExtrusionEntityCollection &brim)
|
||||
static void make_inner_brim(const Print &print,
|
||||
const ConstPrintObjectPtrs &top_level_objects_with_brim,
|
||||
const std::vector<ExPolygons> &bottom_layers_expolygons,
|
||||
ExtrusionEntityCollection &brim)
|
||||
{
|
||||
assert(print.objects().size() == bottom_layers_expolygons.size());
|
||||
Flow flow = print.brim_flow();
|
||||
ExPolygons islands_ex = inner_brim_area(print, top_level_objects_with_brim, float(flow.scaled_spacing()));
|
||||
ExPolygons islands_ex = inner_brim_area(print, top_level_objects_with_brim, bottom_layers_expolygons, float(flow.scaled_spacing()));
|
||||
Polygons loops;
|
||||
islands_ex = offset_ex(islands_ex, -0.5f * float(flow.scaled_spacing()), jtSquare);
|
||||
for (size_t i = 0; !islands_ex.empty(); ++i) {
|
||||
|
@ -334,11 +374,12 @@ static void make_inner_brim(const Print &print, const ConstPrintObjectPtrs &top_
|
|||
// Collect islands_area to be merged into the final 1st layer convex hull.
|
||||
ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cancel, Polygons &islands_area)
|
||||
{
|
||||
Flow flow = print.brim_flow();
|
||||
ConstPrintObjectPtrs top_level_objects_with_brim = get_top_level_objects_with_brim(print);
|
||||
Polygons islands = top_level_outer_brim_islands(top_level_objects_with_brim);
|
||||
ExPolygons islands_area_ex = top_level_outer_brim_area(print, top_level_objects_with_brim, float(flow.scaled_spacing()));
|
||||
islands_area = to_polygons(islands_area_ex);
|
||||
Flow flow = print.brim_flow();
|
||||
std::vector<ExPolygons> bottom_layers_expolygons = get_print_bottom_layers_expolygons(print);
|
||||
ConstPrintObjectPtrs top_level_objects_with_brim = get_top_level_objects_with_brim(print, bottom_layers_expolygons);
|
||||
Polygons islands = top_level_outer_brim_islands(top_level_objects_with_brim);
|
||||
ExPolygons islands_area_ex = top_level_outer_brim_area(print, top_level_objects_with_brim, bottom_layers_expolygons, float(flow.scaled_spacing()));
|
||||
islands_area = to_polygons(islands_area_ex);
|
||||
|
||||
Polygons loops;
|
||||
size_t num_loops = size_t(floor(max_brim_width(print.objects()) / flow.spacing()));
|
||||
|
@ -536,7 +577,7 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance
|
|||
extrusion_entities_append_loops_and_paths(brim.entities, std::move(all_loops), erSkirt, float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height()));
|
||||
}
|
||||
|
||||
make_inner_brim(print, top_level_objects_with_brim, brim);
|
||||
make_inner_brim(print, top_level_objects_with_brim, bottom_layers_expolygons, brim);
|
||||
return brim;
|
||||
}
|
||||
|
||||
|
|
|
@ -178,6 +178,8 @@ add_library(libslic3r STATIC
|
|||
PrintRegion.cpp
|
||||
PNGReadWrite.hpp
|
||||
PNGReadWrite.cpp
|
||||
QuadricEdgeCollapse.cpp
|
||||
QuadricEdgeCollapse.hpp
|
||||
Semver.cpp
|
||||
ShortestPath.cpp
|
||||
ShortestPath.hpp
|
||||
|
|
|
@ -223,10 +223,13 @@ std::vector<std::string> ConfigOptionDef::cli_args(const std::string &key) const
|
|||
{
|
||||
std::vector<std::string> args;
|
||||
if (this->cli != ConfigOptionDef::nocli) {
|
||||
std::string cli = this->cli.substr(0, this->cli.find("="));
|
||||
boost::trim_right_if(cli, boost::is_any_of("!"));
|
||||
const std::string &cli = this->cli;
|
||||
//FIXME What was that for? Check the "readline" documentation.
|
||||
// Neither '=' nor '!' is used in any of the cli parameters currently defined by PrusaSlicer.
|
||||
// std::string cli = this->cli.substr(0, this->cli.find("="));
|
||||
// boost::trim_right_if(cli, boost::is_any_of("!"));
|
||||
if (cli.empty()) {
|
||||
// Add the key
|
||||
// Convert an option key to CLI argument by replacing underscores with dashes.
|
||||
std::string opt = key;
|
||||
boost::replace_all(opt, "_", "-");
|
||||
args.emplace_back(std::move(opt));
|
||||
|
@ -245,7 +248,7 @@ ConfigOption* ConfigOptionDef::create_empty_option() const
|
|||
case coPercents: return new ConfigOptionPercentsNullable();
|
||||
case coFloatsOrPercents: return new ConfigOptionFloatsOrPercentsNullable();
|
||||
case coBools: return new ConfigOptionBoolsNullable();
|
||||
default: throw Slic3r::RuntimeError(std::string("Unknown option type for nullable option ") + this->label);
|
||||
default: throw ConfigurationError(std::string("Unknown option type for nullable option ") + this->label);
|
||||
}
|
||||
} else {
|
||||
switch (this->type) {
|
||||
|
@ -266,7 +269,7 @@ ConfigOption* ConfigOptionDef::create_empty_option() const
|
|||
case coBool: return new ConfigOptionBool();
|
||||
case coBools: return new ConfigOptionBools();
|
||||
case coEnum: return new ConfigOptionEnumGeneric(this->enum_keys_map);
|
||||
default: throw Slic3r::RuntimeError(std::string("Unknown option type for option ") + this->label);
|
||||
default: throw ConfigurationError(std::string("Unknown option type for option ") + this->label);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -423,7 +426,19 @@ void ConfigBase::apply_only(const ConfigBase &other, const t_config_option_keys
|
|||
}
|
||||
}
|
||||
|
||||
// this will *ignore* options not present in both configs
|
||||
// Are the two configs equal? Ignoring options not present in both configs.
|
||||
bool ConfigBase::equals(const ConfigBase &other) const
|
||||
{
|
||||
for (const t_config_option_key &opt_key : this->keys()) {
|
||||
const ConfigOption *this_opt = this->option(opt_key);
|
||||
const ConfigOption *other_opt = other.option(opt_key);
|
||||
if (this_opt != nullptr && other_opt != nullptr && *this_opt != *other_opt)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns options differing in the two configs, ignoring options not present in both configs.
|
||||
t_config_option_keys ConfigBase::diff(const ConfigBase &other) const
|
||||
{
|
||||
t_config_option_keys diff;
|
||||
|
@ -436,6 +451,7 @@ t_config_option_keys ConfigBase::diff(const ConfigBase &other) const
|
|||
return diff;
|
||||
}
|
||||
|
||||
// Returns options being equal in the two configs, ignoring options not present in both configs.
|
||||
t_config_option_keys ConfigBase::equal(const ConfigBase &other) const
|
||||
{
|
||||
t_config_option_keys equal;
|
||||
|
@ -494,7 +510,7 @@ bool ConfigBase::set_deserialize_nothrow(const t_config_option_key &opt_key_src,
|
|||
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, substitutions_ctxt, append))
|
||||
throw BadOptionTypeException(format("ConfigBase::set_deserialize() failed for parameter \"%1%\", value \"%2%\"", opt_key_src, value_src));
|
||||
throw BadOptionValueException(format("Invalid value provided for parameter %1%: %2%", opt_key_src, value_src));
|
||||
}
|
||||
|
||||
void ConfigBase::set_deserialize(std::initializer_list<SetDeserializeItem> items, ConfigSubstitutionContext& substitutions_ctxt)
|
||||
|
@ -539,26 +555,50 @@ bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, con
|
|||
|
||||
ConfigOption *opt = this->option(opt_key, true);
|
||||
assert(opt != nullptr);
|
||||
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);
|
||||
bool success = false;
|
||||
bool substituted = false;
|
||||
if (optdef->type == coBools && substitutions_ctxt.rule != ForwardCompatibilitySubstitutionRule::Disable) {
|
||||
//FIXME Special handling of vectors of bools, quick and not so dirty solution before PrusaSlicer 2.3.2 release.
|
||||
bool nullable = opt->nullable();
|
||||
ConfigHelpers::DeserializationSubstitution default_value = ConfigHelpers::DeserializationSubstitution::DefaultsToFalse;
|
||||
if (optdef->default_value) {
|
||||
// Default value for vectors of booleans used in a "per extruder" context, thus the default contains just a single value.
|
||||
assert(dynamic_cast<const ConfigOptionVector<unsigned char>*>(optdef->default_value.get()));
|
||||
auto &values = static_cast<const ConfigOptionVector<unsigned char>*>(optdef->default_value.get())->values;
|
||||
if (values.size() == 1 && values.front() == 1)
|
||||
default_value = ConfigHelpers::DeserializationSubstitution::DefaultsToTrue;
|
||||
}
|
||||
auto result = nullable ?
|
||||
static_cast<ConfigOptionBoolsNullable*>(opt)->deserialize_with_substitutions(value, append, default_value) :
|
||||
static_cast<ConfigOptionBools*>(opt)->deserialize_with_substitutions(value, append, default_value);
|
||||
success = result != ConfigHelpers::DeserializationResult::Failed;
|
||||
substituted = result == ConfigHelpers::DeserializationResult::Substituted;
|
||||
} else {
|
||||
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) && ConfigHelpers::looks_like_enum_value(value)) {
|
||||
// Deserialize failed, try to substitute with a default value.
|
||||
assert(substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::Enable || substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::EnableSilent);
|
||||
if (optdef->type == coBool)
|
||||
static_cast<ConfigOptionBool*>(opt)->value = ConfigHelpers::enum_looks_like_true_value(value);
|
||||
else
|
||||
// Just use the default of the option.
|
||||
opt->set(optdef->default_value.get());
|
||||
success = true;
|
||||
substituted = true;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
if (substituted && (substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::Enable ||
|
||||
substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::EnableSystemSilent)) {
|
||||
// Log the substitution.
|
||||
ConfigSubstitution config_substitution;
|
||||
config_substitution.opt_def = optdef;
|
||||
config_substitution.old_value = value;
|
||||
config_substitution.new_value = ConfigOptionUniquePtr(opt->clone());
|
||||
substitutions_ctxt.substitutions.emplace_back(std::move(config_substitution));
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
@ -585,7 +625,7 @@ double ConfigBase::get_abs_value(const t_config_option_key &opt_key) const
|
|||
return opt_def->ratio_over.empty() ? 0. :
|
||||
static_cast<const ConfigOptionFloatOrPercent*>(raw_opt)->get_abs_value(this->get_abs_value(opt_def->ratio_over));
|
||||
}
|
||||
throw Slic3r::RuntimeError("ConfigBase::get_abs_value(): Not a valid option type for get_abs_value()");
|
||||
throw ConfigurationError("ConfigBase::get_abs_value(): Not a valid option type for get_abs_value()");
|
||||
}
|
||||
|
||||
// Return an absolute value of a possibly relative config variable.
|
||||
|
@ -596,7 +636,7 @@ double ConfigBase::get_abs_value(const t_config_option_key &opt_key, double rati
|
|||
const ConfigOption *raw_opt = this->option(opt_key);
|
||||
assert(raw_opt != nullptr);
|
||||
if (raw_opt->type() != coFloatOrPercent)
|
||||
throw Slic3r::RuntimeError("ConfigBase::get_abs_value(): opt_key is not of coFloatOrPercent");
|
||||
throw ConfigurationError("ConfigBase::get_abs_value(): opt_key is not of coFloatOrPercent");
|
||||
// Compute absolute value.
|
||||
return static_cast<const ConfigOptionFloatOrPercent*>(raw_opt)->get_abs_value(ratio_over);
|
||||
}
|
||||
|
@ -622,18 +662,71 @@ void ConfigBase::setenv_() const
|
|||
ConfigSubstitutions ConfigBase::load(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule)
|
||||
{
|
||||
return is_gcode_file(file) ?
|
||||
this->load_from_gcode_file(file, true /* check header */, compatibility_rule) :
|
||||
this->load_from_gcode_file(file, compatibility_rule) :
|
||||
this->load_from_ini(file, compatibility_rule);
|
||||
}
|
||||
|
||||
ConfigSubstitutions ConfigBase::load_from_ini(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule)
|
||||
{
|
||||
try {
|
||||
boost::property_tree::ptree tree;
|
||||
boost::nowide::ifstream ifs(file);
|
||||
boost::property_tree::read_ini(ifs, tree);
|
||||
return this->load(tree, compatibility_rule);
|
||||
} catch (const ConfigurationError &e) {
|
||||
throw ConfigurationError(format("Failed loading configuration file \"%1%\": %2%", file, e.what()));
|
||||
}
|
||||
}
|
||||
|
||||
ConfigSubstitutions ConfigBase::load_from_ini_string(const std::string &data, ForwardCompatibilitySubstitutionRule compatibility_rule)
|
||||
{
|
||||
boost::property_tree::ptree tree;
|
||||
boost::nowide::ifstream ifs(file);
|
||||
boost::property_tree::read_ini(ifs, tree);
|
||||
std::istringstream iss(data);
|
||||
boost::property_tree::read_ini(iss, tree);
|
||||
return this->load(tree, compatibility_rule);
|
||||
}
|
||||
|
||||
// Loading a "will be one day a legacy format" of configuration stored into 3MF or AMF.
|
||||
// Accepts the same data as load_from_ini_string(), only with each configuration line possibly prefixed with a semicolon (G-code comment).
|
||||
ConfigSubstitutions ConfigBase::load_from_ini_string_commented(std::string &&data, ForwardCompatibilitySubstitutionRule compatibility_rule)
|
||||
{
|
||||
// Convert the "data" string into INI format by removing the semi-colons at the start of a line.
|
||||
// Also the "; generated by PrusaSlicer ..." comment line will be removed.
|
||||
size_t j = 0;
|
||||
for (size_t i = 0; i < data.size();)
|
||||
if (i == 0 || data[i] == '\n') {
|
||||
// Start of a line.
|
||||
if (i != 0) {
|
||||
// Consume LF.
|
||||
assert(data[i] == '\n');
|
||||
// Don't keep empty lines.
|
||||
if (j > 0 && data[j - 1] != '\n')
|
||||
data[j ++] = data[i];
|
||||
++ i;
|
||||
}
|
||||
// Skip all leading spaces;
|
||||
for (; i < data.size() && (data[i] == ' ' || data[i] == '\t'); ++ i) ;
|
||||
// Skip the semicolon (comment indicator).
|
||||
if (i < data.size() && data[i] == ';')
|
||||
++ i;
|
||||
// Skip all leading spaces after semicolon.
|
||||
for (; i < data.size() && (data[i] == ' ' || data[i] == '\t'); ++ i) ;
|
||||
if (strncmp(data.data() + i, "generated by ", 13) == 0) {
|
||||
// Skip the "; generated by ..." line.
|
||||
for (; i < data.size() && data[i] != '\n'; ++ i);
|
||||
}
|
||||
} else if (data[i] == '\r' && i + 1 < data.size() && data[i + 1] == '\n') {
|
||||
// Skip CR.
|
||||
++ i;
|
||||
} else {
|
||||
// Consume the rest of the data.
|
||||
data[j ++] = data[i ++];
|
||||
}
|
||||
data.erase(data.begin() + j, data.end());
|
||||
|
||||
return this->load_from_ini_string(data, compatibility_rule);
|
||||
}
|
||||
|
||||
ConfigSubstitutions ConfigBase::load(const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule)
|
||||
{
|
||||
ConfigSubstitutionContext substitutions_ctxt(compatibility_rule);
|
||||
|
@ -648,37 +741,8 @@ ConfigSubstitutions ConfigBase::load(const boost::property_tree::ptree &tree, Fo
|
|||
return std::move(substitutions_ctxt.substitutions);
|
||||
}
|
||||
|
||||
// Load the config keys from the tail of a G-code file.
|
||||
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);
|
||||
if (check_header) {
|
||||
const char slic3r_gcode_header[] = "; generated by Slic3r ";
|
||||
const char prusaslicer_gcode_header[] = "; generated by PrusaSlicer ";
|
||||
std::string firstline;
|
||||
std::getline(ifs, firstline);
|
||||
if (strncmp(slic3r_gcode_header, firstline.c_str(), strlen(slic3r_gcode_header)) != 0 &&
|
||||
strncmp(prusaslicer_gcode_header, firstline.c_str(), strlen(prusaslicer_gcode_header)) != 0)
|
||||
throw Slic3r::RuntimeError("Not a PrusaSlicer / Slic3r PE generated g-code.");
|
||||
}
|
||||
ifs.seekg(0, ifs.end);
|
||||
auto file_length = ifs.tellg();
|
||||
auto data_length = std::min<std::fstream::pos_type>(65535, file_length);
|
||||
ifs.seekg(file_length - data_length, ifs.beg);
|
||||
std::vector<char> data(size_t(data_length) + 1, 0);
|
||||
ifs.read(data.data(), data_length);
|
||||
ifs.close();
|
||||
|
||||
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, ConfigSubstitutionContext& substitutions)
|
||||
static inline size_t load_from_gcode_string_legacy(ConfigBase &config, const char *str, ConfigSubstitutionContext &substitutions)
|
||||
{
|
||||
if (str == nullptr)
|
||||
return 0;
|
||||
|
@ -701,7 +765,7 @@ size_t ConfigBase::load_from_gcode_string(const char* str, ConfigSubstitutionCon
|
|||
if (end - (++ start) < 10 || start[0] != ';' || start[1] != ' ')
|
||||
break;
|
||||
const char *key = start + 2;
|
||||
if (!(*key >= 'a' && *key <= 'z') || (*key >= 'A' && *key <= 'Z'))
|
||||
if (!((*key >= 'a' && *key <= 'z') || (*key >= 'A' && *key <= 'Z')))
|
||||
// A key must start with a letter.
|
||||
break;
|
||||
const char *sep = key;
|
||||
|
@ -723,7 +787,7 @@ size_t ConfigBase::load_from_gcode_string(const char* str, ConfigSubstitutionCon
|
|||
if (key == nullptr)
|
||||
break;
|
||||
try {
|
||||
this->set_deserialize(std::string(key, key_end), std::string(value, end), substitutions);
|
||||
config.set_deserialize(std::string(key, key_end), std::string(value, end), substitutions);
|
||||
++num_key_value_pairs;
|
||||
}
|
||||
catch (UnknownOptionException & /* e */) {
|
||||
|
@ -732,7 +796,175 @@ size_t ConfigBase::load_from_gcode_string(const char* str, ConfigSubstitutionCon
|
|||
end = start;
|
||||
}
|
||||
|
||||
return num_key_value_pairs;
|
||||
return num_key_value_pairs;
|
||||
}
|
||||
|
||||
// Reading a config from G-code back to front for performance reasons: We don't want to scan
|
||||
// hundreds of MB file for a short config block, which we expect to find at the end of the G-code.
|
||||
class ReverseLineReader
|
||||
{
|
||||
public:
|
||||
using pos_type = boost::nowide::ifstream::pos_type;
|
||||
|
||||
// Stop at file_start
|
||||
ReverseLineReader(boost::nowide::ifstream &ifs, pos_type file_start) : m_ifs(ifs), m_file_start(file_start)
|
||||
{
|
||||
m_ifs.seekg(0, m_ifs.end);
|
||||
m_file_pos = m_ifs.tellg();
|
||||
m_block.assign(m_block_size, 0);
|
||||
}
|
||||
|
||||
bool getline(std::string &out) {
|
||||
out.clear();
|
||||
for (;;) {
|
||||
if (m_block_len == 0) {
|
||||
// Read the next block.
|
||||
m_block_len = size_t(std::min<std::fstream::pos_type>(m_block_size, m_file_pos - m_file_start));
|
||||
if (m_block_len == 0)
|
||||
return false;
|
||||
m_file_pos -= m_block_len;
|
||||
m_ifs.seekg(m_file_pos, m_ifs.beg);
|
||||
if (! m_ifs.read(m_block.data(), m_block_len))
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(m_block_len > 0);
|
||||
// Non-empty buffer. Find another LF.
|
||||
int i = int(m_block_len) - 1;
|
||||
for (; i >= 0; -- i)
|
||||
if (m_block[i] == '\n')
|
||||
break;
|
||||
// i is position of LF or -1 if not found.
|
||||
if (i == -1) {
|
||||
// LF not found. Just make a backup of the buffer and continue.
|
||||
out.insert(out.begin(), m_block.begin(), m_block.begin() + m_block_len);
|
||||
m_block_len = 0;
|
||||
} else {
|
||||
assert(i >= 0);
|
||||
// Copy new line to the output. It may be empty.
|
||||
out.insert(out.begin(), m_block.begin() + i + 1, m_block.begin() + m_block_len);
|
||||
// Block length without the newline.
|
||||
m_block_len = i;
|
||||
// Remove CRLF from the end of the block.
|
||||
if (m_block_len > 0 && m_block[m_block_len - 1] == '\r')
|
||||
-- m_block_len;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
assert(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
boost::nowide::ifstream &m_ifs;
|
||||
std::vector<char> m_block;
|
||||
size_t m_block_size = 65536;
|
||||
size_t m_block_len = 0;
|
||||
pos_type m_file_start;
|
||||
pos_type m_file_pos = 0;
|
||||
};
|
||||
|
||||
// Load the config keys from the tail of a G-code file.
|
||||
ConfigSubstitutions ConfigBase::load_from_gcode_file(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule)
|
||||
{
|
||||
// Read a 64k block from the end of the G-code.
|
||||
boost::nowide::ifstream ifs(file);
|
||||
// Look for Slic3r or PrusaSlicer header.
|
||||
// Look for the header across the whole file as the G-code may have been extended at the start by a post-processing script or the user.
|
||||
bool has_delimiters = false;
|
||||
{
|
||||
static constexpr const char slic3r_gcode_header[] = "; generated by Slic3r ";
|
||||
static constexpr const char prusaslicer_gcode_header[] = "; generated by PrusaSlicer ";
|
||||
std::string header;
|
||||
bool header_found = false;
|
||||
while (std::getline(ifs, header)) {
|
||||
if (strncmp(slic3r_gcode_header, header.c_str(), strlen(slic3r_gcode_header)) == 0) {
|
||||
header_found = true;
|
||||
break;
|
||||
} else if (strncmp(prusaslicer_gcode_header, header.c_str(), strlen(prusaslicer_gcode_header)) == 0) {
|
||||
// Parse PrusaSlicer version.
|
||||
size_t i = strlen(prusaslicer_gcode_header);
|
||||
for (; i < header.size() && header[i] == ' '; ++ i) ;
|
||||
size_t j = i;
|
||||
for (; j < header.size() && header[j] != ' '; ++ j) ;
|
||||
try {
|
||||
Semver semver(header.substr(i, j - i));
|
||||
has_delimiters = semver >= Semver(2, 4, 0, nullptr, "alpha0");
|
||||
} catch (const RuntimeError &) {
|
||||
}
|
||||
header_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (! header_found)
|
||||
throw Slic3r::RuntimeError("Not a PrusaSlicer / Slic3r PE generated g-code.");
|
||||
}
|
||||
|
||||
auto header_end_pos = ifs.tellg();
|
||||
ConfigSubstitutionContext substitutions_ctxt(compatibility_rule);
|
||||
size_t key_value_pairs = 0;
|
||||
|
||||
if (has_delimiters)
|
||||
{
|
||||
// PrusaSlicer starting with 2.4.0-alpha0 delimits the config section stored into G-code with
|
||||
// ; prusaslicer_config = begin
|
||||
// ...
|
||||
// ; prusaslicer_config = end
|
||||
// The begin / end tags look like any other key / value pairs on purpose to be compatible with older G-code viewer.
|
||||
// Read the file in reverse line by line.
|
||||
ReverseLineReader reader(ifs, header_end_pos);
|
||||
// Read the G-code file by 64k blocks back to front.
|
||||
bool begin_found = false;
|
||||
bool end_found = false;
|
||||
std::string line;
|
||||
while (reader.getline(line))
|
||||
if (line == "; prusaslicer_config = end") {
|
||||
end_found = true;
|
||||
break;
|
||||
}
|
||||
if (! end_found)
|
||||
throw Slic3r::RuntimeError(format("Configuration block closing tag \"; prusaslicer_config = end\" not found when reading %1%", file));
|
||||
std::string key, value;
|
||||
while (reader.getline(line)) {
|
||||
if (line == "; prusaslicer_config = begin") {
|
||||
begin_found = true;
|
||||
break;
|
||||
}
|
||||
// line should be a valid key = value pair.
|
||||
auto pos = line.find('=');
|
||||
if (pos != std::string::npos && pos > 1 && line.front() == ';') {
|
||||
key = line.substr(1, pos - 1);
|
||||
value = line.substr(pos + 1);
|
||||
boost::trim(key);
|
||||
boost::trim(value);
|
||||
try {
|
||||
this->set_deserialize(key, value, substitutions_ctxt);
|
||||
++ key_value_pairs;
|
||||
} catch (UnknownOptionException & /* e */) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
if (! begin_found)
|
||||
throw Slic3r::RuntimeError(format("Configuration block opening tag \"; prusaslicer_config = begin\" not found when reading %1%", file));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Slic3r or PrusaSlicer older than 2.4.0-alpha0 do not emit any delimiter.
|
||||
// Try a heuristics reading the G-code from back.
|
||||
ifs.seekg(0, ifs.end);
|
||||
auto file_length = ifs.tellg();
|
||||
auto data_length = std::min<std::fstream::pos_type>(65535, file_length - header_end_pos);
|
||||
ifs.seekg(file_length - data_length, ifs.beg);
|
||||
std::vector<char> data(size_t(data_length) + 1, 0);
|
||||
ifs.read(data.data(), data_length);
|
||||
ifs.close();
|
||||
key_value_pairs = load_from_gcode_string_legacy(*this, 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);
|
||||
}
|
||||
|
||||
void ConfigBase::save(const std::string &file) const
|
||||
|
@ -803,7 +1035,7 @@ ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool cre
|
|||
throw NoDefinitionException(opt_key);
|
||||
const ConfigOptionDef *optdef = def->get(opt_key);
|
||||
if (optdef == nullptr)
|
||||
// throw Slic3r::RuntimeError(std::string("Invalid option name: ") + opt_key);
|
||||
// throw ConfigurationError(std::string("Invalid option name: ") + opt_key);
|
||||
// Let the parent decide what to do if the opt_key is not defined by this->def().
|
||||
return nullptr;
|
||||
ConfigOption *opt = optdef->create_default_option();
|
||||
|
@ -817,22 +1049,12 @@ const ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key) co
|
|||
return (it == options.end()) ? nullptr : it->second.get();
|
||||
}
|
||||
|
||||
void DynamicConfig::read_cli(const std::vector<std::string> &tokens, t_config_option_keys* extra, t_config_option_keys* keys)
|
||||
{
|
||||
std::vector<const char*> args;
|
||||
// push a bogus executable name (argv[0])
|
||||
args.emplace_back("");
|
||||
for (size_t i = 0; i < tokens.size(); ++ i)
|
||||
args.emplace_back(tokens[i].c_str());
|
||||
this->read_cli(int(args.size()), args.data(), extra, keys);
|
||||
}
|
||||
|
||||
bool DynamicConfig::read_cli(int argc, const char* const argv[], t_config_option_keys* extra, t_config_option_keys* keys)
|
||||
{
|
||||
// cache the CLI option => opt_key mapping
|
||||
std::map<std::string,std::string> opts;
|
||||
for (const auto &oit : this->def()->options)
|
||||
for (auto t : oit.second.cli_args(oit.first))
|
||||
for (const std::string &t : oit.second.cli_args(oit.first))
|
||||
opts[t] = oit.first;
|
||||
|
||||
bool parse_options = true;
|
||||
|
@ -854,14 +1076,8 @@ bool DynamicConfig::read_cli(int argc, const char* const argv[], t_config_option
|
|||
parse_options = false;
|
||||
continue;
|
||||
}
|
||||
// Remove leading dashes
|
||||
boost::trim_left_if(token, boost::is_any_of("-"));
|
||||
// Remove the "no-" prefix used to negate boolean options.
|
||||
bool no = false;
|
||||
if (boost::starts_with(token, "no-")) {
|
||||
no = true;
|
||||
boost::replace_first(token, "no-", "");
|
||||
}
|
||||
// Remove leading dashes (one or two).
|
||||
token.erase(token.begin(), token.begin() + (boost::starts_with(token, "--") ? 2 : 1));
|
||||
// Read value when supplied in the --key=value form.
|
||||
std::string value;
|
||||
{
|
||||
|
@ -872,22 +1088,45 @@ bool DynamicConfig::read_cli(int argc, const char* const argv[], t_config_option
|
|||
}
|
||||
}
|
||||
// Look for the cli -> option mapping.
|
||||
const auto it = opts.find(token);
|
||||
auto it = opts.find(token);
|
||||
bool no = false;
|
||||
if (it == opts.end()) {
|
||||
boost::nowide::cerr << "Unknown option --" << token.c_str() << std::endl;
|
||||
return false;
|
||||
// Remove the "no-" prefix used to negate boolean options.
|
||||
std::string yes_token;
|
||||
if (boost::starts_with(token, "no-")) {
|
||||
yes_token = token.substr(3);
|
||||
it = opts.find(yes_token);
|
||||
no = true;
|
||||
}
|
||||
if (it == opts.end()) {
|
||||
boost::nowide::cerr << "Unknown option --" << token.c_str() << std::endl;
|
||||
return false;
|
||||
}
|
||||
if (no)
|
||||
token = yes_token;
|
||||
}
|
||||
const t_config_option_key opt_key = it->second;
|
||||
const ConfigOptionDef &optdef = this->def()->options.at(opt_key);
|
||||
|
||||
const t_config_option_key &opt_key = it->second;
|
||||
const ConfigOptionDef &optdef = this->def()->options.at(opt_key);
|
||||
|
||||
// If the option type expects a value and it was not already provided,
|
||||
// look for it in the next token.
|
||||
if (optdef.type != coBool && optdef.type != coBools && value.empty()) {
|
||||
if (i == (argc-1)) {
|
||||
boost::nowide::cerr << "No value supplied for --" << token.c_str() << std::endl;
|
||||
if (value.empty() && optdef.type != coBool && optdef.type != coBools) {
|
||||
if (i == argc-1) {
|
||||
boost::nowide::cerr << "No value supplied for --" << token.c_str() << std::endl;
|
||||
return false;
|
||||
}
|
||||
value = argv[++ i];
|
||||
}
|
||||
|
||||
if (no) {
|
||||
assert(optdef.type == coBool || optdef.type == coBools);
|
||||
if (! value.empty()) {
|
||||
boost::nowide::cerr << "Boolean options negated by the --no- prefix cannot have a value." << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Store the option value.
|
||||
const bool existing = this->has(opt_key);
|
||||
if (keys != nullptr && ! existing) {
|
||||
|
@ -902,7 +1141,7 @@ bool DynamicConfig::read_cli(int argc, const char* const argv[], t_config_option
|
|||
opt_vector->clear();
|
||||
// Vector values will be chained. Repeated use of a parameter will append the parameter or parameters
|
||||
// to the end of the value.
|
||||
if (opt_base->type() == coBools)
|
||||
if (opt_base->type() == coBools && value.empty())
|
||||
static_cast<ConfigOptionBools*>(opt_base)->values.push_back(!no);
|
||||
else
|
||||
// Deserialize any other vector value (ConfigOptionInts, Floats, Percents, Points) the same way
|
||||
|
@ -911,7 +1150,10 @@ bool DynamicConfig::read_cli(int argc, const char* const argv[], t_config_option
|
|||
// unescaped by the calling shell.
|
||||
opt_vector->deserialize(value, true);
|
||||
} else if (opt_base->type() == coBool) {
|
||||
static_cast<ConfigOptionBool*>(opt_base)->value = !no;
|
||||
if (value.empty())
|
||||
static_cast<ConfigOptionBool*>(opt_base)->value = !no;
|
||||
else
|
||||
opt_base->deserialize(value);
|
||||
} else if (opt_base->type() == coString) {
|
||||
// Do not unescape single string values, the unescaping is left to the calling shell.
|
||||
static_cast<ConfigOptionString*>(opt_base)->value = value;
|
||||
|
@ -961,6 +1203,65 @@ t_config_option_keys StaticConfig::keys() const
|
|||
return keys;
|
||||
}
|
||||
|
||||
// Iterate over the pairs of options with equal keys, call the fn.
|
||||
// Returns true on early exit by fn().
|
||||
template<typename Fn>
|
||||
static inline bool dynamic_config_iterate(const DynamicConfig &lhs, const DynamicConfig &rhs, Fn fn)
|
||||
{
|
||||
std::map<t_config_option_key, std::unique_ptr<ConfigOption>>::const_iterator i = lhs.cbegin();
|
||||
std::map<t_config_option_key, std::unique_ptr<ConfigOption>>::const_iterator j = rhs.cbegin();
|
||||
while (i != lhs.cend() && j != rhs.cend())
|
||||
if (i->first < j->first)
|
||||
++ i;
|
||||
else if (i->first > j->first)
|
||||
++ j;
|
||||
else {
|
||||
assert(i->first == j->first);
|
||||
if (fn(i->first, i->second.get(), j->second.get()))
|
||||
// Early exit by fn.
|
||||
return true;
|
||||
++ i;
|
||||
++ j;
|
||||
}
|
||||
// Finished to the end.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Are the two configs equal? Ignoring options not present in both configs.
|
||||
bool DynamicConfig::equals(const DynamicConfig &other) const
|
||||
{
|
||||
return ! dynamic_config_iterate(*this, other,
|
||||
[](const t_config_option_key & /* key */, const ConfigOption *l, const ConfigOption *r) { return *l != *r; });
|
||||
}
|
||||
|
||||
// Returns options differing in the two configs, ignoring options not present in both configs.
|
||||
t_config_option_keys DynamicConfig::diff(const DynamicConfig &other) const
|
||||
{
|
||||
t_config_option_keys diff;
|
||||
dynamic_config_iterate(*this, other,
|
||||
[&diff](const t_config_option_key &key, const ConfigOption *l, const ConfigOption *r) {
|
||||
if (*l != *r)
|
||||
diff.emplace_back(key);
|
||||
// Continue iterating.
|
||||
return false;
|
||||
});
|
||||
return diff;
|
||||
}
|
||||
|
||||
// Returns options being equal in the two configs, ignoring options not present in both configs.
|
||||
t_config_option_keys DynamicConfig::equal(const DynamicConfig &other) const
|
||||
{
|
||||
t_config_option_keys equal;
|
||||
dynamic_config_iterate(*this, other,
|
||||
[&equal](const t_config_option_key &key, const ConfigOption *l, const ConfigOption *r) {
|
||||
if (*l == *r)
|
||||
equal.emplace_back(key);
|
||||
// Continue iterating.
|
||||
return false;
|
||||
});
|
||||
return equal;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#include <cereal/types/polymorphic.hpp>
|
||||
|
|
|
@ -80,32 +80,77 @@ extern bool unescape_strings_cstyle(const std::string &str, std::vector<
|
|||
|
||||
extern std::string escape_ampersand(const std::string& str);
|
||||
|
||||
/// Specialization of std::exception to indicate that an unknown config option has been encountered.
|
||||
class UnknownOptionException : public Slic3r::RuntimeError {
|
||||
public:
|
||||
UnknownOptionException() :
|
||||
Slic3r::RuntimeError("Unknown option exception") {}
|
||||
UnknownOptionException(const std::string &opt_key) :
|
||||
Slic3r::RuntimeError(std::string("Unknown option exception: ") + opt_key) {}
|
||||
namespace ConfigHelpers {
|
||||
inline bool looks_like_enum_value(std::string value)
|
||||
{
|
||||
boost::trim(value);
|
||||
if (value.empty() || value.size() > 64 || ! isalpha(value.front()))
|
||||
return false;
|
||||
for (const char c : value)
|
||||
if (! (isalnum(c) || c == '_' || c == '-'))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool enum_looks_like_true_value(std::string value) {
|
||||
boost::trim(value);
|
||||
return boost::iequals(value, "enabled") || boost::iequals(value, "on");
|
||||
}
|
||||
|
||||
enum class DeserializationSubstitution {
|
||||
Disabled,
|
||||
DefaultsToFalse,
|
||||
DefaultsToTrue
|
||||
};
|
||||
|
||||
enum class DeserializationResult {
|
||||
Loaded,
|
||||
Substituted,
|
||||
Failed,
|
||||
};
|
||||
};
|
||||
|
||||
/// Indicate that the ConfigBase derived class does not provide config definition (the method def() returns null).
|
||||
class NoDefinitionException : public Slic3r::RuntimeError
|
||||
// Base for all exceptions thrown by the configuration layer.
|
||||
class ConfigurationError : public Slic3r::RuntimeError {
|
||||
public:
|
||||
using RuntimeError::RuntimeError;
|
||||
};
|
||||
|
||||
// Specialization of std::exception to indicate that an unknown config option has been encountered.
|
||||
class UnknownOptionException : public ConfigurationError {
|
||||
public:
|
||||
UnknownOptionException() :
|
||||
ConfigurationError("Unknown option exception") {}
|
||||
UnknownOptionException(const std::string &opt_key) :
|
||||
ConfigurationError(std::string("Unknown option exception: ") + opt_key) {}
|
||||
};
|
||||
|
||||
// Indicate that the ConfigBase derived class does not provide config definition (the method def() returns null).
|
||||
class NoDefinitionException : public ConfigurationError
|
||||
{
|
||||
public:
|
||||
NoDefinitionException() :
|
||||
Slic3r::RuntimeError("No definition exception") {}
|
||||
ConfigurationError("No definition exception") {}
|
||||
NoDefinitionException(const std::string &opt_key) :
|
||||
Slic3r::RuntimeError(std::string("No definition exception: ") + opt_key) {}
|
||||
ConfigurationError(std::string("No definition exception: ") + opt_key) {}
|
||||
};
|
||||
|
||||
/// Indicate that an unsupported accessor was called on a config option.
|
||||
class BadOptionTypeException : public Slic3r::RuntimeError
|
||||
// Indicate that an unsupported accessor was called on a config option.
|
||||
class BadOptionTypeException : public ConfigurationError
|
||||
{
|
||||
public:
|
||||
BadOptionTypeException() : Slic3r::RuntimeError("Bad option type exception") {}
|
||||
BadOptionTypeException(const std::string &message) : Slic3r::RuntimeError(message) {}
|
||||
BadOptionTypeException(const char* message) : Slic3r::RuntimeError(message) {}
|
||||
BadOptionTypeException() : ConfigurationError("Bad option type exception") {}
|
||||
BadOptionTypeException(const std::string &message) : ConfigurationError(message) {}
|
||||
BadOptionTypeException(const char* message) : ConfigurationError(message) {}
|
||||
};
|
||||
|
||||
// Indicate that an option has been deserialized from an invalid value.
|
||||
class BadOptionValueException : public ConfigurationError
|
||||
{
|
||||
public:
|
||||
BadOptionValueException() : ConfigurationError("Bad option value exception") {}
|
||||
BadOptionValueException(const std::string &message) : ConfigurationError(message) {}
|
||||
BadOptionValueException(const char* message) : ConfigurationError(message) {}
|
||||
};
|
||||
|
||||
// Type of a configuration value.
|
||||
|
@ -166,9 +211,16 @@ enum PrinterTechnology : unsigned char
|
|||
|
||||
enum ForwardCompatibilitySubstitutionRule
|
||||
{
|
||||
// Disable susbtitution, throw exception if an option value is not recognized.
|
||||
Disable,
|
||||
// Enable substitution of an unknown option value with default. Log the substitution.
|
||||
Enable,
|
||||
// Enable substitution of an unknown option value with default. Don't log the substitution.
|
||||
EnableSilent,
|
||||
// Enable substitution of an unknown option value with default. Log substitutions in user profiles, don't log substitutions in system profiles.
|
||||
EnableSystemSilent,
|
||||
// Enable silent substitution of an unknown option value with default when loading user profiles. Throw on an unknown option value in a system profile.
|
||||
EnableSilentDisableSystem,
|
||||
};
|
||||
|
||||
class ConfigOption;
|
||||
|
@ -252,7 +304,7 @@ public:
|
|||
void set(const ConfigOption *rhs) override
|
||||
{
|
||||
if (rhs->type() != this->type())
|
||||
throw Slic3r::RuntimeError("ConfigOptionSingle: Assigning an incompatible type");
|
||||
throw ConfigurationError("ConfigOptionSingle: Assigning an incompatible type");
|
||||
assert(dynamic_cast<const ConfigOptionSingle<T>*>(rhs));
|
||||
this->value = static_cast<const ConfigOptionSingle<T>*>(rhs)->value;
|
||||
}
|
||||
|
@ -260,7 +312,7 @@ public:
|
|||
bool operator==(const ConfigOption &rhs) const override
|
||||
{
|
||||
if (rhs.type() != this->type())
|
||||
throw Slic3r::RuntimeError("ConfigOptionSingle: Comparing incompatible types");
|
||||
throw ConfigurationError("ConfigOptionSingle: Comparing incompatible types");
|
||||
assert(dynamic_cast<const ConfigOptionSingle<T>*>(&rhs));
|
||||
return this->value == static_cast<const ConfigOptionSingle<T>*>(&rhs)->value;
|
||||
}
|
||||
|
@ -327,7 +379,7 @@ public:
|
|||
void set(const ConfigOption *rhs) override
|
||||
{
|
||||
if (rhs->type() != this->type())
|
||||
throw Slic3r::RuntimeError("ConfigOptionVector: Assigning an incompatible type");
|
||||
throw ConfigurationError("ConfigOptionVector: Assigning an incompatible type");
|
||||
assert(dynamic_cast<const ConfigOptionVector<T>*>(rhs));
|
||||
this->values = static_cast<const ConfigOptionVector<T>*>(rhs)->values;
|
||||
}
|
||||
|
@ -344,12 +396,12 @@ public:
|
|||
if (opt->type() == this->type()) {
|
||||
auto other = static_cast<const ConfigOptionVector<T>*>(opt);
|
||||
if (other->values.empty())
|
||||
throw Slic3r::RuntimeError("ConfigOptionVector::set(): Assigning from an empty vector");
|
||||
throw ConfigurationError("ConfigOptionVector::set(): Assigning from an empty vector");
|
||||
this->values.emplace_back(other->values.front());
|
||||
} else if (opt->type() == this->scalar_type())
|
||||
this->values.emplace_back(static_cast<const ConfigOptionSingle<T>*>(opt)->value);
|
||||
else
|
||||
throw Slic3r::RuntimeError("ConfigOptionVector::set():: Assigning an incompatible type");
|
||||
throw ConfigurationError("ConfigOptionVector::set():: Assigning an incompatible type");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -368,12 +420,12 @@ public:
|
|||
// Assign the first value of the rhs vector.
|
||||
auto other = static_cast<const ConfigOptionVector<T>*>(rhs);
|
||||
if (other->values.empty())
|
||||
throw Slic3r::RuntimeError("ConfigOptionVector::set_at(): Assigning from an empty vector");
|
||||
throw ConfigurationError("ConfigOptionVector::set_at(): Assigning from an empty vector");
|
||||
this->values[i] = other->get_at(j);
|
||||
} else if (rhs->type() == this->scalar_type())
|
||||
this->values[i] = static_cast<const ConfigOptionSingle<T>*>(rhs)->value;
|
||||
else
|
||||
throw Slic3r::RuntimeError("ConfigOptionVector::set_at(): Assigning an incompatible type");
|
||||
throw ConfigurationError("ConfigOptionVector::set_at(): Assigning an incompatible type");
|
||||
}
|
||||
|
||||
const T& get_at(size_t i) const
|
||||
|
@ -398,9 +450,9 @@ public:
|
|||
else if (n > this->values.size()) {
|
||||
if (this->values.empty()) {
|
||||
if (opt_default == nullptr)
|
||||
throw Slic3r::RuntimeError("ConfigOptionVector::resize(): No default value provided.");
|
||||
throw ConfigurationError("ConfigOptionVector::resize(): No default value provided.");
|
||||
if (opt_default->type() != this->type())
|
||||
throw Slic3r::RuntimeError("ConfigOptionVector::resize(): Extending with an incompatible type.");
|
||||
throw ConfigurationError("ConfigOptionVector::resize(): Extending with an incompatible type.");
|
||||
this->values.resize(n, static_cast<const ConfigOptionVector<T>*>(opt_default)->values.front());
|
||||
} else {
|
||||
// Resize by duplicating the last value.
|
||||
|
@ -417,7 +469,7 @@ public:
|
|||
bool operator==(const ConfigOption &rhs) const override
|
||||
{
|
||||
if (rhs.type() != this->type())
|
||||
throw Slic3r::RuntimeError("ConfigOptionVector: Comparing incompatible types");
|
||||
throw ConfigurationError("ConfigOptionVector: Comparing incompatible types");
|
||||
assert(dynamic_cast<const ConfigOptionVector<T>*>(&rhs));
|
||||
return this->values == static_cast<const ConfigOptionVector<T>*>(&rhs)->values;
|
||||
}
|
||||
|
@ -437,9 +489,9 @@ public:
|
|||
// An option overrides another option if it is not nil and not equal.
|
||||
bool overriden_by(const ConfigOption *rhs) const override {
|
||||
if (this->nullable())
|
||||
throw Slic3r::RuntimeError("Cannot override a nullable ConfigOption.");
|
||||
throw ConfigurationError("Cannot override a nullable ConfigOption.");
|
||||
if (rhs->type() != this->type())
|
||||
throw Slic3r::RuntimeError("ConfigOptionVector.overriden_by() applied to different types.");
|
||||
throw ConfigurationError("ConfigOptionVector.overriden_by() applied to different types.");
|
||||
auto rhs_vec = static_cast<const ConfigOptionVector<T>*>(rhs);
|
||||
if (! rhs->nullable())
|
||||
// Overridding a non-nullable object with another non-nullable object.
|
||||
|
@ -457,9 +509,9 @@ public:
|
|||
// Apply an override option, possibly a nullable one.
|
||||
bool apply_override(const ConfigOption *rhs) override {
|
||||
if (this->nullable())
|
||||
throw Slic3r::RuntimeError("Cannot override a nullable ConfigOption.");
|
||||
throw ConfigurationError("Cannot override a nullable ConfigOption.");
|
||||
if (rhs->type() != this->type())
|
||||
throw Slic3r::RuntimeError("ConfigOptionVector.apply_override() applied to different types.");
|
||||
throw ConfigurationError("ConfigOptionVector.apply_override() applied to different types.");
|
||||
auto rhs_vec = static_cast<const ConfigOptionVector<T>*>(rhs);
|
||||
if (! rhs->nullable()) {
|
||||
// Overridding a non-nullable object with another non-nullable object.
|
||||
|
@ -550,7 +602,7 @@ public:
|
|||
bool operator< (const ConfigOptionFloatsTempl &rhs) const throw() { return vectors_lower(this->values, rhs.values); }
|
||||
bool operator==(const ConfigOption &rhs) const override {
|
||||
if (rhs.type() != this->type())
|
||||
throw Slic3r::RuntimeError("ConfigOptionFloatsTempl: Comparing incompatible types");
|
||||
throw ConfigurationError("ConfigOptionFloatsTempl: Comparing incompatible types");
|
||||
assert(dynamic_cast<const ConfigOptionVector<double>*>(&rhs));
|
||||
return vectors_equal(this->values, static_cast<const ConfigOptionVector<double>*>(&rhs)->values);
|
||||
}
|
||||
|
@ -597,7 +649,7 @@ public:
|
|||
if (NULLABLE)
|
||||
this->values.push_back(nil_value());
|
||||
else
|
||||
throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object");
|
||||
throw ConfigurationError("Deserializing nil into a non-nullable object");
|
||||
} else {
|
||||
std::istringstream iss(item_str);
|
||||
double value;
|
||||
|
@ -622,9 +674,9 @@ protected:
|
|||
if (NULLABLE)
|
||||
ss << "nil";
|
||||
else
|
||||
throw Slic3r::RuntimeError("Serializing NaN");
|
||||
throw ConfigurationError("Serializing NaN");
|
||||
} else
|
||||
throw Slic3r::RuntimeError("Serializing invalid number");
|
||||
throw ConfigurationError("Serializing invalid number");
|
||||
}
|
||||
static bool vectors_equal(const std::vector<double> &v1, const std::vector<double> &v2) {
|
||||
if (NULLABLE) {
|
||||
|
@ -756,7 +808,7 @@ public:
|
|||
if (NULLABLE)
|
||||
this->values.push_back(nil_value());
|
||||
else
|
||||
throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object");
|
||||
throw ConfigurationError("Deserializing nil into a non-nullable object");
|
||||
} else {
|
||||
std::istringstream iss(item_str);
|
||||
int value;
|
||||
|
@ -773,7 +825,7 @@ private:
|
|||
if (NULLABLE)
|
||||
ss << "nil";
|
||||
else
|
||||
throw Slic3r::RuntimeError("Serializing NaN");
|
||||
throw ConfigurationError("Serializing NaN");
|
||||
} else
|
||||
ss << v;
|
||||
}
|
||||
|
@ -963,7 +1015,7 @@ public:
|
|||
bool operator==(const ConfigOption &rhs) const override
|
||||
{
|
||||
if (rhs.type() != this->type())
|
||||
throw Slic3r::RuntimeError("ConfigOptionFloatOrPercent: Comparing incompatible types");
|
||||
throw ConfigurationError("ConfigOptionFloatOrPercent: Comparing incompatible types");
|
||||
assert(dynamic_cast<const ConfigOptionFloatOrPercent*>(&rhs));
|
||||
return *this == *static_cast<const ConfigOptionFloatOrPercent*>(&rhs);
|
||||
}
|
||||
|
@ -979,7 +1031,7 @@ public:
|
|||
|
||||
void set(const ConfigOption *rhs) override {
|
||||
if (rhs->type() != this->type())
|
||||
throw Slic3r::RuntimeError("ConfigOptionFloatOrPercent: Assigning an incompatible type");
|
||||
throw ConfigurationError("ConfigOptionFloatOrPercent: Assigning an incompatible type");
|
||||
assert(dynamic_cast<const ConfigOptionFloatOrPercent*>(rhs));
|
||||
*this = *static_cast<const ConfigOptionFloatOrPercent*>(rhs);
|
||||
}
|
||||
|
@ -1023,7 +1075,7 @@ public:
|
|||
bool operator==(const ConfigOptionFloatsOrPercentsTempl &rhs) const throw() { return vectors_equal(this->values, rhs.values); }
|
||||
bool operator==(const ConfigOption &rhs) const override {
|
||||
if (rhs.type() != this->type())
|
||||
throw Slic3r::RuntimeError("ConfigOptionFloatsOrPercentsTempl: Comparing incompatible types");
|
||||
throw ConfigurationError("ConfigOptionFloatsOrPercentsTempl: Comparing incompatible types");
|
||||
assert(dynamic_cast<const ConfigOptionVector<FloatOrPercent>*>(&rhs));
|
||||
return vectors_equal(this->values, static_cast<const ConfigOptionVector<FloatOrPercent>*>(&rhs)->values);
|
||||
}
|
||||
|
@ -1072,7 +1124,7 @@ public:
|
|||
if (NULLABLE)
|
||||
this->values.push_back(nil_value());
|
||||
else
|
||||
throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object");
|
||||
throw ConfigurationError("Deserializing nil into a non-nullable object");
|
||||
} else {
|
||||
bool percent = item_str.find_first_of("%") != std::string::npos;
|
||||
std::istringstream iss(item_str);
|
||||
|
@ -1100,9 +1152,9 @@ protected:
|
|||
if (NULLABLE)
|
||||
ss << "nil";
|
||||
else
|
||||
throw Slic3r::RuntimeError("Serializing NaN");
|
||||
throw ConfigurationError("Serializing NaN");
|
||||
} else
|
||||
throw Slic3r::RuntimeError("Serializing invalid number");
|
||||
throw ConfigurationError("Serializing invalid number");
|
||||
}
|
||||
static bool vectors_equal(const std::vector<FloatOrPercent> &v1, const std::vector<FloatOrPercent> &v2) {
|
||||
if (NULLABLE) {
|
||||
|
@ -1308,11 +1360,11 @@ public:
|
|||
bool deserialize(const std::string &str, bool append = false) override
|
||||
{
|
||||
UNUSED(append);
|
||||
if (str == "1" || boost::iequals(str, "enabled") || boost::iequals(str, "on")) {
|
||||
if (str == "1") {
|
||||
this->value = true;
|
||||
return true;
|
||||
}
|
||||
if (str == "0" || boost::iequals(str, "disabled") || boost::iequals(str, "off")) {
|
||||
if (str == "0") {
|
||||
this->value = false;
|
||||
return true;
|
||||
}
|
||||
|
@ -1378,24 +1430,39 @@ public:
|
|||
}
|
||||
return vv;
|
||||
}
|
||||
|
||||
bool deserialize(const std::string &str, bool append = false) override
|
||||
|
||||
ConfigHelpers::DeserializationResult deserialize_with_substitutions(const std::string &str, bool append, ConfigHelpers::DeserializationSubstitution substitution)
|
||||
{
|
||||
if (! append)
|
||||
this->values.clear();
|
||||
std::istringstream is(str);
|
||||
std::string item_str;
|
||||
bool substituted = false;
|
||||
while (std::getline(is, item_str, ',')) {
|
||||
boost::trim(item_str);
|
||||
unsigned char new_value = 0;
|
||||
if (item_str == "nil") {
|
||||
if (NULLABLE)
|
||||
this->values.push_back(nil_value());
|
||||
new_value = nil_value();
|
||||
else
|
||||
throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object");
|
||||
throw ConfigurationError("Deserializing nil into a non-nullable object");
|
||||
} else if (item_str == "1") {
|
||||
new_value = true;
|
||||
} else if (item_str == "0") {
|
||||
new_value = false;
|
||||
} else if (substitution != ConfigHelpers::DeserializationSubstitution::Disabled && ConfigHelpers::looks_like_enum_value(item_str)) {
|
||||
new_value = ConfigHelpers::enum_looks_like_true_value(item_str) || substitution == ConfigHelpers::DeserializationSubstitution::DefaultsToTrue;
|
||||
substituted = true;
|
||||
} else
|
||||
this->values.push_back(item_str.compare("1") == 0);
|
||||
return ConfigHelpers::DeserializationResult::Failed;
|
||||
this->values.push_back(new_value);
|
||||
}
|
||||
return true;
|
||||
return substituted ? ConfigHelpers::DeserializationResult::Substituted : ConfigHelpers::DeserializationResult::Loaded;
|
||||
}
|
||||
|
||||
bool deserialize(const std::string &str, bool append = false) override
|
||||
{
|
||||
return this->deserialize_with_substitutions(str, append, ConfigHelpers::DeserializationSubstitution::Disabled) == ConfigHelpers::DeserializationResult::Loaded;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
@ -1404,7 +1471,7 @@ protected:
|
|||
if (NULLABLE)
|
||||
ss << "nil";
|
||||
else
|
||||
throw Slic3r::RuntimeError("Serializing NaN");
|
||||
throw ConfigurationError("Serializing NaN");
|
||||
} else
|
||||
ss << (v ? "1" : "0");
|
||||
}
|
||||
|
@ -1442,14 +1509,14 @@ public:
|
|||
bool operator==(const ConfigOption &rhs) const override
|
||||
{
|
||||
if (rhs.type() != this->type())
|
||||
throw Slic3r::RuntimeError("ConfigOptionEnum<T>: Comparing incompatible types");
|
||||
throw ConfigurationError("ConfigOptionEnum<T>: Comparing incompatible types");
|
||||
// rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T>
|
||||
return this->value == (T)rhs.getInt();
|
||||
}
|
||||
|
||||
void set(const ConfigOption *rhs) override {
|
||||
if (rhs->type() != this->type())
|
||||
throw Slic3r::RuntimeError("ConfigOptionEnum<T>: Assigning an incompatible type");
|
||||
throw ConfigurationError("ConfigOptionEnum<T>: Assigning an incompatible type");
|
||||
// rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T>
|
||||
this->value = (T)rhs->getInt();
|
||||
}
|
||||
|
@ -1512,14 +1579,14 @@ public:
|
|||
bool operator==(const ConfigOption &rhs) const override
|
||||
{
|
||||
if (rhs.type() != this->type())
|
||||
throw Slic3r::RuntimeError("ConfigOptionEnumGeneric: Comparing incompatible types");
|
||||
throw ConfigurationError("ConfigOptionEnumGeneric: Comparing incompatible types");
|
||||
// rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T>
|
||||
return this->value == rhs.getInt();
|
||||
}
|
||||
|
||||
void set(const ConfigOption *rhs) override {
|
||||
if (rhs->type() != this->type())
|
||||
throw Slic3r::RuntimeError("ConfigOptionEnumGeneric: Assigning an incompatible type");
|
||||
throw ConfigurationError("ConfigOptionEnumGeneric: Assigning an incompatible type");
|
||||
// rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T>
|
||||
this->value = rhs->getInt();
|
||||
}
|
||||
|
@ -1592,7 +1659,7 @@ public:
|
|||
case coInts: { auto opt = new ConfigOptionIntsNullable(); archive(*opt); return opt; }
|
||||
case coPercents: { auto opt = new ConfigOptionPercentsNullable();archive(*opt); return opt; }
|
||||
case coBools: { auto opt = new ConfigOptionBoolsNullable(); archive(*opt); return opt; }
|
||||
default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown nullable option type for option ") + this->opt_key);
|
||||
default: throw ConfigurationError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown nullable option type for option ") + this->opt_key);
|
||||
}
|
||||
} else {
|
||||
switch (this->type) {
|
||||
|
@ -1611,7 +1678,7 @@ public:
|
|||
case coBool: { auto opt = new ConfigOptionBool(); archive(*opt); return opt; }
|
||||
case coBools: { auto opt = new ConfigOptionBools(); archive(*opt); return opt; }
|
||||
case coEnum: { auto opt = new ConfigOptionEnumGeneric(this->enum_keys_map); archive(*opt); return opt; }
|
||||
default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown option type for option ") + this->opt_key);
|
||||
default: throw ConfigurationError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown option type for option ") + this->opt_key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1623,7 +1690,7 @@ public:
|
|||
case coInts: archive(*static_cast<const ConfigOptionIntsNullable*>(opt)); break;
|
||||
case coPercents: archive(*static_cast<const ConfigOptionPercentsNullable*>(opt));break;
|
||||
case coBools: archive(*static_cast<const ConfigOptionBoolsNullable*>(opt)); break;
|
||||
default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown nullable option type for option ") + this->opt_key);
|
||||
default: throw ConfigurationError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown nullable option type for option ") + this->opt_key);
|
||||
}
|
||||
} else {
|
||||
switch (this->type) {
|
||||
|
@ -1642,7 +1709,7 @@ public:
|
|||
case coBool: archive(*static_cast<const ConfigOptionBool*>(opt)); break;
|
||||
case coBools: archive(*static_cast<const ConfigOptionBools*>(opt)); break;
|
||||
case coEnum: archive(*static_cast<const ConfigOptionEnumGeneric*>(opt)); break;
|
||||
default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown option type for option ") + this->opt_key);
|
||||
default: throw ConfigurationError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown option type for option ") + this->opt_key);
|
||||
}
|
||||
}
|
||||
// Make the compiler happy, shut up the warnings.
|
||||
|
@ -1765,7 +1832,7 @@ public:
|
|||
return out;
|
||||
}
|
||||
|
||||
/// Iterate through all of the CLI options and write them to a stream.
|
||||
// Iterate through all of the CLI options and write them to a stream.
|
||||
std::ostream& print_cli_help(
|
||||
std::ostream& out, bool show_defaults,
|
||||
std::function<bool(const ConfigOptionDef &)> filter = [](const ConfigOptionDef &){ return true; }) const;
|
||||
|
@ -1826,8 +1893,8 @@ public:
|
|||
// The configuration definition is static: It does not carry the actual configuration values,
|
||||
// but it carries the defaults of the configuration values.
|
||||
|
||||
ConfigBase() {}
|
||||
~ConfigBase() override {}
|
||||
ConfigBase() = default;
|
||||
~ConfigBase() override = default;
|
||||
|
||||
// Virtual overridables:
|
||||
public:
|
||||
|
@ -1886,8 +1953,11 @@ public:
|
|||
// An UnknownOptionException is thrown in case some option keys are not defined by this->def(),
|
||||
// or this ConfigBase is of a StaticConfig type and it does not support some of the keys, and ignore_nonexistent is not set.
|
||||
void apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false);
|
||||
bool equals(const ConfigBase &other) const { return this->diff(other).empty(); }
|
||||
// Are the two configs equal? Ignoring options not present in both configs.
|
||||
bool equals(const ConfigBase &other) const;
|
||||
// Returns options differing in the two configs, ignoring options not present in both configs.
|
||||
t_config_option_keys diff(const ConfigBase &other) const;
|
||||
// Returns options being equal in the two configs, ignoring options not present in both configs.
|
||||
t_config_option_keys equal(const ConfigBase &other) const;
|
||||
std::string opt_serialize(const t_config_option_key &opt_key) const;
|
||||
|
||||
|
@ -1934,9 +2004,11 @@ public:
|
|||
void setenv_() const;
|
||||
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, ConfigSubstitutionContext& substitutions);
|
||||
ConfigSubstitutions load_from_ini_string(const std::string &data, ForwardCompatibilitySubstitutionRule compatibility_rule);
|
||||
// Loading a "will be one day a legacy format" of configuration stored into 3MF or AMF.
|
||||
// Accepts the same data as load_from_ini_string(), only with each configuration line possibly prefixed with a semicolon (G-code comment).
|
||||
ConfigSubstitutions load_from_ini_string_commented(std::string &&data, ForwardCompatibilitySubstitutionRule compatibility_rule);
|
||||
ConfigSubstitutions load_from_gcode_file(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule);
|
||||
ConfigSubstitutions load(const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule);
|
||||
void save(const std::string &file) const;
|
||||
|
||||
|
@ -1953,12 +2025,12 @@ private:
|
|||
class DynamicConfig : public virtual ConfigBase
|
||||
{
|
||||
public:
|
||||
DynamicConfig() {}
|
||||
DynamicConfig() = default;
|
||||
DynamicConfig(const DynamicConfig &rhs) { *this = rhs; }
|
||||
DynamicConfig(DynamicConfig &&rhs) noexcept : options(std::move(rhs.options)) { rhs.options.clear(); }
|
||||
explicit DynamicConfig(const ConfigBase &rhs, const t_config_option_keys &keys);
|
||||
explicit DynamicConfig(const ConfigBase& rhs) : DynamicConfig(rhs, rhs.keys()) {}
|
||||
virtual ~DynamicConfig() override { clear(); }
|
||||
virtual ~DynamicConfig() override = default;
|
||||
|
||||
// Copy a content of one DynamicConfig to another DynamicConfig.
|
||||
// If rhs.def() is not null, then it has to be equal to this->def().
|
||||
|
@ -2075,6 +2147,13 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
// Are the two configs equal? Ignoring options not present in both configs.
|
||||
bool equals(const DynamicConfig &other) const;
|
||||
// Returns options differing in the two configs, ignoring options not present in both configs.
|
||||
t_config_option_keys diff(const DynamicConfig &other) const;
|
||||
// Returns options being equal in the two configs, ignoring options not present in both configs.
|
||||
t_config_option_keys equal(const DynamicConfig &other) const;
|
||||
|
||||
std::string& opt_string(const t_config_option_key &opt_key, bool create = false) { return this->option<ConfigOptionString>(opt_key, create)->value; }
|
||||
const std::string& opt_string(const t_config_option_key &opt_key) const { return const_cast<DynamicConfig*>(this)->opt_string(opt_key); }
|
||||
std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionStrings>(opt_key)->get_at(idx); }
|
||||
|
@ -2099,7 +2178,6 @@ public:
|
|||
bool opt_bool(const t_config_option_key &opt_key, unsigned int idx) const { return this->option<ConfigOptionBools>(opt_key)->get_at(idx) != 0; }
|
||||
|
||||
// Command line processing
|
||||
void read_cli(const std::vector<std::string> &tokens, t_config_option_keys* extra, t_config_option_keys* keys = nullptr);
|
||||
bool read_cli(int argc, const char* const argv[], t_config_option_keys* extra, t_config_option_keys* keys = nullptr);
|
||||
|
||||
std::map<t_config_option_key, std::unique_ptr<ConfigOption>>::const_iterator cbegin() const { return options.cbegin(); }
|
||||
|
@ -2113,9 +2191,9 @@ private:
|
|||
template<class Archive> void serialize(Archive &ar) { ar(options); }
|
||||
};
|
||||
|
||||
/// Configuration store with a static definition of configuration values.
|
||||
/// In Slic3r, the static configuration stores are during the slicing / g-code generation for efficiency reasons,
|
||||
/// because the configuration values could be accessed directly.
|
||||
// Configuration store with a static definition of configuration values.
|
||||
// In Slic3r, the static configuration stores are during the slicing / g-code generation for efficiency reasons,
|
||||
// because the configuration values could be accessed directly.
|
||||
class StaticConfig : public virtual ConfigBase
|
||||
{
|
||||
public:
|
||||
|
|
|
@ -595,7 +595,7 @@ namespace Slic3r {
|
|||
|
||||
mz_zip_archive_file_stat stat;
|
||||
|
||||
m_name = boost::filesystem::path(filename).filename().stem().string();
|
||||
m_name = boost::filesystem::path(filename).stem().string();
|
||||
|
||||
// we first loop the entries to read from the archive the .model file only, in order to extract the version from it
|
||||
for (mz_uint i = 0; i < num_entries; ++i) {
|
||||
|
@ -875,7 +875,9 @@ namespace Slic3r {
|
|||
add_error("Error while reading config data to buffer");
|
||||
return;
|
||||
}
|
||||
config.load_from_gcode_string(buffer.data(), config_substitutions);
|
||||
//FIXME Loading a "will be one day a legacy format" of configuration in a form of a G-code comment.
|
||||
// Each config line is prefixed with a semicolon (G-code comment), that is ugly.
|
||||
config_substitutions.substitutions = config.load_from_ini_string_commented(std::move(buffer), config_substitutions.rule);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1406,6 +1408,13 @@ namespace Slic3r {
|
|||
m_model->delete_object(model_object);
|
||||
}
|
||||
|
||||
if (m_version == 0) {
|
||||
// if the 3mf was not produced by PrusaSlicer and there is only one object,
|
||||
// set the object name to match the filename
|
||||
if (m_model->objects.size() == 1)
|
||||
m_model->objects.front()->name = m_name;
|
||||
}
|
||||
|
||||
// applies instances' matrices
|
||||
for (Instance& instance : m_instances) {
|
||||
if (instance.instance != nullptr && instance.instance->get_object() != nullptr)
|
||||
|
@ -2860,9 +2869,10 @@ namespace Slic3r {
|
|||
stream << prefix << SOURCE_OFFSET_Y_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(1) << "\"/>\n";
|
||||
stream << prefix << SOURCE_OFFSET_Z_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(2) << "\"/>\n";
|
||||
}
|
||||
assert(! volume->source.is_converted_from_inches || ! volume->source.is_converted_from_meters);
|
||||
if (volume->source.is_converted_from_inches)
|
||||
stream << prefix << SOURCE_IN_INCHES << "\" " << VALUE_ATTR << "=\"1\"/>\n";
|
||||
if (volume->source.is_converted_from_meters)
|
||||
else if (volume->source.is_converted_from_meters)
|
||||
stream << prefix << SOURCE_IN_METERS << "\" " << VALUE_ATTR << "=\"1\"/>\n";
|
||||
}
|
||||
|
||||
|
|
|
@ -706,7 +706,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(), *m_config_substitutions);
|
||||
//FIXME Loading a "will be one day a legacy format" of configuration in a form of a G-code comment.
|
||||
// Each config line is prefixed with a semicolon (G-code comment), that is ugly.
|
||||
m_config_substitutions->substitutions = m_config->load_from_ini_string_commented(std::move(m_value[1].c_str()), m_config_substitutions->rule);
|
||||
}
|
||||
else if (strncmp(m_value[0].c_str(), "slic3r.", 7) == 0) {
|
||||
const char *opt_key = m_value[0].c_str() + 7;
|
||||
|
@ -1241,9 +1243,10 @@ bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config,
|
|||
stream << " <metadata type=\"slic3r.source_offset_y\">" << volume->source.mesh_offset(1) << "</metadata>\n";
|
||||
stream << " <metadata type=\"slic3r.source_offset_z\">" << volume->source.mesh_offset(2) << "</metadata>\n";
|
||||
}
|
||||
assert(! volume->source.is_converted_from_inches || ! volume->source.is_converted_from_meters);
|
||||
if (volume->source.is_converted_from_inches)
|
||||
stream << " <metadata type=\"slic3r.source_in_inches\">1</metadata>\n";
|
||||
if (volume->source.is_converted_from_meters)
|
||||
else if (volume->source.is_converted_from_meters)
|
||||
stream << " <metadata type=\"slic3r.source_in_meters\">1</metadata>\n";
|
||||
stream << std::setprecision(std::numeric_limits<float>::max_digits10);
|
||||
const indexed_triangle_set &its = volume->mesh().its;
|
||||
|
|
|
@ -203,7 +203,7 @@ RasterParams get_raster_params(const DynamicPrintConfig &cfg)
|
|||
|
||||
if (!opt_disp_cols || !opt_disp_rows || !opt_disp_w || !opt_disp_h ||
|
||||
!opt_mirror_x || !opt_mirror_y || !opt_orient)
|
||||
throw Slic3r::FileIOError("Invalid SL1 file");
|
||||
throw Slic3r::FileIOError("Invalid SL1 / SL1S file");
|
||||
|
||||
RasterParams rstp;
|
||||
|
||||
|
@ -229,7 +229,7 @@ SliceParams get_slice_params(const DynamicPrintConfig &cfg)
|
|||
auto *opt_init_layerh = cfg.option<ConfigOptionFloat>("initial_layer_height");
|
||||
|
||||
if (!opt_layerh || !opt_init_layerh)
|
||||
throw Slic3r::FileIOError("Invalid SL1 file");
|
||||
throw Slic3r::FileIOError("Invalid SL1 / SL1S file");
|
||||
|
||||
return SliceParams{opt_layerh->getFloat(), opt_init_layerh->getFloat()};
|
||||
}
|
||||
|
|
|
@ -1464,13 +1464,15 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
|
|||
_write_format(file, "; total toolchanges = %i\n", print.m_print_statistics.total_toolchanges);
|
||||
_write_format(file, ";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder).c_str());
|
||||
|
||||
// Append full config.
|
||||
_write(file, "\n");
|
||||
// Append full config, delimited by two 'phony' configuration keys prusaslicer_config = begin and prusaslicer_config = end.
|
||||
// The delimiters are structured as configuration key / value pairs to be parsable by older versions of PrusaSlicer G-code viewer.
|
||||
{
|
||||
_write(file, "\n; prusaslicer_config = begin\n");
|
||||
std::string full_config;
|
||||
append_full_config(print, full_config);
|
||||
if (!full_config.empty())
|
||||
_write(file, full_config);
|
||||
_write(file, "; prusaslicer_config = end\n");
|
||||
}
|
||||
print.throw_if_canceled();
|
||||
}
|
||||
|
@ -2714,7 +2716,9 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
|||
|
||||
// calculate extrusion length per distance unit
|
||||
double e_per_mm = m_writer.extruder()->e_per_mm3() * path.mm3_per_mm;
|
||||
if (m_writer.extrusion_axis().empty()) e_per_mm = 0;
|
||||
if (m_writer.extrusion_axis().empty())
|
||||
// gcfNoExtrusion
|
||||
e_per_mm = 0;
|
||||
|
||||
// set speed
|
||||
if (speed == -1) {
|
||||
|
|
|
@ -24,6 +24,9 @@
|
|||
static const float INCHES_TO_MM = 25.4f;
|
||||
static const float MMMIN_TO_MMSEC = 1.0f / 60.0f;
|
||||
static const float DEFAULT_ACCELERATION = 1500.0f; // Prusa Firmware 1_75mm_MK2
|
||||
#if ENABLE_RETRACT_ACCELERATION
|
||||
static const float DEFAULT_RETRACT_ACCELERATION = 1500.0f; // Prusa Firmware 1_75mm_MK2
|
||||
#endif // ENABLE_RETRACT_ACCELERATION
|
||||
static const float DEFAULT_TRAVEL_ACCELERATION = 1250.0f;
|
||||
|
||||
static const size_t MIN_EXTRUDERS_COUNT = 5;
|
||||
|
@ -178,6 +181,10 @@ void GCodeProcessor::TimeMachine::reset()
|
|||
enabled = false;
|
||||
acceleration = 0.0f;
|
||||
max_acceleration = 0.0f;
|
||||
#if ENABLE_RETRACT_ACCELERATION
|
||||
retract_acceleration = 0.0f;
|
||||
max_retract_acceleration = 0.0f;
|
||||
#endif // ENABLE_RETRACT_ACCELERATION
|
||||
travel_acceleration = 0.0f;
|
||||
max_travel_acceleration = 0.0f;
|
||||
extrude_factor_override_percentage = 1.0f;
|
||||
|
@ -834,6 +841,11 @@ void GCodeProcessor::apply_config(const PrintConfig& config)
|
|||
float max_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_extruding, i);
|
||||
m_time_processor.machines[i].max_acceleration = max_acceleration;
|
||||
m_time_processor.machines[i].acceleration = (max_acceleration > 0.0f) ? max_acceleration : DEFAULT_ACCELERATION;
|
||||
#if ENABLE_RETRACT_ACCELERATION
|
||||
float max_retract_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_retracting, i);
|
||||
m_time_processor.machines[i].max_retract_acceleration = max_retract_acceleration;
|
||||
m_time_processor.machines[i].retract_acceleration = (max_retract_acceleration > 0.0f) ? max_retract_acceleration : DEFAULT_RETRACT_ACCELERATION;
|
||||
#endif // ENABLE_RETRACT_ACCELERATION
|
||||
float max_travel_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_travel, i);
|
||||
m_time_processor.machines[i].max_travel_acceleration = max_travel_acceleration;
|
||||
m_time_processor.machines[i].travel_acceleration = (max_travel_acceleration > 0.0f) ? max_travel_acceleration : DEFAULT_TRAVEL_ACCELERATION;
|
||||
|
@ -1052,6 +1064,11 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config)
|
|||
float max_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_extruding, i);
|
||||
m_time_processor.machines[i].max_acceleration = max_acceleration;
|
||||
m_time_processor.machines[i].acceleration = (max_acceleration > 0.0f) ? max_acceleration : DEFAULT_ACCELERATION;
|
||||
#if ENABLE_RETRACT_ACCELERATION
|
||||
float max_retract_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_retracting, i);
|
||||
m_time_processor.machines[i].max_retract_acceleration = max_retract_acceleration;
|
||||
m_time_processor.machines[i].retract_acceleration = (max_retract_acceleration > 0.0f) ? max_retract_acceleration : DEFAULT_RETRACT_ACCELERATION;
|
||||
#endif // ENABLE_RETRACT_ACCELERATION
|
||||
float max_travel_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_travel, i);
|
||||
m_time_processor.machines[i].max_travel_acceleration = max_travel_acceleration;
|
||||
m_time_processor.machines[i].travel_acceleration = (max_travel_acceleration > 0.0f) ? max_travel_acceleration : DEFAULT_TRAVEL_ACCELERATION;
|
||||
|
@ -1172,7 +1189,7 @@ void GCodeProcessor::process_file(const std::string& filename, bool apply_postpr
|
|||
// 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);
|
||||
config.load_from_gcode_file(filename, ForwardCompatibilitySubstitutionRule::EnableSilent);
|
||||
apply_config(config);
|
||||
}
|
||||
else if (m_producer == EProducer::Simplify3D)
|
||||
|
@ -2713,14 +2730,22 @@ void GCodeProcessor::process_M204(const GCodeReader::GCodeLine& line)
|
|||
set_acceleration(static_cast<PrintEstimatedStatistics::ETimeMode>(i), value);
|
||||
set_travel_acceleration(static_cast<PrintEstimatedStatistics::ETimeMode>(i), value);
|
||||
if (line.has_value('T', value))
|
||||
#if ENABLE_RETRACT_ACCELERATION
|
||||
set_retract_acceleration(static_cast<PrintEstimatedStatistics::ETimeMode>(i), value);
|
||||
#else
|
||||
set_option_value(m_time_processor.machine_limits.machine_max_acceleration_retracting, i, value);
|
||||
#endif // ENABLE_RETRACT_ACCELERATION
|
||||
}
|
||||
else {
|
||||
// New acceleration format, compatible with the upstream Marlin.
|
||||
if (line.has_value('P', value))
|
||||
set_acceleration(static_cast<PrintEstimatedStatistics::ETimeMode>(i), value);
|
||||
if (line.has_value('R', value))
|
||||
#if ENABLE_RETRACT_ACCELERATION
|
||||
set_retract_acceleration(static_cast<PrintEstimatedStatistics::ETimeMode>(i), value);
|
||||
#else
|
||||
set_option_value(m_time_processor.machine_limits.machine_max_acceleration_retracting, i, value);
|
||||
#endif // ENABLE_RETRACT_ACCELERATION
|
||||
if (line.has_value('T', value))
|
||||
// Interpret the T value as the travel acceleration in the new Marlin format.
|
||||
set_travel_acceleration(static_cast<PrintEstimatedStatistics::ETimeMode>(i), value);
|
||||
|
@ -2979,10 +3004,30 @@ float GCodeProcessor::get_axis_max_jerk(PrintEstimatedStatistics::ETimeMode mode
|
|||
}
|
||||
}
|
||||
|
||||
#if ENABLE_RETRACT_ACCELERATION
|
||||
float GCodeProcessor::get_retract_acceleration(PrintEstimatedStatistics::ETimeMode mode) const
|
||||
{
|
||||
size_t id = static_cast<size_t>(mode);
|
||||
return (id < m_time_processor.machines.size()) ? m_time_processor.machines[id].retract_acceleration : DEFAULT_RETRACT_ACCELERATION;
|
||||
}
|
||||
#else
|
||||
float GCodeProcessor::get_retract_acceleration(PrintEstimatedStatistics::ETimeMode mode) const
|
||||
{
|
||||
return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_retracting, static_cast<size_t>(mode));
|
||||
}
|
||||
#endif // ENABLE_RETRACT_ACCELERATION
|
||||
|
||||
#if ENABLE_RETRACT_ACCELERATION
|
||||
void GCodeProcessor::set_retract_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value)
|
||||
{
|
||||
size_t id = static_cast<size_t>(mode);
|
||||
if (id < m_time_processor.machines.size()) {
|
||||
m_time_processor.machines[id].retract_acceleration = (m_time_processor.machines[id].max_retract_acceleration == 0.0f) ? value :
|
||||
// Clamp the acceleration with the maximum.
|
||||
std::min(value, m_time_processor.machines[id].max_retract_acceleration);
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_RETRACT_ACCELERATION
|
||||
|
||||
float GCodeProcessor::get_acceleration(PrintEstimatedStatistics::ETimeMode mode) const
|
||||
{
|
||||
|
|
|
@ -242,6 +242,11 @@ namespace Slic3r {
|
|||
float acceleration; // mm/s^2
|
||||
// hard limit for the acceleration, to which the firmware will clamp.
|
||||
float max_acceleration; // mm/s^2
|
||||
#if ENABLE_RETRACT_ACCELERATION
|
||||
float retract_acceleration; // mm/s^2
|
||||
// hard limit for the acceleration, to which the firmware will clamp.
|
||||
float max_retract_acceleration; // mm/s^2
|
||||
#endif // ENABLE_RETRACT_ACCELERATION
|
||||
float travel_acceleration; // mm/s^2
|
||||
// hard limit for the travel acceleration, to which the firmware will clamp.
|
||||
float max_travel_acceleration; // mm/s^2
|
||||
|
@ -337,9 +342,9 @@ namespace Slic3r {
|
|||
std::string printer;
|
||||
|
||||
void reset() {
|
||||
print = "";
|
||||
filament = std::vector<std::string>();
|
||||
printer = "";
|
||||
print.clear();
|
||||
filament.clear();
|
||||
printer.clear();
|
||||
}
|
||||
};
|
||||
std::string filename;
|
||||
|
@ -669,6 +674,9 @@ namespace Slic3r {
|
|||
float get_axis_max_acceleration(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const;
|
||||
float get_axis_max_jerk(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const;
|
||||
float get_retract_acceleration(PrintEstimatedStatistics::ETimeMode mode) const;
|
||||
#if ENABLE_RETRACT_ACCELERATION
|
||||
void set_retract_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value);
|
||||
#endif // ENABLE_RETRACT_ACCELERATION
|
||||
float get_acceleration(PrintEstimatedStatistics::ETimeMode mode) const;
|
||||
void set_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value);
|
||||
float get_travel_acceleration(PrintEstimatedStatistics::ETimeMode mode) const;
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
#include "PostProcessor.hpp"
|
||||
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "libslic3r/format.hpp"
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/nowide/convert.hpp>
|
||||
#include <boost/nowide/cenv.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
|
||||
#ifdef WIN32
|
||||
|
||||
|
@ -179,41 +184,146 @@ static int run_script(const std::string &script, const std::string &gcode, std::
|
|||
|
||||
namespace Slic3r {
|
||||
|
||||
void run_post_process_scripts(const std::string &path, const DynamicPrintConfig &config)
|
||||
// Run post processing script / scripts if defined.
|
||||
// Returns true if a post-processing script was executed.
|
||||
// Returns false if no post-processing script was defined.
|
||||
// Throws an exception on error.
|
||||
// host is one of "File", "PrusaLink", "Repetier", "SL1Host", "OctoPrint", "FlashAir", "Duet", "AstroBox" ...
|
||||
// For a "File" target, a temp file will be created for src_path by adding a ".pp" suffix and src_path will be updated.
|
||||
// In that case the caller is responsible to delete the temp file created.
|
||||
// output_name is the final name of the G-code on SD card or when uploaded to PrusaLink or OctoPrint.
|
||||
// If uploading to PrusaLink or OctoPrint, then the file will be renamed to output_name first on the target host.
|
||||
// The post-processing script may change the output_name.
|
||||
bool run_post_process_scripts(std::string &src_path, bool make_copy, const std::string &host, std::string &output_name, const DynamicPrintConfig &config)
|
||||
{
|
||||
const auto* post_process = config.opt<ConfigOptionStrings>("post_process");
|
||||
const auto *post_process = config.opt<ConfigOptionStrings>("post_process");
|
||||
if (// likely running in SLA mode
|
||||
post_process == nullptr ||
|
||||
// no post-processing script
|
||||
post_process->values.empty())
|
||||
return;
|
||||
return false;
|
||||
|
||||
std::string path;
|
||||
if (make_copy) {
|
||||
// Don't run the post-processing script on the input file, it will be memory mapped by the G-code viewer.
|
||||
// Make a copy.
|
||||
path = src_path + ".pp";
|
||||
// First delete an old file if it exists.
|
||||
try {
|
||||
if (boost::filesystem::exists(path))
|
||||
boost::filesystem::remove(path);
|
||||
} catch (const std::exception &err) {
|
||||
BOOST_LOG_TRIVIAL(error) << Slic3r::format("Failed deleting an old temporary file %1% before running a post-processing script: %2%", path, err.what());
|
||||
}
|
||||
// Second make a copy.
|
||||
std::string error_message;
|
||||
if (copy_file(src_path, path, error_message, false) != SUCCESS)
|
||||
throw Slic3r::RuntimeError(Slic3r::format("Failed making a temporary copy of G-code file %1% before running a post-processing script: %2%", src_path, error_message));
|
||||
} else {
|
||||
// Don't make a copy of the G-code before running the post-processing script.
|
||||
path = src_path;
|
||||
}
|
||||
|
||||
auto delete_copy = [&path, &src_path, make_copy]() {
|
||||
if (make_copy)
|
||||
try {
|
||||
if (boost::filesystem::exists(path))
|
||||
boost::filesystem::remove(path);
|
||||
} catch (const std::exception &err) {
|
||||
BOOST_LOG_TRIVIAL(error) << Slic3r::format("Failed deleting a temporary copy %1% of a G-code file %2% : %3%", path, src_path, err.what());
|
||||
}
|
||||
};
|
||||
|
||||
// Store print configuration into environment variables.
|
||||
config.setenv_();
|
||||
auto gcode_file = boost::filesystem::path(path);
|
||||
if (! boost::filesystem::exists(gcode_file))
|
||||
throw Slic3r::RuntimeError(std::string("Post-processor can't find exported gcode file"));
|
||||
|
||||
for (const std::string &scripts : post_process->values) {
|
||||
std::vector<std::string> lines;
|
||||
boost::split(lines, scripts, boost::is_any_of("\r\n"));
|
||||
for (std::string script : lines) {
|
||||
// Ignore empty post processing script lines.
|
||||
boost::trim(script);
|
||||
if (script.empty())
|
||||
continue;
|
||||
BOOST_LOG_TRIVIAL(info) << "Executing script " << script << " on file " << path;
|
||||
// Store print configuration into environment variables.
|
||||
config.setenv_();
|
||||
// Let the post-processing script know the target host ("File", "PrusaLink", "Repetier", "SL1Host", "OctoPrint", "FlashAir", "Duet", "AstroBox" ...)
|
||||
boost::nowide::setenv("SLIC3R_PP_HOST", host.c_str(), 1);
|
||||
// Let the post-processing script know the final file name. For "File" host, it is a full path of the target file name and its location, for example pointing to an SD card.
|
||||
// For "PrusaLink" or "OctoPrint", it is a file name optionally with a directory on the target host.
|
||||
boost::nowide::setenv("SLIC3R_PP_OUTPUT_NAME", output_name.c_str(), 1);
|
||||
|
||||
std::string std_err;
|
||||
const int result = run_script(script, gcode_file.string(), std_err);
|
||||
if (result != 0) {
|
||||
const std::string msg = std_err.empty() ? (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%") % script % path % result).str()
|
||||
: (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%\nOutput:\n%4%") % script % path % result % std_err).str();
|
||||
BOOST_LOG_TRIVIAL(error) << msg;
|
||||
throw Slic3r::RuntimeError(msg);
|
||||
// Path to an optional file that the post-processing script may create and populate it with a single line containing the output_name replacement.
|
||||
std::string path_output_name = path + ".output_name";
|
||||
auto remove_output_name_file = [&path_output_name, &src_path]() {
|
||||
try {
|
||||
if (boost::filesystem::exists(path_output_name))
|
||||
boost::filesystem::remove(path_output_name);
|
||||
} catch (const std::exception &err) {
|
||||
BOOST_LOG_TRIVIAL(error) << Slic3r::format("Failed deleting a file %1% carrying the final name / path of a G-code file %2%: %3%", path_output_name, src_path, err.what());
|
||||
}
|
||||
};
|
||||
// Remove possible stalled path_output_name of the previous run.
|
||||
remove_output_name_file();
|
||||
|
||||
try {
|
||||
for (const std::string &scripts : post_process->values) {
|
||||
std::vector<std::string> lines;
|
||||
boost::split(lines, scripts, boost::is_any_of("\r\n"));
|
||||
for (std::string script : lines) {
|
||||
// Ignore empty post processing script lines.
|
||||
boost::trim(script);
|
||||
if (script.empty())
|
||||
continue;
|
||||
BOOST_LOG_TRIVIAL(info) << "Executing script " << script << " on file " << path;
|
||||
std::string std_err;
|
||||
const int result = run_script(script, gcode_file.string(), std_err);
|
||||
if (result != 0) {
|
||||
const std::string msg = std_err.empty() ? (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%") % script % path % result).str()
|
||||
: (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%\nOutput:\n%4%") % script % path % result % std_err).str();
|
||||
BOOST_LOG_TRIVIAL(error) << msg;
|
||||
delete_copy();
|
||||
throw Slic3r::RuntimeError(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (boost::filesystem::exists(path_output_name)) {
|
||||
try {
|
||||
// Read a single line from path_output_name, which should contain the new output name of the post-processed G-code.
|
||||
boost::nowide::fstream f;
|
||||
f.open(path_output_name, std::ios::in);
|
||||
std::string new_output_name;
|
||||
std::getline(f, new_output_name);
|
||||
f.close();
|
||||
|
||||
if (host == "File") {
|
||||
namespace fs = boost::filesystem;
|
||||
fs::path op(new_output_name);
|
||||
if (op.is_relative() && op.has_filename() && op.parent_path().empty()) {
|
||||
// Is this just a filename? Make it an absolute path.
|
||||
auto outpath = fs::path(output_name).parent_path();
|
||||
outpath /= op.string();
|
||||
new_output_name = outpath.string();
|
||||
}
|
||||
else {
|
||||
if (! op.is_absolute() || ! op.has_filename())
|
||||
throw Slic3r::RuntimeError("Unable to parse desired new path from output name file");
|
||||
}
|
||||
if (! fs::exists(fs::path(new_output_name).parent_path()))
|
||||
throw Slic3r::RuntimeError(Slic3r::format("Output directory does not exist: %1%",
|
||||
fs::path(new_output_name).parent_path().string()));
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "Post-processing script changed the file name from " << output_name << " to " << new_output_name;
|
||||
output_name = new_output_name;
|
||||
} catch (const std::exception &err) {
|
||||
throw Slic3r::RuntimeError(Slic3r::format("run_post_process_scripts: Failed reading a file %1% "
|
||||
"carrying the final name / path of a G-code file: %2%",
|
||||
path_output_name, err.what()));
|
||||
}
|
||||
remove_output_name_file();
|
||||
}
|
||||
} catch (...) {
|
||||
remove_output_name_file();
|
||||
delete_copy();
|
||||
throw;
|
||||
}
|
||||
|
||||
src_path = std::move(path);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
|
|
@ -8,7 +8,23 @@
|
|||
|
||||
namespace Slic3r {
|
||||
|
||||
extern void run_post_process_scripts(const std::string &path, const DynamicPrintConfig &config);
|
||||
// Run post processing script / scripts if defined.
|
||||
// Returns true if a post-processing script was executed.
|
||||
// Returns false if no post-processing script was defined.
|
||||
// Throws an exception on error.
|
||||
// host is one of "File", "PrusaLink", "Repetier", "SL1Host", "OctoPrint", "FlashAir", "Duet", "AstroBox" ...
|
||||
// If make_copy, then a temp file will be created for src_path by adding a ".pp" suffix and src_path will be updated.
|
||||
// In that case the caller is responsible to delete the temp file created.
|
||||
// output_name is the final name of the G-code on SD card or when uploaded to PrusaLink or OctoPrint.
|
||||
// If uploading to PrusaLink or OctoPrint, then the file will be renamed to output_name first on the target host.
|
||||
// The post-processing script may change the output_name.
|
||||
extern bool run_post_process_scripts(std::string &src_path, bool make_copy, const std::string &host, std::string &output_name, const DynamicPrintConfig &config);
|
||||
|
||||
inline bool run_post_process_scripts(std::string &src_path, const DynamicPrintConfig &config)
|
||||
{
|
||||
std::string src_path_name = src_path;
|
||||
return run_post_process_scripts(src_path, false, "File", src_path_name, config);
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
|
|
|
@ -12,16 +12,24 @@
|
|||
|
||||
namespace Slic3r {
|
||||
|
||||
static inline char get_extrusion_axis_char(const GCodeConfig &config)
|
||||
{
|
||||
std::string axis = get_extrusion_axis(config);
|
||||
assert(axis.size() <= 1);
|
||||
// Return 0 for gcfNoExtrusion
|
||||
return axis.empty() ? 0 : axis[0];
|
||||
}
|
||||
|
||||
void GCodeReader::apply_config(const GCodeConfig &config)
|
||||
{
|
||||
m_config = config;
|
||||
m_extrusion_axis = get_extrusion_axis(m_config)[0];
|
||||
m_extrusion_axis = get_extrusion_axis_char(m_config);
|
||||
}
|
||||
|
||||
void GCodeReader::apply_config(const DynamicPrintConfig &config)
|
||||
{
|
||||
m_config.apply(config, true);
|
||||
m_extrusion_axis = get_extrusion_axis(m_config)[0];
|
||||
m_extrusion_axis = get_extrusion_axis_char(m_config);
|
||||
}
|
||||
|
||||
const char* GCodeReader::parse_line_internal(const char *ptr, GCodeLine &gline, std::pair<const char*, const char*> &command)
|
||||
|
@ -52,9 +60,10 @@ const char* GCodeReader::parse_line_internal(const char *ptr, GCodeLine &gline,
|
|||
case 'Z': axis = Z; break;
|
||||
case 'F': axis = F; break;
|
||||
default:
|
||||
if (*c == m_extrusion_axis)
|
||||
axis = E;
|
||||
else if (*c >= 'A' && *c <= 'Z')
|
||||
if (*c == m_extrusion_axis) {
|
||||
if (m_extrusion_axis != 0)
|
||||
axis = E;
|
||||
} else if (*c >= 'A' && *c <= 'Z')
|
||||
// Unknown axis, but we still want to remember that such a axis was seen.
|
||||
axis = UNKNOWN_AXIS;
|
||||
break;
|
||||
|
@ -190,6 +199,8 @@ void GCodeReader::GCodeLine::set(const GCodeReader &reader, const Axis axis, con
|
|||
match[1] = 'F';
|
||||
else {
|
||||
assert(axis == E);
|
||||
// Extruder axis is set.
|
||||
assert(reader.extrusion_axis() != 0);
|
||||
match[1] = reader.extrusion_axis();
|
||||
}
|
||||
|
||||
|
|
|
@ -122,8 +122,9 @@ public:
|
|||
float& f() { return m_position[F]; }
|
||||
float f() const { return m_position[F]; }
|
||||
|
||||
// Returns 0 for gcfNoExtrusion.
|
||||
char extrusion_axis() const { return m_extrusion_axis; }
|
||||
void set_extrusion_axis(char axis) { m_extrusion_axis = axis; }
|
||||
// void set_extrusion_axis(char axis) { m_extrusion_axis = axis; }
|
||||
|
||||
private:
|
||||
const char* parse_line_internal(const char *ptr, GCodeLine &gline, std::pair<const char*, const char*> &command);
|
||||
|
|
|
@ -405,8 +405,10 @@ std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std:
|
|||
|
||||
std::ostringstream gcode;
|
||||
gcode << "G1 X" << XYZF_NUM(point(0))
|
||||
<< " Y" << XYZF_NUM(point(1))
|
||||
<< " " << m_extrusion_axis << E_NUM(m_extruder->E());
|
||||
<< " Y" << XYZF_NUM(point(1));
|
||||
if (! m_extrusion_axis.empty())
|
||||
// not gcfNoExtrusion
|
||||
gcode << " " << m_extrusion_axis << E_NUM(m_extruder->E());
|
||||
COMMENT(comment);
|
||||
gcode << "\n";
|
||||
return gcode.str();
|
||||
|
@ -421,8 +423,10 @@ std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std
|
|||
std::ostringstream gcode;
|
||||
gcode << "G1 X" << XYZF_NUM(point(0))
|
||||
<< " Y" << XYZF_NUM(point(1))
|
||||
<< " Z" << XYZF_NUM(point(2))
|
||||
<< " " << m_extrusion_axis << E_NUM(m_extruder->E());
|
||||
<< " Z" << XYZF_NUM(point(2));
|
||||
if (! m_extrusion_axis.empty())
|
||||
// not gcfNoExtrusion
|
||||
gcode << " " << m_extrusion_axis << E_NUM(m_extruder->E());
|
||||
COMMENT(comment);
|
||||
gcode << "\n";
|
||||
return gcode.str();
|
||||
|
@ -474,7 +478,7 @@ std::string GCodeWriter::_retract(double length, double restart_extra, const std
|
|||
gcode << "G22 ; retract\n";
|
||||
else
|
||||
gcode << "G10 ; retract\n";
|
||||
} else {
|
||||
} else if (! m_extrusion_axis.empty()) {
|
||||
gcode << "G1 " << m_extrusion_axis << E_NUM(m_extruder->E())
|
||||
<< " F" << XYZF_NUM(m_extruder->retract_speed() * 60.);
|
||||
COMMENT(comment);
|
||||
|
@ -503,7 +507,7 @@ std::string GCodeWriter::unretract()
|
|||
else
|
||||
gcode << "G11 ; unretract\n";
|
||||
gcode << this->reset_e();
|
||||
} else {
|
||||
} else if (! m_extrusion_axis.empty()) {
|
||||
// use G1 instead of G0 because G0 will blend the restart with the previous travel move
|
||||
gcode << "G1 " << m_extrusion_axis << E_NUM(m_extruder->E())
|
||||
<< " F" << XYZF_NUM(m_extruder->deretract_speed() * 60.);
|
||||
|
|
|
@ -25,6 +25,7 @@ public:
|
|||
Extruder* extruder() { return m_extruder; }
|
||||
const Extruder* extruder() const { return m_extruder; }
|
||||
|
||||
// Returns empty string for gcfNoExtrusion.
|
||||
std::string extrusion_axis() const { return m_extrusion_axis; }
|
||||
void apply_print_config(const PrintConfig &print_config);
|
||||
// Extruders are expected to be sorted in an increasing order.
|
||||
|
|
|
@ -118,6 +118,11 @@ void triangle_mesh_to_cgal(const std::vector<stl_vertex> & V,
|
|||
{
|
||||
if (F.empty()) return;
|
||||
|
||||
size_t vertices_count = V.size();
|
||||
size_t edges_count = (F.size()* 3) / 2;
|
||||
size_t faces_count = F.size();
|
||||
out.reserve(vertices_count, edges_count, faces_count);
|
||||
|
||||
for (auto &v : V)
|
||||
out.add_vertex(typename _Mesh::Point{v.x(), v.y(), v.z()});
|
||||
|
||||
|
|
|
@ -157,6 +157,7 @@ template<class Its> bool its_is_splittable(const Its &m)
|
|||
const auto& neighbor_index = ItsWithNeighborsIndex_<Its>::get_index(m);
|
||||
|
||||
std::vector<char> visited(its.indices.size(), false);
|
||||
its_find_unvisited_neighbors(its, neighbor_index, visited);
|
||||
auto faces = its_find_unvisited_neighbors(its, neighbor_index, visited);
|
||||
|
||||
return !faces.empty();
|
||||
|
|
|
@ -424,7 +424,7 @@ void Model::convert_multipart_object(unsigned int max_extruders)
|
|||
|
||||
ModelObject* object = new ModelObject(this);
|
||||
object->input_file = this->objects.front()->input_file;
|
||||
object->name = this->objects.front()->name;
|
||||
object->name = boost::filesystem::path(this->objects.front()->input_file).stem().string();
|
||||
//FIXME copy the config etc?
|
||||
|
||||
unsigned int extruder_counter = 0;
|
||||
|
@ -439,7 +439,7 @@ void Model::convert_multipart_object(unsigned int max_extruders)
|
|||
int counter = 1;
|
||||
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->name = (counter > 1) ? o->name + "_" + std::to_string(counter++) : o->name;
|
||||
new_v->config.set("extruder", auto_extruder_id(max_extruders, extruder_counter));
|
||||
return new_v;
|
||||
};
|
||||
|
@ -460,13 +460,15 @@ void Model::convert_multipart_object(unsigned int max_extruders)
|
|||
this->objects.push_back(object);
|
||||
}
|
||||
|
||||
static constexpr const double volume_threshold_inches = 9.0; // 9 = 3*3*3;
|
||||
|
||||
bool Model::looks_like_imperial_units() const
|
||||
{
|
||||
if (this->objects.size() == 0)
|
||||
return false;
|
||||
|
||||
for (ModelObject* obj : this->objects)
|
||||
if (obj->get_object_stl_stats().volume < 9.0) // 9 = 3*3*3;
|
||||
if (obj->get_object_stl_stats().volume < volume_threshold_inches)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
|
@ -474,22 +476,26 @@ bool Model::looks_like_imperial_units() const
|
|||
|
||||
void Model::convert_from_imperial_units(bool only_small_volumes)
|
||||
{
|
||||
double in_to_mm = 25.4;
|
||||
static constexpr const double in_to_mm = 25.4;
|
||||
for (ModelObject* obj : this->objects)
|
||||
if (! only_small_volumes || obj->get_object_stl_stats().volume < 9.0) { // 9 = 3*3*3;
|
||||
if (! only_small_volumes || obj->get_object_stl_stats().volume < volume_threshold_inches) {
|
||||
obj->scale_mesh_after_creation(Vec3d(in_to_mm, in_to_mm, in_to_mm));
|
||||
for (ModelVolume* v : obj->volumes)
|
||||
for (ModelVolume* v : obj->volumes) {
|
||||
assert(! v->source.is_converted_from_meters);
|
||||
v->source.is_converted_from_inches = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr const double volume_threshold_meters = 0.001; // 0.001 = 0.1*0.1*0.1
|
||||
|
||||
bool Model::looks_like_saved_in_meters() const
|
||||
{
|
||||
if (this->objects.size() == 0)
|
||||
return false;
|
||||
|
||||
for (ModelObject* obj : this->objects)
|
||||
if (obj->get_object_stl_stats().volume < 0.001) // 0.001 = 0.1*0.1*0.1;
|
||||
if (obj->get_object_stl_stats().volume < volume_threshold_meters)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
|
@ -497,12 +503,14 @@ bool Model::looks_like_saved_in_meters() const
|
|||
|
||||
void Model::convert_from_meters(bool only_small_volumes)
|
||||
{
|
||||
double m_to_mm = 1000;
|
||||
static constexpr const double m_to_mm = 1000;
|
||||
for (ModelObject* obj : this->objects)
|
||||
if (! only_small_volumes || obj->get_object_stl_stats().volume < 0.001) { // 0.001 = 0.1*0.1*0.1;
|
||||
if (! only_small_volumes || obj->get_object_stl_stats().volume < volume_threshold_meters) {
|
||||
obj->scale_mesh_after_creation(Vec3d(m_to_mm, m_to_mm, m_to_mm));
|
||||
for (ModelVolume* v : obj->volumes)
|
||||
for (ModelVolume* v : obj->volumes) {
|
||||
assert(! v->source.is_converted_from_inches);
|
||||
v->source.is_converted_from_meters = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -948,9 +956,26 @@ void ModelObject::center_around_origin(bool include_modifiers)
|
|||
|
||||
void ModelObject::ensure_on_bed(bool allow_negative_z)
|
||||
{
|
||||
const double min_z = get_min_z();
|
||||
if (!allow_negative_z || min_z > SINKING_Z_THRESHOLD)
|
||||
translate_instances({ 0.0, 0.0, -min_z });
|
||||
double z_offset = 0.0;
|
||||
|
||||
if (allow_negative_z) {
|
||||
if (parts_count() == 1) {
|
||||
const double min_z = get_min_z();
|
||||
const double max_z = get_max_z();
|
||||
if (min_z >= SINKING_Z_THRESHOLD || max_z < 0.0)
|
||||
z_offset = -min_z;
|
||||
}
|
||||
else {
|
||||
const double max_z = get_max_z();
|
||||
if (max_z < SINKING_MIN_Z_THRESHOLD)
|
||||
z_offset = SINKING_MIN_Z_THRESHOLD - max_z;
|
||||
}
|
||||
}
|
||||
else
|
||||
z_offset = -get_min_z();
|
||||
|
||||
if (z_offset != 0.0)
|
||||
translate_instances(z_offset * Vec3d::UnitZ());
|
||||
}
|
||||
|
||||
void ModelObject::translate_instances(const Vec3d& vector)
|
||||
|
@ -1075,6 +1100,7 @@ void ModelObject::convert_units(ModelObjectPtrs& new_objects, ConversionType con
|
|||
vol->source.is_converted_from_inches = conv_type == ConversionType::CONV_FROM_INCH;
|
||||
if (conv_type == ConversionType::CONV_FROM_METER || conv_type == ConversionType::CONV_TO_METER)
|
||||
vol->source.is_converted_from_meters = conv_type == ConversionType::CONV_FROM_METER;
|
||||
assert(! vol->source.is_converted_from_inches || ! vol->source.is_converted_from_meters);
|
||||
}
|
||||
else
|
||||
vol->set_offset(volume->get_offset());
|
||||
|
@ -1105,6 +1131,15 @@ size_t ModelObject::facets_count() const
|
|||
return num;
|
||||
}
|
||||
|
||||
size_t ModelObject::parts_count() const
|
||||
{
|
||||
size_t num = 0;
|
||||
for (const ModelVolume* v : this->volumes)
|
||||
if (v->is_model_part())
|
||||
++num;
|
||||
return num;
|
||||
}
|
||||
|
||||
bool ModelObject::needed_repair() const
|
||||
{
|
||||
for (const ModelVolume *v : this->volumes)
|
||||
|
@ -1420,6 +1455,19 @@ double ModelObject::get_min_z() const
|
|||
}
|
||||
}
|
||||
|
||||
double ModelObject::get_max_z() const
|
||||
{
|
||||
if (instances.empty())
|
||||
return 0.0;
|
||||
else {
|
||||
double max_z = -DBL_MAX;
|
||||
for (size_t i = 0; i < instances.size(); ++i) {
|
||||
max_z = std::max(max_z, get_instance_max_z(i));
|
||||
}
|
||||
return max_z;
|
||||
}
|
||||
}
|
||||
|
||||
double ModelObject::get_instance_min_z(size_t instance_idx) const
|
||||
{
|
||||
double min_z = DBL_MAX;
|
||||
|
@ -1441,6 +1489,27 @@ double ModelObject::get_instance_min_z(size_t instance_idx) const
|
|||
return min_z + inst->get_offset(Z);
|
||||
}
|
||||
|
||||
double ModelObject::get_instance_max_z(size_t instance_idx) const
|
||||
{
|
||||
double max_z = -DBL_MAX;
|
||||
|
||||
const ModelInstance* inst = instances[instance_idx];
|
||||
const Transform3d& mi = inst->get_matrix(true);
|
||||
|
||||
for (const ModelVolume* v : volumes) {
|
||||
if (!v->is_model_part())
|
||||
continue;
|
||||
|
||||
const Transform3d mv = mi * v->get_matrix();
|
||||
const TriangleMesh& hull = v->get_convex_hull();
|
||||
for (const stl_facet& facet : hull.stl.facet_start)
|
||||
for (int i = 0; i < 3; ++i)
|
||||
max_z = std::max(max_z, (mv * facet.vertex[i].cast<double>()).z());
|
||||
}
|
||||
|
||||
return max_z + inst->get_offset(Z);
|
||||
}
|
||||
|
||||
unsigned int ModelObject::check_instances_print_volume_state(const BoundingBoxf3& print_volume)
|
||||
{
|
||||
unsigned int num_printable = 0;
|
||||
|
@ -1827,6 +1896,7 @@ void ModelVolume::transform_this_mesh(const Matrix3d &matrix, bool fix_left_hand
|
|||
|
||||
void ModelVolume::convert_from_imperial_units()
|
||||
{
|
||||
assert(! this->source.is_converted_from_meters);
|
||||
double in_to_mm = 25.4;
|
||||
this->scale_geometry_after_creation(Vec3d(in_to_mm, in_to_mm, in_to_mm));
|
||||
this->set_offset(Vec3d(0, 0, 0));
|
||||
|
@ -1835,6 +1905,7 @@ void ModelVolume::convert_from_imperial_units()
|
|||
|
||||
void ModelVolume::convert_from_meters()
|
||||
{
|
||||
assert(! this->source.is_converted_from_inches);
|
||||
double m_to_mm = 1000;
|
||||
this->scale_geometry_after_creation(Vec3d(m_to_mm, m_to_mm, m_to_mm));
|
||||
this->set_offset(Vec3d(0, 0, 0));
|
||||
|
|
|
@ -347,6 +347,7 @@ public:
|
|||
|
||||
size_t materials_count() const;
|
||||
size_t facets_count() const;
|
||||
size_t parts_count() const;
|
||||
bool needed_repair() const;
|
||||
ModelObjectPtrs cut(size_t instance, coordf_t z, ModelObjectCutAttributes attributes);
|
||||
void split(ModelObjectPtrs* new_objects);
|
||||
|
@ -358,7 +359,9 @@ public:
|
|||
void bake_xy_rotation_into_meshes(size_t instance_idx);
|
||||
|
||||
double get_min_z() const;
|
||||
double get_max_z() const;
|
||||
double get_instance_min_z(size_t instance_idx) const;
|
||||
double get_instance_max_z(size_t instance_idx) const;
|
||||
|
||||
// Called by Print::validate() from the UI thread.
|
||||
unsigned int check_instances_print_volume_state(const BoundingBoxf3& print_volume);
|
||||
|
@ -1177,6 +1180,7 @@ void check_model_ids_equal(const Model &model1, const Model &model2);
|
|||
#endif /* NDEBUG */
|
||||
|
||||
static const float SINKING_Z_THRESHOLD = -0.001f;
|
||||
static const double SINKING_MIN_Z_THRESHOLD = 0.05;
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
|
|
|
@ -1240,221 +1240,6 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
|
|||
const size_t num_layers = input_expolygons.size();
|
||||
const ConstLayerPtrsAdaptor layers = print_object.layers();
|
||||
|
||||
#if 0
|
||||
auto get_extrusion_width = [&layers = std::as_const(layers)](const size_t layer_idx) -> float {
|
||||
auto extrusion_width_it = std::max_element(layers[layer_idx]->regions().begin(), layers[layer_idx]->regions().end(),
|
||||
[](const LayerRegion *l1, const LayerRegion *l2) {
|
||||
return l1->region().config().perimeter_extrusion_width <
|
||||
l2->region().config().perimeter_extrusion_width;
|
||||
});
|
||||
assert(extrusion_width_it != layers[layer_idx]->regions().end());
|
||||
return float((*extrusion_width_it)->region().config().perimeter_extrusion_width);
|
||||
};
|
||||
|
||||
auto get_top_solid_layers = [&layers = std::as_const(layers)](const size_t layer_idx) -> int {
|
||||
auto top_solid_layer_it = std::max_element(layers[layer_idx]->regions().begin(), layers[layer_idx]->regions().end(),
|
||||
[](const LayerRegion *l1, const LayerRegion *l2) {
|
||||
return l1->region().config().top_solid_layers < l2->region().config().top_solid_layers;
|
||||
});
|
||||
assert(top_solid_layer_it != layers[layer_idx]->regions().end());
|
||||
return (*top_solid_layer_it)->region().config().top_solid_layers;
|
||||
};
|
||||
|
||||
auto get_bottom_solid_layers = [&layers = std::as_const(layers)](const size_t layer_idx) -> int {
|
||||
auto top_bottom_layer_it = std::max_element(layers[layer_idx]->regions().begin(), layers[layer_idx]->regions().end(),
|
||||
[](const LayerRegion *l1, const LayerRegion *l2) {
|
||||
return l1->region().config().bottom_solid_layers < l2->region().config().bottom_solid_layers;
|
||||
});
|
||||
assert(top_bottom_layer_it != layers[layer_idx]->regions().end());
|
||||
return (*top_bottom_layer_it)->region().config().bottom_solid_layers;
|
||||
};
|
||||
|
||||
std::vector<ExPolygons> top_layers(num_layers);
|
||||
top_layers.back() = input_expolygons.back();
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(1, num_layers), [&](const tbb::blocked_range<size_t> &range) {
|
||||
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
|
||||
throw_on_cancel_callback();
|
||||
float extrusion_width = 0.1f * float(scale_(get_extrusion_width(layer_idx)));
|
||||
top_layers[layer_idx - 1] = diff_ex(input_expolygons[layer_idx - 1], offset_ex(input_expolygons[layer_idx], extrusion_width));
|
||||
}
|
||||
}); // end of parallel_for
|
||||
|
||||
std::vector<ExPolygons> bottom_layers(num_layers);
|
||||
bottom_layers.front() = input_expolygons.front();
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers - 1), [&](const tbb::blocked_range<size_t> &range) {
|
||||
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
|
||||
throw_on_cancel_callback();
|
||||
float extrusion_width = 0.1f * float(scale_(get_extrusion_width(layer_idx)));
|
||||
bottom_layers[layer_idx + 1] = diff_ex(input_expolygons[layer_idx + 1], offset_ex(input_expolygons[layer_idx], extrusion_width));
|
||||
}
|
||||
}); // end of parallel_for
|
||||
|
||||
std::vector<std::vector<ClipperLib::Paths>> triangles_by_color_raw(num_extruders, std::vector<ClipperLib::Paths>(layers.size()));
|
||||
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - top and bottom layers - projection of painted triangles - begin";
|
||||
{
|
||||
auto delta = float(10 * SCALED_EPSILON);
|
||||
std::vector<float> deltas { delta, delta, delta };
|
||||
Points projected_facet;
|
||||
for (const ModelVolume *mv : print_object.model_object()->volumes)
|
||||
if (mv->is_model_part()) {
|
||||
const Transform3f tr = print_object.trafo().cast<float>() * mv->get_matrix().cast<float>();
|
||||
|
||||
for (size_t extruder_idx = 0; extruder_idx < num_extruders; ++extruder_idx) {
|
||||
const indexed_triangle_set custom_facets = mv->mmu_segmentation_facets.get_facets(*mv, EnforcerBlockerType(extruder_idx));
|
||||
if (custom_facets.indices.empty())
|
||||
continue;
|
||||
|
||||
throw_on_cancel_callback();
|
||||
for (size_t facet_idx = 0; facet_idx < custom_facets.indices.size(); ++facet_idx) {
|
||||
float min_z = std::numeric_limits<float>::max();
|
||||
float max_z = std::numeric_limits<float>::lowest();
|
||||
|
||||
std::array<Vec3f, 3> facet;
|
||||
for (int p_idx = 0; p_idx < 3; ++p_idx) {
|
||||
facet[p_idx] = tr * custom_facets.vertices[custom_facets.indices[facet_idx](p_idx)];
|
||||
max_z = std::max(max_z, facet[p_idx].z());
|
||||
min_z = std::min(min_z, facet[p_idx].z());
|
||||
}
|
||||
|
||||
// Sort the vertices by z-axis for simplification of projected_facet on slices
|
||||
std::sort(facet.begin(), facet.end(), [](const Vec3f &p1, const Vec3f &p2) { return p1.z() < p2.z(); });
|
||||
projected_facet.clear();
|
||||
projected_facet.reserve(3);
|
||||
for (int p_idx = 0; p_idx < 3; ++p_idx)
|
||||
projected_facet.emplace_back(Point(scale_(facet[p_idx].x()), scale_(facet[p_idx].y())) - print_object.center_offset());
|
||||
if (cross2((projected_facet[1] - projected_facet[0]).cast<int64_t>(), (projected_facet[2] - projected_facet[1]).cast<int64_t>()) < 0)
|
||||
// Make CCW.
|
||||
std::swap(projected_facet[1], projected_facet[2]);
|
||||
ClipperLib::Path offsetted = mittered_offset_path_scaled(projected_facet, deltas, 3.);
|
||||
|
||||
// Find lowest slice not below the triangle.
|
||||
auto first_layer = std::upper_bound(layers.begin(), layers.end(), float(min_z - EPSILON),
|
||||
[](float z, const Layer *l1) { return z < l1->slice_z + l1->height * 0.5; });
|
||||
auto last_layer = std::upper_bound(layers.begin(), layers.end(), float(max_z - EPSILON),
|
||||
[](float z, const Layer *l1) { return z < l1->slice_z + l1->height * 0.5; });
|
||||
|
||||
if (last_layer == layers.end())
|
||||
--last_layer;
|
||||
|
||||
if (first_layer == layers.end() || (first_layer != layers.begin() && facet[0].z() < (*first_layer)->print_z - EPSILON))
|
||||
--first_layer;
|
||||
|
||||
for (auto layer_it = first_layer; (layer_it != (last_layer + 1) && layer_it != layers.end()); ++layer_it)
|
||||
if (size_t layer_idx = layer_it - layers.begin(); ! top_layers[layer_idx].empty() || ! bottom_layers[layer_idx].empty())
|
||||
triangles_by_color_raw[extruder_idx][layer_idx].emplace_back(offsetted);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - top and bottom layers - projection of painted triangles - end";
|
||||
|
||||
std::vector<std::vector<ExPolygons>> triangles_by_color(num_extruders, std::vector<ExPolygons>(layers.size()));
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers), [&](const tbb::blocked_range<size_t> &range) {
|
||||
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
|
||||
throw_on_cancel_callback();
|
||||
float offset_factor = 0.1f * float(scale_(get_extrusion_width(layer_idx)));
|
||||
for (size_t extruder_id = 0; extruder_id < num_extruders; ++ extruder_id)
|
||||
if (ClipperLib::Paths &src_paths = triangles_by_color_raw[extruder_id][layer_idx]; !src_paths.empty())
|
||||
triangles_by_color[extruder_id][layer_idx] = offset_ex(offset_ex(ClipperPaths_to_Slic3rExPolygons(src_paths), -offset_factor), offset_factor);
|
||||
}
|
||||
}); // end of parallel_for
|
||||
triangles_by_color_raw.clear();
|
||||
|
||||
std::vector<std::vector<ExPolygons>> triangles_by_color_bottom(num_extruders);
|
||||
std::vector<std::vector<ExPolygons>> triangles_by_color_top(num_extruders);
|
||||
triangles_by_color_bottom.assign(num_extruders, std::vector<ExPolygons>(num_layers));
|
||||
triangles_by_color_top.assign(num_extruders, std::vector<ExPolygons>(num_layers));
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - segmentation of top layer - begin";
|
||||
for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx) {
|
||||
float extrusion_width = scale_(get_extrusion_width(layer_idx));
|
||||
int top_solid_layers = get_top_solid_layers(layer_idx);
|
||||
ExPolygons top_expolygon = top_layers[layer_idx];
|
||||
if (top_expolygon.empty())
|
||||
continue;
|
||||
|
||||
for (size_t color_idx = 0; color_idx < triangles_by_color.size(); ++color_idx) {
|
||||
throw_on_cancel_callback();
|
||||
if (triangles_by_color[color_idx][layer_idx].empty())
|
||||
continue;
|
||||
ExPolygons intersection_poly = intersection_ex(triangles_by_color[color_idx][layer_idx], top_expolygon);
|
||||
if (!intersection_poly.empty()) {
|
||||
triangles_by_color_top[color_idx][layer_idx].insert(triangles_by_color_top[color_idx][layer_idx].end(), intersection_poly.begin(),
|
||||
intersection_poly.end());
|
||||
for (int last_idx = int(layer_idx) - 1; last_idx >= std::max(int(layer_idx - top_solid_layers), int(0)); --last_idx) {
|
||||
float offset_value = float(layer_idx - last_idx) * (-1.0f) * extrusion_width;
|
||||
if (offset_ex(top_expolygon, offset_value).empty())
|
||||
continue;
|
||||
ExPolygons layer_slices_trimmed = input_expolygons[last_idx];
|
||||
|
||||
for (int last_idx_1 = last_idx; last_idx_1 < int(layer_idx); ++last_idx_1) {
|
||||
layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx_1 + 1]);
|
||||
}
|
||||
|
||||
ExPolygons offset_e = offset_ex(layer_slices_trimmed, offset_value);
|
||||
ExPolygons intersection_poly_2 = intersection_ex(triangles_by_color_top[color_idx][layer_idx], offset_e);
|
||||
triangles_by_color_top[color_idx][last_idx].insert(triangles_by_color_top[color_idx][last_idx].end(), intersection_poly_2.begin(),
|
||||
intersection_poly_2.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - segmentation of top layer - end";
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - segmentation of bottom layer - begin";
|
||||
for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx) {
|
||||
float extrusion_width = scale_(get_extrusion_width(layer_idx));
|
||||
int bottom_solid_layers = get_bottom_solid_layers(layer_idx);
|
||||
const ExPolygons &bottom_expolygon = bottom_layers[layer_idx];
|
||||
if (bottom_expolygon.empty())
|
||||
continue;
|
||||
|
||||
for (size_t color_idx = 0; color_idx < triangles_by_color.size(); ++color_idx) {
|
||||
throw_on_cancel_callback();
|
||||
if (triangles_by_color[color_idx][layer_idx].empty())
|
||||
continue;
|
||||
|
||||
ExPolygons intersection_poly = intersection_ex(triangles_by_color[color_idx][layer_idx], bottom_expolygon);
|
||||
if (!intersection_poly.empty()) {
|
||||
triangles_by_color_bottom[color_idx][layer_idx].insert(triangles_by_color_bottom[color_idx][layer_idx].end(), intersection_poly.begin(),
|
||||
intersection_poly.end());
|
||||
for (size_t last_idx = layer_idx + 1; last_idx < std::min(layer_idx + bottom_solid_layers, num_layers); ++last_idx) {
|
||||
float offset_value = float(last_idx - layer_idx) * (-1.0f) * extrusion_width;
|
||||
if (offset_ex(bottom_expolygon, offset_value).empty())
|
||||
continue;
|
||||
ExPolygons layer_slices_trimmed = input_expolygons[last_idx];
|
||||
for (int last_idx_1 = int(last_idx); last_idx_1 > int(layer_idx); --last_idx_1) {
|
||||
layer_slices_trimmed = intersection_ex(layer_slices_trimmed, offset_ex(input_expolygons[last_idx_1 - 1], offset_value));
|
||||
}
|
||||
|
||||
ExPolygons offset_e = offset_ex(layer_slices_trimmed, offset_value);
|
||||
ExPolygons intersection_poly_2 = intersection_ex(triangles_by_color_bottom[color_idx][layer_idx], offset_e);
|
||||
append(triangles_by_color_bottom[color_idx][last_idx], std::move(intersection_poly_2));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - segmentation of bottom layer - end";
|
||||
|
||||
std::vector<std::vector<ExPolygons>> triangles_by_color_merged(num_extruders);
|
||||
triangles_by_color_merged.assign(num_extruders, std::vector<ExPolygons>(num_layers));
|
||||
for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx) {
|
||||
throw_on_cancel_callback();
|
||||
for (size_t color_idx = 0; color_idx < triangles_by_color_merged.size(); ++color_idx) {
|
||||
auto &self = triangles_by_color_merged[color_idx][layer_idx];
|
||||
append(self, std::move(triangles_by_color_bottom[color_idx][layer_idx]));
|
||||
append(self, std::move(triangles_by_color_top[color_idx][layer_idx]));
|
||||
self = union_ex(self);
|
||||
}
|
||||
|
||||
// Cut all colors for cases when two colors are overlapping
|
||||
for (size_t color_idx = 1; color_idx < triangles_by_color_merged.size(); ++color_idx) {
|
||||
triangles_by_color_merged[color_idx][layer_idx] = diff_ex(triangles_by_color_merged[color_idx][layer_idx],
|
||||
triangles_by_color_merged[color_idx - 1][layer_idx]);
|
||||
}
|
||||
}
|
||||
#else
|
||||
|
||||
// Maximum number of top / bottom layers accounts for maximum overlap of one thread group into a neighbor thread group.
|
||||
int max_top_layers = 0;
|
||||
int max_bottom_layers = 0;
|
||||
|
@ -1470,8 +1255,7 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
|
|||
// project downards pointing painted triangles over bottom surfaces.
|
||||
std::vector<std::vector<Polygons>> top_raw(num_extruders), bottom_raw(num_extruders);
|
||||
std::vector<float> zs = zs_from_layers(print_object.layers());
|
||||
Transform3d object_trafo = print_object.trafo();
|
||||
object_trafo.pretranslate(Vec3d(- unscale<double>(print_object.center_offset().x()), - unscale<double>(print_object.center_offset().y()), 0));
|
||||
Transform3d object_trafo = print_object.trafo_centered();
|
||||
|
||||
#ifdef MMU_SEGMENTATION_DEBUG_TOP_BOTTOM
|
||||
static int iRun = 0;
|
||||
|
@ -1650,7 +1434,6 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
|
|||
triangles_by_color_merged[color_idx - 1][layer_idx]);
|
||||
}
|
||||
});
|
||||
#endif
|
||||
|
||||
return triangles_by_color_merged;
|
||||
}
|
||||
|
|
|
@ -354,7 +354,7 @@ inline void MutableSkipHeapPriorityQueue<T, LessPredicate, IndexSetter, blocking
|
|||
#endif /* NDEBUG */
|
||||
{
|
||||
// Mark as removed from the queue.
|
||||
m_index_setter(m_heap.front(), std::numeric_limits<size_t>::max());
|
||||
m_index_setter(m_heap[1], std::numeric_limits<size_t>::max());
|
||||
}
|
||||
// Zero'th element is padding, thus non-empty queue must have at least two elements.
|
||||
if (m_heap.size() > 2) {
|
||||
|
|
|
@ -24,6 +24,7 @@ public:
|
|||
|
||||
PlaceholderParser(const DynamicConfig *external_config = nullptr);
|
||||
|
||||
void clear_config() { m_config.clear(); }
|
||||
// Return a list of keys, which should be changed in m_config from rhs.
|
||||
// This contains keys, which are found in rhs, but not in m_config.
|
||||
std::vector<std::string> config_diff(const DynamicPrintConfig &rhs);
|
||||
|
|
|
@ -413,212 +413,181 @@ void Preset::set_visible_from_appconfig(const AppConfig &app_config)
|
|||
}
|
||||
}
|
||||
|
||||
const std::vector<std::string>& Preset::print_options()
|
||||
{
|
||||
static std::vector<std::string> s_opts {
|
||||
"layer_height", "first_layer_height", "perimeters", "spiral_vase", "slice_closing_radius", "slicing_mode",
|
||||
"top_solid_layers", "top_solid_min_thickness", "bottom_solid_layers", "bottom_solid_min_thickness",
|
||||
"extra_perimeters", "ensure_vertical_shell_thickness", "avoid_crossing_perimeters", "thin_walls", "overhangs",
|
||||
"seam_position", "external_perimeters_first", "fill_density", "fill_pattern", "top_fill_pattern", "bottom_fill_pattern",
|
||||
"infill_every_layers", "infill_only_where_needed", "solid_infill_every_layers", "fill_angle", "bridge_angle",
|
||||
"solid_infill_below_area", "only_retract_when_crossing_perimeters", "infill_first",
|
||||
"ironing", "ironing_type", "ironing_flowrate", "ironing_speed", "ironing_spacing",
|
||||
"max_print_speed", "max_volumetric_speed", "avoid_crossing_perimeters_max_detour",
|
||||
"fuzzy_skin", "fuzzy_skin_thickness", "fuzzy_skin_point_dist",
|
||||
static std::vector<std::string> s_Preset_print_options {
|
||||
"layer_height", "first_layer_height", "perimeters", "spiral_vase", "slice_closing_radius", "slicing_mode",
|
||||
"top_solid_layers", "top_solid_min_thickness", "bottom_solid_layers", "bottom_solid_min_thickness",
|
||||
"extra_perimeters", "ensure_vertical_shell_thickness", "avoid_crossing_perimeters", "thin_walls", "overhangs",
|
||||
"seam_position", "external_perimeters_first", "fill_density", "fill_pattern", "top_fill_pattern", "bottom_fill_pattern",
|
||||
"infill_every_layers", "infill_only_where_needed", "solid_infill_every_layers", "fill_angle", "bridge_angle",
|
||||
"solid_infill_below_area", "only_retract_when_crossing_perimeters", "infill_first",
|
||||
"ironing", "ironing_type", "ironing_flowrate", "ironing_speed", "ironing_spacing",
|
||||
"max_print_speed", "max_volumetric_speed", "avoid_crossing_perimeters_max_detour",
|
||||
"fuzzy_skin", "fuzzy_skin_thickness", "fuzzy_skin_point_dist",
|
||||
#ifdef HAS_PRESSURE_EQUALIZER
|
||||
"max_volumetric_extrusion_rate_slope_positive", "max_volumetric_extrusion_rate_slope_negative",
|
||||
"max_volumetric_extrusion_rate_slope_positive", "max_volumetric_extrusion_rate_slope_negative",
|
||||
#endif /* HAS_PRESSURE_EQUALIZER */
|
||||
"perimeter_speed", "small_perimeter_speed", "external_perimeter_speed", "infill_speed", "solid_infill_speed",
|
||||
"top_solid_infill_speed", "support_material_speed", "support_material_xy_spacing", "support_material_interface_speed",
|
||||
"bridge_speed", "gap_fill_speed", "gap_fill_enabled", "travel_speed", "travel_speed_z", "first_layer_speed", "perimeter_acceleration", "infill_acceleration",
|
||||
"bridge_acceleration", "first_layer_acceleration", "default_acceleration", "skirts", "skirt_distance", "skirt_height", "draft_shield",
|
||||
"min_skirt_length", "brim_width", "brim_offset", "brim_type", "support_material", "support_material_auto", "support_material_threshold", "support_material_enforce_layers",
|
||||
"raft_layers", "raft_first_layer_density", "raft_first_layer_expansion", "raft_contact_distance", "raft_expansion",
|
||||
"support_material_pattern", "support_material_with_sheath", "support_material_spacing", "support_material_closing_radius", "support_material_style",
|
||||
"support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers", "support_material_bottom_interface_layers",
|
||||
"support_material_interface_pattern", "support_material_interface_spacing", "support_material_interface_contact_loops",
|
||||
"support_material_contact_distance", "support_material_bottom_contact_distance",
|
||||
"support_material_buildplate_only", "dont_support_bridges", "thick_bridges", "notes", "complete_objects", "extruder_clearance_radius",
|
||||
"extruder_clearance_height", "gcode_comments", "gcode_label_objects", "output_filename_format", "post_process", "perimeter_extruder",
|
||||
"infill_extruder", "solid_infill_extruder", "support_material_extruder", "support_material_interface_extruder",
|
||||
"ooze_prevention", "standby_temperature_delta", "interface_shells", "extrusion_width", "first_layer_extrusion_width",
|
||||
"perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width",
|
||||
"top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "infill_anchor", "infill_anchor_max", "bridge_flow_ratio", "clip_multipart_objects",
|
||||
"elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y",
|
||||
"wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_bridging", "single_extruder_multi_material_priming", "mmu_segmented_region_max_width",
|
||||
"wipe_tower_no_sparse_layers", "compatible_printers", "compatible_printers_condition", "inherits"
|
||||
};
|
||||
return s_opts;
|
||||
}
|
||||
"perimeter_speed", "small_perimeter_speed", "external_perimeter_speed", "infill_speed", "solid_infill_speed",
|
||||
"top_solid_infill_speed", "support_material_speed", "support_material_xy_spacing", "support_material_interface_speed",
|
||||
"bridge_speed", "gap_fill_speed", "gap_fill_enabled", "travel_speed", "travel_speed_z", "first_layer_speed", "perimeter_acceleration", "infill_acceleration",
|
||||
"bridge_acceleration", "first_layer_acceleration", "default_acceleration", "skirts", "skirt_distance", "skirt_height", "draft_shield",
|
||||
"min_skirt_length", "brim_width", "brim_offset", "brim_type", "support_material", "support_material_auto", "support_material_threshold", "support_material_enforce_layers",
|
||||
"raft_layers", "raft_first_layer_density", "raft_first_layer_expansion", "raft_contact_distance", "raft_expansion",
|
||||
"support_material_pattern", "support_material_with_sheath", "support_material_spacing", "support_material_closing_radius", "support_material_style",
|
||||
"support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers", "support_material_bottom_interface_layers",
|
||||
"support_material_interface_pattern", "support_material_interface_spacing", "support_material_interface_contact_loops",
|
||||
"support_material_contact_distance", "support_material_bottom_contact_distance",
|
||||
"support_material_buildplate_only", "dont_support_bridges", "thick_bridges", "notes", "complete_objects", "extruder_clearance_radius",
|
||||
"extruder_clearance_height", "gcode_comments", "gcode_label_objects", "output_filename_format", "post_process", "perimeter_extruder",
|
||||
"infill_extruder", "solid_infill_extruder", "support_material_extruder", "support_material_interface_extruder",
|
||||
"ooze_prevention", "standby_temperature_delta", "interface_shells", "extrusion_width", "first_layer_extrusion_width",
|
||||
"perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width",
|
||||
"top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "infill_anchor", "infill_anchor_max", "bridge_flow_ratio", "clip_multipart_objects",
|
||||
"elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y",
|
||||
"wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_bridging", "single_extruder_multi_material_priming", "mmu_segmented_region_max_width",
|
||||
"wipe_tower_no_sparse_layers", "compatible_printers", "compatible_printers_condition", "inherits"
|
||||
};
|
||||
|
||||
const std::vector<std::string>& Preset::filament_options()
|
||||
{
|
||||
static std::vector<std::string> s_opts {
|
||||
"filament_colour", "filament_diameter", "filament_type", "filament_soluble", "filament_notes", "filament_max_volumetric_speed",
|
||||
"extrusion_multiplier", "filament_density", "filament_cost", "filament_spool_weight", "filament_loading_speed", "filament_loading_speed_start", "filament_load_time",
|
||||
"filament_unloading_speed", "filament_unloading_speed_start", "filament_unload_time", "filament_toolchange_delay", "filament_cooling_moves",
|
||||
"filament_cooling_initial_speed", "filament_cooling_final_speed", "filament_ramming_parameters", "filament_minimal_purge_on_wipe_tower",
|
||||
"temperature", "first_layer_temperature", "bed_temperature", "first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed",
|
||||
"max_fan_speed", "bridge_fan_speed", "disable_fan_first_layers", "full_fan_speed_layer", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed",
|
||||
"start_filament_gcode", "end_filament_gcode",
|
||||
// Retract overrides
|
||||
"filament_retract_length", "filament_retract_lift", "filament_retract_lift_above", "filament_retract_lift_below", "filament_retract_speed", "filament_deretract_speed", "filament_retract_restart_extra", "filament_retract_before_travel",
|
||||
"filament_retract_layer_change", "filament_wipe", "filament_retract_before_wipe",
|
||||
// Profile compatibility
|
||||
"filament_vendor", "compatible_prints", "compatible_prints_condition", "compatible_printers", "compatible_printers_condition", "inherits"
|
||||
};
|
||||
return s_opts;
|
||||
}
|
||||
static std::vector<std::string> s_Preset_filament_options {
|
||||
"filament_colour", "filament_diameter", "filament_type", "filament_soluble", "filament_notes", "filament_max_volumetric_speed",
|
||||
"extrusion_multiplier", "filament_density", "filament_cost", "filament_spool_weight", "filament_loading_speed", "filament_loading_speed_start", "filament_load_time",
|
||||
"filament_unloading_speed", "filament_unloading_speed_start", "filament_unload_time", "filament_toolchange_delay", "filament_cooling_moves",
|
||||
"filament_cooling_initial_speed", "filament_cooling_final_speed", "filament_ramming_parameters", "filament_minimal_purge_on_wipe_tower",
|
||||
"temperature", "first_layer_temperature", "bed_temperature", "first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed",
|
||||
"max_fan_speed", "bridge_fan_speed", "disable_fan_first_layers", "full_fan_speed_layer", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed",
|
||||
"start_filament_gcode", "end_filament_gcode",
|
||||
// Retract overrides
|
||||
"filament_retract_length", "filament_retract_lift", "filament_retract_lift_above", "filament_retract_lift_below", "filament_retract_speed", "filament_deretract_speed", "filament_retract_restart_extra", "filament_retract_before_travel",
|
||||
"filament_retract_layer_change", "filament_wipe", "filament_retract_before_wipe",
|
||||
// Profile compatibility
|
||||
"filament_vendor", "compatible_prints", "compatible_prints_condition", "compatible_printers", "compatible_printers_condition", "inherits"
|
||||
};
|
||||
|
||||
const std::vector<std::string>& Preset::machine_limits_options()
|
||||
{
|
||||
static std::vector<std::string> s_opts;
|
||||
if (s_opts.empty()) {
|
||||
s_opts = {
|
||||
"machine_max_acceleration_extruding", "machine_max_acceleration_retracting", "machine_max_acceleration_travel",
|
||||
"machine_max_acceleration_x", "machine_max_acceleration_y", "machine_max_acceleration_z", "machine_max_acceleration_e",
|
||||
"machine_max_feedrate_x", "machine_max_feedrate_y", "machine_max_feedrate_z", "machine_max_feedrate_e",
|
||||
"machine_min_extruding_rate", "machine_min_travel_rate",
|
||||
"machine_max_jerk_x", "machine_max_jerk_y", "machine_max_jerk_z", "machine_max_jerk_e",
|
||||
};
|
||||
}
|
||||
return s_opts;
|
||||
}
|
||||
static std::vector<std::string> s_Preset_machine_limits_options {
|
||||
"machine_max_acceleration_extruding", "machine_max_acceleration_retracting", "machine_max_acceleration_travel",
|
||||
"machine_max_acceleration_x", "machine_max_acceleration_y", "machine_max_acceleration_z", "machine_max_acceleration_e",
|
||||
"machine_max_feedrate_x", "machine_max_feedrate_y", "machine_max_feedrate_z", "machine_max_feedrate_e",
|
||||
"machine_min_extruding_rate", "machine_min_travel_rate",
|
||||
"machine_max_jerk_x", "machine_max_jerk_y", "machine_max_jerk_z", "machine_max_jerk_e",
|
||||
};
|
||||
|
||||
static std::vector<std::string> s_Preset_printer_options {
|
||||
"printer_technology",
|
||||
"bed_shape", "bed_custom_texture", "bed_custom_model", "z_offset", "gcode_flavor", "use_relative_e_distances",
|
||||
"use_firmware_retraction", "use_volumetric_e", "variable_layer_height",
|
||||
//FIXME the print host keys are left here just for conversion from the Printer preset to Physical Printer preset.
|
||||
"host_type", "print_host", "printhost_apikey", "printhost_cafile",
|
||||
"single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode",
|
||||
"color_change_gcode", "pause_print_gcode", "template_custom_gcode",
|
||||
"between_objects_gcode", "printer_vendor", "printer_model", "printer_variant", "printer_notes", "cooling_tube_retraction",
|
||||
"cooling_tube_length", "high_current_on_filament_swap", "parking_pos_retraction", "extra_loading_move", "max_print_height",
|
||||
"default_print_profile", "inherits",
|
||||
"remaining_times", "silent_mode",
|
||||
"machine_limits_usage", "thumbnails"
|
||||
};
|
||||
|
||||
static std::vector<std::string> s_Preset_sla_print_options {
|
||||
"layer_height",
|
||||
"faded_layers",
|
||||
"supports_enable",
|
||||
"support_head_front_diameter",
|
||||
"support_head_penetration",
|
||||
"support_head_width",
|
||||
"support_pillar_diameter",
|
||||
"support_small_pillar_diameter_percent",
|
||||
"support_max_bridges_on_pillar",
|
||||
"support_pillar_connection_mode",
|
||||
"support_buildplate_only",
|
||||
"support_pillar_widening_factor",
|
||||
"support_base_diameter",
|
||||
"support_base_height",
|
||||
"support_base_safety_distance",
|
||||
"support_critical_angle",
|
||||
"support_max_bridge_length",
|
||||
"support_max_pillar_link_distance",
|
||||
"support_object_elevation",
|
||||
"support_points_density_relative",
|
||||
"support_points_minimal_distance",
|
||||
"slice_closing_radius",
|
||||
"slicing_mode",
|
||||
"pad_enable",
|
||||
"pad_wall_thickness",
|
||||
"pad_wall_height",
|
||||
"pad_brim_size",
|
||||
"pad_max_merge_distance",
|
||||
// "pad_edge_radius",
|
||||
"pad_wall_slope",
|
||||
"pad_object_gap",
|
||||
"pad_around_object",
|
||||
"pad_around_object_everywhere",
|
||||
"pad_object_connector_stride",
|
||||
"pad_object_connector_width",
|
||||
"pad_object_connector_penetration",
|
||||
"hollowing_enable",
|
||||
"hollowing_min_thickness",
|
||||
"hollowing_quality",
|
||||
"hollowing_closing_distance",
|
||||
"output_filename_format",
|
||||
"default_sla_print_profile",
|
||||
"compatible_printers",
|
||||
"compatible_printers_condition",
|
||||
"inherits"
|
||||
};
|
||||
|
||||
static std::vector<std::string> s_Preset_sla_material_options {
|
||||
"material_type",
|
||||
"initial_layer_height",
|
||||
"bottle_cost",
|
||||
"bottle_volume",
|
||||
"bottle_weight",
|
||||
"material_density",
|
||||
"exposure_time",
|
||||
"initial_exposure_time",
|
||||
"material_correction",
|
||||
"material_notes",
|
||||
"material_vendor",
|
||||
"default_sla_material_profile",
|
||||
"compatible_prints", "compatible_prints_condition",
|
||||
"compatible_printers", "compatible_printers_condition", "inherits"
|
||||
};
|
||||
|
||||
static std::vector<std::string> s_Preset_sla_printer_options {
|
||||
"printer_technology",
|
||||
"bed_shape", "bed_custom_texture", "bed_custom_model", "max_print_height",
|
||||
"display_width", "display_height", "display_pixels_x", "display_pixels_y",
|
||||
"display_mirror_x", "display_mirror_y",
|
||||
"display_orientation",
|
||||
"fast_tilt_time", "slow_tilt_time", "area_fill",
|
||||
"relative_correction",
|
||||
"absolute_correction",
|
||||
"elefant_foot_compensation",
|
||||
"elefant_foot_min_width",
|
||||
"gamma_correction",
|
||||
"min_exposure_time", "max_exposure_time",
|
||||
"min_initial_exposure_time", "max_initial_exposure_time",
|
||||
//FIXME the print host keys are left here just for conversion from the Printer preset to Physical Printer preset.
|
||||
"print_host", "printhost_apikey", "printhost_cafile",
|
||||
"printer_notes",
|
||||
"inherits"
|
||||
};
|
||||
|
||||
const std::vector<std::string>& Preset::print_options() { return s_Preset_print_options; }
|
||||
const std::vector<std::string>& Preset::filament_options() { return s_Preset_filament_options; }
|
||||
const std::vector<std::string>& Preset::machine_limits_options() { return s_Preset_machine_limits_options; }
|
||||
// The following nozzle options of a printer profile will be adjusted to match the size
|
||||
// of the nozzle_diameter vector.
|
||||
const std::vector<std::string>& Preset::nozzle_options() { return print_config_def.extruder_option_keys(); }
|
||||
const std::vector<std::string>& Preset::sla_print_options() { return s_Preset_sla_print_options; }
|
||||
const std::vector<std::string>& Preset::sla_material_options() { return s_Preset_sla_material_options; }
|
||||
const std::vector<std::string>& Preset::sla_printer_options() { return s_Preset_sla_printer_options; }
|
||||
|
||||
const std::vector<std::string>& Preset::printer_options()
|
||||
{
|
||||
static std::vector<std::string> s_opts;
|
||||
if (s_opts.empty()) {
|
||||
s_opts = {
|
||||
"printer_technology",
|
||||
"bed_shape", "bed_custom_texture", "bed_custom_model", "z_offset", "gcode_flavor", "use_relative_e_distances",
|
||||
"use_firmware_retraction", "use_volumetric_e", "variable_layer_height",
|
||||
//FIXME the print host keys are left here just for conversion from the Printer preset to Physical Printer preset.
|
||||
"host_type", "print_host", "printhost_apikey", "printhost_cafile",
|
||||
"single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode",
|
||||
"color_change_gcode", "pause_print_gcode", "template_custom_gcode",
|
||||
"between_objects_gcode", "printer_vendor", "printer_model", "printer_variant", "printer_notes", "cooling_tube_retraction",
|
||||
"cooling_tube_length", "high_current_on_filament_swap", "parking_pos_retraction", "extra_loading_move", "max_print_height",
|
||||
"default_print_profile", "inherits",
|
||||
"remaining_times", "silent_mode",
|
||||
"machine_limits_usage", "thumbnails"
|
||||
};
|
||||
s_opts.insert(s_opts.end(), Preset::machine_limits_options().begin(), Preset::machine_limits_options().end());
|
||||
s_opts.insert(s_opts.end(), Preset::nozzle_options().begin(), Preset::nozzle_options().end());
|
||||
}
|
||||
return s_opts;
|
||||
}
|
||||
|
||||
// The following nozzle options of a printer profile will be adjusted to match the size
|
||||
// of the nozzle_diameter vector.
|
||||
const std::vector<std::string>& Preset::nozzle_options()
|
||||
{
|
||||
return print_config_def.extruder_option_keys();
|
||||
}
|
||||
|
||||
const std::vector<std::string>& Preset::sla_print_options()
|
||||
{
|
||||
static std::vector<std::string> s_opts;
|
||||
if (s_opts.empty()) {
|
||||
s_opts = {
|
||||
"layer_height",
|
||||
"faded_layers",
|
||||
"supports_enable",
|
||||
"support_head_front_diameter",
|
||||
"support_head_penetration",
|
||||
"support_head_width",
|
||||
"support_pillar_diameter",
|
||||
"support_small_pillar_diameter_percent",
|
||||
"support_max_bridges_on_pillar",
|
||||
"support_pillar_connection_mode",
|
||||
"support_buildplate_only",
|
||||
"support_pillar_widening_factor",
|
||||
"support_base_diameter",
|
||||
"support_base_height",
|
||||
"support_base_safety_distance",
|
||||
"support_critical_angle",
|
||||
"support_max_bridge_length",
|
||||
"support_max_pillar_link_distance",
|
||||
"support_object_elevation",
|
||||
"support_points_density_relative",
|
||||
"support_points_minimal_distance",
|
||||
"slice_closing_radius",
|
||||
"slicing_mode",
|
||||
"pad_enable",
|
||||
"pad_wall_thickness",
|
||||
"pad_wall_height",
|
||||
"pad_brim_size",
|
||||
"pad_max_merge_distance",
|
||||
// "pad_edge_radius",
|
||||
"pad_wall_slope",
|
||||
"pad_object_gap",
|
||||
"pad_around_object",
|
||||
"pad_around_object_everywhere",
|
||||
"pad_object_connector_stride",
|
||||
"pad_object_connector_width",
|
||||
"pad_object_connector_penetration",
|
||||
"hollowing_enable",
|
||||
"hollowing_min_thickness",
|
||||
"hollowing_quality",
|
||||
"hollowing_closing_distance",
|
||||
"output_filename_format",
|
||||
"default_sla_print_profile",
|
||||
"compatible_printers",
|
||||
"compatible_printers_condition",
|
||||
"inherits"
|
||||
};
|
||||
}
|
||||
return s_opts;
|
||||
}
|
||||
|
||||
const std::vector<std::string>& Preset::sla_material_options()
|
||||
{
|
||||
static std::vector<std::string> s_opts;
|
||||
if (s_opts.empty()) {
|
||||
s_opts = {
|
||||
"material_type",
|
||||
"initial_layer_height",
|
||||
"bottle_cost",
|
||||
"bottle_volume",
|
||||
"bottle_weight",
|
||||
"material_density",
|
||||
"exposure_time",
|
||||
"initial_exposure_time",
|
||||
"material_correction",
|
||||
"material_notes",
|
||||
"material_vendor",
|
||||
"default_sla_material_profile",
|
||||
"compatible_prints", "compatible_prints_condition",
|
||||
"compatible_printers", "compatible_printers_condition", "inherits"
|
||||
};
|
||||
}
|
||||
return s_opts;
|
||||
}
|
||||
|
||||
const std::vector<std::string>& Preset::sla_printer_options()
|
||||
{
|
||||
static std::vector<std::string> s_opts;
|
||||
if (s_opts.empty()) {
|
||||
s_opts = {
|
||||
"printer_technology",
|
||||
"bed_shape", "bed_custom_texture", "bed_custom_model", "max_print_height",
|
||||
"display_width", "display_height", "display_pixels_x", "display_pixels_y",
|
||||
"display_mirror_x", "display_mirror_y",
|
||||
"display_orientation",
|
||||
"fast_tilt_time", "slow_tilt_time", "area_fill",
|
||||
"relative_correction",
|
||||
"absolute_correction",
|
||||
"elefant_foot_compensation",
|
||||
"elefant_foot_min_width",
|
||||
"gamma_correction",
|
||||
"min_exposure_time", "max_exposure_time",
|
||||
"min_initial_exposure_time", "max_initial_exposure_time",
|
||||
//FIXME the print host keys are left here just for conversion from the Printer preset to Physical Printer preset.
|
||||
"print_host", "printhost_apikey", "printhost_cafile",
|
||||
"printer_notes",
|
||||
"inherits"
|
||||
};
|
||||
}
|
||||
static std::vector<std::string> s_opts = [](){
|
||||
std::vector<std::string> opts = s_Preset_printer_options;
|
||||
append(opts, s_Preset_machine_limits_options);
|
||||
append(opts, Preset::nozzle_options());
|
||||
return opts;
|
||||
}();
|
||||
return s_opts;
|
||||
}
|
||||
|
||||
|
@ -1194,21 +1163,38 @@ inline t_config_option_keys deep_diff(const ConfigBase &config_this, const Confi
|
|||
return diff;
|
||||
}
|
||||
|
||||
static constexpr const std::initializer_list<const char*> optional_keys { "compatible_prints", "compatible_printers" };
|
||||
|
||||
bool PresetCollection::is_dirty(const Preset *edited, const Preset *reference)
|
||||
{
|
||||
if (edited != nullptr && reference != nullptr) {
|
||||
// Only compares options existing in both configs.
|
||||
if (! reference->config.equals(edited->config))
|
||||
return true;
|
||||
// The "compatible_printers" option key is handled differently from the others:
|
||||
// It is not mandatory. If the key is missing, it means it is compatible with any printer.
|
||||
// If the key exists and it is empty, it means it is compatible with no printer.
|
||||
for (auto &opt_key : optional_keys)
|
||||
if (reference->config.has(opt_key) != edited->config.has(opt_key))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::string> PresetCollection::dirty_options(const Preset *edited, const Preset *reference, const bool deep_compare /*= false*/)
|
||||
{
|
||||
std::vector<std::string> changed;
|
||||
if (edited != nullptr && reference != nullptr) {
|
||||
// Only compares options existing in both configs.
|
||||
changed = deep_compare ?
|
||||
deep_diff(edited->config, reference->config) :
|
||||
reference->config.diff(edited->config);
|
||||
// The "compatible_printers" option key is handled differently from the others:
|
||||
// It is not mandatory. If the key is missing, it means it is compatible with any printer.
|
||||
// If the key exists and it is empty, it means it is compatible with no printer.
|
||||
std::initializer_list<const char*> optional_keys { "compatible_prints", "compatible_printers" };
|
||||
for (auto &opt_key : optional_keys) {
|
||||
for (auto &opt_key : optional_keys)
|
||||
if (reference->config.has(opt_key) != edited->config.has(opt_key))
|
||||
changed.emplace_back(opt_key);
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
@ -1385,12 +1371,16 @@ const Preset& PrinterPresetCollection::default_preset_for(const DynamicPrintConf
|
|||
return this->default_preset((opt_printer_technology == nullptr || opt_printer_technology->value == ptFFF) ? 0 : 1);
|
||||
}
|
||||
|
||||
const Preset* PrinterPresetCollection::find_by_model_id(const std::string &model_id) const
|
||||
const Preset* PrinterPresetCollection::find_system_preset_by_model_and_variant(const std::string &model_id, const std::string& variant) const
|
||||
{
|
||||
if (model_id.empty()) { return nullptr; }
|
||||
|
||||
const auto it = std::find_if(cbegin(), cend(), [&](const Preset &preset) {
|
||||
return preset.config.opt_string("printer_model") == model_id;
|
||||
if (!preset.is_system || preset.config.opt_string("printer_model") != model_id)
|
||||
return false;
|
||||
if (variant.empty())
|
||||
return true;
|
||||
return preset.config.opt_string("printer_variant") == variant;
|
||||
});
|
||||
|
||||
return it != cend() ? &*it : nullptr;
|
||||
|
@ -1405,26 +1395,25 @@ std::string PhysicalPrinter::separator()
|
|||
return " * ";
|
||||
}
|
||||
|
||||
static std::vector<std::string> s_PhysicalPrinter_opts {
|
||||
"preset_name", // temporary option to compatibility with older Slicer
|
||||
"preset_names",
|
||||
"printer_technology",
|
||||
"host_type",
|
||||
"print_host",
|
||||
"printhost_apikey",
|
||||
"printhost_cafile",
|
||||
"printhost_port",
|
||||
"printhost_authorization_type",
|
||||
// HTTP digest authentization (RFC 2617)
|
||||
"printhost_user",
|
||||
"printhost_password",
|
||||
"printhost_ssl_ignore_revoke"
|
||||
};
|
||||
|
||||
const std::vector<std::string>& PhysicalPrinter::printer_options()
|
||||
{
|
||||
static std::vector<std::string> s_opts;
|
||||
if (s_opts.empty()) {
|
||||
s_opts = {
|
||||
"preset_name", // temporary option to compatibility with older Slicer
|
||||
"preset_names",
|
||||
"printer_technology",
|
||||
"host_type",
|
||||
"print_host",
|
||||
"printhost_apikey",
|
||||
"printhost_cafile",
|
||||
"printhost_port",
|
||||
"printhost_authorization_type",
|
||||
// HTTP digest authentization (RFC 2617)
|
||||
"printhost_user",
|
||||
"printhost_password"
|
||||
};
|
||||
}
|
||||
return s_opts;
|
||||
return s_PhysicalPrinter_opts;
|
||||
}
|
||||
|
||||
static constexpr auto legacy_print_host_options = {
|
||||
|
|
|
@ -371,7 +371,7 @@ public:
|
|||
const Preset& get_edited_preset() const { return m_edited_preset; }
|
||||
|
||||
// Return the last saved preset.
|
||||
const Preset& get_saved_preset() const { return m_saved_preset; }
|
||||
// const Preset& get_saved_preset() const { return m_saved_preset; }
|
||||
|
||||
// Return vendor of the first parent profile, for which the vendor is defined, or null if such profile does not exist.
|
||||
PresetWithVendorProfile get_preset_with_vendor_profile(const Preset &preset) const;
|
||||
|
@ -395,7 +395,7 @@ public:
|
|||
void discard_current_changes() {
|
||||
m_presets[m_idx_selected].reset_dirty();
|
||||
m_edited_preset = m_presets[m_idx_selected];
|
||||
update_saved_preset_from_current_preset();
|
||||
// update_saved_preset_from_current_preset();
|
||||
}
|
||||
|
||||
// Return a preset by its name. If the preset is active, a temporary copy is returned.
|
||||
|
@ -463,7 +463,8 @@ public:
|
|||
size_t num_visible() const { return std::count_if(m_presets.begin(), m_presets.end(), [](const Preset &preset){return preset.is_visible;}); }
|
||||
|
||||
// Compare the content of get_selected_preset() with get_edited_preset() configs, return true if they differ.
|
||||
bool current_is_dirty() const { return ! this->current_dirty_options().empty(); }
|
||||
bool current_is_dirty() const
|
||||
{ return is_dirty(&this->get_edited_preset(), &this->get_selected_preset()); }
|
||||
// Compare the content of get_selected_preset() with get_edited_preset() configs, return the list of keys where they differ.
|
||||
std::vector<std::string> current_dirty_options(const bool deep_compare = false) const
|
||||
{ return dirty_options(&this->get_edited_preset(), &this->get_selected_preset(), deep_compare); }
|
||||
|
@ -472,10 +473,11 @@ public:
|
|||
{ return dirty_options(&this->get_edited_preset(), this->get_selected_preset_parent(), deep_compare); }
|
||||
|
||||
// Compare the content of get_saved_preset() with get_edited_preset() configs, return true if they differ.
|
||||
bool saved_is_dirty() const { return !this->saved_dirty_options().empty(); }
|
||||
bool saved_is_dirty() const
|
||||
{ return is_dirty(&this->get_edited_preset(), &m_saved_preset); }
|
||||
// Compare the content of get_saved_preset() with get_edited_preset() configs, return the list of keys where they differ.
|
||||
std::vector<std::string> saved_dirty_options(const bool deep_compare = false) const
|
||||
{ return dirty_options(&this->get_edited_preset(), &this->get_saved_preset(), deep_compare); }
|
||||
// std::vector<std::string> saved_dirty_options() const
|
||||
// { return dirty_options(&this->get_edited_preset(), &this->get_saved_preset(), /* deep_compare */ false); }
|
||||
// Copy edited preset into saved preset.
|
||||
void update_saved_preset_from_current_preset() { m_saved_preset = m_edited_preset; }
|
||||
|
||||
|
@ -552,7 +554,8 @@ private:
|
|||
|
||||
size_t update_compatible_internal(const PresetWithVendorProfile &active_printer, const PresetWithVendorProfile *active_print, PresetSelectCompatibleType unselect_if_incompatible);
|
||||
public:
|
||||
static std::vector<std::string> dirty_options(const Preset *edited, const Preset *reference, const bool is_printer_type = false);
|
||||
static bool is_dirty(const Preset *edited, const Preset *reference);
|
||||
static std::vector<std::string> dirty_options(const Preset *edited, const Preset *reference, const bool deep_compare = false);
|
||||
private:
|
||||
// Type of this PresetCollection: TYPE_PRINT, TYPE_FILAMENT or TYPE_PRINTER.
|
||||
Preset::Type m_type;
|
||||
|
@ -592,7 +595,7 @@ public:
|
|||
|
||||
const Preset& default_preset_for(const DynamicPrintConfig &config) const override;
|
||||
|
||||
const Preset* find_by_model_id(const std::string &model_id) const;
|
||||
const Preset* find_system_preset_by_model_and_variant(const std::string &model_id, const std::string &variant) const;
|
||||
|
||||
private:
|
||||
PrinterPresetCollection() = default;
|
||||
|
|
|
@ -188,10 +188,13 @@ void PresetBundle::setup_directories()
|
|||
}
|
||||
}
|
||||
|
||||
PresetsConfigSubstitutions PresetBundle::load_presets(AppConfig &config, ForwardCompatibilitySubstitutionRule substitution_rule, const std::string &preferred_model_id)
|
||||
PresetsConfigSubstitutions PresetBundle::load_presets(AppConfig &config, ForwardCompatibilitySubstitutionRule substitution_rule,
|
||||
const PresetPreferences& preferred_selection/* = PresetPreferences()*/)
|
||||
{
|
||||
// First load the vendor specific system presets.
|
||||
std::string errors_cummulative = this->load_system_presets();
|
||||
PresetsConfigSubstitutions substitutions;
|
||||
std::string errors_cummulative;
|
||||
std::tie(substitutions, errors_cummulative) = this->load_system_presets(substitution_rule);
|
||||
|
||||
const std::string dir_user_presets = data_dir()
|
||||
#ifdef SLIC3R_PROFILE_USE_PRESETS_SUBDIR
|
||||
|
@ -202,7 +205,6 @@ PresetsConfigSubstitutions PresetBundle::load_presets(AppConfig &config, Forward
|
|||
#endif
|
||||
;
|
||||
|
||||
PresetsConfigSubstitutions substitutions;
|
||||
try {
|
||||
this->prints.load_presets(dir_user_presets, "print", substitutions, substitution_rule);
|
||||
} catch (const std::runtime_error &err) {
|
||||
|
@ -238,19 +240,28 @@ PresetsConfigSubstitutions PresetBundle::load_presets(AppConfig &config, Forward
|
|||
if (! errors_cummulative.empty())
|
||||
throw Slic3r::RuntimeError(errors_cummulative);
|
||||
|
||||
this->load_selections(config, preferred_model_id);
|
||||
// ysToDo : set prefered filament or sla_material (relates to print technology) and force o use of preffered printer model if it was added
|
||||
this->load_selections(config, preferred_selection);
|
||||
|
||||
return substitutions;
|
||||
}
|
||||
|
||||
// Load system presets into this PresetBundle.
|
||||
// For each vendor, there will be a single PresetBundle loaded.
|
||||
std::string PresetBundle::load_system_presets()
|
||||
std::pair<PresetsConfigSubstitutions, std::string> PresetBundle::load_system_presets(ForwardCompatibilitySubstitutionRule compatibility_rule)
|
||||
{
|
||||
if (compatibility_rule == ForwardCompatibilitySubstitutionRule::EnableSystemSilent)
|
||||
// Loading system presets, don't log substitutions.
|
||||
compatibility_rule = ForwardCompatibilitySubstitutionRule::EnableSilent;
|
||||
else if (compatibility_rule == ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem)
|
||||
// Loading system presets, throw on unknown option value.
|
||||
compatibility_rule = ForwardCompatibilitySubstitutionRule::Disable;
|
||||
|
||||
// Here the vendor specific read only Config Bundles are stored.
|
||||
boost::filesystem::path dir = (boost::filesystem::path(data_dir()) / "vendor").make_preferred();
|
||||
std::string errors_cummulative;
|
||||
bool first = true;
|
||||
boost::filesystem::path dir = (boost::filesystem::path(data_dir()) / "vendor").make_preferred();
|
||||
PresetsConfigSubstitutions substitutions;
|
||||
std::string errors_cummulative;
|
||||
bool first = true;
|
||||
for (auto &dir_entry : boost::filesystem::directory_iterator(dir))
|
||||
if (Slic3r::is_ini_file(dir_entry)) {
|
||||
std::string name = dir_entry.path().filename().string();
|
||||
|
@ -260,13 +271,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(), PresetBundle::LoadSystem);
|
||||
append(substitutions, this->load_configbundle(dir_entry.path().string(), PresetBundle::LoadSystem, compatibility_rule).first);
|
||||
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(), PresetBundle::LoadSystem);
|
||||
append(substitutions, other.load_configbundle(dir_entry.path().string(), PresetBundle::LoadSystem, compatibility_rule).first);
|
||||
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: ";
|
||||
|
@ -288,7 +299,7 @@ std::string PresetBundle::load_system_presets()
|
|||
}
|
||||
|
||||
this->update_system_maps();
|
||||
return errors_cummulative;
|
||||
return std::make_pair(std::move(substitutions), errors_cummulative);
|
||||
}
|
||||
|
||||
// Merge one vendor's presets with the other vendor's presets, report duplicates.
|
||||
|
@ -432,7 +443,7 @@ void PresetBundle::load_installed_sla_materials(AppConfig &config)
|
|||
|
||||
// Load selections (current print, current filaments, current printer) from config.ini
|
||||
// This is done on application start up or after updates are applied.
|
||||
void PresetBundle::load_selections(AppConfig &config, const std::string &preferred_model_id)
|
||||
void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& preferred_selection/* = PresetPreferences()*/)
|
||||
{
|
||||
// Update visibility of presets based on application vendor / model / variant configuration.
|
||||
this->load_installed_printers(config);
|
||||
|
@ -455,13 +466,21 @@ void PresetBundle::load_selections(AppConfig &config, const std::string &preferr
|
|||
// will be selected by the following call of this->update_compatible(PresetSelectCompatibleType::Always).
|
||||
|
||||
const Preset *initial_printer = printers.find_preset(initial_printer_profile_name);
|
||||
const Preset *preferred_printer = printers.find_by_model_id(preferred_model_id);
|
||||
const Preset *preferred_printer = printers.find_system_preset_by_model_and_variant(preferred_selection.printer_model_id, preferred_selection.printer_variant);
|
||||
printers.select_preset_by_name(
|
||||
(preferred_printer != nullptr && (initial_printer == nullptr || !initial_printer->is_visible)) ?
|
||||
(preferred_printer != nullptr /*&& (initial_printer == nullptr || !initial_printer->is_visible)*/) ?
|
||||
preferred_printer->name :
|
||||
initial_printer_profile_name,
|
||||
true);
|
||||
|
||||
// select preferred filament/sla_material profile if any exists and is visible
|
||||
if (!preferred_selection.filament.empty())
|
||||
if (auto it = filaments.find_preset_internal(preferred_selection.filament); it != filaments.end() && it->is_visible)
|
||||
initial_filament_profile_name = it->name;
|
||||
if (!preferred_selection.sla_material.empty())
|
||||
if (auto it = sla_materials.find_preset_internal(preferred_selection.sla_material); it != sla_materials.end() && it->is_visible)
|
||||
initial_sla_material_profile_name = it->name;
|
||||
|
||||
// Selects the profile, leaves it to -1 if the initial profile name is empty or if it was not found.
|
||||
prints.select_preset_by_name_strict(initial_print_profile_name);
|
||||
filaments.select_preset_by_name_strict(initial_filament_profile_name);
|
||||
|
@ -700,7 +719,7 @@ ConfigSubstitutions PresetBundle::load_config_file(const std::string &path, Forw
|
|||
if (is_gcode_file(path)) {
|
||||
DynamicPrintConfig config;
|
||||
config.apply(FullPrintConfig::defaults());
|
||||
ConfigSubstitutions config_substitutions = config.load_from_gcode_file(path, true /* check_header */, compatibility_rule);
|
||||
ConfigSubstitutions config_substitutions = config.load_from_gcode_file(path, compatibility_rule);
|
||||
Preset::normalize(config);
|
||||
load_config_file_config(path, true, std::move(config));
|
||||
return config_substitutions;
|
||||
|
@ -714,8 +733,8 @@ ConfigSubstitutions PresetBundle::load_config_file(const std::string &path, Forw
|
|||
} catch (const std::ifstream::failure &err) {
|
||||
throw Slic3r::RuntimeError(std::string("The Config Bundle cannot be loaded: ") + path + "\n\tReason: " + err.what());
|
||||
} catch (const boost::property_tree::file_parser_error &err) {
|
||||
throw Slic3r::RuntimeError((boost::format("Failed loading the Config Bundle \"%1%\": %2% at line %3%")
|
||||
% err.filename() % err.message() % err.line()).str());
|
||||
throw Slic3r::RuntimeError(format("Failed loading the Config Bundle \"%1%\": %2% at line %3%",
|
||||
err.filename(), err.message(), err.line()));
|
||||
} catch (const std::runtime_error &err) {
|
||||
throw Slic3r::RuntimeError(std::string("Failed loading the preset file: ") + path + "\n\tReason: " + err.what());
|
||||
}
|
||||
|
@ -723,23 +742,27 @@ ConfigSubstitutions PresetBundle::load_config_file(const std::string &path, Forw
|
|||
// 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);
|
||||
case CONFIG_FILE_TYPE_APP_CONFIG:
|
||||
throw Slic3r::RuntimeError(std::string("Invalid configuration file: ") + path + ". This is an application config file.");
|
||||
case CONFIG_FILE_TYPE_CONFIG:
|
||||
{
|
||||
// Initialize a config from full defaults.
|
||||
DynamicPrintConfig config;
|
||||
config.apply(FullPrintConfig::defaults());
|
||||
config_substitutions = config.load(tree, compatibility_rule);
|
||||
Preset::normalize(config);
|
||||
load_config_file_config(path, true, std::move(config));
|
||||
return config_substitutions;
|
||||
}
|
||||
case CONFIG_FILE_TYPE_CONFIG_BUNDLE:
|
||||
return load_config_file_config_bundle(path, tree);
|
||||
try {
|
||||
switch (config_file_type) {
|
||||
case CONFIG_FILE_TYPE_UNKNOWN:
|
||||
throw Slic3r::RuntimeError(std::string("Unknown configuration file type: ") + path);
|
||||
case CONFIG_FILE_TYPE_APP_CONFIG:
|
||||
throw Slic3r::RuntimeError(std::string("Invalid configuration file: ") + path + ". This is an application config file.");
|
||||
case CONFIG_FILE_TYPE_CONFIG:
|
||||
{
|
||||
// Initialize a config from full defaults.
|
||||
DynamicPrintConfig config;
|
||||
config.apply(FullPrintConfig::defaults());
|
||||
config_substitutions = config.load(tree, compatibility_rule);
|
||||
Preset::normalize(config);
|
||||
load_config_file_config(path, true, std::move(config));
|
||||
return config_substitutions;
|
||||
}
|
||||
case CONFIG_FILE_TYPE_CONFIG_BUNDLE:
|
||||
return load_config_file_config_bundle(path, tree, compatibility_rule);
|
||||
}
|
||||
} catch (const ConfigurationError &e) {
|
||||
throw Slic3r::RuntimeError(format("Invalid configuration file %1%: %2%", path, e.what()));
|
||||
}
|
||||
|
||||
// This shall never happen. Suppres compiler warnings.
|
||||
|
@ -916,13 +939,14 @@ 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.
|
||||
ConfigSubstitutions 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, ForwardCompatibilitySubstitutionRule compatibility_rule)
|
||||
{
|
||||
// 1) Load the config bundle into a temp data.
|
||||
PresetBundle tmp_bundle;
|
||||
// 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, {});
|
||||
auto [presets_substitutions, presets_imported] = tmp_bundle.load_configbundle(path, {}, compatibility_rule);
|
||||
UNUSED(presets_imported);
|
||||
|
||||
std::string bundle_name = std::string(" - ") + boost::filesystem::path(path).filename().string();
|
||||
|
@ -1135,15 +1159,11 @@ 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.
|
||||
std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_configbundle(const std::string &path, LoadConfigBundleAttributes flags)
|
||||
std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_configbundle(
|
||||
const std::string &path, LoadConfigBundleAttributes flags, ForwardCompatibilitySubstitutionRule compatibility_rule)
|
||||
{
|
||||
// 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
|
||||
};
|
||||
|
||||
ConfigSubstitutionContext substitution_context { compatibility_rule };
|
||||
PresetsConfigSubstitutions substitutions;
|
||||
|
||||
if (flags.has(LoadConfigBundleAttribute::ResetUserProfile) || flags.has(LoadConfigBundleAttribute::LoadSystem))
|
||||
|
@ -1238,7 +1258,7 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_configbundle(co
|
|||
active_sla_material = kvp.second.data();
|
||||
} else if (kvp.first == "printer") {
|
||||
active_printer = kvp.second.data();
|
||||
}else if (kvp.first == "physical_printer") {
|
||||
} else if (kvp.first == "physical_printer") {
|
||||
active_physical_printer = kvp.second.data();
|
||||
}
|
||||
}
|
||||
|
@ -1275,32 +1295,36 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_configbundle(co
|
|||
DynamicPrintConfig config;
|
||||
std::string alias_name;
|
||||
std::vector<std::string> renamed_from;
|
||||
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();
|
||||
else if (kvp.first == "renamed_from") {
|
||||
if (! unescape_strings_cstyle(kvp.second.data(), renamed_from)) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The preset \"" <<
|
||||
section.first << "\" contains invalid \"renamed_from\" key, which is being ignored.";
|
||||
}
|
||||
}
|
||||
// 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);
|
||||
try {
|
||||
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();
|
||||
else if (kvp.first == "renamed_from") {
|
||||
if (! unescape_strings_cstyle(kvp.second.data(), renamed_from)) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The preset \"" <<
|
||||
section.first << "\" contains invalid \"renamed_from\" key, which is being ignored.";
|
||||
}
|
||||
}
|
||||
// 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) {
|
||||
// Select the default config based on the printer_technology field extracted from kvp.
|
||||
DynamicPrintConfig config_src;
|
||||
parse_config_section(config_src);
|
||||
default_config = &presets->default_preset_for(config_src).config;
|
||||
config = *default_config;
|
||||
config.apply(config_src);
|
||||
} else {
|
||||
default_config = &presets->default_preset().config;
|
||||
config = *default_config;
|
||||
parse_config_section(config);
|
||||
}
|
||||
};
|
||||
if (presets == &this->printers) {
|
||||
// Select the default config based on the printer_technology field extracted from kvp.
|
||||
DynamicPrintConfig config_src;
|
||||
parse_config_section(config_src);
|
||||
default_config = &presets->default_preset_for(config_src).config;
|
||||
config = *default_config;
|
||||
config.apply(config_src);
|
||||
} else {
|
||||
default_config = &presets->default_preset().config;
|
||||
config = *default_config;
|
||||
parse_config_section(config);
|
||||
} catch (const ConfigurationError &e) {
|
||||
throw ConfigurationError(format("Invalid configuration bundle \"%1%\", section [%2%]: ", path, section.first) + e.what());
|
||||
}
|
||||
Preset::normalize(config);
|
||||
// Report configuration fields, which are misplaced into a wrong group.
|
||||
|
@ -1408,8 +1432,12 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_configbundle(co
|
|||
DynamicPrintConfig config = default_config;
|
||||
|
||||
substitution_context.substitutions.clear();
|
||||
for (auto& kvp : section.second)
|
||||
config.set_deserialize(kvp.first, kvp.second.data(), substitution_context);
|
||||
try {
|
||||
for (auto& kvp : section.second)
|
||||
config.set_deserialize(kvp.first, kvp.second.data(), substitution_context);
|
||||
} catch (const ConfigurationError &e) {
|
||||
throw ConfigurationError(format("Invalid configuration bundle \"%1%\", section [%2%]: ", path, section.first) + e.what());
|
||||
}
|
||||
|
||||
// Report configuration fields, which are misplaced into a wrong group.
|
||||
std::string incorrect_keys = Preset::remove_invalid_keys(config, default_config);
|
||||
|
@ -1449,7 +1477,7 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_configbundle(co
|
|||
if (! active_print.empty())
|
||||
prints.select_preset_by_name(active_print, true);
|
||||
if (! active_sla_print.empty())
|
||||
sla_materials.select_preset_by_name(active_sla_print, true);
|
||||
sla_prints.select_preset_by_name(active_sla_print, true);
|
||||
if (! active_sla_material.empty())
|
||||
sla_materials.select_preset_by_name(active_sla_material, true);
|
||||
if (! active_printer.empty())
|
||||
|
|
|
@ -25,9 +25,18 @@ public:
|
|||
|
||||
void setup_directories();
|
||||
|
||||
struct PresetPreferences {
|
||||
std::string printer_model_id;// name of a preferred printer model
|
||||
std::string printer_variant; // name of a preferred printer variant
|
||||
std::string filament; // name of a preferred filament preset
|
||||
std::string sla_material; // name of a preferred sla_material preset
|
||||
};
|
||||
|
||||
// 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
|
||||
PresetsConfigSubstitutions load_presets(AppConfig &config, ForwardCompatibilitySubstitutionRule rule, const std::string &preferred_model_id = std::string());
|
||||
// select preferred presets, if any exist
|
||||
PresetsConfigSubstitutions load_presets(AppConfig &config, ForwardCompatibilitySubstitutionRule rule,
|
||||
const PresetPreferences& preferred_selection = PresetPreferences());
|
||||
|
||||
// Export selections (current print, current filaments, current printer) into config.ini
|
||||
void export_selections(AppConfig &config);
|
||||
|
@ -102,7 +111,8 @@ public:
|
|||
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);
|
||||
std::pair<PresetsConfigSubstitutions, size_t> load_configbundle(
|
||||
const std::string &path, LoadConfigBundleAttributes flags, ForwardCompatibilitySubstitutionRule compatibility_rule);
|
||||
|
||||
// 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);
|
||||
|
@ -139,7 +149,7 @@ public:
|
|||
|
||||
static const char *PRUSA_BUNDLE;
|
||||
private:
|
||||
std::string load_system_presets();
|
||||
std::pair<PresetsConfigSubstitutions, std::string> load_system_presets(ForwardCompatibilitySubstitutionRule compatibility_rule);
|
||||
// Merge one vendor's presets with the other vendor's presets, report duplicates.
|
||||
std::vector<std::string> merge_presets(PresetBundle &&other);
|
||||
// Update renamed_from and alias maps of system profiles.
|
||||
|
@ -152,13 +162,14 @@ private:
|
|||
|
||||
// Load selections (current print, current filaments, current printer) from config.ini
|
||||
// This is done just once on application start up.
|
||||
void load_selections(AppConfig &config, const std::string &preferred_model_id = "");
|
||||
void load_selections(AppConfig &config, const PresetPreferences& preferred_selection = PresetPreferences());
|
||||
|
||||
// Load print, filament & printer presets from a config. If it is an external config, then the name is extracted from the external path.
|
||||
// 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);
|
||||
ConfigSubstitutions 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, ForwardCompatibilitySubstitutionRule compatibility_rule);
|
||||
|
||||
DynamicPrintConfig full_fff_config() const;
|
||||
DynamicPrintConfig full_sla_config() const;
|
||||
|
|
|
@ -159,7 +159,8 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
|||
|| opt_key == "wipe_tower_rotation_angle") {
|
||||
steps.emplace_back(psSkirtBrim);
|
||||
} else if (
|
||||
opt_key == "nozzle_diameter"
|
||||
opt_key == "first_layer_height"
|
||||
|| opt_key == "nozzle_diameter"
|
||||
|| opt_key == "resolution"
|
||||
// Spiral Vase forces different kind of slicing than the normal model:
|
||||
// In Spiral Vase mode, holes are closed and only the largest area contour is kept at each layer.
|
||||
|
|
|
@ -253,6 +253,9 @@ public:
|
|||
ConstLayerPtrsAdaptor layers() const { return ConstLayerPtrsAdaptor(&m_layers); }
|
||||
ConstSupportLayerPtrsAdaptor support_layers() const { return ConstSupportLayerPtrsAdaptor(&m_support_layers); }
|
||||
const Transform3d& trafo() const { return m_trafo; }
|
||||
// Trafo with the center_offset() applied after the transformation, to center the object in XY before slicing.
|
||||
Transform3d trafo_centered() const
|
||||
{ Transform3d t = this->trafo(); t.pretranslate(Vec3d(- unscale<double>(m_center_offset.x()), - unscale<double>(m_center_offset.y()), 0)); return t; }
|
||||
const PrintInstances& instances() const { return m_instances; }
|
||||
|
||||
// Whoever will get a non-const pointer to PrintObject will be able to modify its layers.
|
||||
|
@ -268,7 +271,11 @@ public:
|
|||
// Centering offset of the sliced mesh from the scaled and rotated mesh of the model.
|
||||
const Point& center_offset() const { return m_center_offset; }
|
||||
|
||||
bool has_brim() const { return this->config().brim_type != btNoBrim && this->config().brim_width.value > 0.; }
|
||||
bool has_brim() const {
|
||||
return this->config().brim_type != btNoBrim
|
||||
&& this->config().brim_width.value > 0.
|
||||
&& ! this->has_raft();
|
||||
}
|
||||
|
||||
// This is the *total* layer count (including support layers)
|
||||
// this value is not supposed to be compared with Layer::id
|
||||
|
@ -318,7 +325,7 @@ public:
|
|||
bool has_raft() const { return m_config.raft_layers > 0; }
|
||||
bool has_support_material() const { return this->has_support() || this->has_raft(); }
|
||||
// Checks if the model object is painted using the multi-material painting gizmo.
|
||||
bool is_mm_painted() const { return this->model_object()->is_mm_painted(); };
|
||||
bool is_mm_painted() const { return this->model_object()->is_mm_painted(); }
|
||||
|
||||
// returns 0-based indices of extruders used to print the object (without brim, support and other helper extrusions)
|
||||
std::vector<unsigned int> object_extruders() const;
|
||||
|
|
|
@ -216,22 +216,25 @@ static t_config_option_keys print_config_diffs(
|
|||
const ConfigOption *opt_new_filament = std::binary_search(extruder_retract_keys.begin(), extruder_retract_keys.end(), opt_key) ? new_full_config.option(filament_prefix + opt_key) : nullptr;
|
||||
if (opt_new_filament != nullptr && ! opt_new_filament->is_nil()) {
|
||||
// An extruder retract override is available at some of the filament presets.
|
||||
if (*opt_old != *opt_new || opt_new->overriden_by(opt_new_filament)) {
|
||||
bool overriden = opt_new->overriden_by(opt_new_filament);
|
||||
if (overriden || *opt_old != *opt_new) {
|
||||
auto opt_copy = opt_new->clone();
|
||||
opt_copy->apply_override(opt_new_filament);
|
||||
if (*opt_old == *opt_copy)
|
||||
delete opt_copy;
|
||||
else {
|
||||
filament_overrides.set_key_value(opt_key, opt_copy);
|
||||
bool changed = *opt_old != *opt_copy;
|
||||
if (changed)
|
||||
print_diff.emplace_back(opt_key);
|
||||
}
|
||||
if (changed || overriden) {
|
||||
// filament_overrides will be applied to the placeholder parser, which layers these parameters over full_print_config.
|
||||
filament_overrides.set_key_value(opt_key, opt_copy);
|
||||
} else
|
||||
delete opt_copy;
|
||||
}
|
||||
} else if (*opt_new != *opt_old)
|
||||
print_diff.emplace_back(opt_key);
|
||||
}
|
||||
|
||||
return print_diff;
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare for storing of the full print config into new_full_config to be exported into the G-code and to be used by the PlaceholderParser.
|
||||
static t_config_option_keys full_print_config_diffs(const DynamicPrintConfig ¤t_full_config, const DynamicPrintConfig &new_full_config)
|
||||
|
@ -812,7 +815,7 @@ static PrintObjectRegions* generate_print_object_regions(
|
|||
layer_ranges_regions.push_back({ range.layer_height_range, range.config });
|
||||
}
|
||||
|
||||
const bool is_mm_painted = std::any_of(model_volumes.cbegin(), model_volumes.cend(), [](const ModelVolume *mv) { return mv->is_mm_painted(); });
|
||||
const bool is_mm_painted = num_extruders > 1 && std::any_of(model_volumes.cbegin(), model_volumes.cend(), [](const ModelVolume *mv) { return mv->is_mm_painted(); });
|
||||
update_volume_bboxes(layer_ranges_regions, out->cached_volume_ids, model_volumes, out->trafo_bboxes, is_mm_painted ? 0.f : std::max(0.f, xy_size_compensation));
|
||||
|
||||
std::vector<PrintRegion*> region_set;
|
||||
|
@ -928,6 +931,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
|||
bool num_extruders_changed = false;
|
||||
if (! full_config_diff.empty()) {
|
||||
update_apply_status(this->invalidate_step(psGCodeExport));
|
||||
m_placeholder_parser.clear_config();
|
||||
// Set the profile aliases for the PrintBase::output_filename()
|
||||
m_placeholder_parser.set("print_preset", new_full_config.option("print_settings_id")->clone());
|
||||
m_placeholder_parser.set("filament_preset", new_full_config.option("filament_settings_id")->clone());
|
||||
|
@ -939,6 +943,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
|||
// It is also safe to change m_config now after this->invalidate_state_by_config_options() call.
|
||||
m_config.apply_only(new_full_config, print_diff, true);
|
||||
//FIXME use move semantics once ConfigBase supports it.
|
||||
// Some filament_overrides may contain values different from new_full_config, but equal to m_config.
|
||||
// As long as these config options don't reallocate memory when copying, we are safe overriding a value, which is in use by a worker thread.
|
||||
m_config.apply(filament_overrides);
|
||||
// Handle changes to object config defaults
|
||||
m_default_object_config.apply_only(new_full_config, object_diff, true);
|
||||
|
@ -946,8 +952,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
|||
m_default_region_config.apply_only(new_full_config, region_diff, true);
|
||||
m_full_print_config = std::move(new_full_config);
|
||||
if (num_extruders != m_config.nozzle_diameter.size()) {
|
||||
num_extruders = m_config.nozzle_diameter.size();
|
||||
num_extruders_changed = true;
|
||||
num_extruders = m_config.nozzle_diameter.size();
|
||||
num_extruders_changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1065,7 +1071,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
|||
// Check whether a model part volume was added or removed, their transformations or order changed.
|
||||
// Only volume IDs, volume types, transformation matrices and their order are checked, configuration and other parameters are NOT checked.
|
||||
bool solid_or_modifier_differ = model_volume_list_changed(model_object, model_object_new, solid_or_modifier_types) ||
|
||||
model_mmu_segmentation_data_changed(model_object, model_object_new);
|
||||
model_mmu_segmentation_data_changed(model_object, model_object_new) ||
|
||||
(model_object_new.is_mm_painted() && num_extruders_changed);
|
||||
bool supports_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_BLOCKER) ||
|
||||
model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_ENFORCER);
|
||||
bool layer_height_ranges_differ = ! layer_height_ranges_equal(model_object.layer_config_ranges, model_object_new.layer_config_ranges, model_object_new.layer_height_profile.empty());
|
||||
|
@ -1267,7 +1274,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
|||
print_object_regions->ref_cnt_inc();
|
||||
}
|
||||
std::vector<unsigned int> painting_extruders;
|
||||
if (const auto &volumes = print_object.model_object()->volumes;
|
||||
if (const auto &volumes = print_object.model_object()->volumes;
|
||||
num_extruders > 1 &&
|
||||
std::find_if(volumes.begin(), volumes.end(), [](const ModelVolume *v) { return ! v->mmu_segmentation_facets.empty(); }) != volumes.end()) {
|
||||
//FIXME be more specific! Don't enumerate extruders that are not used for painting!
|
||||
painting_extruders.assign(num_extruders, 0);
|
||||
|
|
|
@ -232,9 +232,19 @@ void PrintConfigDef::init_common_params()
|
|||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionString(""));
|
||||
|
||||
def = this->add("elefant_foot_compensation", coFloat);
|
||||
def->label = L("Elephant foot compensation");
|
||||
def->category = L("Advanced");
|
||||
def->tooltip = L("The first layer will be shrunk in the XY plane by the configured value "
|
||||
"to compensate for the 1st layer squish aka an Elephant Foot effect.");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(0.));
|
||||
|
||||
def = this->add("thumbnails", coPoints);
|
||||
def->label = L("G-code thumbnails");
|
||||
def->tooltip = L("Picture sizes to be stored into a .gcode and .sl1 files, in the following format: \"XxY, XxY, ...\"");
|
||||
def->tooltip = L("Picture sizes to be stored into a .gcode and .sl1 / .sl1s files, in the following format: \"XxY, XxY, ...\"");
|
||||
def->mode = comExpert;
|
||||
def->gui_type = ConfigOptionDef::GUIType::one_string;
|
||||
def->set_default_value(new ConfigOptionPoints());
|
||||
|
@ -264,6 +274,7 @@ void PrintConfigDef::init_common_params()
|
|||
"Print host behind HAProxy with basic auth enabled can be accessed by putting the user name and password into the URL "
|
||||
"in the following format: https://username:password@your-octopi-address/");
|
||||
def->mode = comAdvanced;
|
||||
def->cli = ConfigOptionDef::nocli;
|
||||
def->set_default_value(new ConfigOptionString(""));
|
||||
|
||||
def = this->add("printhost_apikey", coString);
|
||||
|
@ -271,6 +282,7 @@ void PrintConfigDef::init_common_params()
|
|||
def->tooltip = L("Slic3r can upload G-code files to a printer host. This field should contain "
|
||||
"the API Key or the password required for authentication.");
|
||||
def->mode = comAdvanced;
|
||||
def->cli = ConfigOptionDef::nocli;
|
||||
def->set_default_value(new ConfigOptionString(""));
|
||||
|
||||
def = this->add("printhost_port", coString);
|
||||
|
@ -278,6 +290,7 @@ void PrintConfigDef::init_common_params()
|
|||
def->tooltip = L("Name of the printer");
|
||||
def->gui_type = ConfigOptionDef::GUIType::select_open;
|
||||
def->mode = comAdvanced;
|
||||
def->cli = ConfigOptionDef::nocli;
|
||||
def->set_default_value(new ConfigOptionString(""));
|
||||
|
||||
def = this->add("printhost_cafile", coString);
|
||||
|
@ -285,31 +298,33 @@ void PrintConfigDef::init_common_params()
|
|||
def->tooltip = L("Custom CA certificate file can be specified for HTTPS OctoPrint connections, in crt/pem format. "
|
||||
"If left blank, the default OS CA certificate repository is used.");
|
||||
def->mode = comAdvanced;
|
||||
def->cli = ConfigOptionDef::nocli;
|
||||
def->set_default_value(new ConfigOptionString(""));
|
||||
|
||||
def = this->add("elefant_foot_compensation", coFloat);
|
||||
def->label = L("Elephant foot compensation");
|
||||
def->category = L("Advanced");
|
||||
def->tooltip = L("The first layer will be shrunk in the XY plane by the configured value "
|
||||
"to compensate for the 1st layer squish aka an Elephant Foot effect.");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(0.2));
|
||||
|
||||
// Options used by physical printers
|
||||
|
||||
def = this->add("printhost_user", coString);
|
||||
def->label = L("User");
|
||||
// def->tooltip = L("");
|
||||
def->mode = comAdvanced;
|
||||
def->cli = ConfigOptionDef::nocli;
|
||||
def->set_default_value(new ConfigOptionString(""));
|
||||
|
||||
def = this->add("printhost_password", coString);
|
||||
def->label = L("Password");
|
||||
// def->tooltip = L("");
|
||||
def->mode = comAdvanced;
|
||||
def->cli = ConfigOptionDef::nocli;
|
||||
def->set_default_value(new ConfigOptionString(""));
|
||||
|
||||
// Only available on Windows.
|
||||
def = this->add("printhost_ssl_ignore_revoke", coBool);
|
||||
def->label = L("Ignore HTTPS certificate revocation checks");
|
||||
def->tooltip = L("Ignore HTTPS certificate revocation checks in case of missing or offline distribution points. "
|
||||
"One may want to enable this option for self signed certificates if connection fails.");
|
||||
def->mode = comAdvanced;
|
||||
def->cli = ConfigOptionDef::nocli;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("preset_names", coStrings);
|
||||
def->label = L("Printer preset names");
|
||||
|
@ -317,12 +332,6 @@ void PrintConfigDef::init_common_params()
|
|||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionStrings());
|
||||
|
||||
// temporary workaround for compatibility with older Slicer
|
||||
{
|
||||
def = this->add("preset_name", coString);
|
||||
def->set_default_value(new ConfigOptionString());
|
||||
}
|
||||
|
||||
def = this->add("printhost_authorization_type", coEnum);
|
||||
def->label = L("Authorization Type");
|
||||
// def->tooltip = L("");
|
||||
|
@ -332,7 +341,14 @@ void PrintConfigDef::init_common_params()
|
|||
def->enum_labels.push_back(L("API key"));
|
||||
def->enum_labels.push_back(L("HTTP digest"));
|
||||
def->mode = comAdvanced;
|
||||
def->cli = ConfigOptionDef::nocli;
|
||||
def->set_default_value(new ConfigOptionEnum<AuthorizationType>(atKeyPassword));
|
||||
|
||||
// temporary workaround for compatibility with older Slicer
|
||||
{
|
||||
def = this->add("preset_name", coString);
|
||||
def->set_default_value(new ConfigOptionString());
|
||||
}
|
||||
}
|
||||
|
||||
void PrintConfigDef::init_fff_params()
|
||||
|
@ -465,7 +481,8 @@ void PrintConfigDef::init_fff_params()
|
|||
def = this->add("brim_width", coFloat);
|
||||
def->label = L("Brim width");
|
||||
def->category = L("Skirt and brim");
|
||||
def->tooltip = L("Horizontal width of the brim that will be printed around each object on the first layer.");
|
||||
def->tooltip = L("Horizontal width of the brim that will be printed around each object on the first layer."
|
||||
"When raft is used, no brim is generated (use raft_first_layer_expansion).");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->max = 200;
|
||||
|
@ -491,10 +508,11 @@ void PrintConfigDef::init_fff_params()
|
|||
def = this->add("brim_offset", coFloat);
|
||||
def->label = L("Brim offset");
|
||||
def->category = L("Skirt and brim");
|
||||
def->tooltip = L("The offset of the brim from the printed object.");
|
||||
def->tooltip = L("The offset of the brim from the printed object. The offset is applied after the elephant foot compensation.");
|
||||
def->sidetext = L("mm");
|
||||
def->mode = comSimple;
|
||||
def->set_default_value(new ConfigOptionFloat(0));
|
||||
def->min = 0;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(0.f));
|
||||
|
||||
def = this->add("clip_multipart_objects", coBool);
|
||||
def->label = L("Clip multi-part objects");
|
||||
|
@ -1808,6 +1826,7 @@ void PrintConfigDef::init_fff_params()
|
|||
def->enum_labels.push_back("AstroBox");
|
||||
def->enum_labels.push_back("Repetier");
|
||||
def->mode = comAdvanced;
|
||||
def->cli = ConfigOptionDef::nocli;
|
||||
def->set_default_value(new ConfigOptionEnum<PrintHostType>(htOctoPrint));
|
||||
|
||||
def = this->add("only_retract_when_crossing_perimeters", coBool);
|
||||
|
@ -4154,9 +4173,10 @@ CLITransformConfigDef::CLITransformConfigDef()
|
|||
def->label = L("Don't arrange");
|
||||
def->tooltip = L("Do not rearrange the given models before merging and keep their original XY coordinates.");
|
||||
|
||||
def = this->add("dont_ensure_on_bed", coBool);
|
||||
def->label = L("Don't ensure on bed");
|
||||
def->tooltip = L("Do not lift the object above the bed when it is partially below.");
|
||||
def = this->add("ensure_on_bed", coBool);
|
||||
def->label = L("Ensure on bed");
|
||||
def->tooltip = L("Lift the object above the bed when it is partially below. Enabled by default, use --no-ensure-on-bed to disable.");
|
||||
def->set_default_value(new ConfigOptionBool(true));
|
||||
|
||||
def = this->add("duplicate", coInt);
|
||||
def->label = L("Duplicate");
|
||||
|
|
|
@ -428,10 +428,8 @@ std::pair<FillAdaptive::OctreePtr, FillAdaptive::OctreePtr> PrintObject::prepare
|
|||
|
||||
indexed_triangle_set mesh = this->model_object()->raw_indexed_triangle_set();
|
||||
// Rotate mesh and build octree on it with axis-aligned (standart base) cubes.
|
||||
Transform3d m = m_trafo;
|
||||
m.pretranslate(Vec3d(- unscale<float>(m_center_offset.x()), - unscale<float>(m_center_offset.y()), 0));
|
||||
auto to_octree = transform_to_octree().toRotationMatrix();
|
||||
its_transform(mesh, to_octree * m, true);
|
||||
its_transform(mesh, to_octree * this->trafo_centered(), true);
|
||||
|
||||
// Triangulate internal bridging surfaces.
|
||||
std::vector<std::vector<Vec3d>> overhangs(this->layers().size());
|
||||
|
@ -537,7 +535,6 @@ bool PrintObject::invalidate_state_by_config_options(
|
|||
steps.emplace_back(posPerimeters);
|
||||
} else if (
|
||||
opt_key == "layer_height"
|
||||
|| opt_key == "first_layer_height"
|
||||
|| opt_key == "mmu_segmented_region_max_width"
|
||||
|| opt_key == "raft_layers"
|
||||
|| opt_key == "raft_contact_distance"
|
||||
|
@ -2298,7 +2295,7 @@ void PrintObject::project_and_append_custom_facets(
|
|||
: mv->supported_facets.get_facets_strict(*mv, type);
|
||||
if (! custom_facets.indices.empty())
|
||||
project_triangles_to_slabs(this->layers(), custom_facets,
|
||||
(Eigen::Translation3d(to_3d(unscaled<double>(this->center_offset()), 0.)) * this->trafo() * mv->get_matrix()).cast<float>(),
|
||||
(this->trafo_centered() * mv->get_matrix()).cast<float>(),
|
||||
seam, out);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -167,8 +167,9 @@ static std::vector<VolumeSlices> slice_volumes_inner(
|
|||
|
||||
params_base.mode_below = params_base.mode;
|
||||
|
||||
const bool is_mm_painted = std::any_of(model_volumes.cbegin(), model_volumes.cend(), [](const ModelVolume *mv) { return mv->is_mm_painted(); });
|
||||
const auto extra_offset = is_mm_painted ? 0.f : std::max(0.f, float(print_object_config.xy_size_compensation.value));
|
||||
const size_t num_extruders = print_config.nozzle_diameter.size();
|
||||
const bool is_mm_painted = num_extruders > 1 && std::any_of(model_volumes.cbegin(), model_volumes.cend(), [](const ModelVolume *mv) { return mv->is_mm_painted(); });
|
||||
const auto extra_offset = is_mm_painted ? 0.f : std::max(0.f, float(print_object_config.xy_size_compensation.value));
|
||||
|
||||
for (const ModelVolume *model_volume : model_volumes)
|
||||
if (model_volume_needs_slicing(*model_volume)) {
|
||||
|
@ -695,11 +696,9 @@ void PrintObject::slice_volumes()
|
|||
}
|
||||
|
||||
std::vector<float> slice_zs = zs_from_layers(m_layers);
|
||||
Transform3d trafo = this->trafo();
|
||||
trafo.pretranslate(Vec3d(- unscale<double>(m_center_offset.x()), - unscale<double>(m_center_offset.y()), 0));
|
||||
std::vector<std::vector<ExPolygons>> region_slices = slices_to_regions(this->model_object()->volumes, *m_shared_regions, slice_zs,
|
||||
slice_volumes_inner(
|
||||
print->config(), this->config(), trafo,
|
||||
print->config(), this->config(), this->trafo_centered(),
|
||||
this->model_object()->volumes, m_shared_regions->layer_ranges, slice_zs, throw_on_cancel_callback),
|
||||
m_config.clip_multipart_objects,
|
||||
throw_on_cancel_callback);
|
||||
|
@ -725,6 +724,7 @@ void PrintObject::slice_volumes()
|
|||
|
||||
// Is any ModelVolume MMU painted?
|
||||
if (const auto& volumes = this->model_object()->volumes;
|
||||
m_print->config().nozzle_diameter.size() > 1 &&
|
||||
std::find_if(volumes.begin(), volumes.end(), [](const ModelVolume* v) { return !v->mmu_segmentation_facets.empty(); }) != volumes.end()) {
|
||||
|
||||
// If XY Size compensation is also enabled, notify the user that XY Size compensation
|
||||
|
@ -745,8 +745,9 @@ void PrintObject::slice_volumes()
|
|||
BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - make_slices in parallel - begin";
|
||||
{
|
||||
// Compensation value, scaled. Only applying the negative scaling here, as the positive scaling has already been applied during slicing.
|
||||
const auto xy_compensation_scaled = this->is_mm_painted() ? scaled<float>(0.f) : scaled<float>(std::min(m_config.xy_size_compensation.value, 0.));
|
||||
const float elephant_foot_compensation_scaled = (m_config.raft_layers == 0) ?
|
||||
const size_t num_extruders = print->config().nozzle_diameter.size();
|
||||
const auto xy_compensation_scaled = (num_extruders > 1 && this->is_mm_painted()) ? scaled<float>(0.f) : scaled<float>(std::min(m_config.xy_size_compensation.value, 0.));
|
||||
const float elephant_foot_compensation_scaled = (m_config.raft_layers == 0) ?
|
||||
// Only enable Elephant foot compensation if printing directly on the print bed.
|
||||
float(scale_(m_config.elefant_foot_compensation.value)) :
|
||||
0.f;
|
||||
|
@ -832,8 +833,7 @@ std::vector<Polygons> PrintObject::slice_support_volumes(const ModelVolumeType m
|
|||
const Print *print = this->print();
|
||||
auto throw_on_cancel_callback = std::function<void()>([print](){ print->throw_if_canceled(); });
|
||||
MeshSlicingParamsEx params;
|
||||
params.trafo = this->trafo();
|
||||
params.trafo.pretranslate(Vec3d(-unscale<float>(m_center_offset.x()), -unscale<float>(m_center_offset.y()), 0));
|
||||
params.trafo = this->trafo_centered();
|
||||
for (; it_volume != it_volume_end; ++ it_volume)
|
||||
if ((*it_volume)->type() == model_volume_type) {
|
||||
std::vector<ExPolygons> slices2 = slice_volume(*(*it_volume), zs, params, throw_on_cancel_callback);
|
||||
|
|
845
src/libslic3r/QuadricEdgeCollapse.cpp
Normal file
845
src/libslic3r/QuadricEdgeCollapse.cpp
Normal file
|
@ -0,0 +1,845 @@
|
|||
#include "QuadricEdgeCollapse.hpp"
|
||||
#include <tuple>
|
||||
#include <optional>
|
||||
#include "MutablePriorityQueue.hpp"
|
||||
#include "SimplifyMeshImpl.hpp"
|
||||
#include <tbb/parallel_for.h>
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
// only private namespace not neccessary be in .hpp
|
||||
namespace QuadricEdgeCollapse {
|
||||
using Vertices = std::vector<stl_vertex>;
|
||||
using Triangle = stl_triangle_vertex_indices;
|
||||
using Indices = std::vector<stl_triangle_vertex_indices>;
|
||||
using SymMat = SimplifyMesh::implementation::SymetricMatrix<double>;
|
||||
using ThrowOnCancel = std::function<void(void)>;
|
||||
using StatusFn = std::function<void(int)>;
|
||||
// smallest error caused by edges, identify smallest edge in triangle
|
||||
struct Error
|
||||
{
|
||||
float value = -1.; // identifying of smallest edge is stored inside of TriangleInfo
|
||||
uint32_t triangle_index = 0;
|
||||
Error(float value, uint32_t triangle_index)
|
||||
: value(value)
|
||||
, triangle_index(triangle_index)
|
||||
{}
|
||||
Error() = default;
|
||||
};
|
||||
using Errors = std::vector<Error>;
|
||||
|
||||
// merge information together - faster access during processing
|
||||
struct TriangleInfo {
|
||||
Vec3f n; // normalized normal - used for check when fliped
|
||||
|
||||
// range(0 .. 2),
|
||||
unsigned char min_index = 0; // identify edge for minimal Error -> lightweight Error structure
|
||||
|
||||
TriangleInfo() = default;
|
||||
bool is_deleted() const { return n.x() > 2.f; }
|
||||
void set_deleted() { n.x() = 3.f; }
|
||||
};
|
||||
using TriangleInfos = std::vector<TriangleInfo>;
|
||||
struct VertexInfo {
|
||||
SymMat q; // sum quadric of surround triangles
|
||||
uint32_t start = 0, count = 0; // vertex neighbor triangles
|
||||
VertexInfo() = default;
|
||||
bool is_deleted() const { return count == 0; }
|
||||
};
|
||||
using VertexInfos = std::vector<VertexInfo>;
|
||||
struct EdgeInfo {
|
||||
uint32_t t_index=0; // triangle index
|
||||
unsigned char edge = 0; // 0 or 1 or 2
|
||||
EdgeInfo() = default;
|
||||
};
|
||||
using EdgeInfos = std::vector<EdgeInfo>;
|
||||
|
||||
// DTO for change neighbors
|
||||
struct CopyEdgeInfo {
|
||||
uint32_t start;
|
||||
uint32_t count;
|
||||
uint32_t move;
|
||||
CopyEdgeInfo(uint32_t start, uint32_t count, uint32_t move)
|
||||
: start(start), count(count), move(move)
|
||||
{}
|
||||
};
|
||||
using CopyEdgeInfos = std::vector<CopyEdgeInfo>;
|
||||
|
||||
Vec3d create_normal(const Triangle &triangle, const Vertices &vertices);
|
||||
std::array<Vec3d,3> create_vertices(uint32_t id_v1, uint32_t id_v2, const Vertices &vertices);
|
||||
std::array<double, 3> vertices_error(const SymMat &q, const std::array<Vec3d, 3> &vertices);
|
||||
double calculate_determinant(const SymMat &q);
|
||||
double calculate_error(uint32_t id_v1, uint32_t id_v2, const SymMat & q, const Vertices &vertices);
|
||||
Vec3f calculate_vertex(uint32_t id_v1, uint32_t id_v2, const SymMat & q, const Vertices &vertices);
|
||||
Vec3d calculate_vertex(double det, const SymMat &q);
|
||||
// calculate error for vertex and quadrics, triangle quadrics and triangle vertex give zero, only pozitive number
|
||||
double vertex_error(const SymMat &q, const Vec3d &vertex);
|
||||
SymMat create_quadric(const Triangle &t, const Vec3d& n, const Vertices &vertices);
|
||||
std::tuple<TriangleInfos, VertexInfos, EdgeInfos, Errors>
|
||||
init(const indexed_triangle_set &its, ThrowOnCancel& throw_on_cancel, StatusFn& status_fn);
|
||||
std::optional<uint32_t> find_triangle_index1(uint32_t vi, const VertexInfo& v_info,
|
||||
uint32_t ti, const EdgeInfos& e_infos, const Indices& indices);
|
||||
bool is_flipped(const Vec3f &new_vertex, uint32_t ti0, uint32_t ti1, const VertexInfo& v_info,
|
||||
const TriangleInfos &t_infos, const EdgeInfos &e_infos, const indexed_triangle_set &its);
|
||||
bool degenerate(uint32_t vi, uint32_t ti0, uint32_t ti1, const VertexInfo &v_info,
|
||||
const EdgeInfos &e_infos, const Indices &indices);
|
||||
// find edge with smallest error in triangle
|
||||
Vec3d calculate_3errors(const Triangle &t, const Vertices &vertices, const VertexInfos &v_infos);
|
||||
Error calculate_error(uint32_t ti, const Triangle& t,const Vertices &vertices, const VertexInfos& v_infos, unsigned char& min_index);
|
||||
void remove_triangle(EdgeInfos &e_infos, VertexInfo &v_info, uint32_t ti);
|
||||
void change_neighbors(EdgeInfos &e_infos, VertexInfos &v_infos, uint32_t ti0, uint32_t ti1,
|
||||
uint32_t vi0, uint32_t vi1, uint32_t vi_top0,
|
||||
const Triangle &t1, CopyEdgeInfos& infos, EdgeInfos &e_infos1);
|
||||
void compact(const VertexInfos &v_infos, const TriangleInfos &t_infos, const EdgeInfos &e_infos, indexed_triangle_set &its);
|
||||
|
||||
#ifndef NDEBUG
|
||||
void store_surround(const char *obj_filename, size_t triangle_index, int depth, const indexed_triangle_set &its,
|
||||
const VertexInfos &v_infos, const EdgeInfos &e_infos);
|
||||
bool check_neighbors(const indexed_triangle_set &its, const TriangleInfos &t_infos,
|
||||
const VertexInfos &v_infos, const EdgeInfos &e_infos);
|
||||
#endif /* NDEBUG */
|
||||
|
||||
} // namespace QuadricEdgeCollapse
|
||||
|
||||
using namespace QuadricEdgeCollapse;
|
||||
|
||||
void Slic3r::its_quadric_edge_collapse(
|
||||
indexed_triangle_set & its,
|
||||
uint32_t triangle_count,
|
||||
float * max_error,
|
||||
std::function<void(void)> throw_on_cancel,
|
||||
std::function<void(int)> status_fn)
|
||||
{
|
||||
// constants --> may be move to config
|
||||
const int status_init_size = 10; // in percents
|
||||
const int check_cancel_period = 16; // how many edge to reduce before call throw_on_cancel
|
||||
|
||||
// check input
|
||||
if (triangle_count >= its.indices.size()) return;
|
||||
float maximal_error = (max_error == nullptr)? std::numeric_limits<float>::max() : *max_error;
|
||||
if (maximal_error <= 0.f) return;
|
||||
if (throw_on_cancel == nullptr) throw_on_cancel = []() {};
|
||||
if (status_fn == nullptr) status_fn = [](int) {};
|
||||
|
||||
StatusFn init_status_fn = [&](int percent) {
|
||||
status_fn(std::round((percent * status_init_size) / 100.));
|
||||
};
|
||||
|
||||
TriangleInfos t_infos; // only normals with information about deleted triangle
|
||||
VertexInfos v_infos;
|
||||
EdgeInfos e_infos;
|
||||
Errors errors;
|
||||
std::tie(t_infos, v_infos, e_infos, errors) = init(its, throw_on_cancel, init_status_fn);
|
||||
throw_on_cancel();
|
||||
status_fn(status_init_size);
|
||||
|
||||
//its_store_triangle(its, "triangle.obj", 1182);
|
||||
//store_surround("triangle_surround1.obj", 1182, 1, its, v_infos, e_infos);
|
||||
|
||||
// convert from triangle index to mutable priority queue index
|
||||
std::vector<size_t> ti_2_mpqi(its.indices.size(), {0});
|
||||
auto setter = [&ti_2_mpqi](const Error &e, size_t index) { ti_2_mpqi[e.triangle_index] = index; };
|
||||
auto less = [](const Error &e1, const Error &e2) -> bool { return e1.value < e2.value; };
|
||||
auto mpq = make_miniheap_mutable_priority_queue<Error, 32, false>(std::move(setter), std::move(less));
|
||||
//MutablePriorityQueue<Error, decltype(setter), decltype(less)> mpq(std::move(setter), std::move(less));
|
||||
mpq.reserve(its.indices.size());
|
||||
for (Error &error :errors) mpq.push(error);
|
||||
|
||||
const size_t max_triangle_count_for_one_vertex = 50;
|
||||
CopyEdgeInfos ceis;
|
||||
ceis.reserve(max_triangle_count_for_one_vertex);
|
||||
EdgeInfos e_infos_swap;
|
||||
e_infos_swap.reserve(max_triangle_count_for_one_vertex);
|
||||
std::vector<uint32_t> changed_triangle_indices;
|
||||
changed_triangle_indices.reserve(2 * max_triangle_count_for_one_vertex);
|
||||
|
||||
uint32_t actual_triangle_count = its.indices.size();
|
||||
uint32_t count_triangle_to_reduce = actual_triangle_count - triangle_count;
|
||||
auto increase_status = [&]() {
|
||||
double reduced = (actual_triangle_count - triangle_count) /
|
||||
(double) count_triangle_to_reduce;
|
||||
double status = status_init_size + (100 - status_init_size) *
|
||||
(1. - reduced);
|
||||
status_fn(static_cast<int>(std::round(status)));
|
||||
};
|
||||
// modulo for update status
|
||||
uint32_t status_mod = std::max(uint32_t(16), count_triangle_to_reduce / 100);
|
||||
|
||||
uint32_t iteration_number = 0;
|
||||
float last_collapsed_error = 0.f;
|
||||
while (actual_triangle_count > triangle_count && !mpq.empty()) {
|
||||
++iteration_number;
|
||||
if (iteration_number % status_mod == 0) increase_status();
|
||||
if (iteration_number % check_cancel_period == 0) throw_on_cancel();
|
||||
|
||||
// triangle index 0
|
||||
Error e = mpq.top(); // copy
|
||||
if (e.value >= maximal_error) break; // Too big error
|
||||
mpq.pop();
|
||||
uint32_t ti0 = e.triangle_index;
|
||||
TriangleInfo &t_info0 = t_infos[ti0];
|
||||
if (t_info0.is_deleted()) continue;
|
||||
assert(t_info0.min_index < 3);
|
||||
|
||||
const Triangle &t0 = its.indices[ti0];
|
||||
uint32_t vi0 = t0[t_info0.min_index];
|
||||
uint32_t vi1 = t0[(t_info0.min_index+1) %3];
|
||||
// Need by move of neighbor edge infos in function: change_neighbors
|
||||
if (vi0 > vi1) std::swap(vi0, vi1);
|
||||
VertexInfo &v_info0 = v_infos[vi0];
|
||||
VertexInfo &v_info1 = v_infos[vi1];
|
||||
assert(!v_info0.is_deleted() && !v_info1.is_deleted());
|
||||
|
||||
// new vertex position
|
||||
SymMat q(v_info0.q);
|
||||
q += v_info1.q;
|
||||
Vec3f new_vertex0 = calculate_vertex(vi0, vi1, q, its.vertices);
|
||||
// set of triangle indices that change quadric
|
||||
auto ti1_opt = (v_info0.count < v_info1.count)?
|
||||
find_triangle_index1(vi1, v_info0, ti0, e_infos, its.indices) :
|
||||
find_triangle_index1(vi0, v_info1, ti0, e_infos, its.indices) ;
|
||||
if (!ti1_opt.has_value() || // edge has only one triangle
|
||||
degenerate(vi0, ti0, *ti1_opt, v_info1, e_infos, its.indices) ||
|
||||
degenerate(vi1, ti0, *ti1_opt, v_info0, e_infos, its.indices) ||
|
||||
is_flipped(new_vertex0, ti0, *ti1_opt, v_info0, t_infos, e_infos, its) ||
|
||||
is_flipped(new_vertex0, ti0, *ti1_opt, v_info1, t_infos, e_infos, its)) {
|
||||
// try other triangle's edge
|
||||
Vec3d errors = calculate_3errors(t0, its.vertices, v_infos);
|
||||
Vec3i ord = (errors[0] < errors[1]) ?
|
||||
((errors[0] < errors[2])?
|
||||
((errors[1] < errors[2]) ? Vec3i(0, 1, 2) : Vec3i(0, 2, 1)) :
|
||||
Vec3i(2, 0, 1)):
|
||||
((errors[1] < errors[2])?
|
||||
((errors[0] < errors[2]) ? Vec3i(1, 0, 2) : Vec3i(1, 2, 0)) :
|
||||
Vec3i(2, 1, 0));
|
||||
if (t_info0.min_index == ord[0]) {
|
||||
t_info0.min_index = ord[1];
|
||||
e.value = errors[t_info0.min_index];
|
||||
} else if (t_info0.min_index == ord[1]) {
|
||||
t_info0.min_index = ord[2];
|
||||
e.value = errors[t_info0.min_index];
|
||||
} else {
|
||||
// error is changed when surround edge is reduced
|
||||
t_info0.min_index = 3; // bad index -> invalidate
|
||||
e.value = maximal_error;
|
||||
}
|
||||
// IMPROVE: check mpq top if it is ti1 with same edge
|
||||
mpq.push(e);
|
||||
continue;
|
||||
}
|
||||
uint32_t ti1 = *ti1_opt;
|
||||
last_collapsed_error = e.value;
|
||||
changed_triangle_indices.clear();
|
||||
changed_triangle_indices.reserve(v_info0.count + v_info1.count - 4);
|
||||
|
||||
// for each vertex0 triangles
|
||||
uint32_t v_info0_end = v_info0.start + v_info0.count;
|
||||
for (uint32_t di = v_info0.start; di < v_info0_end; ++di) {
|
||||
assert(di < e_infos.size());
|
||||
uint32_t ti = e_infos[di].t_index;
|
||||
if (ti == ti0) continue; // ti0 will be deleted
|
||||
if (ti == ti1) continue; // ti1 will be deleted
|
||||
changed_triangle_indices.emplace_back(ti);
|
||||
}
|
||||
|
||||
// for each vertex1 triangles
|
||||
uint32_t v_info1_end = v_info1.start + v_info1.count;
|
||||
for (uint32_t di = v_info1.start; di < v_info1_end; ++di) {
|
||||
assert(di < e_infos.size());
|
||||
EdgeInfo &e_info = e_infos[di];
|
||||
uint32_t ti = e_info.t_index;
|
||||
if (ti == ti0) continue; // ti0 will be deleted
|
||||
if (ti == ti1) continue; // ti1 will be deleted
|
||||
Triangle &t = its.indices[ti];
|
||||
t[e_info.edge] = vi0; // change index
|
||||
changed_triangle_indices.emplace_back(ti);
|
||||
}
|
||||
v_info0.q = q;
|
||||
|
||||
// fix neighbors
|
||||
// vertex index of triangle 0 which is not vi0 nor vi1
|
||||
uint32_t vi_top0 = t0[(t_info0.min_index + 2) % 3];
|
||||
const Triangle &t1 = its.indices[ti1];
|
||||
change_neighbors(e_infos, v_infos, ti0, ti1, vi0, vi1,
|
||||
vi_top0, t1, ceis, e_infos_swap);
|
||||
|
||||
// Change vertex
|
||||
its.vertices[vi0] = new_vertex0;
|
||||
|
||||
// fix errors - must be after set neighbors - v_infos
|
||||
mpq.remove(ti_2_mpqi[ti1]);
|
||||
for (uint32_t ti : changed_triangle_indices) {
|
||||
size_t priority_queue_index = ti_2_mpqi[ti];
|
||||
TriangleInfo& t_info = t_infos[ti];
|
||||
t_info.n = create_normal(its.indices[ti], its.vertices).cast<float>(); // recalc normals
|
||||
mpq[priority_queue_index] = calculate_error(ti, its.indices[ti], its.vertices, v_infos, t_info.min_index);
|
||||
mpq.update(priority_queue_index);
|
||||
}
|
||||
|
||||
// set triangle(0 + 1) indices as deleted
|
||||
TriangleInfo &t_info1 = t_infos[ti1];
|
||||
t_info0.set_deleted();
|
||||
t_info1.set_deleted();
|
||||
// triangle counter decrementation
|
||||
actual_triangle_count-=2;
|
||||
assert(check_neighbors(its, t_infos, v_infos, e_infos));
|
||||
}
|
||||
|
||||
// compact triangle
|
||||
compact(v_infos, t_infos, e_infos, its);
|
||||
if (max_error != nullptr) *max_error = last_collapsed_error;
|
||||
}
|
||||
|
||||
Vec3d QuadricEdgeCollapse::create_normal(const Triangle &triangle,
|
||||
const Vertices &vertices)
|
||||
{
|
||||
Vec3d v0 = vertices[triangle[0]].cast<double>();
|
||||
Vec3d v1 = vertices[triangle[1]].cast<double>();
|
||||
Vec3d v2 = vertices[triangle[2]].cast<double>();
|
||||
// n = triangle normal
|
||||
Vec3d n = (v1 - v0).cross(v2 - v0);
|
||||
n.normalize();
|
||||
return n;
|
||||
}
|
||||
|
||||
double QuadricEdgeCollapse::calculate_determinant(const SymMat &q)
|
||||
{
|
||||
return q.det(0, 1, 2, 1, 4, 5, 2, 5, 7);
|
||||
}
|
||||
|
||||
Vec3d QuadricEdgeCollapse::calculate_vertex(double det, const SymMat &q) {
|
||||
double det_1 = -1 / det;
|
||||
double det_x = q.det(1, 2, 3, 4, 5, 6, 5, 7, 8); // vx = A41/det(q_delta)
|
||||
double det_y = q.det(0, 2, 3, 1, 5, 6, 2, 7, 8); // vy = A42/det(q_delta)
|
||||
double det_z = q.det(0, 1, 3, 1, 4, 6, 2, 5, 8); // vz = A43/det(q_delta)
|
||||
return Vec3d(det_1 * det_x, -det_1 * det_y, det_1 * det_z);
|
||||
}
|
||||
|
||||
std::array<Vec3d,3> QuadricEdgeCollapse::create_vertices(uint32_t id_v1, uint32_t id_v2, const Vertices &vertices)
|
||||
{
|
||||
Vec3d v0 = vertices[id_v1].cast<double>();
|
||||
Vec3d v1 = vertices[id_v2].cast<double>();
|
||||
Vec3d vm = (v0 + v1) / 2.;
|
||||
return {v0, v1, vm};
|
||||
}
|
||||
|
||||
std::array<double, 3> QuadricEdgeCollapse::vertices_error(
|
||||
const SymMat &q, const std::array<Vec3d, 3> &vertices)
|
||||
{
|
||||
return {
|
||||
vertex_error(q, vertices[0]),
|
||||
vertex_error(q, vertices[1]),
|
||||
vertex_error(q, vertices[2])};
|
||||
}
|
||||
|
||||
double QuadricEdgeCollapse::calculate_error(uint32_t id_v1,
|
||||
uint32_t id_v2,
|
||||
const SymMat & q,
|
||||
const Vertices &vertices)
|
||||
{
|
||||
double det = calculate_determinant(q);
|
||||
if (std::abs(det) < std::numeric_limits<double>::epsilon()) {
|
||||
// can't divide by zero
|
||||
auto verts = create_vertices(id_v1, id_v2, vertices);
|
||||
auto errors = vertices_error(q, verts);
|
||||
return *std::min_element(std::begin(errors), std::end(errors));
|
||||
}
|
||||
Vec3d vertex = calculate_vertex(det, q);
|
||||
return vertex_error(q, vertex);
|
||||
}
|
||||
|
||||
// similar as calculate error but focus on new vertex without calculation of error
|
||||
Vec3f QuadricEdgeCollapse::calculate_vertex(uint32_t id_v1,
|
||||
uint32_t id_v2,
|
||||
const SymMat & q,
|
||||
const Vertices &vertices)
|
||||
{
|
||||
double det = calculate_determinant(q);
|
||||
if (std::abs(det) < std::numeric_limits<double>::epsilon()) {
|
||||
// can't divide by zero
|
||||
auto verts = create_vertices(id_v1, id_v2, vertices);
|
||||
auto errors = vertices_error(q, verts);
|
||||
auto mit = std::min_element(std::begin(errors), std::end(errors));
|
||||
return verts[mit - std::begin(errors)].cast<float>();
|
||||
}
|
||||
return calculate_vertex(det, q).cast<float>();
|
||||
}
|
||||
|
||||
double QuadricEdgeCollapse::vertex_error(const SymMat &q, const Vec3d &vertex)
|
||||
{
|
||||
const double &x = vertex.x(), &y = vertex.y(), &z = vertex.z();
|
||||
return q[0] * x * x + 2 * q[1] * x * y + 2 * q[2] * x * z +
|
||||
2 * q[3] * x + q[4] * y * y + 2 * q[5] * y * z +
|
||||
2 * q[6] * y + q[7] * z * z + 2 * q[8] * z + q[9];
|
||||
}
|
||||
|
||||
SymMat QuadricEdgeCollapse::create_quadric(const Triangle &t,
|
||||
const Vec3d & n,
|
||||
const Vertices &vertices)
|
||||
{
|
||||
Vec3d v0 = vertices[t[0]].cast<double>();
|
||||
return SymMat(n.x(), n.y(), n.z(), -n.dot(v0));
|
||||
}
|
||||
|
||||
std::tuple<TriangleInfos, VertexInfos, EdgeInfos, Errors>
|
||||
QuadricEdgeCollapse::init(const indexed_triangle_set &its, ThrowOnCancel& throw_on_cancel, StatusFn& status_fn)
|
||||
{
|
||||
// change speed of progress bargraph
|
||||
const int status_normal_size = 25;
|
||||
const int status_sum_quadric = 25;
|
||||
const int status_set_offsets = 10;
|
||||
const int status_calc_errors = 30;
|
||||
const int status_create_refs = 10;
|
||||
|
||||
int status_offset = 0;
|
||||
TriangleInfos t_infos(its.indices.size());
|
||||
VertexInfos v_infos(its.vertices.size());
|
||||
{
|
||||
std::vector<SymMat> triangle_quadrics(its.indices.size());
|
||||
// calculate normals
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, its.indices.size()),
|
||||
[&](const tbb::blocked_range<size_t> &range) {
|
||||
for (size_t i = range.begin(); i < range.end(); ++i) {
|
||||
const Triangle &t = its.indices[i];
|
||||
TriangleInfo & t_info = t_infos[i];
|
||||
Vec3d normal = create_normal(t, its.vertices);
|
||||
t_info.n = normal.cast<float>();
|
||||
triangle_quadrics[i] = create_quadric(t, normal, its.vertices);
|
||||
if (i % 1000000 == 0) {
|
||||
throw_on_cancel();
|
||||
status_fn(status_offset + (i * status_normal_size) / its.indices.size());
|
||||
}
|
||||
}
|
||||
}); // END parallel for
|
||||
status_offset += status_normal_size;
|
||||
|
||||
// sum quadrics
|
||||
for (size_t i = 0; i < its.indices.size(); i++) {
|
||||
const Triangle &t = its.indices[i];
|
||||
const SymMat & q = triangle_quadrics[i];
|
||||
for (size_t e = 0; e < 3; e++) {
|
||||
VertexInfo &v_info = v_infos[t[e]];
|
||||
v_info.q += q;
|
||||
++v_info.count; // triangle count
|
||||
}
|
||||
if (i % 1000000 == 0) {
|
||||
throw_on_cancel();
|
||||
status_fn(status_offset + (i * status_sum_quadric) / its.indices.size());
|
||||
}
|
||||
}
|
||||
status_offset += status_sum_quadric;
|
||||
} // remove triangle quadrics
|
||||
|
||||
// set offseted starts
|
||||
uint32_t triangle_start = 0;
|
||||
for (VertexInfo &v_info : v_infos) {
|
||||
v_info.start = triangle_start;
|
||||
triangle_start += v_info.count;
|
||||
// set filled vertex to zero
|
||||
v_info.count = 0;
|
||||
}
|
||||
assert(its.indices.size() * 3 == triangle_start);
|
||||
|
||||
status_offset += status_set_offsets;
|
||||
throw_on_cancel();
|
||||
status_fn(status_offset);
|
||||
|
||||
// calc error
|
||||
Errors errors(its.indices.size());
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, its.indices.size()),
|
||||
[&](const tbb::blocked_range<size_t> &range) {
|
||||
for (size_t i = range.begin(); i < range.end(); ++i) {
|
||||
const Triangle &t = its.indices[i];
|
||||
TriangleInfo & t_info = t_infos[i];
|
||||
errors[i] = calculate_error(i, t, its.vertices, v_infos, t_info.min_index);
|
||||
if (i % 1000000 == 0) {
|
||||
throw_on_cancel();
|
||||
status_fn(status_offset + (i * status_calc_errors) / its.indices.size());
|
||||
}
|
||||
if (i % 1000000 == 0) throw_on_cancel();
|
||||
}
|
||||
}); // END parallel for
|
||||
|
||||
status_offset += status_calc_errors;
|
||||
|
||||
// create reference
|
||||
EdgeInfos e_infos(its.indices.size() * 3);
|
||||
for (size_t i = 0; i < its.indices.size(); i++) {
|
||||
const Triangle &t = its.indices[i];
|
||||
for (size_t j = 0; j < 3; ++j) {
|
||||
VertexInfo &v_info = v_infos[t[j]];
|
||||
size_t ei = v_info.start + v_info.count;
|
||||
assert(ei < e_infos.size());
|
||||
EdgeInfo &e_info = e_infos[ei];
|
||||
e_info.t_index = i;
|
||||
e_info.edge = j;
|
||||
++v_info.count;
|
||||
}
|
||||
if (i % 1000000 == 0) {
|
||||
throw_on_cancel();
|
||||
status_fn(status_offset + (i * status_create_refs) / its.indices.size());
|
||||
}
|
||||
}
|
||||
|
||||
throw_on_cancel();
|
||||
status_fn(100);
|
||||
return {t_infos, v_infos, e_infos, errors};
|
||||
}
|
||||
|
||||
std::optional<uint32_t> QuadricEdgeCollapse::find_triangle_index1(uint32_t vi,
|
||||
const VertexInfo &v_info,
|
||||
uint32_t ti0,
|
||||
const EdgeInfos & e_infos,
|
||||
const Indices & indices)
|
||||
{
|
||||
coord_t vi_coord = static_cast<coord_t>(vi);
|
||||
uint32_t end = v_info.start + v_info.count;
|
||||
for (uint32_t ei = v_info.start; ei < end; ++ei) {
|
||||
const EdgeInfo &e_info = e_infos[ei];
|
||||
if (e_info.t_index == ti0) continue;
|
||||
const Triangle& t = indices[e_info.t_index];
|
||||
if (t[(e_info.edge + 1) % 3] == vi_coord ||
|
||||
t[(e_info.edge + 2) % 3] == vi_coord)
|
||||
return e_info.t_index;
|
||||
}
|
||||
// triangle0 is on border and do NOT have twin edge
|
||||
return {};
|
||||
}
|
||||
|
||||
bool QuadricEdgeCollapse::is_flipped(const Vec3f & new_vertex,
|
||||
uint32_t ti0,
|
||||
uint32_t ti1,
|
||||
const VertexInfo & v_info,
|
||||
const TriangleInfos & t_infos,
|
||||
const EdgeInfos & e_infos,
|
||||
const indexed_triangle_set &its)
|
||||
{
|
||||
static const float thr_pos = 1.0f - std::numeric_limits<float>::epsilon();
|
||||
static const float thr_neg = -thr_pos;
|
||||
static const float dot_thr = 0.2f; // Value from simplify mesh cca 80 DEG
|
||||
|
||||
// for each vertex triangles
|
||||
size_t v_info_end = v_info.start + v_info.count;
|
||||
for (size_t ei = v_info.start; ei < v_info_end; ++ei) {
|
||||
assert(ei < e_infos.size());
|
||||
const EdgeInfo &e_info = e_infos[ei];
|
||||
if (e_info.t_index == ti0) continue; // ti0 will be deleted
|
||||
if (e_info.t_index == ti1) continue; // ti1 will be deleted
|
||||
const Triangle &t = its.indices[e_info.t_index];
|
||||
const Vec3f &normal = t_infos[e_info.t_index].n;
|
||||
const Vec3f &vf = its.vertices[t[(e_info.edge + 1) % 3]];
|
||||
const Vec3f &vs = its.vertices[t[(e_info.edge + 2) % 3]];
|
||||
|
||||
Vec3f d1 = vf - new_vertex;
|
||||
d1.normalize();
|
||||
Vec3f d2 = vs - new_vertex;
|
||||
d2.normalize();
|
||||
|
||||
float dot = d1.dot(d2);
|
||||
if (dot > thr_pos || dot < thr_neg) return true;
|
||||
// IMPROVE: propagate new normal
|
||||
Vec3f n = d1.cross(d2);
|
||||
n.normalize();
|
||||
if(n.dot(normal) < dot_thr) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool QuadricEdgeCollapse::degenerate(uint32_t vi,
|
||||
uint32_t ti0,
|
||||
uint32_t ti1,
|
||||
const VertexInfo &v_info,
|
||||
const EdgeInfos & e_infos,
|
||||
const Indices & indices)
|
||||
{
|
||||
// check surround triangle do not contain vertex index
|
||||
// protect from creation of triangle with two same vertices inside
|
||||
size_t v_info_end = v_info.start + v_info.count;
|
||||
for (size_t ei = v_info.start; ei < v_info_end; ++ei) {
|
||||
assert(ei < e_infos.size());
|
||||
const EdgeInfo &e_info = e_infos[ei];
|
||||
if (e_info.t_index == ti0) continue; // ti0 will be deleted
|
||||
if (e_info.t_index == ti1) continue; // ti1 will be deleted
|
||||
const Triangle &t = indices[e_info.t_index];
|
||||
for (size_t i = 0; i < 3; ++i)
|
||||
if (static_cast<uint32_t>(t[i]) == vi) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Vec3d QuadricEdgeCollapse::calculate_3errors(const Triangle & t,
|
||||
const Vertices & vertices,
|
||||
const VertexInfos &v_infos)
|
||||
{
|
||||
Vec3d error;
|
||||
for (size_t j = 0; j < 3; ++j) {
|
||||
size_t j2 = (j == 2) ? 0 : (j + 1);
|
||||
uint32_t vi0 = t[j];
|
||||
uint32_t vi1 = t[j2];
|
||||
SymMat q(v_infos[vi0].q); // copy
|
||||
q += v_infos[vi1].q;
|
||||
error[j] = calculate_error(vi0, vi1, q, vertices);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
Error QuadricEdgeCollapse::calculate_error(uint32_t ti,
|
||||
const Triangle & t,
|
||||
const Vertices & vertices,
|
||||
const VertexInfos &v_infos,
|
||||
unsigned char & min_index)
|
||||
{
|
||||
Vec3d error = calculate_3errors(t, vertices, v_infos);
|
||||
// select min error
|
||||
min_index = (error[0] < error[1]) ? ((error[0] < error[2]) ? 0 : 2) :
|
||||
((error[1] < error[2]) ? 1 : 2);
|
||||
return Error(static_cast<float>(error[min_index]), ti);
|
||||
}
|
||||
|
||||
void QuadricEdgeCollapse::remove_triangle(EdgeInfos & e_infos,
|
||||
VertexInfo &v_info,
|
||||
uint32_t ti)
|
||||
{
|
||||
auto e_info = e_infos.begin() + v_info.start;
|
||||
auto e_info_end = e_info + v_info.count - 1;
|
||||
for (; e_info != e_info_end; ++e_info) {
|
||||
if (e_info->t_index == ti) {
|
||||
*e_info = *e_info_end;
|
||||
--v_info.count;
|
||||
return;
|
||||
}
|
||||
}
|
||||
assert(e_info_end->t_index == ti);
|
||||
// last triangle is ti
|
||||
--v_info.count;
|
||||
}
|
||||
|
||||
void QuadricEdgeCollapse::change_neighbors(EdgeInfos & e_infos,
|
||||
VertexInfos & v_infos,
|
||||
uint32_t ti0,
|
||||
uint32_t ti1,
|
||||
uint32_t vi0,
|
||||
uint32_t vi1,
|
||||
uint32_t vi_top0,
|
||||
const Triangle &t1,
|
||||
CopyEdgeInfos& infos,
|
||||
EdgeInfos & e_infos1)
|
||||
{
|
||||
// have to copy Edge info from higher vertex index into smaller
|
||||
assert(vi0 < vi1);
|
||||
|
||||
// vertex index of triangle 1 which is not vi0 nor vi1
|
||||
uint32_t vi_top1 = t1[0];
|
||||
if (vi_top1 == vi0 || vi_top1 == vi1) {
|
||||
vi_top1 = t1[1];
|
||||
if (vi_top1 == vi0 || vi_top1 == vi1) vi_top1 = t1[2];
|
||||
}
|
||||
|
||||
remove_triangle(e_infos, v_infos[vi_top0], ti0);
|
||||
remove_triangle(e_infos, v_infos[vi_top1], ti1);
|
||||
|
||||
VertexInfo &v_info0 = v_infos[vi0];
|
||||
VertexInfo &v_info1 = v_infos[vi1];
|
||||
|
||||
uint32_t new_triangle_count = v_info0.count + v_info1.count - 4;
|
||||
remove_triangle(e_infos, v_info0, ti0);
|
||||
remove_triangle(e_infos, v_info0, ti1);
|
||||
|
||||
// copy second's edge infos out of e_infos, to free size
|
||||
e_infos1.clear();
|
||||
e_infos1.reserve(v_info1.count - 2);
|
||||
uint32_t v_info_s_end = v_info1.start + v_info1.count;
|
||||
for (uint32_t ei = v_info1.start; ei < v_info_s_end; ++ei) {
|
||||
const EdgeInfo &e_info = e_infos[ei];
|
||||
if (e_info.t_index == ti0) continue;
|
||||
if (e_info.t_index == ti1) continue;
|
||||
e_infos1.emplace_back(e_info);
|
||||
}
|
||||
v_info1.count = 0;
|
||||
|
||||
uint32_t need = (new_triangle_count < v_info0.count)? 0:
|
||||
(new_triangle_count - v_info0.count);
|
||||
|
||||
uint32_t act_vi = vi0 + 1;
|
||||
VertexInfo *act_v_info = &v_infos[act_vi];
|
||||
uint32_t act_start = act_v_info->start;
|
||||
uint32_t last_end = v_info0.start + v_info0.count;
|
||||
|
||||
infos.clear();
|
||||
infos.reserve(need);
|
||||
|
||||
while (true) {
|
||||
uint32_t save = act_start - last_end;
|
||||
if (save > 0) {
|
||||
if (save >= need) break;
|
||||
need -= save;
|
||||
infos.emplace_back(act_v_info->start, act_v_info->count, need);
|
||||
} else {
|
||||
infos.back().count += act_v_info->count;
|
||||
}
|
||||
last_end = act_v_info->start + act_v_info->count;
|
||||
act_v_info->start += need;
|
||||
++act_vi;
|
||||
if (act_vi < v_infos.size()) {
|
||||
act_v_info = &v_infos[act_vi];
|
||||
act_start = act_v_info->start;
|
||||
} else
|
||||
act_start = e_infos.size(); // fix for edge between last two triangles
|
||||
}
|
||||
|
||||
// copy by c_infos
|
||||
for (uint32_t i = infos.size(); i > 0; --i) {
|
||||
const CopyEdgeInfo &c_info = infos[i - 1];
|
||||
for (uint32_t ei = c_info.start + c_info.count - 1; ei >= c_info.start; --ei)
|
||||
e_infos[ei + c_info.move] = e_infos[ei]; // copy
|
||||
}
|
||||
|
||||
// copy triangle from first info into second
|
||||
for (uint32_t ei_s = 0; ei_s < e_infos1.size(); ++ei_s) {
|
||||
uint32_t ei_f = v_info0.start + v_info0.count;
|
||||
e_infos[ei_f] = e_infos1[ei_s]; // copy
|
||||
++v_info0.count;
|
||||
}
|
||||
}
|
||||
|
||||
void QuadricEdgeCollapse::compact(const VertexInfos & v_infos,
|
||||
const TriangleInfos & t_infos,
|
||||
const EdgeInfos & e_infos,
|
||||
indexed_triangle_set &its)
|
||||
{
|
||||
uint32_t vi_new = 0;
|
||||
for (uint32_t vi = 0; vi < v_infos.size(); ++vi) {
|
||||
const VertexInfo &v_info = v_infos[vi];
|
||||
if (v_info.is_deleted()) continue; // deleted
|
||||
uint32_t e_info_end = v_info.start + v_info.count;
|
||||
for (uint32_t ei = v_info.start; ei < e_info_end; ++ei) {
|
||||
const EdgeInfo &e_info = e_infos[ei];
|
||||
// change vertex index
|
||||
its.indices[e_info.t_index][e_info.edge] = vi_new;
|
||||
}
|
||||
// compact vertices
|
||||
its.vertices[vi_new++] = its.vertices[vi];
|
||||
}
|
||||
// remove vertices tail
|
||||
its.vertices.erase(its.vertices.begin() + vi_new, its.vertices.end());
|
||||
|
||||
uint32_t ti_new = 0;
|
||||
for (uint32_t ti = 0; ti < t_infos.size(); ti++) {
|
||||
const TriangleInfo &t_info = t_infos[ti];
|
||||
if (t_info.is_deleted()) continue;
|
||||
its.indices[ti_new++] = its.indices[ti];
|
||||
}
|
||||
its.indices.erase(its.indices.begin() + ti_new, its.indices.end());
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
// store triangle surrounding to file
|
||||
void QuadricEdgeCollapse::store_surround(const char *obj_filename,
|
||||
size_t triangle_index,
|
||||
int depth,
|
||||
const indexed_triangle_set &its,
|
||||
const VertexInfos & v_infos,
|
||||
const EdgeInfos & e_infos)
|
||||
{
|
||||
std::set<size_t> triangles;
|
||||
// triangle index, depth
|
||||
using Item = std::pair<size_t, int>;
|
||||
std::queue<Item> process;
|
||||
process.push({triangle_index, depth});
|
||||
|
||||
while (!process.empty()) {
|
||||
Item item = process.front();
|
||||
process.pop();
|
||||
size_t ti = item.first;
|
||||
auto it = triangles.find(ti);
|
||||
if (it != triangles.end()) continue;
|
||||
triangles.insert(ti);
|
||||
if (item.second == 0) continue;
|
||||
|
||||
const Vec3i &t = its.indices[ti];
|
||||
for (size_t i = 0; i < 3; ++i) {
|
||||
const auto &v_info = v_infos[t[i]];
|
||||
for (size_t d = 0; d < v_info.count; ++d) {
|
||||
size_t ei = v_info.start + d;
|
||||
const auto &e_info = e_infos[ei];
|
||||
auto it = triangles.find(e_info.t_index);
|
||||
if (it != triangles.end()) continue;
|
||||
process.push({e_info.t_index, item.second - 1});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<size_t> trs;
|
||||
trs.reserve(triangles.size());
|
||||
for (size_t ti : triangles) trs.push_back(ti);
|
||||
its_store_triangles(its, obj_filename, trs);
|
||||
// its_write_obj(its,"original.obj");
|
||||
}
|
||||
|
||||
bool QuadricEdgeCollapse::check_neighbors(const indexed_triangle_set &its,
|
||||
const TriangleInfos & t_infos,
|
||||
const VertexInfos & v_infos,
|
||||
const EdgeInfos & e_infos)
|
||||
{
|
||||
VertexInfos v_infos2(v_infos.size());
|
||||
size_t count_indices = 0;
|
||||
|
||||
for (size_t ti = 0; ti < its.indices.size(); ti++) {
|
||||
if (t_infos[ti].is_deleted()) continue;
|
||||
++count_indices;
|
||||
const Triangle &t = its.indices[ti];
|
||||
for (size_t e = 0; e < 3; e++) {
|
||||
VertexInfo &v_info = v_infos2[t[e]];
|
||||
++v_info.count; // triangle count
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t triangle_start = 0;
|
||||
for (VertexInfo &v_info : v_infos2) {
|
||||
v_info.start = triangle_start;
|
||||
triangle_start += v_info.count;
|
||||
// set filled vertex to zero
|
||||
v_info.count = 0;
|
||||
}
|
||||
|
||||
// create reference
|
||||
EdgeInfos e_infos2(count_indices * 3);
|
||||
for (size_t ti = 0; ti < its.indices.size(); ti++) {
|
||||
if (t_infos[ti].is_deleted()) continue;
|
||||
const Triangle &t = its.indices[ti];
|
||||
for (size_t j = 0; j < 3; ++j) {
|
||||
VertexInfo &v_info = v_infos2[t[j]];
|
||||
size_t ei = v_info.start + v_info.count;
|
||||
assert(ei < e_infos2.size());
|
||||
EdgeInfo &e_info = e_infos2[ei];
|
||||
e_info.t_index = ti;
|
||||
e_info.edge = j;
|
||||
++v_info.count;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t vi = 0; vi < its.vertices.size(); vi++) {
|
||||
const VertexInfo &v_info = v_infos[vi];
|
||||
if (v_info.is_deleted()) continue;
|
||||
const VertexInfo &v_info2 = v_infos2[vi];
|
||||
if (v_info.count != v_info2.count) { return false; }
|
||||
EdgeInfos eis;
|
||||
eis.reserve(v_info.count);
|
||||
std::copy(e_infos.begin() + v_info.start,
|
||||
e_infos.begin() + v_info.start + v_info.count,
|
||||
std::back_inserter(eis));
|
||||
auto compare = [](const EdgeInfo &ei1, const EdgeInfo &ei2) {
|
||||
return ei1.t_index < ei2.t_index;
|
||||
};
|
||||
std::sort(eis.begin(), eis.end(), compare);
|
||||
std::sort(e_infos2.begin() + v_info2.start,
|
||||
e_infos2.begin() + v_info2.start + v_info2.count, compare);
|
||||
for (size_t ei = 0; ei < v_info.count; ++ei) {
|
||||
if (eis[ei].t_index != e_infos2[ei + v_info2.start].t_index) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif /* NDEBUG */
|
28
src/libslic3r/QuadricEdgeCollapse.hpp
Normal file
28
src/libslic3r/QuadricEdgeCollapse.hpp
Normal file
|
@ -0,0 +1,28 @@
|
|||
// paper: https://people.eecs.berkeley.edu/~jrs/meshpapers/GarlandHeckbert2.pdf
|
||||
// sum up: https://users.csc.calpoly.edu/~zwood/teaching/csc570/final06/jseeba/
|
||||
// inspiration: https://github.com/sp4cerat/Fast-Quadric-Mesh-Simplification
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include "TriangleMesh.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
/// <summary>
|
||||
/// Simplify mesh by Quadric metric
|
||||
/// </summary>
|
||||
/// <param name="its">IN/OUT triangle mesh to be simplified.</param>
|
||||
/// <param name="triangle_count">Wanted triangle count.</param>
|
||||
/// <param name="max_error">Maximal Quadric for reduce.
|
||||
/// When nullptr then max float is used
|
||||
/// Output: Last used ErrorValue to collapse edge</param>
|
||||
/// <param name="throw_on_cancel">Could stop process of calculation.</param>
|
||||
/// <param name="statusfn">Give a feed back to user about progress. Values 1 - 100</param>
|
||||
void its_quadric_edge_collapse(
|
||||
indexed_triangle_set & its,
|
||||
uint32_t triangle_count = 0,
|
||||
float * max_error = nullptr,
|
||||
std::function<void(void)> throw_on_cancel = nullptr,
|
||||
std::function<void(int)> statusfn = nullptr);
|
||||
|
||||
} // namespace Slic3r
|
|
@ -58,29 +58,6 @@ T sum_score(AccessFn &&accessfn, size_t facecount, size_t Nthreads)
|
|||
return execution::reduce(ex_tbb, from, to, initv, mergefn, accessfn, grainsize);
|
||||
}
|
||||
|
||||
// Try to guess the number of support points needed to support a mesh
|
||||
double get_misalginment_score(const TriangleMesh &mesh, const Transform3f &tr)
|
||||
{
|
||||
if (mesh.its.vertices.empty()) return std::nan("");
|
||||
|
||||
auto accessfn = [&mesh, &tr](size_t fi) {
|
||||
auto triangle = get_transformed_triangle(mesh, tr, fi);
|
||||
Vec3f U = triangle[1] - triangle[0];
|
||||
Vec3f V = triangle[2] - triangle[0];
|
||||
Vec3f C = U.cross(V);
|
||||
|
||||
// We should score against the alignment with the reference planes
|
||||
return scaled<int_fast64_t>(std::abs(C.dot(Vec3f::UnitX())) +
|
||||
std::abs(C.dot(Vec3f::UnitY())));
|
||||
};
|
||||
|
||||
size_t facecount = mesh.its.indices.size();
|
||||
size_t Nthreads = std::thread::hardware_concurrency();
|
||||
double S = unscaled(sum_score<int_fast64_t>(accessfn, facecount, Nthreads));
|
||||
|
||||
return S / facecount;
|
||||
}
|
||||
|
||||
// Get area and normal of a triangle
|
||||
struct Facestats {
|
||||
Vec3f normal;
|
||||
|
@ -96,21 +73,45 @@ struct Facestats {
|
|||
}
|
||||
};
|
||||
|
||||
// Try to guess the number of support points needed to support a mesh
|
||||
double get_misalginment_score(const TriangleMesh &mesh, const Transform3f &tr)
|
||||
{
|
||||
if (mesh.its.vertices.empty()) return std::nan("");
|
||||
|
||||
auto accessfn = [&mesh, &tr](size_t fi) {
|
||||
Facestats fc{get_transformed_triangle(mesh, tr, fi)};
|
||||
|
||||
float score = fc.area
|
||||
* (std::abs(fc.normal.dot(Vec3f::UnitX()))
|
||||
+ std::abs(fc.normal.dot(Vec3f::UnitY()))
|
||||
+ std::abs(fc.normal.dot(Vec3f::UnitZ())));
|
||||
|
||||
// We should score against the alignment with the reference planes
|
||||
return scaled<int_fast64_t>(score);
|
||||
};
|
||||
|
||||
size_t facecount = mesh.its.indices.size();
|
||||
size_t Nthreads = std::thread::hardware_concurrency();
|
||||
double S = unscaled(sum_score<int_fast64_t>(accessfn, facecount, Nthreads));
|
||||
|
||||
return S / facecount;
|
||||
}
|
||||
|
||||
// The score function for a particular face
|
||||
inline double get_supportedness_score(const Facestats &fc)
|
||||
{
|
||||
// Simply get the angle (acos of dot product) between the face normal and
|
||||
// the DOWN vector.
|
||||
float phi = 1. - std::acos(fc.normal.dot(DOWN)) / float(PI);
|
||||
|
||||
// Only consider faces that have slopes below 90 deg:
|
||||
phi = phi * (phi >= 0.5f);
|
||||
float cosphi = fc.normal.dot(DOWN);
|
||||
float phi = 1.f - std::acos(cosphi) / float(PI);
|
||||
|
||||
// Make the huge slopes more significant than the smaller slopes
|
||||
phi = phi * phi * phi;
|
||||
|
||||
// Multiply with the area of the current face
|
||||
return fc.area * POINTS_PER_UNIT_AREA * phi;
|
||||
// Multiply with the square root of face area of the current face,
|
||||
// the area is less important as it grows.
|
||||
// This makes many smaller overhangs a bigger impact.
|
||||
return std::sqrt(fc.area) * POINTS_PER_UNIT_AREA * phi;
|
||||
}
|
||||
|
||||
// Try to guess the number of support points needed to support a mesh
|
||||
|
@ -120,8 +121,7 @@ double get_supportedness_score(const TriangleMesh &mesh, const Transform3f &tr)
|
|||
|
||||
auto accessfn = [&mesh, &tr](size_t fi) {
|
||||
Facestats fc{get_transformed_triangle(mesh, tr, fi)};
|
||||
|
||||
return get_supportedness_score(fc);
|
||||
return scaled<int_fast64_t>(get_supportedness_score(fc));
|
||||
};
|
||||
|
||||
size_t facecount = mesh.its.indices.size();
|
||||
|
@ -164,7 +164,7 @@ float get_supportedness_onfloor_score(const TriangleMesh &mesh,
|
|||
Facestats fc{tri};
|
||||
|
||||
if (tri[0].z() <= zlvl && tri[1].z() <= zlvl && tri[2].z() <= zlvl)
|
||||
return -fc.area * POINTS_PER_UNIT_AREA;
|
||||
return -2 * fc.area * POINTS_PER_UNIT_AREA;
|
||||
|
||||
return get_supportedness_score(fc);
|
||||
};
|
||||
|
@ -283,95 +283,81 @@ std::array<double, N> find_min_score(Fn &&fn, It from, It to, StopCond &&stopfn)
|
|||
|
||||
} // namespace
|
||||
|
||||
|
||||
|
||||
template<unsigned MAX_ITER>
|
||||
struct RotfinderBoilerplate {
|
||||
static constexpr unsigned MAX_TRIES = MAX_ITER;
|
||||
|
||||
int status = 0;
|
||||
TriangleMesh mesh;
|
||||
unsigned max_tries;
|
||||
const RotOptimizeParams ¶ms;
|
||||
|
||||
// Assemble the mesh with the correct transformation to be used in rotation
|
||||
// optimization.
|
||||
static TriangleMesh get_mesh_to_rotate(const ModelObject &mo)
|
||||
{
|
||||
TriangleMesh mesh = mo.raw_mesh();
|
||||
mesh.require_shared_vertices();
|
||||
|
||||
ModelInstance *mi = mo.instances[0];
|
||||
auto rotation = Vec3d::Zero();
|
||||
auto offset = Vec3d::Zero();
|
||||
Transform3d trafo_instance =
|
||||
Geometry::assemble_transform(offset, rotation,
|
||||
mi->get_scaling_factor(),
|
||||
mi->get_mirror());
|
||||
|
||||
mesh.transform(trafo_instance);
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
RotfinderBoilerplate(const ModelObject &mo, const RotOptimizeParams &p)
|
||||
: mesh{get_mesh_to_rotate(mo)}
|
||||
, params{p}
|
||||
, max_tries(p.accuracy() * MAX_TRIES)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void statusfn() { params.statuscb()(++status * 100.0 / max_tries); }
|
||||
bool stopcond() { return ! params.statuscb()(-1); }
|
||||
};
|
||||
|
||||
Vec2d find_best_misalignment_rotation(const ModelObject & mo,
|
||||
const RotOptimizeParams ¶ms)
|
||||
{
|
||||
static constexpr unsigned MAX_TRIES = 1000;
|
||||
|
||||
// return value
|
||||
XYRotation rot;
|
||||
|
||||
// We will use only one instance of this converted mesh to examine different
|
||||
// rotations
|
||||
TriangleMesh mesh = mo.raw_mesh();
|
||||
mesh.require_shared_vertices();
|
||||
|
||||
// To keep track of the number of iterations
|
||||
int status = 0;
|
||||
|
||||
// The maximum number of iterations
|
||||
auto max_tries = unsigned(params.accuracy() * MAX_TRIES);
|
||||
|
||||
auto &statuscb = params.statuscb();
|
||||
|
||||
// call status callback with zero, because we are at the start
|
||||
statuscb(status);
|
||||
|
||||
auto statusfn = [&statuscb, &status, &max_tries] {
|
||||
// report status
|
||||
statuscb(++status * 100.0/max_tries);
|
||||
};
|
||||
|
||||
auto stopcond = [&statuscb] {
|
||||
return ! statuscb(-1);
|
||||
};
|
||||
RotfinderBoilerplate<1000> bp{mo, params};
|
||||
|
||||
// Preparing the optimizer.
|
||||
size_t gridsize = std::sqrt(max_tries);
|
||||
opt::Optimizer<opt::AlgBruteForce> solver(opt::StopCriteria{}
|
||||
.max_iterations(max_tries)
|
||||
.stop_condition(stopcond),
|
||||
gridsize);
|
||||
size_t gridsize = std::sqrt(bp.max_tries);
|
||||
opt::Optimizer<opt::AlgBruteForce> solver(
|
||||
opt::StopCriteria{}.max_iterations(bp.max_tries)
|
||||
.stop_condition([&bp] { return bp.stopcond(); }),
|
||||
gridsize
|
||||
);
|
||||
|
||||
// We are searching rotations around only two axes x, y. Thus the
|
||||
// problem becomes a 2 dimensional optimization task.
|
||||
// We can specify the bounds for a dimension in the following way:
|
||||
auto bounds = opt::bounds({ {-PI/2, PI/2}, {-PI/2, PI/2} });
|
||||
auto bounds = opt::bounds({ {-PI, PI}, {-PI, PI} });
|
||||
|
||||
auto result = solver.to_max().optimize(
|
||||
[&mesh, &statusfn] (const XYRotation &rot)
|
||||
[&bp] (const XYRotation &rot)
|
||||
{
|
||||
statusfn();
|
||||
return get_misalginment_score(mesh, to_transform3f(rot));
|
||||
bp.statusfn();
|
||||
return get_misalginment_score(bp.mesh, to_transform3f(rot));
|
||||
}, opt::initvals({0., 0.}), bounds);
|
||||
|
||||
rot = result.optimum;
|
||||
|
||||
return {rot[0], rot[1]};
|
||||
return {result.optimum[0], result.optimum[1]};
|
||||
}
|
||||
|
||||
Vec2d find_least_supports_rotation(const ModelObject & mo,
|
||||
const RotOptimizeParams ¶ms)
|
||||
{
|
||||
static const unsigned MAX_TRIES = 1000;
|
||||
|
||||
// return value
|
||||
XYRotation rot;
|
||||
|
||||
// We will use only one instance of this converted mesh to examine different
|
||||
// rotations
|
||||
TriangleMesh mesh = mo.raw_mesh();
|
||||
mesh.require_shared_vertices();
|
||||
|
||||
// To keep track of the number of iterations
|
||||
unsigned status = 0;
|
||||
|
||||
// The maximum number of iterations
|
||||
auto max_tries = unsigned(params.accuracy() * MAX_TRIES);
|
||||
|
||||
auto &statuscb = params.statuscb();
|
||||
|
||||
// call status callback with zero, because we are at the start
|
||||
statuscb(status);
|
||||
|
||||
auto statusfn = [&statuscb, &status, &max_tries] {
|
||||
// report status
|
||||
statuscb(unsigned(++status * 100.0/max_tries) );
|
||||
};
|
||||
|
||||
auto stopcond = [&statuscb] {
|
||||
return ! statuscb(-1);
|
||||
};
|
||||
RotfinderBoilerplate<1000> bp{mo, params};
|
||||
|
||||
SLAPrintObjectConfig pocfg;
|
||||
if (params.print_config())
|
||||
|
@ -379,31 +365,35 @@ Vec2d find_least_supports_rotation(const ModelObject & mo,
|
|||
|
||||
pocfg.apply(mo.config.get());
|
||||
|
||||
XYRotation rot;
|
||||
|
||||
// Different search methods have to be used depending on the model elevation
|
||||
if (is_on_floor(pocfg)) {
|
||||
|
||||
std::vector<XYRotation> inputs = get_chull_rotations(mesh, max_tries);
|
||||
max_tries = inputs.size();
|
||||
std::vector<XYRotation> inputs = get_chull_rotations(bp.mesh, bp.max_tries);
|
||||
bp.max_tries = inputs.size();
|
||||
|
||||
// If the model can be placed on the bed directly, we only need to
|
||||
// check the 3D convex hull face rotations.
|
||||
|
||||
auto objfn = [&mesh, &statusfn](const XYRotation &rot) {
|
||||
statusfn();
|
||||
auto objfn = [&bp](const XYRotation &rot) {
|
||||
bp.statusfn();
|
||||
Transform3f tr = to_transform3f(rot);
|
||||
return get_supportedness_onfloor_score(mesh, tr);
|
||||
return get_supportedness_onfloor_score(bp.mesh, tr);
|
||||
};
|
||||
|
||||
rot = find_min_score<2>(objfn, inputs.begin(), inputs.end(), stopcond);
|
||||
rot = find_min_score<2>(objfn, inputs.begin(), inputs.end(), [&bp] {
|
||||
return bp.stopcond();
|
||||
});
|
||||
|
||||
} else {
|
||||
|
||||
// Preparing the optimizer.
|
||||
size_t gridsize = std::sqrt(max_tries); // 2D grid has gridsize^2 calls
|
||||
opt::Optimizer<opt::AlgBruteForce> solver(opt::StopCriteria{}
|
||||
.max_iterations(max_tries)
|
||||
.stop_condition(stopcond),
|
||||
gridsize);
|
||||
size_t gridsize = std::sqrt(bp.max_tries); // 2D grid has gridsize^2 calls
|
||||
opt::Optimizer<opt::AlgBruteForce> solver(
|
||||
opt::StopCriteria{}.max_iterations(bp.max_tries)
|
||||
.stop_condition([&bp] { return bp.stopcond(); }),
|
||||
gridsize
|
||||
);
|
||||
|
||||
// We are searching rotations around only two axes x, y. Thus the
|
||||
// problem becomes a 2 dimensional optimization task.
|
||||
|
@ -411,10 +401,10 @@ Vec2d find_least_supports_rotation(const ModelObject & mo,
|
|||
auto bounds = opt::bounds({ {-PI, PI}, {-PI, PI} });
|
||||
|
||||
auto result = solver.to_min().optimize(
|
||||
[&mesh, &statusfn] (const XYRotation &rot)
|
||||
[&bp] (const XYRotation &rot)
|
||||
{
|
||||
statusfn();
|
||||
return get_supportedness_score(mesh, to_transform3f(rot));
|
||||
bp.statusfn();
|
||||
return get_supportedness_score(bp.mesh, to_transform3f(rot));
|
||||
}, opt::initvals({0., 0.}), bounds);
|
||||
|
||||
// Save the result
|
||||
|
@ -424,4 +414,66 @@ Vec2d find_least_supports_rotation(const ModelObject & mo,
|
|||
return {rot[0], rot[1]};
|
||||
}
|
||||
|
||||
inline BoundingBoxf3 bounding_box_with_tr(const indexed_triangle_set &its,
|
||||
const Transform3f &tr)
|
||||
{
|
||||
if (its.vertices.empty())
|
||||
return {};
|
||||
|
||||
Vec3f bmin = tr * its.vertices.front(), bmax = tr * its.vertices.front();
|
||||
|
||||
for (const Vec3f &p : its.vertices) {
|
||||
Vec3f pp = tr * p;
|
||||
bmin = pp.cwiseMin(bmin);
|
||||
bmax = pp.cwiseMax(bmax);
|
||||
}
|
||||
|
||||
return {bmin.cast<double>(), bmax.cast<double>()};
|
||||
}
|
||||
|
||||
Vec2d find_min_z_height_rotation(const ModelObject &mo,
|
||||
const RotOptimizeParams ¶ms)
|
||||
{
|
||||
RotfinderBoilerplate<1000> bp{mo, params};
|
||||
|
||||
TriangleMesh chull = bp.mesh.convex_hull_3d();
|
||||
chull.require_shared_vertices();
|
||||
auto inputs = reserve_vector<XYRotation>(chull.its.indices.size());
|
||||
auto rotcmp = [](const XYRotation &r1, const XYRotation &r2) {
|
||||
double xdiff = r1[X] - r2[X], ydiff = r1[Y] - r2[Y];
|
||||
return std::abs(xdiff) < EPSILON ? ydiff < 0. : xdiff < 0.;
|
||||
};
|
||||
auto eqcmp = [](const XYRotation &r1, const XYRotation &r2) {
|
||||
double xdiff = r1[X] - r2[X], ydiff = r1[Y] - r2[Y];
|
||||
return std::abs(xdiff) < EPSILON && std::abs(ydiff) < EPSILON;
|
||||
};
|
||||
|
||||
for (size_t fi = 0; fi < chull.its.indices.size(); ++fi) {
|
||||
Facestats fc{get_triangle_vertices(chull, fi)};
|
||||
|
||||
auto q = Eigen::Quaternionf{}.FromTwoVectors(fc.normal, DOWN);
|
||||
XYRotation rot = from_transform3f(Transform3f::Identity() * q);
|
||||
|
||||
auto it = std::lower_bound(inputs.begin(), inputs.end(), rot, rotcmp);
|
||||
|
||||
if (it == inputs.end() || !eqcmp(*it, rot))
|
||||
inputs.insert(it, rot);
|
||||
}
|
||||
|
||||
inputs.shrink_to_fit();
|
||||
bp.max_tries = inputs.size();
|
||||
|
||||
auto objfn = [&bp, &chull](const XYRotation &rot) {
|
||||
bp.statusfn();
|
||||
Transform3f tr = to_transform3f(rot);
|
||||
return bounding_box_with_tr(chull.its, tr).size().z();
|
||||
};
|
||||
|
||||
XYRotation rot = find_min_score<2>(objfn, inputs.begin(), inputs.end(), [&bp] {
|
||||
return bp.stopcond();
|
||||
});
|
||||
|
||||
return {rot[0], rot[1]};
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::sla
|
||||
|
|
|
@ -63,7 +63,8 @@ Vec2d find_best_misalignment_rotation(const ModelObject &modelobj,
|
|||
Vec2d find_least_supports_rotation(const ModelObject &modelobj,
|
||||
const RotOptimizeParams & = {});
|
||||
|
||||
double find_Z_fit_to_bed_rotation(const ModelObject &mo, const BoundingBox &bed);
|
||||
Vec2d find_min_z_height_rotation(const ModelObject &mo,
|
||||
const RotOptimizeParams ¶ms = {});
|
||||
|
||||
} // namespace sla
|
||||
} // namespace Slic3r
|
||||
|
|
|
@ -519,7 +519,7 @@ bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &hjp,
|
|||
auto [polar, azimuth] = dir_to_spheric(dir);
|
||||
polar = PI - m_cfg.bridge_slope;
|
||||
Vec3d d = spheric_to_dir(polar, azimuth).normalized();
|
||||
double t = bridge_mesh_distance(endp, dir, radius);
|
||||
double t = bridge_mesh_distance(endp, d, radius);
|
||||
double tmax = std::min(m_cfg.max_bridge_length_mm, t);
|
||||
t = 0.;
|
||||
|
||||
|
|
|
@ -25,8 +25,17 @@ public:
|
|||
Semver() : ver(semver_zero()) {}
|
||||
|
||||
Semver(int major, int minor, int patch,
|
||||
boost::optional<const std::string&> metadata = boost::none,
|
||||
boost::optional<const std::string&> prerelease = boost::none)
|
||||
boost::optional<const std::string&> metadata, boost::optional<const std::string&> prerelease)
|
||||
: ver(semver_zero())
|
||||
{
|
||||
ver.major = major;
|
||||
ver.minor = minor;
|
||||
ver.patch = patch;
|
||||
set_metadata(metadata);
|
||||
set_prerelease(prerelease);
|
||||
}
|
||||
|
||||
Semver(int major, int minor, int patch, const char *metadata = nullptr, const char *prerelease = nullptr)
|
||||
: ver(semver_zero())
|
||||
{
|
||||
ver.major = major;
|
||||
|
@ -102,7 +111,9 @@ public:
|
|||
void set_min(int min) { ver.minor = min; }
|
||||
void set_patch(int patch) { ver.patch = patch; }
|
||||
void set_metadata(boost::optional<const std::string&> meta) { ver.metadata = meta ? strdup(*meta) : nullptr; }
|
||||
void set_metadata(const char *meta) { ver.metadata = meta ? strdup(meta) : nullptr; }
|
||||
void set_prerelease(boost::optional<const std::string&> pre) { ver.prerelease = pre ? strdup(*pre) : nullptr; }
|
||||
void set_prerelease(const char *pre) { ver.prerelease = pre ? strdup(pre) : nullptr; }
|
||||
|
||||
// Comparison
|
||||
bool operator<(const Semver &b) const { return ::semver_compare(ver, b.ver) == -1; }
|
||||
|
|
|
@ -107,7 +107,7 @@ public:
|
|||
// Determinant
|
||||
T det(int a11, int a12, int a13,
|
||||
int a21, int a22, int a23,
|
||||
int a31, int a32, int a33)
|
||||
int a31, int a32, int a33) const
|
||||
{
|
||||
T det = m[a11] * m[a22] * m[a33] + m[a13] * m[a21] * m[a32] +
|
||||
m[a12] * m[a23] * m[a31] - m[a13] * m[a22] * m[a31] -
|
||||
|
@ -121,7 +121,7 @@ public:
|
|||
for (size_t i = 0; i < N; ++i) m[i] += n[i];
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
SymetricMatrix operator+(const SymetricMatrix& n)
|
||||
{
|
||||
SymetricMatrix self = *this;
|
||||
|
|
|
@ -47,6 +47,8 @@
|
|||
#define ENABLE_FIX_IMPORTING_COLOR_PRINT_VIEW_INTO_GCODEVIEWER (1 && ENABLE_2_4_0_ALPHA0)
|
||||
// Enable drawing contours, at cut level, for sinking volumes
|
||||
#define ENABLE_SINKING_CONTOURS (1 && ENABLE_2_4_0_ALPHA0)
|
||||
// Enable implementation of retract acceleration in gcode processor
|
||||
#define ENABLE_RETRACT_ACCELERATION (1 && ENABLE_2_4_0_ALPHA0)
|
||||
// Enable rendering seams (and other options) in preview using models
|
||||
#define ENABLE_SEAMS_USING_MODELS (1 && ENABLE_2_4_0_ALPHA0)
|
||||
// Enable rendering seams (and other options) in preview using instanced models
|
||||
|
|
|
@ -957,6 +957,48 @@ int its_compactify_vertices(indexed_triangle_set &its, bool shrink_to_fit)
|
|||
return removed;
|
||||
}
|
||||
|
||||
bool its_store_triangle(const indexed_triangle_set &its,
|
||||
const char * obj_filename,
|
||||
size_t triangle_index)
|
||||
{
|
||||
if (its.indices.size() <= triangle_index) return false;
|
||||
Vec3i t = its.indices[triangle_index];
|
||||
indexed_triangle_set its2;
|
||||
its2.indices = {{0, 1, 2}};
|
||||
its2.vertices = {its.vertices[t[0]], its.vertices[t[1]],
|
||||
its.vertices[t[2]]};
|
||||
return its_write_obj(its2, obj_filename);
|
||||
}
|
||||
|
||||
bool its_store_triangles(const indexed_triangle_set &its,
|
||||
const char * obj_filename,
|
||||
const std::vector<size_t> & triangles)
|
||||
{
|
||||
indexed_triangle_set its2;
|
||||
its2.vertices.reserve(triangles.size() * 3);
|
||||
its2.indices.reserve(triangles.size());
|
||||
std::map<size_t, size_t> vertex_map;
|
||||
for (auto ti : triangles) {
|
||||
if (its.indices.size() <= ti) return false;
|
||||
Vec3i t = its.indices[ti];
|
||||
Vec3i new_t;
|
||||
for (size_t i = 0; i < 3; ++i) {
|
||||
size_t vi = t[i];
|
||||
auto it = vertex_map.find(vi);
|
||||
if (it != vertex_map.end()) {
|
||||
new_t[i] = it->second;
|
||||
continue;
|
||||
}
|
||||
size_t new_vi = its2.vertices.size();
|
||||
its2.vertices.push_back(its.vertices[vi]);
|
||||
vertex_map[vi] = new_vi;
|
||||
new_t[i] = new_vi;
|
||||
}
|
||||
its2.indices.push_back(new_t);
|
||||
}
|
||||
return its_write_obj(its2, obj_filename);
|
||||
}
|
||||
|
||||
void its_shrink_to_fit(indexed_triangle_set &its)
|
||||
{
|
||||
its.indices.shrink_to_fit();
|
||||
|
|
|
@ -140,6 +140,10 @@ int its_remove_degenerate_faces(indexed_triangle_set &its, bool shrink_to_fit =
|
|||
// Remove vertices, which none of the faces references. Return number of freed vertices.
|
||||
int its_compactify_vertices(indexed_triangle_set &its, bool shrink_to_fit = true);
|
||||
|
||||
// store part of index triangle set
|
||||
bool its_store_triangle(const indexed_triangle_set &its, const char *obj_filename, size_t triangle_index);
|
||||
bool its_store_triangles(const indexed_triangle_set &its, const char *obj_filename, const std::vector<size_t>& triangles);
|
||||
|
||||
std::vector<indexed_triangle_set> its_split(const indexed_triangle_set &its);
|
||||
|
||||
bool its_is_splittable(const indexed_triangle_set &its);
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#include <boost/container/small_vector.hpp>
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define EXPENSIVE_DEBUG_CHECKS
|
||||
// #define EXPENSIVE_DEBUG_CHECKS
|
||||
#endif // NDEBUG
|
||||
|
||||
namespace Slic3r {
|
||||
|
@ -178,12 +178,12 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start,
|
|||
}
|
||||
}
|
||||
|
||||
void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_start, float seed_fill_angle)
|
||||
void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_start, float seed_fill_angle, bool force_reselection)
|
||||
{
|
||||
assert(facet_start < m_orig_size_indices);
|
||||
|
||||
// Recompute seed fill only if the cursor is pointing on facet unselected by seed fill.
|
||||
if (int start_facet_idx = select_unsplit_triangle(hit, facet_start); start_facet_idx >= 0 && m_triangles[start_facet_idx].is_selected_by_seed_fill())
|
||||
if (int start_facet_idx = select_unsplit_triangle(hit, facet_start); start_facet_idx >= 0 && m_triangles[start_facet_idx].is_selected_by_seed_fill() && !force_reselection)
|
||||
return;
|
||||
|
||||
this->seed_fill_unselect_all_triangles();
|
||||
|
@ -278,7 +278,7 @@ void TriangleSelector::append_touching_subtriangles(int itriangle, int vertexi,
|
|||
return;
|
||||
|
||||
auto process_subtriangle = [this, &itriangle, &vertexi, &vertexj, &touching_subtriangles_out](const int subtriangle_idx, Partition partition) -> void {
|
||||
assert(subtriangle_idx == -1);
|
||||
assert(subtriangle_idx != -1);
|
||||
if (!m_triangles[subtriangle_idx].is_split())
|
||||
touching_subtriangles_out.emplace_back(subtriangle_idx);
|
||||
else if (int midpoint = this->triangle_midpoint(itriangle, vertexi, vertexj); midpoint != -1)
|
||||
|
@ -295,11 +295,48 @@ void TriangleSelector::append_touching_subtriangles(int itriangle, int vertexi,
|
|||
process_subtriangle(touching.second, Partition::Second);
|
||||
}
|
||||
|
||||
void TriangleSelector::bucket_fill_select_triangles(const Vec3f& hit, int facet_start, bool propagate)
|
||||
// It appends all edges that are touching the edge (vertexi, vertexj) of the triangle and are not selected by seed fill
|
||||
// It doesn't append the edges that are touching the triangle only by part of the edge that means the triangles are from lower depth.
|
||||
void TriangleSelector::append_touching_edges(int itriangle, int vertexi, int vertexj, std::vector<Vec2i> &touching_edges_out) const
|
||||
{
|
||||
if (itriangle == -1)
|
||||
return;
|
||||
|
||||
auto process_subtriangle = [this, &itriangle, &vertexi, &vertexj, &touching_edges_out](const int subtriangle_idx, Partition partition) -> void {
|
||||
assert(subtriangle_idx != -1);
|
||||
if (!m_triangles[subtriangle_idx].is_split()) {
|
||||
if (!m_triangles[subtriangle_idx].is_selected_by_seed_fill()) {
|
||||
int midpoint = this->triangle_midpoint(itriangle, vertexi, vertexj);
|
||||
if (partition == Partition::First && midpoint != -1) {
|
||||
touching_edges_out.emplace_back(vertexi, midpoint);
|
||||
} else if (partition == Partition::First && midpoint == -1) {
|
||||
touching_edges_out.emplace_back(vertexi, vertexj);
|
||||
} else {
|
||||
assert(midpoint != -1 && partition == Partition::Second);
|
||||
touching_edges_out.emplace_back(midpoint, vertexj);
|
||||
}
|
||||
}
|
||||
} else if (int midpoint = this->triangle_midpoint(itriangle, vertexi, vertexj); midpoint != -1)
|
||||
append_touching_edges(subtriangle_idx, partition == Partition::First ? vertexi : midpoint, partition == Partition::First ? midpoint : vertexj,
|
||||
touching_edges_out);
|
||||
else
|
||||
append_touching_edges(subtriangle_idx, vertexi, vertexj, touching_edges_out);
|
||||
};
|
||||
|
||||
std::pair<int, int> touching = this->triangle_subtriangles(itriangle, vertexi, vertexj);
|
||||
if (touching.first != -1)
|
||||
process_subtriangle(touching.first, Partition::First);
|
||||
|
||||
if (touching.second != -1)
|
||||
process_subtriangle(touching.second, Partition::Second);
|
||||
}
|
||||
|
||||
void TriangleSelector::bucket_fill_select_triangles(const Vec3f& hit, int facet_start, bool propagate, bool force_reselection)
|
||||
{
|
||||
int start_facet_idx = select_unsplit_triangle(hit, facet_start);
|
||||
assert(start_facet_idx != -1);
|
||||
// Recompute bucket fill only if the cursor is pointing on facet unselected by bucket fill.
|
||||
if (start_facet_idx == -1 || m_triangles[start_facet_idx].is_selected_by_seed_fill())
|
||||
if (start_facet_idx == -1 || (m_triangles[start_facet_idx].is_selected_by_seed_fill() && !force_reselection))
|
||||
return;
|
||||
|
||||
assert(!m_triangles[start_facet_idx].is_split());
|
||||
|
@ -312,7 +349,7 @@ void TriangleSelector::bucket_fill_select_triangles(const Vec3f& hit, int facet_
|
|||
}
|
||||
|
||||
auto get_all_touching_triangles = [this](int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated) -> std::vector<int> {
|
||||
assert(facet_idx != -1 && facet_idx < m_triangles.size());
|
||||
assert(facet_idx != -1 && facet_idx < int(m_triangles.size()));
|
||||
assert(this->verify_triangle_neighbors(m_triangles[facet_idx], neighbors));
|
||||
std::vector<int> touching_triangles;
|
||||
Vec3i vertices = {m_triangles[facet_idx].verts_idxs[0], m_triangles[facet_idx].verts_idxs[1], m_triangles[facet_idx].verts_idxs[2]};
|
||||
|
@ -1358,6 +1395,48 @@ void TriangleSelector::get_facets_split_by_tjoints(const Vec3i &vertices, const
|
|||
}
|
||||
}
|
||||
|
||||
std::vector<Vec2i> TriangleSelector::get_seed_fill_contour() const {
|
||||
std::vector<Vec2i> edges_out;
|
||||
for (int facet_idx = 0; facet_idx < this->m_orig_size_indices; ++facet_idx) {
|
||||
const Vec3i neighbors = root_neighbors(*m_mesh, facet_idx);
|
||||
assert(this->verify_triangle_neighbors(m_triangles[facet_idx], neighbors));
|
||||
this->get_seed_fill_contour_recursive(facet_idx, neighbors, neighbors, edges_out);
|
||||
}
|
||||
|
||||
return edges_out;
|
||||
}
|
||||
|
||||
void TriangleSelector::get_seed_fill_contour_recursive(const int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated, std::vector<Vec2i> &edges_out) const {
|
||||
assert(facet_idx != -1 && facet_idx < int(m_triangles.size()));
|
||||
assert(this->verify_triangle_neighbors(m_triangles[facet_idx], neighbors));
|
||||
const Triangle *tr = &m_triangles[facet_idx];
|
||||
if (!tr->valid())
|
||||
return;
|
||||
|
||||
if (tr->is_split()) {
|
||||
int num_of_children = tr->number_of_split_sides() + 1;
|
||||
if (num_of_children != 1) {
|
||||
for (int i = 0; i < num_of_children; ++i) {
|
||||
assert(i < int(tr->children.size()));
|
||||
assert(tr->children[i] < int(m_triangles.size()));
|
||||
// Recursion, deep first search over the children of this triangle.
|
||||
// All children of this triangle were created by splitting a single source triangle of the original mesh.
|
||||
this->get_seed_fill_contour_recursive(tr->children[i], this->child_neighbors(*tr, neighbors, i), this->child_neighbors_propagated(*tr, neighbors_propagated, i), edges_out);
|
||||
}
|
||||
}
|
||||
} else if (tr->is_selected_by_seed_fill()) {
|
||||
Vec3i vertices = {m_triangles[facet_idx].verts_idxs[0], m_triangles[facet_idx].verts_idxs[1], m_triangles[facet_idx].verts_idxs[2]};
|
||||
append_touching_edges(neighbors(0), vertices(1), vertices(0), edges_out);
|
||||
append_touching_edges(neighbors(1), vertices(2), vertices(1), edges_out);
|
||||
append_touching_edges(neighbors(2), vertices(0), vertices(2), edges_out);
|
||||
|
||||
// It appends the edges that are touching the triangle only by part of the edge that means the triangles are from lower depth.
|
||||
for (int idx = 0; idx < 3; ++idx)
|
||||
if (int neighbor_tr_idx = neighbors_propagated(idx); neighbor_tr_idx != -1 && !m_triangles[neighbor_tr_idx].is_split() && !m_triangles[neighbor_tr_idx].is_selected_by_seed_fill())
|
||||
edges_out.emplace_back(vertices(idx), vertices(next_idx_modulo(idx, 3)));
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<std::vector<std::pair<int, int>>, std::vector<bool>> TriangleSelector::serialize() const
|
||||
{
|
||||
// Each original triangle of the mesh is assigned a number encoding its state
|
||||
|
|
|
@ -49,11 +49,13 @@ public:
|
|||
|
||||
void seed_fill_select_triangles(const Vec3f &hit, // point where to start
|
||||
int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to
|
||||
float seed_fill_angle); // the maximal angle between two facets to be painted by the same color
|
||||
float seed_fill_angle, // the maximal angle between two facets to be painted by the same color
|
||||
bool force_reselection = false); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle
|
||||
|
||||
void bucket_fill_select_triangles(const Vec3f &hit, // point where to start
|
||||
int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to
|
||||
bool propagate); // if bucket fill is propagated to neighbor faces or if it fills the only facet of the modified mesh that the hit point belongs to.
|
||||
int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to
|
||||
bool propagate, // if bucket fill is propagated to neighbor faces or if it fills the only facet of the modified mesh that the hit point belongs to.
|
||||
bool force_reselection = false); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle
|
||||
|
||||
bool has_facets(EnforcerBlockerType state) const;
|
||||
static bool has_facets(const std::pair<std::vector<std::pair<int, int>>, std::vector<bool>> &data, EnforcerBlockerType test_state);
|
||||
|
@ -62,6 +64,8 @@ public:
|
|||
indexed_triangle_set get_facets(EnforcerBlockerType state) const;
|
||||
// Get facets at a given state. Triangulate T-joints.
|
||||
indexed_triangle_set get_facets_strict(EnforcerBlockerType state) const;
|
||||
// Get edges around the selected area by seed fill.
|
||||
std::vector<Vec2i> get_seed_fill_contour() const;
|
||||
|
||||
// Set facet of the mesh to a given state. Only works for original triangles.
|
||||
void set_facet(int facet_idx, EnforcerBlockerType state);
|
||||
|
@ -221,6 +225,7 @@ private:
|
|||
std::pair<int, int> triangle_subtriangles(int itriangle, int vertexi, int vertexj) const;
|
||||
|
||||
void append_touching_subtriangles(int itriangle, int vertexi, int vertexj, std::vector<int> &touching_subtriangles_out) const;
|
||||
void append_touching_edges(int itriangle, int vertexi, int vertexj, std::vector<Vec2i> &touching_edges_out) const;
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool verify_triangle_neighbors(const Triangle& tr, const Vec3i& neighbors) const;
|
||||
|
@ -234,6 +239,8 @@ private:
|
|||
std::vector<stl_triangle_vertex_indices> &out_triangles) const;
|
||||
void get_facets_split_by_tjoints(const Vec3i &vertices, const Vec3i &neighbors, std::vector<stl_triangle_vertex_indices> &out_triangles) const;
|
||||
|
||||
void get_seed_fill_contour_recursive(int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated, std::vector<Vec2i> &edges_out) const;
|
||||
|
||||
int m_free_triangles_head { -1 };
|
||||
int m_free_vertices_head { -1 };
|
||||
};
|
||||
|
|
|
@ -238,7 +238,7 @@ inline typename CONTAINER_TYPE::value_type& next_value_modulo(typename CONTAINER
|
|||
return container[next_idx_modulo(idx, container.size())];
|
||||
}
|
||||
|
||||
extern std::string xml_escape(std::string text);
|
||||
extern std::string xml_escape(std::string text, bool is_marked = false);
|
||||
|
||||
|
||||
#if defined __GNUC__ && __GNUC__ < 5 && !defined __clang__
|
||||
|
|
|
@ -888,7 +888,7 @@ unsigned get_current_pid()
|
|||
#endif
|
||||
}
|
||||
|
||||
std::string xml_escape(std::string text)
|
||||
std::string xml_escape(std::string text, bool is_marked/* = false*/)
|
||||
{
|
||||
std::string::size_type pos = 0;
|
||||
for (;;)
|
||||
|
@ -903,8 +903,8 @@ std::string xml_escape(std::string text)
|
|||
case '\"': replacement = """; break;
|
||||
case '\'': replacement = "'"; break;
|
||||
case '&': replacement = "&"; break;
|
||||
case '<': replacement = "<"; break;
|
||||
case '>': replacement = ">"; break;
|
||||
case '<': replacement = is_marked ? "<" :"<"; break;
|
||||
case '>': replacement = is_marked ? ">" :">"; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue