mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-08-24 10:27:35 +00:00
Layer subdivision fix
This commit is contained in:
@@ -6101,7 +6101,17 @@ LayerResult GCode::process_layer(
|
||||
const bool island_level_ordering = print.config().print_sequence != PrintSequence::ByObject &&
|
||||
single_object_instance_idx == size_t(-1) &&
|
||||
print.config().print_order != PrintOrder::AsObjectList;
|
||||
for (unsigned int filament_id : layer_tools.extruders) {
|
||||
// A mixed-color slot is absent from layer_tools.extruders by design: resolve_mixed_filaments()
|
||||
// replaced it with its physical components. Its geometry is still keyed under the slot in
|
||||
// by_extruder though, and the sublayer emitter looks the plan up by slot id, so append the
|
||||
// slots here. Appended (not merged) so the existing order is untouched, and empty for every
|
||||
// configuration without sublayer splitting.
|
||||
std::vector<unsigned int> plan_filaments = layer_tools.extruders;
|
||||
for (const auto &grp : layer_tools.mixed_sub_layer_groups)
|
||||
if (std::find(plan_filaments.begin(), plan_filaments.end(), grp.mixed_slot_0based) == plan_filaments.end())
|
||||
plan_filaments.push_back(grp.mixed_slot_0based);
|
||||
|
||||
for (unsigned int filament_id : plan_filaments) {
|
||||
auto objects_by_extruder_it = by_extruder.find(filament_id);
|
||||
if (objects_by_extruder_it == by_extruder.end()) continue;
|
||||
|
||||
@@ -6282,8 +6292,22 @@ LayerResult GCode::process_layer(
|
||||
}
|
||||
|
||||
if (print.config().print_sequence == PrintSequence::ByLayer && m_enable_exclude_object && print.config().support_object_skip_flush.value) {
|
||||
std::vector<size_t> filament_instances_id;
|
||||
for (InstanceToPrint &instance : filament_to_print_instances[extruder_id].first) filament_instances_id.emplace_back(instance.label_object_id);
|
||||
std::set<size_t> all_label_ids;
|
||||
for (InstanceToPrint &instance : filament_to_print_instances[extruder_id].first)
|
||||
all_label_ids.insert(instance.label_object_id);
|
||||
// This extruder may also be printing sub-layers on behalf of a mixed slot, whose
|
||||
// instances live under the slot id. Their labels belong in the same skip set, or
|
||||
// exclude-object would not skip that geometry.
|
||||
for (const auto &grp : layer_tools.mixed_sub_layer_groups)
|
||||
for (unsigned int comp : grp.components_0based)
|
||||
if (comp == extruder_id) {
|
||||
auto mit = filament_to_print_instances.find(grp.mixed_slot_0based);
|
||||
if (mit != filament_to_print_instances.end())
|
||||
for (const InstanceToPrint &inst : mit->second.first)
|
||||
all_label_ids.insert(inst.label_object_id);
|
||||
break;
|
||||
}
|
||||
std::vector<size_t> filament_instances_id(all_label_ids.begin(), all_label_ids.end());
|
||||
m_filament_instances_code = _encode_label_ids_to_base64(filament_instances_id);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_perimeters.cpp
|
||||
test_print.cpp
|
||||
test_printobject.cpp
|
||||
test_mixed_filament.cpp
|
||||
test_skirt_brim.cpp
|
||||
test_slicing_pipeline_hook.cpp
|
||||
test_support_material.cpp
|
||||
|
||||
137
tests/fff_print/test_mixed_filament.cpp
Normal file
137
tests/fff_print/test_mixed_filament.cpp
Normal file
@@ -0,0 +1,137 @@
|
||||
#include <catch2/catch_all.hpp>
|
||||
|
||||
#include "libslic3r/GCode/ToolOrdering.hpp"
|
||||
#include "libslic3r/Print.hpp"
|
||||
|
||||
#include "test_helpers.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::Test;
|
||||
|
||||
namespace {
|
||||
|
||||
// Two physical filaments plus one mixed slot (config index 2, 1-based id 3) blending them 60/40.
|
||||
// The mixed arrays are parallel to filament_colour and must be sized to the filament count.
|
||||
// Note ConfigOptionBools deserializes on ',' while ConfigOptionStrings uses ';'.
|
||||
DynamicPrintConfig mixed_config(bool sublayer_on, const char *ratios = "0.6,0.4")
|
||||
{
|
||||
DynamicPrintConfig config = multifilament_config(3);
|
||||
config.set_deserialize_strict({
|
||||
{"filament_is_mixed", "0,0,1"},
|
||||
{"filament_mixed_components", ";;1,2"},
|
||||
{"filament_mixed_sublayer_ratios", std::string(";;") + ratios},
|
||||
{"filament_mixed_gradient", "0,0,0"},
|
||||
{"filament_mixed_gradient_range", ";;"},
|
||||
{"filament_mixed_gradient_curve", ";;"},
|
||||
{"filament_mixed_gradient_per_part","0,0,0"},
|
||||
{"enable_mixed_color_sublayer", sublayer_on ? "1" : "0"},
|
||||
// Assign every region role to the mixed slot so it actually participates in slicing.
|
||||
{"outer_wall_filament_id", "3"},
|
||||
{"inner_wall_filament_id", "3"},
|
||||
{"sparse_infill_filament_id", "3"},
|
||||
{"internal_solid_filament_id", "3"},
|
||||
{"top_surface_filament_id", "3"},
|
||||
{"bottom_surface_filament_id", "3"},
|
||||
});
|
||||
return config;
|
||||
}
|
||||
|
||||
// Total sub-layer groups and per-layer DRR resolutions across the whole tool ordering.
|
||||
void count_mixed(ToolOrdering &to, size_t &groups, size_t &resolutions)
|
||||
{
|
||||
groups = resolutions = 0;
|
||||
for (const LayerTools < : to.layer_tools()) {
|
||||
groups += lt.mixed_sub_layer_groups.size();
|
||||
resolutions += lt.mixed_filament_resolution.size();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("enable_mixed_color_sublayer reaches the Print config", "[MixedFilament]")
|
||||
{
|
||||
Print print;
|
||||
Model model;
|
||||
init_print({cube(20)}, print, model, mixed_config(true));
|
||||
|
||||
// The option lives in PrintConfig; if it did not survive Print::apply the slicer would
|
||||
// silently fall back to the whole-layer path.
|
||||
CHECK(print.config().enable_mixed_color_sublayer.value == true);
|
||||
REQUIRE(print.config().filament_is_mixed.values.size() == 3);
|
||||
CHECK(print.config().filament_is_mixed.values[2] == true);
|
||||
REQUIRE(print.config().filament_mixed_components.values.size() == 3);
|
||||
CHECK(print.config().filament_mixed_components.values[2] == "1,2");
|
||||
}
|
||||
|
||||
TEST_CASE("Mixed filament splits layers into sub-layers when the option is on", "[MixedFilament]")
|
||||
{
|
||||
Print print;
|
||||
Model model;
|
||||
init_print({cube(20)}, print, model, mixed_config(true));
|
||||
print.process();
|
||||
|
||||
ToolOrdering &to = const_cast<ToolOrdering &>(print.tool_ordering());
|
||||
REQUIRE(!to.layer_tools().empty());
|
||||
|
||||
size_t groups = 0, resolutions = 0;
|
||||
count_mixed(to, groups, resolutions);
|
||||
|
||||
INFO("layers=" << to.layer_tools().size() << " groups=" << groups);
|
||||
CHECK(groups > 0);
|
||||
}
|
||||
|
||||
TEST_CASE("Mixed filament alternates whole layers when the option is off", "[MixedFilament]")
|
||||
{
|
||||
Print print;
|
||||
Model model;
|
||||
init_print({cube(20)}, print, model, mixed_config(false));
|
||||
print.process();
|
||||
|
||||
ToolOrdering &to = const_cast<ToolOrdering &>(print.tool_ordering());
|
||||
REQUIRE(!to.layer_tools().empty());
|
||||
|
||||
size_t groups = 0, resolutions = 0;
|
||||
count_mixed(to, groups, resolutions);
|
||||
|
||||
// With splitting off the slot is realized by the deficit round-robin scheduler instead:
|
||||
// no sub-layer groups, but a per-layer resolution to one physical component.
|
||||
INFO("layers=" << to.layer_tools().size() << " resolutions=" << resolutions);
|
||||
CHECK(groups == 0);
|
||||
CHECK(resolutions > 0);
|
||||
}
|
||||
|
||||
TEST_CASE("Sub-layer splitting emits the scaled sub-heights into G-code", "[MixedFilament]")
|
||||
{
|
||||
// layer_height 0.2 split 60/40 gives sub-layers of 0.12 and 0.08. The emitter reports the
|
||||
// sub-height (not the nominal layer height) in the HEIGHT tag and scales flow to match.
|
||||
DynamicPrintConfig config = mixed_config(true);
|
||||
config.set_deserialize_strict({{"layer_height", "0.2"}, {"initial_layer_print_height", "0.2"}});
|
||||
|
||||
Print print;
|
||||
Model model;
|
||||
init_print({cube(20)}, print, model, config);
|
||||
print.process();
|
||||
const std::string gc = Slic3r::Test::gcode(print);
|
||||
|
||||
REQUIRE(!gc.empty());
|
||||
INFO("gcode bytes=" << gc.size());
|
||||
CHECK(gc.find(";HEIGHT:0.12") != std::string::npos);
|
||||
CHECK(gc.find(";HEIGHT:0.08") != std::string::npos);
|
||||
}
|
||||
|
||||
TEST_CASE("Whole-layer mixing emits only the nominal layer height", "[MixedFilament]")
|
||||
{
|
||||
DynamicPrintConfig config = mixed_config(false);
|
||||
config.set_deserialize_strict({{"layer_height", "0.2"}, {"initial_layer_print_height", "0.2"}});
|
||||
|
||||
Print print;
|
||||
Model model;
|
||||
init_print({cube(20)}, print, model, config);
|
||||
print.process();
|
||||
const std::string gc = Slic3r::Test::gcode(print);
|
||||
|
||||
REQUIRE(!gc.empty());
|
||||
// No sub-layer split, so the 60/40 sub-heights must never appear.
|
||||
CHECK(gc.find(";HEIGHT:0.12") == std::string::npos);
|
||||
CHECK(gc.find(";HEIGHT:0.08") == std::string::npos);
|
||||
}
|
||||
Reference in New Issue
Block a user