From 252bbbf9f50c1b0a1afa6d70cc9b0b0c3060fcbb Mon Sep 17 00:00:00 2001 From: Sergei <119299827+ghzserg@users.noreply.github.com> Date: Mon, 19 Oct 2026 16:17:20 +0500 Subject: [PATCH] Make the exclude-object outline tolerance a user setting (#16503) * Make the exclude-object outline tolerance configurable Signed-off-by: Sergei Rozhkov * fix(config): move exclude_object_tolerance to developer mode Signed-off-by: Sergei Rozhkov * fix(config): move exclude_object_tolerance to expert mode Signed-off-by: Sergei Rozhkov * feat(ui): hide exclude_object_tolerance for non-Klipper firmwares mend Signed-off-by: Sergei Rozhkov * Test that a larger exclude object tolerance writes fewer outline points Signed-off-by: Sergei Rozhkov --------- Signed-off-by: Sergei Rozhkov --- localization/i18n/OrcaSlicer.pot | 6 +++++ src/libslic3r/Preset.cpp | 1 + src/libslic3r/Print.cpp | 3 ++- src/libslic3r/PrintConfig.cpp | 11 ++++++++ src/libslic3r/PrintConfig.hpp | 1 + src/slic3r/GUI/ConfigManipulation.cpp | 2 ++ src/slic3r/GUI/Tab.cpp | 1 + tests/fff_print/test_gcode.cpp | 38 +++++++++++++++++++++++++++ 8 files changed, 62 insertions(+), 1 deletion(-) diff --git a/localization/i18n/OrcaSlicer.pot b/localization/i18n/OrcaSlicer.pot index 1fbe90b60c..5b9cf2da67 100644 --- a/localization/i18n/OrcaSlicer.pot +++ b/localization/i18n/OrcaSlicer.pot @@ -15648,6 +15648,12 @@ msgstr "" msgid "Enable this option to add EXCLUDE OBJECT command in G-code." msgstr "" +msgid "Outline tolerance" +msgstr "" + +msgid "Maximum deviation of the object outline written to EXCLUDE_OBJECT_DEFINE from the original outline. Larger values give fewer points, which helps firmware that stalls while parsing G-code with many objects. 0 keeps every point." +msgstr "" + msgid "Verbose G-code" msgstr "" diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 4ae928cc95..9135f8fd2e 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1421,6 +1421,7 @@ static std::vector s_Preset_print_options{ "initial_layer_travel_acceleration", "initial_layer_travel_jerk", "exclude_object", + "exclude_object_tolerance", "slow_down_layers", "infill_anchor", "infill_anchor_max", diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 93a71dcd8d..e9f741b890 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -309,6 +309,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n "gcode_comments", "gcode_label_objects", "exclude_object", + "exclude_object_tolerance", "support_material_interface_fan_speed", "internal_bridge_fan_speed", // ORCA: Add support for separate internal bridge fan speed control "ironing_fan_speed", @@ -6929,7 +6930,7 @@ BoundingBoxf3 PrintInstance::get_bounding_box() const { Polygon PrintInstance::get_convex_hull_2d() { Polygon poly = print_object->model_object()->convex_hull_2d(model_instance->get_matrix()); - poly.douglas_peucker(scale_(0.1)); + poly.douglas_peucker(scale_(print_object->print()->config().exclude_object_tolerance.value)); return poly; } diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index bab1d1b586..1d9316e8df 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -4629,6 +4629,17 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(false)); + def = this->add("exclude_object_tolerance", coFloat); + def->label = L("Outline tolerance"); + def->tooltip = L("Maximum deviation of the object outline written to EXCLUDE_OBJECT_DEFINE from the " + "original outline. Larger values give fewer points, which helps firmware that stalls " + "while parsing G-code with many objects. 0 keeps every point."); + def->sidetext = L("mm"); + def->min = 0; + def->max = 5; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(0.1)); + def = this->add("gcode_comments", coBool); def->label = L("Verbose G-code"); def->tooltip = L("Enable this to get a commented G-code file, with each line explained by a descriptive text. " diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 8b724a7058..ed793ec3cc 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -2130,6 +2130,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionPercents, filament_shrinkage_compensation_z)) ((ConfigOptionBool, gcode_label_objects)) ((ConfigOptionBool, exclude_object)) + ((ConfigOptionFloat, exclude_object_tolerance)) ((ConfigOptionFloats, grab_length)) ((ConfigOptionBool, gcode_comments)) ((ConfigOptionInt, slow_down_layers)) diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index cd7e44b5d6..8bf5b843cc 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -1245,6 +1245,8 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in if(gcf_is_klipper) toggle_field("accel_to_decel_factor", config->opt_bool("accel_to_decel_enable")); + toggle_line("exclude_object_tolerance", gcf_is_klipper && config->opt_bool("exclude_object")); + bool have_make_overhang_printable = config->opt_bool("make_overhang_printable"); toggle_line("make_overhang_printable_angle", have_make_overhang_printable); toggle_line("make_overhang_printable_hole_size", have_make_overhang_printable); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 9d67e7c0fb..721e97df92 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3269,6 +3269,7 @@ void TabPrint::build() optgroup->append_single_option_line("gcode_comments", "others_settings_g_code_output#verbose-g-code"); optgroup->append_single_option_line("gcode_label_objects", "others_settings_g_code_output#label-objects"); optgroup->append_single_option_line("exclude_object", "others_settings_g_code_output#exclude-objects"); + optgroup->append_single_option_line("exclude_object_tolerance", "others_settings_g_code_output#outline-tolerance"); option = optgroup->get_option("filename_format"); // option.opt.full_width = true; option.opt.is_code = true; diff --git a/tests/fff_print/test_gcode.cpp b/tests/fff_print/test_gcode.cpp index 07091eddef..cb707ae3d4 100644 --- a/tests/fff_print/test_gcode.cpp +++ b/tests/fff_print/test_gcode.cpp @@ -51,6 +51,44 @@ TEST_CASE("Klipper object labels name each copy without the characters Klipper c } } +// The outline written to EXCLUDE_OBJECT_DEFINE is simplified with exclude_object_tolerance. A larger value must +// leave fewer points, which keeps the exclude-object array small on printers that stall while parsing it. +TEST_CASE("A larger exclude object tolerance writes fewer outline points", "[GCode]") +{ + const auto slice_with_tolerance = [](const std::string &tolerance) { + DynamicPrintConfig config = DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({{"gcode_flavor", "klipper"}, {"exclude_object", "1"}, + {"exclude_object_tolerance", tolerance}}); + // Default facet angle (2 degrees): 180 hull points, well inside either tolerance. + return Test::slice({make_cylinder(20., 5.)}, config); + }; + // Points of the POLYGON=[[x,y],...] on the first EXCLUDE_OBJECT_DEFINE line. + const auto polygon_points = [](const std::string &gcode) { + const size_t define = gcode.find("EXCLUDE_OBJECT_DEFINE"); + REQUIRE(define != std::string::npos); + const size_t begin = gcode.find("POLYGON=", define); + REQUIRE(begin != std::string::npos); + const std::string polygon = gcode.substr(begin, gcode.find('\n', begin) - begin); + size_t points = 1; + for (size_t pos = polygon.find("],["); pos != std::string::npos; pos = polygon.find("],[", pos + 1)) + ++points; + return points; + }; + + const size_t fine = polygon_points(slice_with_tolerance("0.1")); + const size_t coarse = polygon_points(slice_with_tolerance("0.4")); + INFO("points at 0.1 mm: " << fine << ", at 0.4 mm: " << coarse); + CHECK(coarse >= 4); + CHECK(coarse < fine); +} + +// Existing profiles must keep writing the outlines they wrote when the tolerance was a constant 0.1 mm. +TEST_CASE("The exclude object tolerance defaults to the former fixed 0.1 mm", "[GCode]") +{ + CHECK_THAT(DynamicPrintConfig::full_print_config().opt_float("exclude_object_tolerance"), + Catch::Matchers::WithinAbs(0.1, 1e-9)); +} + // Firmwares read the compatibility header lines (Anycubic's GoKlipper heats the bed with // first_layer_bed_temperature), so they must describe the filament the plate prints, not filament 1. TEST_CASE("The G-code header names the first layer temperatures of the filament the plate prints", "[GCode]")