Fix skirt and brim handling for duplicated objects (instances) (#14683)

This commit is contained in:
Kiss Lorand
2026-07-19 01:57:56 +03:00
committed by GitHub
parent 3926475bb7
commit d6ca71be95
11 changed files with 267 additions and 273 deletions

View File

@@ -399,6 +399,26 @@ std::string slice_two_cubes_apart(double gap, std::initializer_list<Slic3r::Conf
return gcode(print);
}
void place_two_cube_instances_apart(double gap, std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items,
Print &print, Model &model)
{
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
config.set_deserialize_strict(config_items);
config.set_key_value("gcode_comments", new ConfigOptionBool(true));
ModelObject *object = model.add_object();
object->name += "object.stl";
object->add_volume(cube(20));
object->add_instance()->set_offset(Vec3d(80, 80, 0));
object->add_instance()->set_offset(Vec3d(80 + 20 + gap, 80, 0));
object->ensure_on_bed();
print.auto_assign_extruders(object);
print.apply(model, config);
print.validate();
print.set_status_silent();
}
std::set<double> layers_with_role(const std::string &gcode, const std::string &role)
{
std::set<double> layers;

View File

@@ -108,6 +108,10 @@ void place_two_cubes_apart(double gap, std::initializer_list<Slic3r::ConfigBase:
// Slice two 20mm cubes `gap` mm apart (not auto-arranged) and return the G-code.
std::string slice_two_cubes_apart(double gap, std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items);
// Place two instances of one 20mm cube `gap` mm apart edge-to-edge.
void place_two_cube_instances_apart(double gap, std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items,
Slic3r::Print &print, Slic3r::Model &model);
// Distinct layer Z heights carrying an extrusion of the given `role` (e.g. "skirt").
std::set<double> layers_with_role(const std::string &gcode, const std::string &role);

View File

@@ -86,6 +86,27 @@ TEST_CASE("Per-object skirts group when objects are close", "[SkirtBrim]")
}
}
TEST_CASE("Per-object skirt is generated per instance", "[SkirtBrim]")
{
Print print;
Model model;
place_two_cube_instances_apart(60, {
{ "skirt_type", "perobject" },
{ "skirt_height", 1 },
{ "skirt_distance", 2 },
{ "skirt_loops", 1 },
{ "brim_type", "no_brim" },
}, print, model);
print.process();
REQUIRE(print.skirt_brim_groups().size() == 2);
REQUIRE(print.skirt().items_count() == 2);
for (const Print::SkirtBrimGroup &group : print.skirt_brim_groups()) {
REQUIRE(group.instances.size() == 1);
REQUIRE(group.instances.front().object_id == print.get_object(0)->id());
}
}
TEST_CASE("Combine brims merges touching brims", "[SkirtBrim]")
{
auto [gap, combine, expected_brims] = GENERATE(table<double, int, int>({
@@ -112,6 +133,45 @@ TEST_CASE("Combine brims merges touching brims", "[SkirtBrim]")
}
}
TEST_CASE("Object brims are generated per instance", "[SkirtBrim]")
{
Print print;
Model model;
place_two_cube_instances_apart(60, {
{ "skirt_loops", 0 },
{ "brim_type", "outer_only" },
{ "brim_width", 5 },
{ "combine_brims", 0 },
}, print, model);
print.process();
REQUIRE(print.skirt_brim_groups().size() == 1);
REQUIRE(print.skirt_brim_groups().front().brims.size() == 2);
for (const Print::SkirtBrimGroup::Brim &brim : print.skirt_brim_groups().front().brims) {
REQUIRE(brim.instances.size() == 1);
REQUIRE(brim.instances.front().object_id == print.get_object(0)->id());
}
}
TEST_CASE("Combine brims merges neighboring object instances", "[SkirtBrim]")
{
Print print;
Model model;
place_two_cube_instances_apart(5, {
{ "skirt_loops", 0 },
{ "brim_type", "outer_only" },
{ "brim_width", 5 },
{ "combine_brims", 1 },
}, print, model);
print.process();
REQUIRE(print.skirt_brim_groups().size() == 1);
REQUIRE(print.skirt_brim_groups().front().brims.size() == 1);
REQUIRE(print.skirt_brim_groups().front().brims.front().instances.size() == 2);
const std::vector<std::string> expected{ "brim", "perimeter" };
CHECK(role_sequence(gcode(print), { "brim", "perimeter" }) == expected);
}
// Each object's skirt and brim come right before that object, not all skirts then all brims first.
TEST_CASE("By-layer per-object skirt and brim precede each object", "[SkirtBrim]")
{