mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-07-20 17:32:26 +00:00
feat(slicing): enforce filament flow-type restrictions in auto grouping
Filament grouping already consumed per-filament forbidden nozzle volume types, but every call site passed an empty map, so a variant-restricted filament (e.g. one limited to "Direct Drive TPU High Flow") could be auto-grouped onto an incompatible nozzle flow type on multi-variant printers. - add convert_to_nvt_type() to parse extruder variant strings - add Print::get_filament_unprintable_flow(): forbidden volume types = printer extruder variants minus the filament's declared variants; filaments declaring no variants stay unrestricted - feed the map into grouping at the by-object path (Print.cpp) and all six mapping/planning sites in reorder_extruders_for_minimum_flush_volume - unit-test the string parser Non-restricted configurations produce an empty map, so existing printers' grouping and g-code are unchanged.
This commit is contained in:
@@ -1944,6 +1944,7 @@ void ToolOrdering::reorder_extruders_for_minimum_flush_volume(bool reorder_first
|
||||
|
||||
std::vector<std::set<int>>geometric_unprintables = m_print->get_geometric_unprintable_filaments();
|
||||
std::vector<std::set<int>>physical_unprintables = m_print->get_physical_unprintable_filaments(used_filaments);
|
||||
auto filament_unprintable_volumes = m_print->get_filament_unprintable_flow(used_filaments);
|
||||
|
||||
filament_maps = m_print->get_filament_maps();
|
||||
map_mode = m_print->get_filament_map_mode();
|
||||
@@ -2012,7 +2013,7 @@ void ToolOrdering::reorder_extruders_for_minimum_flush_volume(bool reorder_first
|
||||
// per-layer maps in a selector (4-arg create) result — which sets support_dynamic_nozzle_map and
|
||||
// lights the GCode per-layer hotend/nozzle placeholders. filament_sequences is produced here, so
|
||||
// the static reorder below is skipped for this branch.
|
||||
auto grouping_context = build_filament_group_context(m_print, layer_filaments, physical_unprintables, geometric_unprintables, {}, FilamentMapMode::fmmAutoForFlush,
|
||||
auto grouping_context = build_filament_group_context(m_print, layer_filaments, physical_unprintables, geometric_unprintables, filament_unprintable_volumes, FilamentMapMode::fmmAutoForFlush,
|
||||
m_initial_nozzle_status.get_nozzle_filament_map());
|
||||
// The time estimator's per-extruder print times are global and do not apply to per-range
|
||||
// grouping.
|
||||
@@ -2020,7 +2021,7 @@ void ToolOrdering::reorder_extruders_for_minimum_flush_volume(bool reorder_first
|
||||
|
||||
m_nozzle_status = m_initial_nozzle_status;
|
||||
auto dynamic_plan_res = plan_filament_mapping_and_order_by_combo_ranges(m_print, grouping_context, get_custom_seq, FilamentMapMode::fmmAutoForFlush,
|
||||
physical_unprintables, geometric_unprintables, {}, &m_nozzle_status);
|
||||
physical_unprintables, geometric_unprintables, filament_unprintable_volumes, &m_nozzle_status);
|
||||
|
||||
std::vector<std::vector<int>> nozzle_map_per_layer;
|
||||
for (auto& res : dynamic_plan_res) {
|
||||
@@ -2042,7 +2043,7 @@ void ToolOrdering::reorder_extruders_for_minimum_flush_volume(bool reorder_first
|
||||
}
|
||||
// only check and map in sequence mode, in by object mode, we check the map in print.cpp
|
||||
else if (print_config->print_sequence != PrintSequence::ByObject || m_print->objects().size() == 1) {
|
||||
grouping_result = ToolOrdering::get_recommended_filament_maps(layer_filaments, m_print, map_mode, physical_unprintables, geometric_unprintables);
|
||||
grouping_result = ToolOrdering::get_recommended_filament_maps(layer_filaments, m_print, map_mode, physical_unprintables, geometric_unprintables, filament_unprintable_volumes);
|
||||
std::vector<int> derived_maps = grouping_result.get_extruder_map(false); // 1-based extruder map
|
||||
|
||||
if (map_mode < FilamentMapMode::fmmManual) {
|
||||
@@ -2145,12 +2146,12 @@ void ToolOrdering::reorder_extruders_for_minimum_flush_volume(bool reorder_first
|
||||
// is_dynamic_group_reorder() implies filament_map_mode == fmmAutoForFlush, contradicting
|
||||
// this map_mode != fmmAutoForFlush guard, so this sub-branch is unreachable under the
|
||||
// current predicate. It is provably inert.
|
||||
auto best_context = build_filament_group_context(m_print, layer_filaments, physical_unprintables, geometric_unprintables, {}, FilamentMapMode::fmmAutoForFlush,
|
||||
auto best_context = build_filament_group_context(m_print, layer_filaments, physical_unprintables, geometric_unprintables, filament_unprintable_volumes, FilamentMapMode::fmmAutoForFlush,
|
||||
m_initial_nozzle_status.get_nozzle_filament_map());
|
||||
best_context.speed_info.group_with_time = false;
|
||||
MultiNozzleUtils::NozzleStatusRecorder best_nozzle_status = m_initial_nozzle_status;
|
||||
auto best_plan = plan_filament_mapping_and_order_by_combo_ranges(m_print, best_context, get_custom_seq, FilamentMapMode::fmmAutoForFlush,
|
||||
physical_unprintables, geometric_unprintables, {}, &best_nozzle_status);
|
||||
physical_unprintables, geometric_unprintables, filament_unprintable_volumes, &best_nozzle_status);
|
||||
std::vector<std::vector<int>> best_nozzle_map_per_layer;
|
||||
for (auto& res : best_plan) {
|
||||
best_sequences.emplace_back(cast<unsigned int>(res.fil_order));
|
||||
@@ -2164,7 +2165,7 @@ void ToolOrdering::reorder_extruders_for_minimum_flush_volume(bool reorder_first
|
||||
// Best-for-flush grouping (nozzle-aware result). The extruder-level map fed to the flush
|
||||
// reorder is derived exactly as before, so best_sequences (and the flush weight) are
|
||||
// identical; only flush_filament_change_count is now charged per physical nozzle.
|
||||
auto best_group_result = get_recommended_filament_maps(layer_filaments, m_print, fmmAutoForFlush, physical_unprintables, geometric_unprintables);
|
||||
auto best_group_result = get_recommended_filament_maps(layer_filaments, m_print, fmmAutoForFlush, physical_unprintables, geometric_unprintables, filament_unprintable_volumes);
|
||||
std::vector<int> best_maps = best_group_result.get_extruder_map();
|
||||
reorder_filaments_for_minimum_flush_volume(
|
||||
filament_lists,
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <numeric>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <sstream>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
@@ -2504,13 +2505,14 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
|
||||
|
||||
auto physical_unprintables = this->get_physical_unprintable_filaments(used_filaments);
|
||||
auto geometric_unprintables = this->get_geometric_unprintable_filaments();
|
||||
auto filament_unprintable_volumes = this->get_filament_unprintable_flow(used_filaments);
|
||||
std::vector<int>filament_maps = this->get_filament_maps();
|
||||
auto map_mode = get_filament_map_mode();
|
||||
// get recommended filament map
|
||||
if (map_mode < FilamentMapMode::fmmManual) {
|
||||
// Grouping returns a nozzle-aware result; the 1-based extruder map
|
||||
// for the by-object path is derived from it.
|
||||
auto grouping_result = ToolOrdering::get_recommended_filament_maps(all_filaments, this, map_mode, physical_unprintables, geometric_unprintables);
|
||||
auto grouping_result = ToolOrdering::get_recommended_filament_maps(all_filaments, this, map_mode, physical_unprintables, geometric_unprintables, filament_unprintable_volumes);
|
||||
auto derived_maps = grouping_result.get_extruder_map(false);
|
||||
if (!derived_maps.empty()) {
|
||||
filament_maps = derived_maps;
|
||||
@@ -3280,6 +3282,41 @@ std::vector<std::set<int>> Print::get_physical_unprintable_filaments(const std::
|
||||
return physical_unprintables;
|
||||
}
|
||||
|
||||
std::map<int, std::set<NozzleVolumeType>> Print::get_filament_unprintable_flow(const std::vector<unsigned int> &used_filaments) const
|
||||
{
|
||||
std::map<int, std::set<NozzleVolumeType>> ret;
|
||||
std::vector<std::string> extruder_variant_list = m_config.printer_extruder_variant.values;
|
||||
// A filament that declares no extruder variants carries no flow restriction.
|
||||
const ConfigOptionStrings *filament_variant_opt = m_ori_full_print_config.option<ConfigOptionStrings>("filament_extruder_variant");
|
||||
if (filament_variant_opt == nullptr)
|
||||
return ret;
|
||||
std::vector<std::string> filament_variant_list = filament_variant_opt->values;
|
||||
std::vector<int> filament_self_index;
|
||||
if (!m_ori_full_print_config.has("filament_self_index"))
|
||||
filament_self_index.resize(filament_variant_list.size(), 1);
|
||||
else
|
||||
filament_self_index = m_ori_full_print_config.option<ConfigOptionInts>("filament_self_index")->values;
|
||||
std::unordered_set<int> used_fils_set(used_filaments.begin(), used_filaments.end());
|
||||
|
||||
std::unordered_map<int, std::set<NozzleVolumeType>> filament_variant_map;
|
||||
for(int i = 0; i < filament_variant_list.size(); ++i){
|
||||
NozzleVolumeType volume = convert_to_nvt_type(filament_variant_list[i]);
|
||||
if(volume != nvtHybrid) filament_variant_map[filament_self_index[i]].insert(volume);
|
||||
}
|
||||
|
||||
for (auto iter : filament_variant_map) {
|
||||
int fil_idx = iter.first - 1;
|
||||
if (used_fils_set.find(fil_idx) == used_fils_set.end()) continue;
|
||||
const std::set<NozzleVolumeType> &volumes = iter.second;
|
||||
for (int exd_idx = 0; exd_idx < extruder_variant_list.size(); ++exd_idx) {
|
||||
auto exd_volume = convert_to_nvt_type(extruder_variant_list[exd_idx]);
|
||||
assert(exd_volume != nvtHybrid);
|
||||
if (volumes.find(exd_volume) == volumes.end() && exd_volume != nvtHybrid) ret[fil_idx].insert(exd_volume);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
std::vector<double> Print::get_extruder_printable_height() const
|
||||
{
|
||||
|
||||
@@ -1057,6 +1057,18 @@ public:
|
||||
*/
|
||||
std::vector<std::set<int>> get_physical_unprintable_filaments(const std::vector<unsigned int>& used_filaments) const;
|
||||
|
||||
/**
|
||||
* @brief Determines the forbidden nozzle volume types for each used filament
|
||||
*
|
||||
* A filament may declare the extruder variants it supports. Every volume type offered by the
|
||||
* printer's extruders that the filament does not support is forbidden for that filament.
|
||||
* Hybrid volumes are ignored on both sides, and filaments declaring no variants are unrestricted.
|
||||
*
|
||||
* @param used_filaments Totally used filaments when slicing
|
||||
* @return A map from used filament index to the set of nozzle volume types it cannot print on
|
||||
*/
|
||||
std::map<int, std::set<NozzleVolumeType>> get_filament_unprintable_flow(const std::vector<unsigned int> &used_filaments) const;
|
||||
|
||||
std::vector<double> get_extruder_printable_height() const;
|
||||
std::vector<Polygons> get_extruder_printable_polygons() const;
|
||||
std::vector<Polygons> get_extruder_unprintable_polygons() const;
|
||||
|
||||
@@ -689,6 +689,35 @@ std::vector<std::string> save_extruder_ams_count_to_string(const std::vector<std
|
||||
return extruder_ams_count_str;
|
||||
}
|
||||
|
||||
// Maps a full extruder variant string such as "Direct Drive High Flow" to its
|
||||
// NozzleVolumeType: the extruder-type prefix is stripped, the remainder is trimmed
|
||||
// and looked up as a volume-type name. Returns nvtHybrid when the string cannot
|
||||
// be parsed.
|
||||
NozzleVolumeType convert_to_nvt_type(const std::string &variant_str) {
|
||||
const auto &ext_types = ConfigOptionEnum<ExtruderType>::get_enum_names();
|
||||
|
||||
auto trim = [](std::string &s) {
|
||||
s.erase(s.find_last_not_of(" \t\r\n") + 1);
|
||||
s.erase(0, s.find_first_not_of(" \t\r\n"));
|
||||
};
|
||||
|
||||
for (auto ext_type : ext_types) {
|
||||
size_t pos = variant_str.find(ext_type);
|
||||
if (pos == std::string::npos)
|
||||
continue;
|
||||
|
||||
std::string result = variant_str;
|
||||
result.erase(pos, ext_type.size());
|
||||
trim(result);
|
||||
|
||||
auto iter = s_keys_map_NozzleVolumeType.find(result);
|
||||
if (iter != s_keys_map_NozzleVolumeType.end())
|
||||
return NozzleVolumeType(iter->second);
|
||||
}
|
||||
|
||||
return nvtHybrid;
|
||||
}
|
||||
|
||||
// Parses the extruder_nozzle_stats option: one "|"-separated token list per
|
||||
// extruder, each token
|
||||
// "<volume_type_name>#<count>". Empty string => no per-type stats for that extruder.
|
||||
|
||||
@@ -568,6 +568,9 @@ extern bool is_filament_extruder_override_key(const std::string &opt_key);
|
||||
extern std::vector<std::map<int, int>> get_extruder_ams_count(const std::vector<std::string> &strs);
|
||||
extern std::vector<std::string> save_extruder_ams_count_to_string(const std::vector<std::map<int, int>> &extruder_ams_count);
|
||||
|
||||
// maps a full extruder variant string (e.g. "Direct Drive High Flow") to its NozzleVolumeType; nvtHybrid if unparsable
|
||||
extern NozzleVolumeType convert_to_nvt_type(const std::string& variant_str);
|
||||
|
||||
// for parse extruder_nozzle_stats (per-extruder physical nozzle inventory by volume type)
|
||||
extern std::vector<std::map<NozzleVolumeType, int>> get_extruder_nozzle_stats(const std::vector<std::string> &strs);
|
||||
extern std::vector<std::string> save_extruder_nozzle_stats_to_string(const std::vector<std::map<NozzleVolumeType, int>> &extruder_nozzle_stats);
|
||||
|
||||
@@ -21,6 +21,7 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_polygon.cpp
|
||||
test_mutable_polygon.cpp
|
||||
test_mutable_priority_queue.cpp
|
||||
test_nozzle_volume_type.cpp
|
||||
test_stl.cpp
|
||||
test_meshboolean.cpp
|
||||
test_marchingsquares.cpp
|
||||
|
||||
31
tests/libslic3r/test_nozzle_volume_type.cpp
Normal file
31
tests/libslic3r/test_nozzle_volume_type.cpp
Normal file
@@ -0,0 +1,31 @@
|
||||
#include <catch2/catch_all.hpp>
|
||||
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
TEST_CASE("convert_to_nvt_type maps extruder variant strings to nozzle volume types", "[Config]")
|
||||
{
|
||||
SECTION("Direct Drive variants") {
|
||||
REQUIRE(convert_to_nvt_type("Direct Drive Standard") == nvtStandard);
|
||||
REQUIRE(convert_to_nvt_type("Direct Drive High Flow") == nvtHighFlow);
|
||||
REQUIRE(convert_to_nvt_type("Direct Drive TPU High Flow") == nvtTPUHighFlow);
|
||||
}
|
||||
|
||||
SECTION("Bowden variants") {
|
||||
REQUIRE(convert_to_nvt_type("Bowden Standard") == nvtStandard);
|
||||
REQUIRE(convert_to_nvt_type("Bowden High Flow") == nvtHighFlow);
|
||||
}
|
||||
|
||||
SECTION("Unparsable strings fall back to hybrid") {
|
||||
REQUIRE(convert_to_nvt_type("Unknown Extruder") == nvtHybrid);
|
||||
REQUIRE(convert_to_nvt_type("") == nvtHybrid);
|
||||
REQUIRE(convert_to_nvt_type("High Flow") == nvtHybrid);
|
||||
REQUIRE(convert_to_nvt_type("Direct Drive") == nvtHybrid);
|
||||
}
|
||||
|
||||
SECTION("Whitespace around the volume-type remainder is trimmed") {
|
||||
REQUIRE(convert_to_nvt_type("Direct Drive High Flow ") == nvtHighFlow);
|
||||
REQUIRE(convert_to_nvt_type(" Bowden Standard") == nvtStandard);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user