SPE-2400: Add an option for setting the gap size between the beginning and the end of closed perimeters.

Negative values extend the loop, causing the endpoints to overlap slightly.
This commit is contained in:
Lukáš Hejl 2024-08-21 13:01:37 +02:00 committed by Lukas Matena
parent 6cc7ff754c
commit 56a2a5973d
9 changed files with 141 additions and 34 deletions

View File

@ -69,6 +69,8 @@ namespace Slic3r {
double value; double value;
bool percent; bool percent;
double get_abs_value(double ratio_over) const { return this->percent ? (ratio_over * this->value / 100) : this->value; }
private: private:
friend class cereal::access; friend class cereal::access;
template<class Archive> void serialize(Archive& ar) { ar(this->value); ar(this->percent); } template<class Archive> void serialize(Archive& ar) { ar(this->value); ar(this->percent); }
@ -2265,17 +2267,18 @@ public:
bool is_scalar() const { return (int(this->type) & int(coVectorType)) == 0; } bool is_scalar() const { return (int(this->type) & int(coVectorType)) == 0; }
template<class Archive> ConfigOption* load_option_from_archive(Archive &archive) const { template<class Archive> ConfigOption* load_option_from_archive(Archive &archive) const {
if (this->nullable) { if (this->nullable) {
switch (this->type) { switch (this->type) {
case coFloat: { auto opt = new ConfigOptionFloatNullable(); archive(*opt); return opt; } case coFloat: { auto opt = new ConfigOptionFloatNullable(); archive(*opt); return opt; }
case coInt: { auto opt = new ConfigOptionIntNullable(); archive(*opt); return opt; } case coInt: { auto opt = new ConfigOptionIntNullable(); archive(*opt); return opt; }
case coFloats: { auto opt = new ConfigOptionFloatsNullable(); archive(*opt); return opt; } case coFloats: { auto opt = new ConfigOptionFloatsNullable(); archive(*opt); return opt; }
case coInts: { auto opt = new ConfigOptionIntsNullable(); archive(*opt); return opt; } case coInts: { auto opt = new ConfigOptionIntsNullable(); archive(*opt); return opt; }
case coPercents: { auto opt = new ConfigOptionPercentsNullable();archive(*opt); return opt; } case coPercents: { auto opt = new ConfigOptionPercentsNullable(); archive(*opt); return opt; }
case coBools: { auto opt = new ConfigOptionBoolsNullable(); archive(*opt); return opt; } case coBools: { auto opt = new ConfigOptionBoolsNullable(); archive(*opt); return opt; }
default: throw ConfigurationError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown nullable option type for option ") + this->opt_key); case coFloatsOrPercents: { auto opt = new ConfigOptionFloatsOrPercentsNullable(); archive(*opt); return opt; }
} default: throw ConfigurationError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown nullable option type for option ") + this->opt_key);
} else { }
} else {
switch (this->type) { switch (this->type) {
case coFloat: { auto opt = new ConfigOptionFloat(); archive(*opt); return opt; } case coFloat: { auto opt = new ConfigOptionFloat(); archive(*opt); return opt; }
case coFloats: { auto opt = new ConfigOptionFloats(); archive(*opt); return opt; } case coFloats: { auto opt = new ConfigOptionFloats(); archive(*opt); return opt; }
@ -2300,16 +2303,17 @@ public:
} }
template<class Archive> ConfigOption* save_option_to_archive(Archive &archive, const ConfigOption *opt) const { template<class Archive> ConfigOption* save_option_to_archive(Archive &archive, const ConfigOption *opt) const {
if (this->nullable) { if (this->nullable) {
switch (this->type) { switch (this->type) {
case coFloat: archive(*static_cast<const ConfigOptionFloatNullable*>(opt)); break; case coFloat: archive(*static_cast<const ConfigOptionFloatNullable*>(opt)); break;
case coInt: archive(*static_cast<const ConfigOptionIntNullable*>(opt)); break; case coInt: archive(*static_cast<const ConfigOptionIntNullable*>(opt)); break;
case coFloats: archive(*static_cast<const ConfigOptionFloatsNullable*>(opt)); break; case coFloats: archive(*static_cast<const ConfigOptionFloatsNullable*>(opt)); break;
case coInts: archive(*static_cast<const ConfigOptionIntsNullable*>(opt)); break; case coInts: archive(*static_cast<const ConfigOptionIntsNullable*>(opt)); break;
case coPercents: archive(*static_cast<const ConfigOptionPercentsNullable*>(opt));break; case coPercents: archive(*static_cast<const ConfigOptionPercentsNullable*>(opt)); break;
case coBools: archive(*static_cast<const ConfigOptionBoolsNullable*>(opt)); break; case coBools: archive(*static_cast<const ConfigOptionBoolsNullable*>(opt)); break;
default: throw ConfigurationError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown nullable option type for option ") + this->opt_key); case coFloatsOrPercents: archive(*static_cast<const ConfigOptionFloatsOrPercentsNullable*>(opt)); break;
} default: throw ConfigurationError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown nullable option type for option ") + this->opt_key);
}
} else { } else {
switch (this->type) { switch (this->type) {
case coFloat: archive(*static_cast<const ConfigOptionFloat*>(opt)); break; case coFloat: archive(*static_cast<const ConfigOptionFloat*>(opt)); break;

View File

@ -2311,6 +2311,17 @@ std::pair<GCode::SmoothPath, std::size_t> split_with_seam(
} }
} // namespace GCode } // namespace GCode
static inline double get_seam_gap_distance_value(const PrintConfig &config, const unsigned extruder_id)
{
const double nozzle_diameter = config.nozzle_diameter.get_at(extruder_id);
const FloatOrPercent seam_gap_distance_override = config.filament_seam_gap_distance.get_at(extruder_id);
if (!std::isnan(seam_gap_distance_override.value)) {
return seam_gap_distance_override.get_abs_value(nozzle_diameter);
}
return config.seam_gap_distance.get_abs_value(nozzle_diameter);
}
using GCode::ExtrusionOrder::InstancePoint; using GCode::ExtrusionOrder::InstancePoint;
struct SmoothPathGenerator struct SmoothPathGenerator
@ -2361,12 +2372,20 @@ struct SmoothPathGenerator
// Clip the path to avoid the extruder to get exactly on the first point of the // Clip the path to avoid the extruder to get exactly on the first point of the
// loop; if polyline was shorter than the clipping distance we'd get a null // loop; if polyline was shorter than the clipping distance we'd get a null
// polyline, so we discard it in that case. // polyline, so we discard it in that case.
const auto nozzle_diameter{config.nozzle_diameter.get_at(extruder_id)}; if (const double extrusion_clipping = get_seam_gap_distance_value(config, extruder_id); enable_loop_clipping && extrusion_clipping > 0.) {
if (enable_loop_clipping) {
clip_end( clip_end(
result, scale_(nozzle_diameter) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER, result,
scaled<double>(extrusion_clipping),
scaled<double>(GCode::ExtrusionOrder::min_gcode_segment_length) scaled<double>(GCode::ExtrusionOrder::min_gcode_segment_length)
); );
} else if (enable_loop_clipping && extrusion_clipping < 0.) {
// Extend the extrusion slightly after the seam.
const double smooth_path_extension_length = -1. * scaled<double>(extrusion_clipping);
const double smooth_path_extension_cut_length = length(result) - smooth_path_extension_length;
GCode::SmoothPath smooth_path_extension = result;
clip_end(smooth_path_extension, smooth_path_extension_cut_length, scaled<double>(GCode::ExtrusionOrder::min_gcode_segment_length));
Slic3r::append(result, smooth_path_extension);
} }
assert(validate_smooth_path(result, !enable_loop_clipping)); assert(validate_smooth_path(result, !enable_loop_clipping));

View File

@ -456,7 +456,8 @@ static std::vector<std::string> s_Preset_print_options {
"top_solid_layers", "top_solid_min_thickness", "bottom_solid_layers", "bottom_solid_min_thickness", "top_solid_layers", "top_solid_min_thickness", "bottom_solid_layers", "bottom_solid_min_thickness",
"ensure_vertical_shell_thickness", "extra_perimeters", "extra_perimeters_on_overhangs", "ensure_vertical_shell_thickness", "extra_perimeters", "extra_perimeters_on_overhangs",
"avoid_crossing_curled_overhangs", "avoid_crossing_perimeters", "thin_walls", "overhangs", "avoid_crossing_curled_overhangs", "avoid_crossing_perimeters", "thin_walls", "overhangs",
"seam_position","staggered_inner_seams", "external_perimeters_first", "fill_density", "fill_pattern", "top_fill_pattern", "bottom_fill_pattern", "seam_position", "staggered_inner_seams", "seam_gap_distance",
"external_perimeters_first", "fill_density", "fill_pattern", "top_fill_pattern", "bottom_fill_pattern",
"scarf_seam_placement", "scarf_seam_only_on_smooth", "scarf_seam_start_height", "scarf_seam_entire_loop", "scarf_seam_length", "scarf_seam_max_segment_length", "scarf_seam_on_inner_perimeters", "scarf_seam_placement", "scarf_seam_only_on_smooth", "scarf_seam_start_height", "scarf_seam_entire_loop", "scarf_seam_length", "scarf_seam_max_segment_length", "scarf_seam_on_inner_perimeters",
"infill_every_layers", /*"infill_only_where_needed",*/ "solid_infill_every_layers", "fill_angle", "bridge_angle", "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", "solid_infill_below_area", "only_retract_when_crossing_perimeters", "infill_first",
@ -512,6 +513,8 @@ static std::vector<std::string> s_Preset_filament_options {
"filament_vendor", "compatible_prints", "compatible_prints_condition", "compatible_printers", "compatible_printers_condition", "inherits", "filament_vendor", "compatible_prints", "compatible_prints_condition", "compatible_printers", "compatible_printers_condition", "inherits",
// Shrinkage compensation // Shrinkage compensation
"filament_shrinkage_compensation_xy", "filament_shrinkage_compensation_z", "filament_shrinkage_compensation_xy", "filament_shrinkage_compensation_z",
// Seams overrides
"filament_seam_gap_distance"
}; };
static std::vector<std::string> s_Preset_machine_limits_options { static std::vector<std::string> s_Preset_machine_limits_options {

View File

@ -120,6 +120,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
"filament_density", "filament_density",
"filament_notes", "filament_notes",
"filament_cost", "filament_cost",
"filament_seam_gap_distance",
"filament_spool_weight", "filament_spool_weight",
"first_layer_acceleration", "first_layer_acceleration",
"first_layer_acceleration_over_raft", "first_layer_acceleration_over_raft",
@ -161,6 +162,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
"retract_restart_extra", "retract_restart_extra",
"retract_restart_extra_toolchange", "retract_restart_extra_toolchange",
"retract_speed", "retract_speed",
"seam_gap_distance",
"single_extruder_multi_material_priming", "single_extruder_multi_material_priming",
"slowdown_below_layer_time", "slowdown_below_layer_time",
"solid_infill_acceleration", "solid_infill_acceleration",

View File

@ -2654,6 +2654,17 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced; def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloats { 0. }); def->set_default_value(new ConfigOptionFloats { 0. });
def = this->add("seam_gap_distance", coFloatOrPercent);
def->label = L("Seam gap distance");
def->tooltip = L("The distance between the endpoints of a closed loop perimeter. "
"Positive values will shorten and interrupt the loop slightly to reduce the seam. "
"Negative values will extend the loop, causing the endpoints to overlap slightly. "
"When percents are used, the distance is derived from the nozzle diameter. "
"Set to zero to disable this feature.");
def->sidetext = L("mm or %");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloatOrPercent{ 15., true });
def = this->add("seam_position", coEnum); def = this->add("seam_position", coEnum);
def->label = L("Seam position"); def->label = L("Seam position");
def->category = L("Layers and Perimeters"); def->category = L("Layers and Perimeters");
@ -3775,9 +3786,9 @@ void PrintConfigDef::init_fff_params()
auto it_opt = options.find(opt_key); auto it_opt = options.find(opt_key);
assert(it_opt != options.end()); assert(it_opt != options.end());
def = this->add_nullable(std::string("filament_") + opt_key, it_opt->second.type); def = this->add_nullable(std::string("filament_") + opt_key, it_opt->second.type);
def->label = it_opt->second.label; def->label = it_opt->second.label;
def->full_label = it_opt->second.full_label; def->full_label = it_opt->second.full_label;
def->tooltip = it_opt->second.tooltip; def->tooltip = it_opt->second.tooltip;
def->sidetext = it_opt->second.sidetext; def->sidetext = it_opt->second.sidetext;
def->mode = it_opt->second.mode; def->mode = it_opt->second.mode;
switch (def->type) { switch (def->type) {
@ -3787,6 +3798,44 @@ void PrintConfigDef::init_fff_params()
default: assert(false); default: assert(false);
} }
} }
// Declare values for filament profile, overriding printer's profile.
for (const char *opt_key : {
// Floats or Percents
"seam_gap_distance"}) {
auto it_opt = options.find(opt_key);
assert(it_opt != options.end());
switch (it_opt->second.type) {
case coFloatOrPercent: {
def = this->add_nullable(std::string("filament_") + opt_key, coFloatsOrPercents);
break;
}
default: {
assert(false);
break;
}
}
def->label = it_opt->second.label;
def->full_label = it_opt->second.full_label;
def->tooltip = it_opt->second.tooltip;
def->sidetext = it_opt->second.sidetext;
def->mode = it_opt->second.mode;
switch (def->type) {
case coFloatsOrPercents: {
const auto &default_value = *static_cast<const ConfigOptionFloatOrPercent *>(it_opt->second.default_value.get());
def->set_default_value(new ConfigOptionFloatsOrPercentsNullable{{default_value.value, default_value.percent}});
break;
}
default: {
assert(false);
break;
}
}
}
} }
void PrintConfigDef::init_extruder_option_keys() void PrintConfigDef::init_extruder_option_keys()

View File

@ -830,6 +830,7 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionFloats, filament_multitool_ramming_flow)) ((ConfigOptionFloats, filament_multitool_ramming_flow))
((ConfigOptionFloats, filament_stamping_loading_speed)) ((ConfigOptionFloats, filament_stamping_loading_speed))
((ConfigOptionFloats, filament_stamping_distance)) ((ConfigOptionFloats, filament_stamping_distance))
((ConfigOptionFloatsOrPercentsNullable, filament_seam_gap_distance))
((ConfigOptionPercents, filament_shrinkage_compensation_xy)) ((ConfigOptionPercents, filament_shrinkage_compensation_xy))
((ConfigOptionPercents, filament_shrinkage_compensation_z)) ((ConfigOptionPercents, filament_shrinkage_compensation_z))
((ConfigOptionBool, gcode_comments)) ((ConfigOptionBool, gcode_comments))
@ -860,6 +861,7 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionFloats, retract_restart_extra)) ((ConfigOptionFloats, retract_restart_extra))
((ConfigOptionFloats, retract_restart_extra_toolchange)) ((ConfigOptionFloats, retract_restart_extra_toolchange))
((ConfigOptionFloats, retract_speed)) ((ConfigOptionFloats, retract_speed))
((ConfigOptionFloatOrPercent, seam_gap_distance))
((ConfigOptionString, start_gcode)) ((ConfigOptionString, start_gcode))
((ConfigOptionStrings, start_filament_gcode)) ((ConfigOptionStrings, start_filament_gcode))
((ConfigOptionBool, single_extruder_multi_material)) ((ConfigOptionBool, single_extruder_multi_material))

View File

@ -331,19 +331,22 @@ void Field::get_value_by_opt_type(wxString& str, const bool check_value/* = true
if ((m_opt.type == coFloatOrPercent || m_opt.type == coFloatsOrPercents) && !str.IsEmpty() && str.Last() != '%') if ((m_opt.type == coFloatOrPercent || m_opt.type == coFloatsOrPercents) && !str.IsEmpty() && str.Last() != '%')
{ {
double val = 0.; double val = 0.;
bool is_na_value = m_opt.nullable && str == na_value();
const char dec_sep = is_decimal_separator_point() ? '.' : ','; const char dec_sep = is_decimal_separator_point() ? '.' : ',';
const char dec_sep_alt = dec_sep == '.' ? ',' : '.'; const char dec_sep_alt = dec_sep == '.' ? ',' : '.';
// Replace the first incorrect separator in decimal number. // Replace the first incorrect separator in decimal number.
if (str.Replace(dec_sep_alt, dec_sep, false) != 0) if (!is_na_value && str.Replace(dec_sep_alt, dec_sep, false) != 0)
set_value(str, false); set_value(str, false);
// remove space and "mm" substring, if any exists // remove space and "mm" substring, if any exists
str.Replace(" ", "", true); str.Replace(" ", "", true);
str.Replace("m", "", true); str.Replace("m", "", true);
if (!str.ToDouble(&val)) if (is_na_value) {
{ val = ConfigOptionFloatsOrPercentsNullable::nil_value().value;
} else if (!str.ToDouble(&val)) {
if (!check_value) { if (!check_value) {
m_value.clear(); m_value.clear();
break; break;
@ -453,8 +456,11 @@ void TextCtrl::BUILD() {
case coFloatsOrPercents: { case coFloatsOrPercents: {
const auto val = m_opt.get_default_value<ConfigOptionFloatsOrPercents>()->get_at(m_opt_idx); const auto val = m_opt.get_default_value<ConfigOptionFloatsOrPercents>()->get_at(m_opt_idx);
text_value = double_to_string(val.value); text_value = double_to_string(val.value);
if (val.percent) if (val.percent) {
text_value += "%"; text_value += "%";
}
m_last_meaningful_value = text_value;
break; break;
} }
case coPercent: case coPercent:

View File

@ -22,6 +22,7 @@
#include <boost/algorithm/string/split.hpp> #include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/classification.hpp> #include <boost/algorithm/string/classification.hpp>
#include "slic3r/GUI/Search.hpp" // IWYU pragma: keep #include "slic3r/GUI/Search.hpp" // IWYU pragma: keep
#include "slic3r/GUI/Field.hpp"
#include "libslic3r/Exception.hpp" #include "libslic3r/Exception.hpp"
#include "libslic3r/Utils.hpp" #include "libslic3r/Utils.hpp"
#include "libslic3r/AppConfig.hpp" #include "libslic3r/AppConfig.hpp"
@ -175,8 +176,10 @@ void OptionsGroup::change_opt_value(DynamicPrintConfig& config, const t_config_o
str.pop_back(); str.pop_back();
percent = true; percent = true;
} }
double val = std::stod(str); // locale-dependent (on purpose - the input is the actual content of the field)
ConfigOptionFloatsOrPercents* vec_new = new ConfigOptionFloatsOrPercents({ {val, percent} }); const bool is_na_value = opt_def->nullable && str == _(L("N/A"));
const FloatOrPercent val = is_na_value ? ConfigOptionFloatsOrPercentsNullable::nil_value() : FloatOrPercent{std::stod(str), percent};
ConfigOptionFloatsOrPercents *vec_new = new ConfigOptionFloatsOrPercents({val});
config.option<ConfigOptionFloatsOrPercents>(opt_key)->set_at(vec_new, opt_index, opt_index); config.option<ConfigOptionFloatsOrPercents>(opt_key)->set_at(vec_new, opt_index, opt_index);
break; break;
} }
@ -1020,6 +1023,21 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config
ret = double_to_string(val); } ret = double_to_string(val); }
} }
break; break;
case coFloatsOrPercents: {
if (config.option(opt_key)->is_nil()) {
ret = _(L("N/A"));
} else {
const auto &config_option = config.option<ConfigOptionFloatsOrPercentsNullable>(opt_key)->get_at(idx);
text_value = double_to_string(config_option.value);
if (config_option.percent) {
text_value += "%";
}
ret = text_value;
}
break;
}
case coBools: case coBools:
ret = config.option<ConfigOptionBoolsNullable>(opt_key)->values[idx]; ret = config.option<ConfigOptionBoolsNullable>(opt_key)->values[idx];
break; break;

View File

@ -1458,6 +1458,7 @@ void TabPrint::build()
optgroup = page->new_optgroup(L("Advanced")); optgroup = page->new_optgroup(L("Advanced"));
optgroup->append_single_option_line("seam_position", category_path + "seam-position"); optgroup->append_single_option_line("seam_position", category_path + "seam-position");
optgroup->append_single_option_line("seam_gap_distance", category_path + "seam-gap-distance");
optgroup->append_single_option_line("staggered_inner_seams", category_path + "staggered-inner-seams"); optgroup->append_single_option_line("staggered_inner_seams", category_path + "staggered-inner-seams");
optgroup->append_single_option_line("scarf_seam_placement", category_path + "scarf-seam-placement"); optgroup->append_single_option_line("scarf_seam_placement", category_path + "scarf-seam-placement");
@ -1968,6 +1969,9 @@ std::vector<std::pair<std::string, std::vector<std::string>>> filament_overrides
{"Retraction when tool is disabled", { {"Retraction when tool is disabled", {
"filament_retract_length_toolchange", "filament_retract_length_toolchange",
"filament_retract_restart_extra_toolchange" "filament_retract_restart_extra_toolchange"
}},
{"Seams", {
"filament_seam_gap_distance"
}} }}
}; };