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;
bool percent;
double get_abs_value(double ratio_over) const { return this->percent ? (ratio_over * this->value / 100) : this->value; }
private:
friend class cereal::access;
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; }
template<class Archive> ConfigOption* load_option_from_archive(Archive &archive) const {
if (this->nullable) {
switch (this->type) {
case coFloat: { auto opt = new ConfigOptionFloatNullable(); 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 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 ConfigurationError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown nullable option type for option ") + this->opt_key);
}
} else {
if (this->nullable) {
switch (this->type) {
case coFloat: { auto opt = new ConfigOptionFloatNullable(); 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 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; }
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 {
switch (this->type) {
case coFloat: { auto opt = new ConfigOptionFloat(); 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 {
if (this->nullable) {
switch (this->type) {
case coFloat: archive(*static_cast<const ConfigOptionFloatNullable*>(opt)); break;
case coInt: archive(*static_cast<const ConfigOptionIntNullable*>(opt)); break;
case coFloats: archive(*static_cast<const ConfigOptionFloatsNullable*>(opt)); break;
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 ConfigurationError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown nullable option type for option ") + this->opt_key);
}
if (this->nullable) {
switch (this->type) {
case coFloat: archive(*static_cast<const ConfigOptionFloatNullable*>(opt)); break;
case coInt: archive(*static_cast<const ConfigOptionIntNullable*>(opt)); break;
case coFloats: archive(*static_cast<const ConfigOptionFloatsNullable*>(opt)); break;
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;
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 {
switch (this->type) {
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
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;
struct SmoothPathGenerator
@ -2361,12 +2372,20 @@ struct SmoothPathGenerator
// 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
// polyline, so we discard it in that case.
const auto nozzle_diameter{config.nozzle_diameter.get_at(extruder_id)};
if (enable_loop_clipping) {
if (const double extrusion_clipping = get_seam_gap_distance_value(config, extruder_id); enable_loop_clipping && extrusion_clipping > 0.) {
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)
);
} 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));

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",
"ensure_vertical_shell_thickness", "extra_perimeters", "extra_perimeters_on_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",
"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",
@ -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",
// Shrinkage compensation
"filament_shrinkage_compensation_xy", "filament_shrinkage_compensation_z",
// Seams overrides
"filament_seam_gap_distance"
};
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_notes",
"filament_cost",
"filament_seam_gap_distance",
"filament_spool_weight",
"first_layer_acceleration",
"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_toolchange",
"retract_speed",
"seam_gap_distance",
"single_extruder_multi_material_priming",
"slowdown_below_layer_time",
"solid_infill_acceleration",

View File

@ -2654,6 +2654,17 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced;
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->label = L("Seam position");
def->category = L("Layers and Perimeters");
@ -3775,9 +3786,9 @@ void PrintConfigDef::init_fff_params()
auto it_opt = options.find(opt_key);
assert(it_opt != options.end());
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->tooltip = it_opt->second.tooltip;
def->tooltip = it_opt->second.tooltip;
def->sidetext = it_opt->second.sidetext;
def->mode = it_opt->second.mode;
switch (def->type) {
@ -3787,6 +3798,44 @@ void PrintConfigDef::init_fff_params()
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()

View File

@ -830,6 +830,7 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionFloats, filament_multitool_ramming_flow))
((ConfigOptionFloats, filament_stamping_loading_speed))
((ConfigOptionFloats, filament_stamping_distance))
((ConfigOptionFloatsOrPercentsNullable, filament_seam_gap_distance))
((ConfigOptionPercents, filament_shrinkage_compensation_xy))
((ConfigOptionPercents, filament_shrinkage_compensation_z))
((ConfigOptionBool, gcode_comments))
@ -860,6 +861,7 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionFloats, retract_restart_extra))
((ConfigOptionFloats, retract_restart_extra_toolchange))
((ConfigOptionFloats, retract_speed))
((ConfigOptionFloatOrPercent, seam_gap_distance))
((ConfigOptionString, start_gcode))
((ConfigOptionStrings, start_filament_gcode))
((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() != '%')
{
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_alt = dec_sep == '.' ? ',' : '.';
// 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);
// remove space and "mm" substring, if any exists
str.Replace(" ", "", 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) {
m_value.clear();
break;
@ -453,8 +456,11 @@ void TextCtrl::BUILD() {
case coFloatsOrPercents: {
const auto val = m_opt.get_default_value<ConfigOptionFloatsOrPercents>()->get_at(m_opt_idx);
text_value = double_to_string(val.value);
if (val.percent)
if (val.percent) {
text_value += "%";
}
m_last_meaningful_value = text_value;
break;
}
case coPercent:

View File

@ -22,6 +22,7 @@
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/classification.hpp>
#include "slic3r/GUI/Search.hpp" // IWYU pragma: keep
#include "slic3r/GUI/Field.hpp"
#include "libslic3r/Exception.hpp"
#include "libslic3r/Utils.hpp"
#include "libslic3r/AppConfig.hpp"
@ -175,8 +176,10 @@ void OptionsGroup::change_opt_value(DynamicPrintConfig& config, const t_config_o
str.pop_back();
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);
break;
}
@ -1020,6 +1023,21 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config
ret = double_to_string(val); }
}
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:
ret = config.option<ConfigOptionBoolsNullable>(opt_key)->values[idx];
break;

View File

@ -1458,6 +1458,7 @@ void TabPrint::build()
optgroup = page->new_optgroup(L("Advanced"));
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("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", {
"filament_retract_length_toolchange",
"filament_retract_restart_extra_toolchange"
}},
{"Seams", {
"filament_seam_gap_distance"
}}
};