diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 99bf6437cd6..e140edb5367 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -496,7 +496,7 @@ static std::vector s_Preset_print_options { "support_material_contact_distance", "support_material_bottom_contact_distance", "support_material_buildplate_only", "support_tree_angle", "support_tree_angle_slow", "support_tree_branch_diameter", "support_tree_branch_diameter_angle", "support_tree_branch_diameter_double_wall", - "support_tree_top_rate", "support_tree_branch_distance", "support_tree_tip_diameter", + "support_tree_top_rate", "support_tree_branch_distance", "support_tree_tip_diameter", "support_tree_base_layers", "dont_support_bridges", "thick_bridges", "notes", "custom_parameters_print", "complete_objects", "gcode_comments", "gcode_label_objects", "output_filename_format", "post_process", "gcode_substitutions", "perimeter_extruder", "infill_extruder", "solid_infill_extruder", "support_material_extruder", "support_material_interface_extruder", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 872a0c3954b..2b7ba85c741 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3614,6 +3614,16 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionPercent(15)); + def = this->add("support_tree_base_layers", coInt); + def->label = L("Branch Base Layers"); + def->category = L("Support material"); + // TRN PrintSettings: "Organic supports" > "Support Base Layers" + def->tooltip = L("Number of base layers to generate for Organic supports. Increasing this can make the support base sturdier and easier to remove."); + def->min = 1; + def->max = 10; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionInt(1)); + def = this->add("temperature", coInts); def->label = L("Other layers"); def->tooltip = L("Nozzle temperature for layers after the first one. Set this to zero to disable " diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index fbb14f6a35e..f9c4782c582 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -685,6 +685,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionPercent, support_tree_top_rate)) ((ConfigOptionFloat, support_tree_branch_distance)) ((ConfigOptionFloat, support_tree_tip_diameter)) + ((ConfigOptionInt, support_tree_base_layers)) // The rest ((ConfigOptionBool, thick_bridges)) ((ConfigOptionFloat, xy_size_compensation)) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index dc0b6e454a0..804449f9b0d 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -812,6 +812,7 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "support_tree_top_rate" || opt_key == "support_tree_branch_distance" || opt_key == "support_tree_tip_diameter" + || opt_key == "support_tree_base_layers" || opt_key == "raft_expansion" || opt_key == "raft_first_layer_density" || opt_key == "raft_first_layer_expansion" diff --git a/src/libslic3r/Support/OrganicSupport.cpp b/src/libslic3r/Support/OrganicSupport.cpp index f7065eee0a4..24e4d054fa4 100644 --- a/src/libslic3r/Support/OrganicSupport.cpp +++ b/src/libslic3r/Support/OrganicSupport.cpp @@ -1399,6 +1399,18 @@ void organic_draw_branches( } } + if (int base_layers = std::clamp(config.support_tree_base_layers, 1, 10); + base_layers > 1 && ! slices.empty()) { + Polygons base_polygons = slices[0].num_branches > 1 ? union_(slices[0].polygons) : slices[0].polygons; + if (! base_polygons.empty()) { + size_t limit = std::min(size_t(base_layers), slices.size()); + for (size_t i = 1; i < limit; ++i) { + append(slices[i].polygons, base_polygons); + slices[i].num_branches++; + } + } + } + tbb::parallel_for(tbb::blocked_range(0, std::min(move_bounds.size(), slices.size()), 1), [&print_object, &config, &slices, &bottom_contacts, &top_contacts, &intermediate_layers, &layer_storage, &throw_on_cancel](const tbb::blocked_range &range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { diff --git a/src/libslic3r/Support/SupportCommon.cpp b/src/libslic3r/Support/SupportCommon.cpp index 26bd31ea634..f9c06534980 100644 --- a/src/libslic3r/Support/SupportCommon.cpp +++ b/src/libslic3r/Support/SupportCommon.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -458,24 +459,45 @@ SupportGeneratorLayersPtr generate_raft_base( new_layer.contact_polygons = std::make_unique(columns); } } else { - if (columns_base != nullptr) { - // Expand the bases of the support columns in the 1st layer. - Polygons &raft = columns_base->polygons; - Polygons trimming = offset(object.layers().front()->lslices, (float)scale_(support_params.gap_xy), SUPPORT_SURFACES_OFFSET_PARAMETERS); + int expansion_layers = std::clamp(object.config().support_tree_base_layers.value, 1, 10); + for (int i = 0; i < expansion_layers; ++i) { + if (i >= int(base_layers.size())) break; + SupportGeneratorLayer *layer = base_layers[i]; + if (layer == nullptr) continue; + + // Ensure we have a corresponding object layer for trimming. + if (i >= int(object.layers().size())) break; + + Polygons &raft = layer->polygons; + Polygons trimming = offset(object.layers()[i]->lslices, (float)scale_(support_params.gap_xy), SUPPORT_SURFACES_OFFSET_PARAMETERS); + if (inflate_factor_1st_layer > SCALED_EPSILON) { // Inflate in multiple steps to avoid leaking of the support 1st layer through object walls. auto nsteps = std::max(5, int(ceil(inflate_factor_1st_layer / support_params.first_layer_flow.scaled_width()))); float step = inflate_factor_1st_layer / nsteps; - for (int i = 0; i < nsteps; ++ i) + for (int k = 0; k < nsteps; ++ k) raft = diff(expand(raft, step), trimming); } else raft = diff(raft, trimming); - if (! interface_polygons.empty()) - columns_base->polygons = diff(columns_base->polygons, interface_polygons); + + // Trim the expanded base layer against other support types at the same layer to avoid overlap. + auto trim_against = [&layer](const SupportGeneratorLayersPtr &layers, int idx) { + if (idx < int(layers.size()) && layers[idx] != nullptr && !layers[idx]->polygons.empty()) + layer->polygons = diff(layer->polygons, layers[idx]->polygons); + }; + trim_against(interface_layers, i); + trim_against(base_interface_layers, i); + trim_against(top_contacts, i); + + if (i == 0) { + if (! interface_polygons.empty()) + layer->polygons = diff(layer->polygons, interface_polygons); + if (! brim.empty()) + layer->polygons = diff(layer->polygons, brim); + } } + if (! brim.empty()) { - if (columns_base) - columns_base->polygons = diff(columns_base->polygons, brim); if (contacts) contacts->polygons = diff(contacts->polygons, brim); if (interfaces) @@ -1505,6 +1527,7 @@ void generate_support_toolpaths( // Insert the raft base layers. auto n_raft_layers = std::min(support_layers.size(), std::max(0, int(slicing_params.raft_layers()) - 1)); + const size_t tree_support_base_layers = size_t(std::clamp(config.support_tree_base_layers.value, 1, 10)); tbb::parallel_for(tbb::blocked_range(0, n_raft_layers), [&support_layers, &raft_layers, &intermediate_layers, &config, &support_params, &slicing_params, @@ -1616,7 +1639,7 @@ void generate_support_toolpaths( tbb::parallel_for(tbb::blocked_range(n_raft_layers, support_layers.size()), [&config, &slicing_params, &support_params, &support_layers, &bottom_contacts, &top_contacts, &intermediate_layers, &interface_layers, &base_interface_layers, &layer_caches, &loop_interface_processor, - &bbox_object, &angles, n_raft_layers, link_max_length_factor] + &bbox_object, &angles, n_raft_layers, link_max_length_factor, tree_support_base_layers] (const tbb::blocked_range& range) { // Indices of the 1st layer in their respective container at the support layer height. size_t idx_layer_bottom_contact = size_t(-1); @@ -1815,8 +1838,20 @@ void generate_support_toolpaths( sheath = true; no_sort = true; } else if (config.support_material_style == SupportMaterialStyle::smsOrganic) { - tree_supports_generate_paths(base_layer.extrusions, base_layer.polygons_to_extrude(), flow, support_params); - done = true; + if (support_layer_id < n_raft_layers + tree_support_base_layers) { + // Additional solid base layers for organic support. + filler = filler_support.get(); + filler->angle = angles[support_layer_id % angles.size()]; + density = 1.f; // Solid + // Use the standard support flow (already calculated above) + filler->spacing = support_params.support_material_flow.spacing(); + filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density)); + sheath = true; + no_sort = true; + } else { + tree_supports_generate_paths(base_layer.extrusions, base_layer.polygons_to_extrude(), flow, support_params); + done = true; + } } if (! done) fill_expolygons_with_sheath_generate_paths( diff --git a/src/libslic3r/Support/TreeSupportCommon.cpp b/src/libslic3r/Support/TreeSupportCommon.cpp index ea3e675220d..8d5e398b78d 100644 --- a/src/libslic3r/Support/TreeSupportCommon.cpp +++ b/src/libslic3r/Support/TreeSupportCommon.cpp @@ -86,6 +86,7 @@ TreeSupportMeshGroupSettings::TreeSupportMeshGroupSettings(const PrintObject &pr this->support_tree_top_rate = config.support_tree_top_rate.value; // percent // this->support_tree_tip_diameter = this->support_line_width; this->support_tree_tip_diameter = std::clamp(scaled(config.support_tree_tip_diameter.value), 0, this->support_tree_branch_diameter); + this->support_tree_base_layers = std::clamp(config.support_tree_base_layers.value, 1, 10); } TreeSupportSettings::TreeSupportSettings(const TreeSupportMeshGroupSettings &mesh_group_settings, const SlicingParameters &slicing_params) @@ -119,6 +120,7 @@ TreeSupportSettings::TreeSupportSettings(const TreeSupportMeshGroupSettings &mes support_line_spacing(mesh_group_settings.support_line_spacing), support_bottom_offset(mesh_group_settings.support_bottom_offset), support_wall_count(mesh_group_settings.support_wall_count), + support_tree_base_layers(mesh_group_settings.support_tree_base_layers), resolution(mesh_group_settings.resolution), support_roof_line_distance(mesh_group_settings.support_roof_line_distance), // in the end the actual infill has to be calculated to subtract interface from support areas according to interface_preference. settings(mesh_group_settings), @@ -200,4 +202,4 @@ void tree_supports_show_error(std::string_view message, bool critical) #endif // TREE_SUPPORT_SHOW_ERRORS_WIN32 } -} // namespace Slic3r::FFFTreeSupport \ No newline at end of file +} // namespace Slic3r::FFFTreeSupport diff --git a/src/libslic3r/Support/TreeSupportCommon.hpp b/src/libslic3r/Support/TreeSupportCommon.hpp index b81f033a3fb..f1479dc43d4 100644 --- a/src/libslic3r/Support/TreeSupportCommon.hpp +++ b/src/libslic3r/Support/TreeSupportCommon.hpp @@ -220,6 +220,8 @@ struct TreeSupportMeshGroupSettings { // The diameter of the top of the tip of the branches of tree support. // minimum: min_wall_line_width, minimum warning: min_wall_line_width+0.05, maximum_value: support_tree_branch_diameter, value: support_line_width coord_t support_tree_tip_diameter { scaled(0.4) }; + // Number of base layers to generate for Organic supports. + int support_tree_base_layers { 1 }; // Support Interface Priority // How support interface and support will interact when they overlap. Currently only implemented for support roof. @@ -355,6 +357,10 @@ struct TreeSupportSettings * \brief Amount of walls the support area will have. */ int support_wall_count; + /* + * \brief Number of base layers to generate for Organic supports. + */ + int support_tree_base_layers; /* * \brief Maximum allowed deviation when simplifying. */ diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index b838a267d83..3c02885c3f8 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -368,7 +368,8 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) config->opt_int("support_material_enforce_layers") > 0); for (const std::string& key : { "support_tree_angle", "support_tree_angle_slow", "support_tree_branch_diameter", "support_tree_branch_diameter_angle", "support_tree_branch_diameter_double_wall", - "support_tree_tip_diameter", "support_tree_branch_distance", "support_tree_top_rate" }) + "support_tree_tip_diameter", "support_tree_branch_distance", "support_tree_top_rate", + "support_tree_base_layers" }) toggle_field(key, has_organic_supports); for (auto el : { "support_material_bottom_interface_layers", "support_material_interface_spacing", "support_material_interface_extruder", diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index d719ec7eec2..828a11dd3d6 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1592,6 +1592,7 @@ void TabPrint::build() optgroup->append_single_option_line("support_tree_tip_diameter", path); optgroup->append_single_option_line("support_tree_branch_distance", path); optgroup->append_single_option_line("support_tree_top_rate", path); + optgroup->append_single_option_line("support_tree_base_layers", path); page = add_options_page(L("Speed"), "time"); optgroup = page->new_optgroup(L("Speed for print moves")); diff --git a/tests/fff_print/test_support_material.cpp b/tests/fff_print/test_support_material.cpp index 56ee30bb8a9..74958d34e86 100644 --- a/tests/fff_print/test_support_material.cpp +++ b/tests/fff_print/test_support_material.cpp @@ -1,4 +1,5 @@ #include +#include #include "libslic3r/GCodeReader.hpp" #include "libslic3r/Layer.hpp" @@ -495,3 +496,139 @@ Old Perl tests, which were disabled by Vojtech at the time of first Support Gene } */ +#include + +#include "libslic3r/GCodeReader.hpp" +#include "libslic3r/Layer.hpp" +#include "libslic3r/Print.hpp" +#include "libslic3r/PrintConfig.hpp" + +#include "test_data.hpp" + +using namespace Slic3r; +using namespace Slic3r::Test; + +static size_t count_no_sort_layers(SpanOfConstPtrs support_layers) +{ + return std::count_if(support_layers.begin(), support_layers.end(), + [](const SupportLayer *layer) { + return std::any_of(layer->support_fills.entities.begin(), layer->support_fills.entities.end(), + [](const ExtrusionEntity *entity) { + if (auto collection = dynamic_cast(entity)) + return collection->no_sort; + return false; + }); + }); +} + +TEST_CASE("Organic Support: Base Layers", "[OrganicSupport]") +{ + TriangleMesh mesh = Slic3r::Test::mesh(Slic3r::Test::TestMesh::cube_20x20x20); + mesh.translate(0, 0, 10); // Lift it up by 10mm. + + Slic3r::Model model; + ModelObject *object = model.add_object(); + object->name = "floating_cube.stl"; + object->add_volume(mesh); + object->add_instance(); // Default instance at 0,0,0 + + Slic3r::Print print; + DynamicPrintConfig config = DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({ + { "support_material", 1 }, + { "support_material_style", "organic" }, + { "support_tree_base_layers", 5 }, + { "layer_height", 0.2 }, + { "first_layer_height", 0.2 }, + { "support_material_threshold", 0 } + }); + + print.apply(model, config); + print.validate(); + print.process(); + + auto print_object = print.objects().front(); + const auto& support_layers = print_object->support_layers(); + + REQUIRE(support_layers.size() > 5); + + double area0 = 0; + for (const ExPolygon& expoly : support_layers[0]->support_islands) area0 += expoly.area(); + + REQUIRE(area0 > 0); + + for (size_t i = 1; i < 5; ++i) { + double areai = 0; + for (const ExPolygon& expoly : support_layers[i]->support_islands) areai += expoly.area(); + + REQUIRE(areai >= area0 - 1.0); + } + + REQUIRE(count_no_sort_layers(support_layers) == size_t(std::clamp(5, 1, 10))); +} + +TEST_CASE("Organic Support: Negative base layers clamp to at least one", "[OrganicSupport]") +{ + TriangleMesh mesh = Slic3r::Test::mesh(Slic3r::Test::TestMesh::cube_20x20x20); + mesh.translate(0, 0, 10); + + Slic3r::Model model; + ModelObject *object = model.add_object(); + object->name = "floating_cube.stl"; + object->add_volume(mesh); + object->add_instance(); + + Slic3r::Print print; + DynamicPrintConfig config = DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({ + { "support_material", 1 }, + { "support_material_style", "organic" }, + { "support_tree_base_layers", -1 }, // Misconfigured input should clamp to minimum. + { "layer_height", 0.2 }, + { "first_layer_height", 0.2 }, + { "support_material_threshold", 0 } + }); + + print.apply(model, config); + print.validate(); + print.process(); + + const auto &support_layers = print.objects().front()->support_layers(); + REQUIRE_FALSE(support_layers.empty()); + REQUIRE(std::any_of(support_layers.begin(), support_layers.end(), + [](const SupportLayer *layer) { return !layer->support_fills.empty(); })); + + REQUIRE(count_no_sort_layers(support_layers) == size_t(1)); +} + +TEST_CASE("Organic Support: Excessive base layers clamp to max", "[OrganicSupport]") +{ + TriangleMesh mesh = Slic3r::Test::mesh(Slic3r::Test::TestMesh::cube_20x20x20); + mesh.translate(0, 0, 10); + + Slic3r::Model model; + ModelObject *object = model.add_object(); + object->name = "floating_cube.stl"; + object->add_volume(mesh); + object->add_instance(); + + Slic3r::Print print; + DynamicPrintConfig config = DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({ + { "support_material", 1 }, + { "support_material_style", "organic" }, + { "support_tree_base_layers", 60 }, + { "layer_height", 0.2 }, + { "first_layer_height", 0.2 }, + { "support_material_threshold", 0 } + }); + + print.apply(model, config); + print.validate(); + print.process(); + + const auto &support_layers = print.objects().front()->support_layers(); + REQUIRE_FALSE(support_layers.empty()); + + REQUIRE(count_no_sort_layers(support_layers) == size_t(10)); +}