diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index 9d3d19362e..13408b50f6 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -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 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 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 ConfigOption* save_option_to_archive(Archive &archive, const ConfigOption *opt) const { - if (this->nullable) { - switch (this->type) { - case coFloat: archive(*static_cast(opt)); break; - case coInt: archive(*static_cast(opt)); break; - case coFloats: archive(*static_cast(opt)); break; - case coInts: archive(*static_cast(opt)); break; - case coPercents: archive(*static_cast(opt));break; - case coBools: archive(*static_cast(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(opt)); break; + case coInt: archive(*static_cast(opt)); break; + case coFloats: archive(*static_cast(opt)); break; + case coInts: archive(*static_cast(opt)); break; + case coPercents: archive(*static_cast(opt)); break; + case coBools: archive(*static_cast(opt)); break; + case coFloatsOrPercents: archive(*static_cast(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(opt)); break; diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 21488a339d..14f1bc91a7 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2311,6 +2311,17 @@ std::pair 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(extrusion_clipping), scaled(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(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(GCode::ExtrusionOrder::min_gcode_segment_length)); + Slic3r::append(result, smooth_path_extension); } assert(validate_smooth_path(result, !enable_loop_clipping)); diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index d4182df666..1fd87f3e2a 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -456,7 +456,8 @@ static std::vector 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 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 s_Preset_machine_limits_options { diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index d9db8d0cb0..4da9807adc 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -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", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index f5c9746ae6..885d126e23 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -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(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() diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index da0fa245f1..93bb6747be 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -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)) diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index e9e92b37b1..fe00dd229a 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -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()->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: diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 6dae3aa17f..574aac0ab4 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -22,6 +22,7 @@ #include #include #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(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(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(opt_key)->values[idx]; break; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 8b36bd8cec..ff50474aaf 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -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>> filament_overrides {"Retraction when tool is disabled", { "filament_retract_length_toolchange", "filament_retract_restart_extra_toolchange" + }}, + {"Seams", { + "filament_seam_gap_distance" }} };