mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-08-24 18:37:39 +00:00
Expand mixed slots in by-object filament bookkeeping
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
#include "Brim.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "Extruder.hpp"
|
||||
#include "FilamentMixer.hpp"
|
||||
#include "Flow.hpp"
|
||||
#include "Geometry/ConvexHull.hpp"
|
||||
#include "I18N.hpp"
|
||||
@@ -2601,28 +2602,38 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
|
||||
if (this->config().print_sequence == PrintSequence::ByObject) {
|
||||
// Order object instances for sequential print.
|
||||
print_object_instances_ordering = sort_object_instances_by_model_order(*this);
|
||||
// A mixed slot is virtual; only its components reach a nozzle. These per-object orderings
|
||||
// are unsorted (no resolve_mixed_filaments), so expand the slots here for the grouping, the
|
||||
// unprintable sets and the slice-used lists. No-op without mixed filaments.
|
||||
// Orca: the slice-used lists stay sourced from these expanded lists rather than from the
|
||||
// sorted orderings (which may add the wipe-tower filament or seed dontcare layers
|
||||
// differently), so prints without mixed filaments keep their used-filament set; the
|
||||
// first-layer set therefore lists every component of a mixed slot, not just the one layer 0
|
||||
// resolves to.
|
||||
const auto &is_mixed = m_config.filament_is_mixed.values;
|
||||
const auto &comp_strs = m_config.filament_mixed_components.values;
|
||||
const bool has_mixed = has_any_mixed_filament(is_mixed);
|
||||
std::vector<unsigned int> first_layer_used_filaments;
|
||||
std::vector<unsigned int> used_mixed_filaments;
|
||||
std::vector<std::vector<unsigned int>> all_filaments;
|
||||
for (print_object_instance_sequential_active = print_object_instances_ordering.begin(); print_object_instance_sequential_active != print_object_instances_ordering.end(); ++print_object_instance_sequential_active) {
|
||||
tool_ordering = ToolOrdering(*(*print_object_instance_sequential_active)->print_object, initial_extruder_id);
|
||||
for (size_t idx = 0; idx < tool_ordering.layer_tools().size(); ++idx) {
|
||||
auto& layer_filament = tool_ordering.layer_tools()[idx].extruders;
|
||||
auto layer_filament = tool_ordering.layer_tools()[idx].extruders;
|
||||
if (has_mixed)
|
||||
layer_filament = expand_mixed_filaments(layer_filament, is_mixed, comp_strs);
|
||||
all_filaments.emplace_back(layer_filament);
|
||||
if (idx == 0)
|
||||
first_layer_used_filaments.insert(first_layer_used_filaments.end(), layer_filament.begin(), layer_filament.end());
|
||||
}
|
||||
used_mixed_filaments.insert(used_mixed_filaments.end(),
|
||||
tool_ordering.used_mixed_filaments().begin(), tool_ordering.used_mixed_filaments().end());
|
||||
}
|
||||
sort_remove_duplicates(first_layer_used_filaments);
|
||||
sort_remove_duplicates(used_mixed_filaments);
|
||||
auto used_filaments = collect_sorted_used_filaments(all_filaments);
|
||||
this->set_slice_used_filaments(first_layer_used_filaments,used_filaments);
|
||||
this->set_slice_used_mixed_filaments(used_mixed_filaments);
|
||||
|
||||
auto physical_unprintables = this->get_physical_unprintable_filaments(used_filaments);
|
||||
auto geometric_unprintables = this->get_geometric_unprintable_filaments();
|
||||
if (has_mixed)
|
||||
expand_mixed_slots_in_unprintables(geometric_unprintables, is_mixed, comp_strs);
|
||||
auto filament_unprintable_volumes = this->get_filament_unprintable_flow(used_filaments);
|
||||
// Selector (per-layer regroup) prints skip the static grouping: their print-wide result
|
||||
// is stitched from the per-object plans after the ordering loop below.
|
||||
@@ -2674,6 +2685,7 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
|
||||
std::vector<std::vector<int>> nozzle_map_per_layer;
|
||||
std::vector<std::vector<unsigned int>> stitched_layer_filaments;
|
||||
print_object_instance_sequential_active = print_object_instances_ordering.begin();
|
||||
std::vector<unsigned int> used_mixed_filaments;
|
||||
for (; print_object_instance_sequential_active != print_object_instances_ordering.end(); ++print_object_instance_sequential_active) {
|
||||
const PrintObject *print_object = (*print_object_instance_sequential_active)->print_object;
|
||||
if (dynamic_reorder) {
|
||||
@@ -2705,10 +2717,15 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
|
||||
if (!tool_ordering.layer_tools().empty())
|
||||
seq_mixed_resolution[print_object->id()] = tool_ordering.layer_tools().front().mixed_filament_resolution;
|
||||
}
|
||||
// Only sorted orderings have run resolve_mixed_filaments, so only they know which
|
||||
// mixed slots actually print.
|
||||
append(used_mixed_filaments, tool_ordering.used_mixed_filaments());
|
||||
if ((initial_extruder_id = tool_ordering.first_extruder()) != static_cast<unsigned int>(-1)) {
|
||||
append(printExtruders, tool_ordering.tools_for_layer(layers_to_print.front().first).extruders);
|
||||
}
|
||||
}
|
||||
sort_remove_duplicates(used_mixed_filaments);
|
||||
this->set_slice_used_mixed_filaments(used_mixed_filaments);
|
||||
if (dynamic_reorder && m_objects.size() > 1) {
|
||||
// Stitch the per-object plans into one print-wide selector result. A single-object
|
||||
// sequential print publishes (and writes back) from its own ordering instead: the
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <catch2/catch_all.hpp>
|
||||
|
||||
#include "libslic3r/GCode/ToolOrdering.hpp"
|
||||
#include "libslic3r/MultiNozzleUtils.hpp"
|
||||
#include "libslic3r/Print.hpp"
|
||||
|
||||
#include "test_helpers.hpp"
|
||||
@@ -135,3 +136,78 @@ TEST_CASE("Whole-layer mixing emits only the nominal layer height", "[MixedFilam
|
||||
CHECK(gc.find(";HEIGHT:0.12") == std::string::npos);
|
||||
CHECK(gc.find(";HEIGHT:0.08") == std::string::npos);
|
||||
}
|
||||
|
||||
TEST_CASE("By-object prints without mixed filaments keep their used-filament set", "[MixedFilament]")
|
||||
{
|
||||
// Regression guard for the mixed gate: with no mixed slot the by-object bookkeeping must
|
||||
// be untouched by this change. Object 2 prints with filament 2, so both filaments are used
|
||||
// and no mixed filament is reported.
|
||||
DynamicPrintConfig config = multifilament_config(2, {{"print_sequence", "by object"}});
|
||||
const std::vector<std::vector<ConfigBase::SetDeserializeItem>> overrides{ {}, { {"extruder", "2"} } };
|
||||
|
||||
Print print;
|
||||
Model model;
|
||||
init_print(std::vector<TriangleMesh>{cube(20), cube(20)}, print, model, config, &overrides);
|
||||
REQUIRE(print.objects().size() == 2);
|
||||
print.process();
|
||||
|
||||
CHECK(print.get_slice_used_filaments(false) == std::vector<unsigned int>{0, 1});
|
||||
CHECK(print.get_slice_used_filaments(true) == std::vector<unsigned int>{0, 1});
|
||||
CHECK(print.get_slice_used_mixed_filaments().empty());
|
||||
}
|
||||
|
||||
TEST_CASE("By-layer prints record a mixed slot's components and the slot itself", "[MixedFilament]")
|
||||
{
|
||||
// Control for the by-object case below: the by-layer path publishes the physical
|
||||
// components (0-based 0 and 1) as used filaments and the mixed slot (config index 2) as
|
||||
// a used mixed filament. By-object prints must report exactly the same.
|
||||
Print print;
|
||||
Model model;
|
||||
init_print({cube(20)}, print, model, mixed_config(false));
|
||||
print.process();
|
||||
|
||||
CHECK(print.get_slice_used_filaments(false) == std::vector<unsigned int>{0, 1});
|
||||
CHECK(print.get_slice_used_mixed_filaments() == std::vector<unsigned int>{2});
|
||||
}
|
||||
|
||||
TEST_CASE("By-object prints expand a mixed slot to its components in the slice bookkeeping", "[MixedFilament]")
|
||||
{
|
||||
// Sequential prints build their filament lists from unsorted per-object orderings, which
|
||||
// still carry the virtual slot (config index 2). The slice-used sets and the published
|
||||
// grouping result must see the physical components 0 and 1 instead, and the slot itself
|
||||
// must still be reported as a used mixed filament — exactly what the by-layer path yields.
|
||||
DynamicPrintConfig config = mixed_config(false);
|
||||
config.set_deserialize_strict({{"print_sequence", "by object"}});
|
||||
|
||||
Print print;
|
||||
Model model;
|
||||
init_print({cube(20), cube(20)}, print, model, config);
|
||||
REQUIRE(print.objects().size() == 2);
|
||||
print.process();
|
||||
|
||||
const std::vector<unsigned int> components{0, 1};
|
||||
CHECK(print.get_slice_used_filaments(false) == components);
|
||||
CHECK(print.get_slice_used_filaments(true) == components);
|
||||
CHECK(print.get_slice_used_mixed_filaments() == std::vector<unsigned int>{2});
|
||||
|
||||
auto group_result = print.get_layered_nozzle_group_result();
|
||||
REQUIRE(group_result != nullptr);
|
||||
CHECK(group_result->get_used_filaments() == components);
|
||||
}
|
||||
|
||||
TEST_CASE("By-object G-code lists a mixed slot's components in the filament header", "[MixedFilament]")
|
||||
{
|
||||
DynamicPrintConfig config = mixed_config(false);
|
||||
config.set_deserialize_strict({{"print_sequence", "by object"}});
|
||||
|
||||
Print print;
|
||||
Model model;
|
||||
init_print({cube(20), cube(20)}, print, model, config);
|
||||
const std::string gc = Slic3r::Test::gcode(print);
|
||||
|
||||
REQUIRE(!gc.empty());
|
||||
// The header names the filaments that must be loaded (components 1 and 2, 1-based),
|
||||
// never the virtual slot 3.
|
||||
CHECK(gc.find("; filament: 1,2\n") != std::string::npos);
|
||||
CHECK(gc.find("; filament: 3") == std::string::npos);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user