mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-09 18:27:00 +00:00
feat(engine): write per-variant filament values back for selector regroups
When the per-layer filament selector (enable_filament_dynamic_map) migrates a filament across nozzle variants (e.g. Standard -> High Flow), the config write-back only stored the derived extruder map; every per-variant filament value (retraction, nozzle temperature, flow, flush...) kept the numbers resolved from the pre-slice static mapping. Now both dynamic write-back sites (the by-layer branch and the sequential stitch) branch on the result's dynamic support. Migrating results run a mixed-filament expansion that regathers every filament_options_with_variant key from the pristine per-variant superset, giving a migrating filament one config slot per (extruder type x nozzle volume type) it lands on - filament_self_index, filament_extruder_variant, and all value arrays grow in lockstep - and recompute the retract overrides with per-slot machine indices so a nil slot falls back to its own variant's machine value. Non-migrating dynamic results take the merged three-map write-back so re-applies reproduce from the written maps. Unrouted filaments resolve from the result's own default map, so slot resolution never depends on filament_map round-tripping through the plate config. Print::apply reproduces the identical expansion from the persisted group result (shared dedupe helper, expansion function, and slot indices on both sides): the expanded keys sit in the psWipeTower / psGCodeExport invalidate lists, so without the reproduction every re-apply after a selector slice would diff non-empty and permanently invalidate. cal_non_support_filaments now resolves the extruder per layer from the published result for dynamic groupings. filament_map_2 keeps its apply-time static derivation; nothing on the dynamic path reads it (the per-slot machine indices key the override merge), and per-(extruder x volume-type) machine limits in the g-code processor remain a documented follow-up. Every change is gated behind is_dynamic_group_reorder() or a persisted result with dynamic support; no profile sets the flag, so the static fleet's instruction stream is unchanged (20/20 pinned-slice byte gate identical, incl. the sequential repro sliced twice, deterministic). Tests: expansion unit coverage (migrating slots, unrouted fallback via the default map, mis-sized volume map ignored, nullable retract keys in lockstep, slot machine index layout), an end-to-end stub-driven write-back asserting expanded slots, per-layer config-index resolution, the override merge incl. the nil-slot variant fallback, and re-apply stability, plus a real selector slice staying valid across re-apply. Suites green (libslic3r 48987/168, fff_print 633/60).
This commit is contained in:
@@ -1063,12 +1063,24 @@ bool ToolOrdering::cal_non_support_filaments(const PrintConfig &config,
|
|||||||
int find_count = 0;
|
int find_count = 0;
|
||||||
int find_first_filaments_count = 0;
|
int find_first_filaments_count = 0;
|
||||||
bool has_non_support = has_non_support_filament(config);
|
bool has_non_support = has_non_support_filament(config);
|
||||||
for (const LayerTools &layer_tool : m_layer_tools) {
|
// The selector can move a filament between extruders per layer; resolve the extruder from
|
||||||
for (const unsigned int &filament : layer_tool.extruders) {
|
// the published result then, so the first filament attributed to an extruder is one it
|
||||||
|
// actually prints there. Static results keep the cross-layer filament_map arithmetic.
|
||||||
|
const bool use_dynamic_map = m_nozzle_group_result.is_support_dynamic_nozzle_map() && m_nozzle_group_result.get_layer_count() > 0;
|
||||||
|
auto extruder_for_filament = [&](unsigned int filament, size_t layer_idx) -> int {
|
||||||
|
if (use_dynamic_map)
|
||||||
|
return m_nozzle_group_result.get_extruder_id(static_cast<int>(filament), static_cast<int>(layer_idx));
|
||||||
|
return config.filament_map.values[filament] - 1;
|
||||||
|
};
|
||||||
|
for (size_t layer_idx = 0; layer_idx < m_layer_tools.size(); ++layer_idx) {
|
||||||
|
for (const unsigned int &filament : m_layer_tools[layer_idx].extruders) {
|
||||||
//check first filament
|
//check first filament
|
||||||
if (!config.filament_map.values.empty() && initial_filaments[config.filament_map.values[filament] - 1] == -1) {
|
if (!config.filament_map.values.empty()) {
|
||||||
initial_filaments[config.filament_map.values[filament] - 1] = filament;
|
const int extruder_id = extruder_for_filament(filament, layer_idx);
|
||||||
find_first_filaments_count++;
|
if (extruder_id >= 0 && extruder_id < static_cast<int>(initial_filaments.size()) && initial_filaments[extruder_id] == -1) {
|
||||||
|
initial_filaments[extruder_id] = filament;
|
||||||
|
find_first_filaments_count++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (has_non_support) {
|
if (has_non_support) {
|
||||||
@@ -1083,8 +1095,9 @@ bool ToolOrdering::cal_non_support_filaments(const PrintConfig &config,
|
|||||||
if (config.filament_map.values.empty())
|
if (config.filament_map.values.empty())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (initial_non_support_filaments[config.filament_map.values[filament] - 1] == -1) {
|
const int extruder_id = extruder_for_filament(filament, layer_idx);
|
||||||
initial_non_support_filaments[config.filament_map.values[filament] - 1] = filament;
|
if (extruder_id >= 0 && extruder_id < static_cast<int>(initial_non_support_filaments.size()) && initial_non_support_filaments[extruder_id] == -1) {
|
||||||
|
initial_non_support_filaments[extruder_id] = filament;
|
||||||
find_count++;
|
find_count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2080,17 +2093,15 @@ void ToolOrdering::reorder_extruders_for_minimum_flush_volume(bool reorder_first
|
|||||||
auto result = MultiNozzleUtils::LayeredNozzleGroupResult::create(nozzle_map_per_layer, grouping_context.nozzle_info.nozzle_list, used_filaments, filament_sequences);
|
auto result = MultiNozzleUtils::LayeredNozzleGroupResult::create(nozzle_map_per_layer, grouping_context.nozzle_info.nozzle_list, used_filaments, filament_sequences);
|
||||||
grouping_result = result ? *result : MultiNozzleUtils::LayeredNozzleGroupResult();
|
grouping_result = result ? *result : MultiNozzleUtils::LayeredNozzleGroupResult();
|
||||||
|
|
||||||
// Derive the extruder-level map for the stats path + config write-back.
|
// Derive the extruder-level map for the stats path; the write-back resolves the
|
||||||
// Orca: the dynamic (per-layer) result carries no single volume/nozzle map, so only the
|
// per-variant slots from the full grouping result itself.
|
||||||
// extruder map is written back here; the full per-nozzle config write-back
|
|
||||||
// (a dedicated dynamic-map path) is a follow-up behind the dev flag.
|
|
||||||
std::vector<int> derived_maps = grouping_result.get_extruder_map(false); // 1-based
|
std::vector<int> derived_maps = grouping_result.get_extruder_map(false); // 1-based
|
||||||
if (!derived_maps.empty()) {
|
if (!derived_maps.empty()) {
|
||||||
filament_maps = derived_maps;
|
filament_maps = derived_maps;
|
||||||
// A sequential per-object plan must not write its own map: the objects' plans are
|
// A sequential per-object plan must not write its own map: the objects' plans are
|
||||||
// stitched print-wide afterwards and written back once from there.
|
// stitched print-wide afterwards and written back once from there.
|
||||||
if (not_sequential)
|
if (not_sequential)
|
||||||
m_print->update_filament_maps_to_config(filament_maps);
|
m_print->update_to_config_by_nozzle_group_result(grouping_result);
|
||||||
}
|
}
|
||||||
std::transform(filament_maps.begin(), filament_maps.end(), filament_maps.begin(), [](int value) { return value - 1; });
|
std::transform(filament_maps.begin(), filament_maps.end(), filament_maps.begin(), [](int value) { return value - 1; });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2603,12 +2603,7 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
|
|||||||
stitched_layer_filaments, used_filaments, physical_unprintables,
|
stitched_layer_filaments, used_filaments, physical_unprintables,
|
||||||
geometric_unprintables, filament_unprintable_volumes);
|
geometric_unprintables, filament_unprintable_volumes);
|
||||||
this->set_nozzle_group_result(std::make_shared<MultiNozzleUtils::LayeredNozzleGroupResult>(stitched));
|
this->set_nozzle_group_result(std::make_shared<MultiNozzleUtils::LayeredNozzleGroupResult>(stitched));
|
||||||
// Orca: the dynamic (per-layer) result carries no single volume/nozzle map, so only
|
update_to_config_by_nozzle_group_result(stitched);
|
||||||
// the extruder map is written back (matching the by-layer selector branch); the full
|
|
||||||
// per-nozzle config write-back is a follow-up behind the dev flag.
|
|
||||||
std::vector<int> derived_maps = stitched.get_extruder_map(false); // 1-based
|
|
||||||
if (!derived_maps.empty())
|
|
||||||
update_filament_maps_to_config(derived_maps);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -3370,6 +3365,133 @@ void Print::update_filament_maps_to_config(std::vector<int> f_maps, std::vector<
|
|||||||
m_has_auto_filament_map_result = true;
|
m_has_auto_filament_map_result = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Print::collect_filament_variant_uses(const MultiNozzleUtils::LayeredNozzleGroupResult& group_result,
|
||||||
|
const DynamicPrintConfig& config,
|
||||||
|
std::unordered_map<int, std::vector<FilamentVariantUse>>& uses) const
|
||||||
|
{
|
||||||
|
auto opt_filament_type = config.option<ConfigOptionStrings>("filament_type");
|
||||||
|
auto opt_extruder_type = dynamic_cast<const ConfigOptionEnumsGeneric*>(config.option("extruder_type"));
|
||||||
|
if (!opt_filament_type || !opt_extruder_type)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const size_t filament_count = opt_filament_type->values.size();
|
||||||
|
const size_t extruder_count = opt_extruder_type->values.size();
|
||||||
|
auto add_use = [&](std::set<FilamentVariantUse> &variant_set, const MultiNozzleUtils::NozzleInfo &nozzle) {
|
||||||
|
// Orca: a persisted result can outlive a printer swap; never index the extruder
|
||||||
|
// arrays with a stale nozzle record.
|
||||||
|
if (nozzle.extruder_id < 0 || static_cast<size_t>(nozzle.extruder_id) >= extruder_count)
|
||||||
|
return;
|
||||||
|
FilamentVariantUse use;
|
||||||
|
use.extruder_type = static_cast<ExtruderType>(opt_extruder_type->get_at(nozzle.extruder_id));
|
||||||
|
use.nozzle_volume_type = nozzle.volume_type;
|
||||||
|
use.extruder_id = nozzle.extruder_id;
|
||||||
|
variant_set.insert(use);
|
||||||
|
};
|
||||||
|
for (size_t f_index = 0; f_index < filament_count; ++f_index) {
|
||||||
|
std::set<FilamentVariantUse> variant_set;
|
||||||
|
for (const MultiNozzleUtils::NozzleInfo &nozzle : group_result.get_nozzles_for_filament(static_cast<int>(f_index)))
|
||||||
|
add_use(variant_set, nozzle);
|
||||||
|
// A filament the plan never routes (not printed) still needs a deterministic slot: take
|
||||||
|
// its default-map assignment from the result itself, so both the slice-time write-back
|
||||||
|
// and the apply-time reproduction resolve the same slot even when the surrounding
|
||||||
|
// filament_map has not round-tripped through the plate config in between.
|
||||||
|
if (variant_set.empty()) {
|
||||||
|
if (auto default_nozzle = group_result.get_nozzle_for_filament(static_cast<int>(f_index), -1); default_nozzle.has_value())
|
||||||
|
add_use(variant_set, *default_nozzle);
|
||||||
|
}
|
||||||
|
// Filaments still without a variant stay absent from the map: the slot rebuild then
|
||||||
|
// resolves them from their static filament_map / filament_volume_map assignment.
|
||||||
|
if (!variant_set.empty())
|
||||||
|
uses[static_cast<int>(f_index)] = std::vector<FilamentVariantUse>(variant_set.begin(), variant_set.end());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Print::update_to_config_by_nozzle_group_result(const MultiNozzleUtils::LayeredNozzleGroupResult& group_result)
|
||||||
|
{
|
||||||
|
std::vector<int> derived_maps = group_result.get_extruder_map(false); // 1-based
|
||||||
|
if (derived_maps.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!group_result.is_support_dynamic_nozzle_map()) {
|
||||||
|
// No filament actually migrated between nozzles, so the plan reduces to a single
|
||||||
|
// grouping: write all maps like the static paths do, and the next apply re-derives
|
||||||
|
// identical slots from the written maps.
|
||||||
|
std::vector<int> base_filament_map = m_config.filament_map.values;
|
||||||
|
if (base_filament_map.size() != derived_maps.size())
|
||||||
|
base_filament_map.assign(derived_maps.size(), 1);
|
||||||
|
std::vector<int> base_volume_map = m_config.filament_volume_map.values;
|
||||||
|
if (base_volume_map.size() != derived_maps.size())
|
||||||
|
base_volume_map.assign(derived_maps.size(), (int)nvtStandard);
|
||||||
|
const std::vector<unsigned int> used_filaments = group_result.get_used_filaments();
|
||||||
|
update_filament_maps_to_config(FilamentGroupUtils::update_used_filament_values(base_filament_map, derived_maps, used_filaments),
|
||||||
|
FilamentGroupUtils::update_used_filament_values(base_volume_map, group_result.get_volume_map(), used_filaments),
|
||||||
|
group_result.get_nozzle_map());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Orca: keep the coarse per-filament extruder map published even though the per-layer truth
|
||||||
|
// lives in the grouping result: the pre-export consumers, the plate read-back after slicing
|
||||||
|
// and the preview panel all key on filament_map. The write is direct — the full map
|
||||||
|
// write-back's single-slot rebuild would undo the per-variant expansion below.
|
||||||
|
m_ori_full_print_config.option<ConfigOptionInts>("filament_map", true)->values = derived_maps;
|
||||||
|
m_config.filament_map.values = derived_maps;
|
||||||
|
|
||||||
|
std::unordered_map<int, std::vector<FilamentVariantUse>> filament_variant_uses;
|
||||||
|
if (!collect_filament_variant_uses(group_result, m_ori_full_print_config, filament_variant_uses)) {
|
||||||
|
// Degenerate config (no filament/extruder typing): fall back to the single-slot
|
||||||
|
// write-back so the maps and overrides stay coherent.
|
||||||
|
update_filament_maps_to_config(derived_maps);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int extruder_count = 1, extruder_volume_type_count = 1;
|
||||||
|
m_ori_full_print_config.support_different_extruders(extruder_count);
|
||||||
|
std::vector<std::vector<NozzleVolumeType>> nozzle_volume_types;
|
||||||
|
extruder_volume_type_count = m_ori_full_print_config.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types);
|
||||||
|
|
||||||
|
// Note: filament_map_2 keeps its apply-time (static) derivation here; the per-slot machine
|
||||||
|
// indices below key the override merge instead, so nothing on this path reads it. Its other
|
||||||
|
// consumers are the three-map write-back (which recomputes it) and the diagnostic copy in
|
||||||
|
// the g-code header; per-(extruder x volume-type) machine limits in the g-code processor
|
||||||
|
// remain a follow-up (see GCodeProcessor::get_axis_max_feedrate).
|
||||||
|
m_full_print_config = m_ori_full_print_config;
|
||||||
|
std::set<std::string> filament_keys = filament_options_with_variant;
|
||||||
|
filament_keys.insert("filament_self_index");
|
||||||
|
std::vector<int> slot_machine_indices;
|
||||||
|
m_full_print_config.update_filament_config_values_for_multiple_extruders(m_full_print_config, filament_variant_uses,
|
||||||
|
extruder_count, extruder_volume_type_count,
|
||||||
|
filament_keys, "filament_self_index", "filament_extruder_variant",
|
||||||
|
&slot_machine_indices);
|
||||||
|
|
||||||
|
const std::vector<std::string> &extruder_retract_keys = print_config_def.extruder_retract_keys();
|
||||||
|
const std::string filament_prefix = "filament_";
|
||||||
|
t_config_option_keys print_diff;
|
||||||
|
DynamicPrintConfig filament_overrides;
|
||||||
|
for (auto& opt_key: extruder_retract_keys)
|
||||||
|
{
|
||||||
|
const ConfigOption *opt_new_filament = m_full_print_config.option(filament_prefix + opt_key);
|
||||||
|
const ConfigOption *opt_new_machine = m_full_print_config.option(opt_key);
|
||||||
|
const ConfigOption *opt_old_machine = m_config.option(opt_key);
|
||||||
|
|
||||||
|
if (opt_new_filament)
|
||||||
|
compute_filament_override_value(opt_key, opt_old_machine, opt_new_machine, opt_new_filament, m_full_print_config, print_diff, filament_overrides, slot_machine_indices);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
t_config_option_keys keys(filament_options_with_variant.begin(), filament_options_with_variant.end());
|
||||||
|
keys.push_back("filament_self_index");
|
||||||
|
m_config.apply_only(m_full_print_config, keys, true);
|
||||||
|
}
|
||||||
|
if (!print_diff.empty()) {
|
||||||
|
m_placeholder_parser.apply_config(filament_overrides);
|
||||||
|
m_config.apply(filament_overrides);
|
||||||
|
}
|
||||||
|
|
||||||
|
update_filament_self_index_cache();
|
||||||
|
m_has_auto_filament_map_result = true;
|
||||||
|
}
|
||||||
|
|
||||||
void Print::apply_config_for_render(const DynamicConfig &config)
|
void Print::apply_config_for_render(const DynamicConfig &config)
|
||||||
{
|
{
|
||||||
m_config.apply(config);
|
m_config.apply(config);
|
||||||
|
|||||||
@@ -1006,6 +1006,13 @@ public:
|
|||||||
const ToolOrdering& tool_ordering() const { return m_tool_ordering; }
|
const ToolOrdering& tool_ordering() const { return m_tool_ordering; }
|
||||||
|
|
||||||
void update_filament_maps_to_config(std::vector<int> f_maps, std::vector<int> f_volume_maps = std::vector<int>{}, std::vector<int> f_nozzle_maps = std::vector<int>{});
|
void update_filament_maps_to_config(std::vector<int> f_maps, std::vector<int> f_volume_maps = std::vector<int>{}, std::vector<int> f_nozzle_maps = std::vector<int>{});
|
||||||
|
// Write-back for a selector (per-layer planned) grouping result. When a filament actually
|
||||||
|
// migrates between nozzle variants, rebuilds the per-slot filament arrays so it holds one
|
||||||
|
// slot per variant and recomputes the extruder retract overrides against the expanded
|
||||||
|
// slots — update_filament_maps_to_config's single-slot rebuild cannot represent a
|
||||||
|
// migration. A result without migration reduces to a single grouping and takes the
|
||||||
|
// three-map write-back like the static paths.
|
||||||
|
void update_to_config_by_nozzle_group_result(const MultiNozzleUtils::LayeredNozzleGroupResult& group_result);
|
||||||
void apply_config_for_render(const DynamicConfig &config);
|
void apply_config_for_render(const DynamicConfig &config);
|
||||||
|
|
||||||
// 1 based group ids
|
// 1 based group ids
|
||||||
@@ -1242,6 +1249,15 @@ private:
|
|||||||
void _make_wipe_tower();
|
void _make_wipe_tower();
|
||||||
void finalize_first_layer_convex_hull();
|
void finalize_first_layer_convex_hull();
|
||||||
void update_filament_self_index_cache();
|
void update_filament_self_index_cache();
|
||||||
|
// Deduplicates, per filament, the (extruder type x volume type) variants the grouping
|
||||||
|
// result routes it through; filaments the plan never routes get their default-map
|
||||||
|
// assignment so the slot resolution never depends on the (mutable) filament_map. config
|
||||||
|
// must carry extruder_type; returns false when it does not. Both the slice-time write-back
|
||||||
|
// and the apply-time reproduction call this with m_ori_full_print_config so the two
|
||||||
|
// expansions resolve identical slots.
|
||||||
|
bool collect_filament_variant_uses(const MultiNozzleUtils::LayeredNozzleGroupResult& group_result,
|
||||||
|
const DynamicPrintConfig& config,
|
||||||
|
std::unordered_map<int, std::vector<FilamentVariantUse>>& uses) const;
|
||||||
|
|
||||||
// Islands of objects and their supports extruded at the 1st layer.
|
// Islands of objects and their supports extruded at the 1st layer.
|
||||||
Polygons first_layer_islands() const;
|
Polygons first_layer_islands() const;
|
||||||
|
|||||||
@@ -224,7 +224,11 @@ static t_config_option_keys print_config_diffs(
|
|||||||
const DynamicPrintConfig &new_full_config,
|
const DynamicPrintConfig &new_full_config,
|
||||||
DynamicPrintConfig &filament_overrides,
|
DynamicPrintConfig &filament_overrides,
|
||||||
int plate_index,
|
int plate_index,
|
||||||
std::vector<int>& filament_maps)
|
std::vector<int>& filament_maps,
|
||||||
|
// Per-slot machine indices when the filament arrays hold the per-variant expansion of a
|
||||||
|
// selector result (one slot per variant a filament migrates through); the per-filament
|
||||||
|
// map cannot index the expanded override arrays. Null on the single-slot path.
|
||||||
|
const std::vector<int>* dynamic_override_indices = nullptr)
|
||||||
{
|
{
|
||||||
const std::vector<std::string> &extruder_retract_keys = print_config_def.extruder_retract_keys();
|
const std::vector<std::string> &extruder_retract_keys = print_config_def.extruder_retract_keys();
|
||||||
const std::string filament_prefix = "filament_";
|
const std::string filament_prefix = "filament_";
|
||||||
@@ -240,9 +244,14 @@ static t_config_option_keys print_config_diffs(
|
|||||||
const ConfigOption *opt_new_filament = std::binary_search(extruder_retract_keys.begin(), extruder_retract_keys.end(), opt_key) ? new_full_config.option(filament_prefix + opt_key) : nullptr;
|
const ConfigOption *opt_new_filament = std::binary_search(extruder_retract_keys.begin(), extruder_retract_keys.end(), opt_key) ? new_full_config.option(filament_prefix + opt_key) : nullptr;
|
||||||
|
|
||||||
if (opt_new_filament != nullptr) {
|
if (opt_new_filament != nullptr) {
|
||||||
std::vector<int> filament_map_indices(filament_maps.size(), 0);
|
std::vector<int> filament_map_indices;
|
||||||
for (int i = 0; i < filament_maps.size(); i++)
|
if (dynamic_override_indices)
|
||||||
filament_map_indices[i] = filament_maps[i] - 1;
|
filament_map_indices = *dynamic_override_indices;
|
||||||
|
else {
|
||||||
|
filament_map_indices.assign(filament_maps.size(), 0);
|
||||||
|
for (int i = 0; i < filament_maps.size(); i++)
|
||||||
|
filament_map_indices[i] = filament_maps[i] - 1;
|
||||||
|
}
|
||||||
compute_filament_override_value(opt_key, opt_old, opt_new, opt_new_filament, new_full_config, print_diff, filament_overrides, filament_map_indices);
|
compute_filament_override_value(opt_key, opt_old, opt_new, opt_new_filament, new_full_config, print_diff, filament_overrides, filament_map_indices);
|
||||||
} else if (*opt_new != *opt_old) {
|
} else if (*opt_new != *opt_old) {
|
||||||
//BBS: add plate_index logic for wipe_tower_x/wipe_tower_y
|
//BBS: add plate_index logic for wipe_tower_x/wipe_tower_y
|
||||||
@@ -1171,6 +1180,9 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
|||||||
std::vector<std::vector<NozzleVolumeType>> nozzle_volume_types;
|
std::vector<std::vector<NozzleVolumeType>> nozzle_volume_types;
|
||||||
int extruder_count = 1, extruder_volume_type_count = 1;
|
int extruder_count = 1, extruder_volume_type_count = 1;
|
||||||
bool different_extruder = false;
|
bool different_extruder = false;
|
||||||
|
// Filled only when the filament arrays are rebuilt from a persisted selector result below;
|
||||||
|
// print_config_diffs then keys the retract overrides per expanded slot.
|
||||||
|
std::vector<int> dynamic_slot_indices;
|
||||||
|
|
||||||
different_extruder = new_full_config.support_different_extruders(extruder_count);
|
different_extruder = new_full_config.support_different_extruders(extruder_count);
|
||||||
extruder_volume_type_count = new_full_config.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types);
|
extruder_volume_type_count = new_full_config.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types);
|
||||||
@@ -1189,7 +1201,20 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
|||||||
|
|
||||||
std::set<std::string> filament_keys = filament_options_with_variant;
|
std::set<std::string> filament_keys = filament_options_with_variant;
|
||||||
filament_keys.insert("filament_self_index");
|
filament_keys.insert("filament_self_index");
|
||||||
if ((extruder_count > 1) || different_extruder)
|
// A persisted selector result with an actual migration means the last slice rebuilt the
|
||||||
|
// per-slot filament arrays from it (one slot per variant a filament prints through).
|
||||||
|
// Reproduce that exact expansion here so an unchanged config diffs empty — the expanded
|
||||||
|
// keys invalidate the wipe tower / g-code export, and the placeholder parser aliases
|
||||||
|
// the full config — instead of trimming back to one slot per filament.
|
||||||
|
auto group_result = std::dynamic_pointer_cast<MultiNozzleUtils::LayeredNozzleGroupResult>(this->get_nozzle_group_result());
|
||||||
|
std::unordered_map<int, std::vector<FilamentVariantUse>> filament_variant_uses;
|
||||||
|
if (group_result && group_result->is_support_dynamic_nozzle_map()
|
||||||
|
&& collect_filament_variant_uses(*group_result, m_ori_full_print_config, filament_variant_uses))
|
||||||
|
new_full_config.update_filament_config_values_for_multiple_extruders(m_ori_full_print_config, filament_variant_uses,
|
||||||
|
extruder_count, extruder_volume_type_count, filament_keys,
|
||||||
|
"filament_self_index", "filament_extruder_variant",
|
||||||
|
&dynamic_slot_indices);
|
||||||
|
else if ((extruder_count > 1) || different_extruder)
|
||||||
new_full_config.update_values_to_printer_extruders_for_multiple_filaments(m_ori_full_print_config, extruder_count, extruder_volume_type_count, filament_keys,
|
new_full_config.update_values_to_printer_extruders_for_multiple_filaments(m_ori_full_print_config, extruder_count, extruder_volume_type_count, filament_keys,
|
||||||
"filament_self_index", "filament_extruder_variant");
|
"filament_self_index", "filament_extruder_variant");
|
||||||
}
|
}
|
||||||
@@ -1210,7 +1235,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
|||||||
// Find modified keys of the various configs. Resolve overrides extruder retract values by filament profiles.
|
// Find modified keys of the various configs. Resolve overrides extruder retract values by filament profiles.
|
||||||
DynamicPrintConfig filament_overrides;
|
DynamicPrintConfig filament_overrides;
|
||||||
//BBS: add plate index
|
//BBS: add plate index
|
||||||
t_config_option_keys print_diff = print_config_diffs(m_config, new_full_config, filament_overrides, this->m_plate_index, filament_maps);
|
t_config_option_keys print_diff = print_config_diffs(m_config, new_full_config, filament_overrides, this->m_plate_index, filament_maps,
|
||||||
|
dynamic_slot_indices.empty() ? nullptr : &dynamic_slot_indices);
|
||||||
// Orca: filament_map_2 is engine-derived state, never a user input: the rebuild below
|
// Orca: filament_map_2 is engine-derived state, never a user input: the rebuild below
|
||||||
// recomputes it from filament_map/filament_volume_map/the variant slots on every apply
|
// recomputes it from filament_map/filament_volume_map/the variant slots on every apply
|
||||||
// (all of which are diffed and invalidation-listed on their own), and the grouping
|
// (all of which are diffed and invalidation-listed on their own), and the grouping
|
||||||
|
|||||||
@@ -10759,6 +10759,145 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Regathers a vector option's values through per-slot source indices (one input index per
|
||||||
|
// output slot). Out-of-range indices keep the first value, matching get_at's fallback.
|
||||||
|
template<typename OptType, typename ValueType>
|
||||||
|
static void gather_option_values(const std::string &key, OptType *opt, const std::vector<int> &slot_param_indices)
|
||||||
|
{
|
||||||
|
if (!opt || opt->values.empty()) {
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found or empty, skipping")%__LINE__%key;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::vector<ValueType> new_values;
|
||||||
|
new_values.reserve(slot_param_indices.size());
|
||||||
|
for (int idx : slot_param_indices) {
|
||||||
|
if (idx < 0 || static_cast<size_t>(idx) >= opt->values.size()) {
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% slot index %3% out of range, keeping first value")%__LINE__%key%idx;
|
||||||
|
new_values.emplace_back(opt->values.front());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
new_values.emplace_back(opt->values[idx]);
|
||||||
|
}
|
||||||
|
opt->values = std::move(new_values);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DynamicPrintConfig::update_filament_config_values_for_multiple_extruders(DynamicPrintConfig& printer_config,
|
||||||
|
const std::unordered_map<int, std::vector<FilamentVariantUse>>& filament_variant_uses,
|
||||||
|
int extruder_count, int extruder_nozzle_volume_count,
|
||||||
|
std::set<std::string>& key_set, std::string id_name, std::string variant_name,
|
||||||
|
std::vector<int>* slot_machine_indices)
|
||||||
|
{
|
||||||
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Line %1%: extruder_count %2%, extruder_nozzle_volume_count %3%")%__LINE__ %extruder_count %extruder_nozzle_volume_count;
|
||||||
|
|
||||||
|
auto opt_filament_map = printer_config.option<ConfigOptionInts>("filament_map");
|
||||||
|
if (!opt_filament_map) {
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: filament_map option not found, skipping")%__LINE__;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::vector<int> filament_maps = opt_filament_map->values;
|
||||||
|
size_t filament_count = filament_maps.size();
|
||||||
|
auto opt_extruder_type = dynamic_cast<const ConfigOptionEnumsGeneric*>(printer_config.option("extruder_type"));
|
||||||
|
auto opt_nozzle_volume_type = dynamic_cast<const ConfigOptionEnumsGeneric*>(printer_config.option("nozzle_volume_type"));
|
||||||
|
if (!opt_extruder_type || !opt_nozzle_volume_type) {
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: extruder_type or nozzle_volume_type option not found, skipping")%__LINE__;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto opt_filament_volume_maps = dynamic_cast<const ConfigOptionInts*>(printer_config.option("filament_volume_map"));
|
||||||
|
std::vector<int> filament_volume_maps;
|
||||||
|
// Orca: same sizing guard as the single-slot rebuild above — honour the per-filament volume
|
||||||
|
// map only when a producer sized it to the filament count.
|
||||||
|
if (opt_filament_volume_maps && opt_filament_volume_maps->values.size() == filament_count)
|
||||||
|
filament_volume_maps = opt_filament_volume_maps->values;
|
||||||
|
auto opt_ids = id_name.empty() ? nullptr : dynamic_cast<const ConfigOptionInts*>(this->option(id_name));
|
||||||
|
|
||||||
|
std::vector<int> slot_param_indices;
|
||||||
|
slot_param_indices.reserve(filament_count * 2);
|
||||||
|
if (slot_machine_indices) {
|
||||||
|
slot_machine_indices->clear();
|
||||||
|
slot_machine_indices->reserve(filament_count * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto resolve_slot = [&](int f_index, ExtruderType extruder_type, NozzleVolumeType nozzle_volume_type, int extruder_id_1based) {
|
||||||
|
int param_index = get_index_for_extruder(f_index + 1, id_name, extruder_type, nozzle_volume_type, variant_name);
|
||||||
|
if (param_index < 0) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: could not found extruder_type %2%, nozzle_volume_type %3%, filament_index %4%, extruder index %5%")
|
||||||
|
%__LINE__ %s_keys_names_ExtruderType[extruder_type] % s_keys_names_NozzleVolumeType[nozzle_volume_type] % (f_index+1) %extruder_id_1based;
|
||||||
|
assert(false);
|
||||||
|
//for some updates happens in a invalid state(caused by popup window)
|
||||||
|
//we need to avoid crash
|
||||||
|
param_index = 0;
|
||||||
|
if (opt_ids) {
|
||||||
|
for (int i = 0; i < opt_ids->values.size(); i++)
|
||||||
|
if (opt_ids->values[i] == (f_index + 1)) {
|
||||||
|
param_index = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
slot_param_indices.push_back(param_index);
|
||||||
|
if (slot_machine_indices) {
|
||||||
|
// Orca: key each output slot to the machine slot of its own variant (the same
|
||||||
|
// derivation the per-filament slot map uses), so the retract-override nil fallback
|
||||||
|
// stays aligned when a filament occupies more than one slot.
|
||||||
|
int machine_index = printer_config.get_index_for_extruder(extruder_id_1based, "print_extruder_id", extruder_type, nozzle_volume_type, "print_extruder_variant");
|
||||||
|
slot_machine_indices->push_back(std::max(machine_index, 0));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int f_index = 0; f_index < static_cast<int>(filament_count); f_index++) {
|
||||||
|
auto uses_it = filament_variant_uses.find(f_index);
|
||||||
|
if (uses_it != filament_variant_uses.end() && !uses_it->second.empty()) {
|
||||||
|
for (const FilamentVariantUse &use : uses_it->second)
|
||||||
|
resolve_slot(f_index, use.extruder_type, use.nozzle_volume_type, use.extruder_id + 1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ExtruderType extruder_type = (ExtruderType)(opt_extruder_type->get_at(filament_maps[f_index] - 1));
|
||||||
|
NozzleVolumeType nozzle_volume_type = (NozzleVolumeType)(opt_nozzle_volume_type->get_at(filament_maps[f_index] - 1));
|
||||||
|
if ((extruder_nozzle_volume_count > extruder_count || nozzle_volume_type == nvtHybrid) && !filament_volume_maps.empty())
|
||||||
|
nozzle_volume_type = (NozzleVolumeType)(filament_volume_maps[f_index]);
|
||||||
|
resolve_slot(f_index, extruder_type, nozzle_volume_type, filament_maps[f_index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ConfigDef *config_def = this->def();
|
||||||
|
if (!config_def) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: can not find config define")%__LINE__;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bool has_id_key = !id_name.empty() && key_set.count(id_name) > 0;
|
||||||
|
for (auto &key : key_set) {
|
||||||
|
if (has_id_key && key == id_name)
|
||||||
|
continue;
|
||||||
|
const ConfigOptionDef *optdef = config_def->get(key);
|
||||||
|
if (!optdef) {
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: can not find opt define for %2%")%__LINE__%key;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
switch (optdef->type) {
|
||||||
|
case coStrings: gather_option_values<ConfigOptionStrings, std::string>(key, this->option<ConfigOptionStrings>(key), slot_param_indices); break;
|
||||||
|
case coInts: gather_option_values<ConfigOptionInts, int>(key, this->option<ConfigOptionInts>(key), slot_param_indices); break;
|
||||||
|
case coFloats: gather_option_values<ConfigOptionFloats, double>(key, this->option<ConfigOptionFloats>(key), slot_param_indices); break;
|
||||||
|
case coPercents: gather_option_values<ConfigOptionPercents, double>(key, this->option<ConfigOptionPercents>(key), slot_param_indices); break;
|
||||||
|
case coFloatsOrPercents: gather_option_values<ConfigOptionFloatsOrPercents, FloatOrPercent>(key, this->option<ConfigOptionFloatsOrPercents>(key), slot_param_indices); break;
|
||||||
|
case coBools: gather_option_values<ConfigOptionBools, unsigned char>(key, this->option<ConfigOptionBools>(key), slot_param_indices); break;
|
||||||
|
case coEnums: gather_option_values<ConfigOptionEnumsGeneric, int>(key, this->option<ConfigOptionEnumsGeneric>(key), slot_param_indices); break;
|
||||||
|
default:
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: unsupported option type for %2%")%__LINE__%key;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Orca: also require a non-empty id list; get_at on an empty vector is undefined.
|
||||||
|
if (has_id_key && opt_ids && !opt_ids->values.empty()) {
|
||||||
|
// remap the id list itself last: the slot resolution above still needs the original ids
|
||||||
|
std::vector<int> new_values;
|
||||||
|
new_values.reserve(slot_param_indices.size());
|
||||||
|
for (int idx : slot_param_indices)
|
||||||
|
new_values.push_back(opt_ids->get_at(idx));
|
||||||
|
const_cast<ConfigOptionInts*>(opt_ids)->values = std::move(new_values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
// Options in printer_options_with_variant_2 are stored as (normal,silent) pairs per printer variant.
|
// Options in printer_options_with_variant_2 are stored as (normal,silent) pairs per printer variant.
|
||||||
// Some legacy presets/projects carry a variant list but still store only one pair; normalize to avoid crashes.
|
// Some legacy presets/projects carry a variant list but still store only one pair; normalize to avoid crashes.
|
||||||
|
|||||||
@@ -683,6 +683,23 @@ class StaticPrintConfig;
|
|||||||
// Minimum object distance for arrangement, based on printer technology.
|
// Minimum object distance for arrangement, based on printer technology.
|
||||||
double min_object_distance(const ConfigBase &cfg);
|
double min_object_distance(const ConfigBase &cfg);
|
||||||
|
|
||||||
|
// One (extruder type x nozzle volume type) parameter variant a filament prints through, plus a
|
||||||
|
// representative physical extruder observed using it. Ordering (and set-dedup identity) covers
|
||||||
|
// the variant pair only, so the same variant reached through two extruders keeps one config slot.
|
||||||
|
struct FilamentVariantUse
|
||||||
|
{
|
||||||
|
ExtruderType extruder_type{etDirectDrive};
|
||||||
|
NozzleVolumeType nozzle_volume_type{nvtStandard};
|
||||||
|
int extruder_id{0}; // 0-based, first extruder seen using this variant
|
||||||
|
|
||||||
|
bool operator<(const FilamentVariantUse &other) const
|
||||||
|
{
|
||||||
|
if (extruder_type != other.extruder_type)
|
||||||
|
return extruder_type < other.extruder_type;
|
||||||
|
return nozzle_volume_type < other.nozzle_volume_type;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Slic3r dynamic configuration, used to override the configuration
|
// Slic3r dynamic configuration, used to override the configuration
|
||||||
// per object, per modification volume or per printing material.
|
// per object, per modification volume or per printing material.
|
||||||
// The dynamic configuration is also used to store user modifications of the print global parameters,
|
// The dynamic configuration is also used to store user modifications of the print global parameters,
|
||||||
@@ -748,6 +765,18 @@ public:
|
|||||||
std::vector<int> update_values_to_printer_extruders(DynamicPrintConfig& printer_config, int extruder_count, int extruder_nozzle_volume_count, std::vector<std::vector<NozzleVolumeType>>& nv_types,
|
std::vector<int> update_values_to_printer_extruders(DynamicPrintConfig& printer_config, int extruder_count, int extruder_nozzle_volume_count, std::vector<std::vector<NozzleVolumeType>>& nv_types,
|
||||||
std::set<std::string>& key_set, std::string id_name, std::string variant_name, unsigned int stride = 1, unsigned int extruder_id = 0, NozzleVolumeType filament_nvt = nvtStandard);
|
std::set<std::string>& key_set, std::string id_name, std::string variant_name, unsigned int stride = 1, unsigned int extruder_id = 0, NozzleVolumeType filament_nvt = nvtStandard);
|
||||||
void update_values_to_printer_extruders_for_multiple_filaments(DynamicPrintConfig& printer_config, int extruder_count, int extruder_nozzle_volume_count, std::set<std::string>& key_set, std::string id_name, std::string variant_name);
|
void update_values_to_printer_extruders_for_multiple_filaments(DynamicPrintConfig& printer_config, int extruder_count, int extruder_nozzle_volume_count, std::set<std::string>& key_set, std::string id_name, std::string variant_name);
|
||||||
|
// Rebuilds the per-slot filament arrays from a per-layer grouping outcome: a filament that
|
||||||
|
// prints through several (extruder x nozzle volume type) variants keeps one slot per variant
|
||||||
|
// (unlike the single-slot rebuild above), so layer-aware consumers can resolve the slot the
|
||||||
|
// current layer actually prints with. Filaments absent from filament_variant_uses keep a
|
||||||
|
// single slot resolved from filament_map / filament_volume_map. When slot_machine_indices is
|
||||||
|
// non-null it receives one machine-variant slot index per output slot (the nil-value fallback
|
||||||
|
// keying for the extruder retract overrides; a per-filament map cannot index expanded arrays).
|
||||||
|
void update_filament_config_values_for_multiple_extruders(DynamicPrintConfig& printer_config,
|
||||||
|
const std::unordered_map<int, std::vector<FilamentVariantUse>>& filament_variant_uses,
|
||||||
|
int extruder_count, int extruder_nozzle_volume_count,
|
||||||
|
std::set<std::string>& key_set, std::string id_name, std::string variant_name,
|
||||||
|
std::vector<int>* slot_machine_indices = nullptr);
|
||||||
|
|
||||||
void update_non_diff_values_to_base_config(DynamicPrintConfig& new_config, const t_config_option_keys& keys, const std::set<std::string>& different_keys, std::string extruder_id_name, std::string extruder_variant_name,
|
void update_non_diff_values_to_base_config(DynamicPrintConfig& new_config, const t_config_option_keys& keys, const std::set<std::string>& different_keys, std::string extruder_id_name, std::string extruder_variant_name,
|
||||||
std::set<std::string>& key_set1, std::set<std::string>& key_set2);
|
std::set<std::string>& key_set1, std::set<std::string>& key_set2);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <boost/filesystem.hpp>
|
#include <boost/filesystem.hpp>
|
||||||
@@ -605,8 +606,8 @@ TEST_CASE("Sequential selector prints publish a stitched result and cache the pl
|
|||||||
{
|
{
|
||||||
// By-object + smart filament assign: the by-object branch of Print::process must plan each
|
// By-object + smart filament assign: the by-object branch of Print::process must plan each
|
||||||
// object with nozzle-status threading, cache the plans for the g-code export, stitch them
|
// object with nozzle-status threading, cache the plans for the g-code export, stitch them
|
||||||
// into the published print-wide result, and write the derived extruder map back once
|
// into the published print-wide result, and write the grouping result back to the config
|
||||||
// (per-object orderings must not churn the config).
|
// once (per-object orderings must not churn the config).
|
||||||
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
||||||
config.option<ConfigOptionFloats>("nozzle_diameter", true)->values = {0.4, 0.4};
|
config.option<ConfigOptionFloats>("nozzle_diameter", true)->values = {0.4, 0.4};
|
||||||
config.option<ConfigOptionStrings>("extruder_nozzle_stats", true)->values = {"Standard#1", "Standard#1|High Flow#2"};
|
config.option<ConfigOptionStrings>("extruder_nozzle_stats", true)->values = {"Standard#1", "Standard#1|High Flow#2"};
|
||||||
@@ -655,6 +656,10 @@ TEST_CASE("Sequential selector prints publish a stitched result and cache the pl
|
|||||||
REQUIRE(result->get_layer_count() > 0);
|
REQUIRE(result->get_layer_count() > 0);
|
||||||
// The write-back mirrors the stitched result's extruder map.
|
// The write-back mirrors the stitched result's extruder map.
|
||||||
REQUIRE(print.config().filament_map.values == result->get_extruder_map(false));
|
REQUIRE(print.config().filament_map.values == result->get_extruder_map(false));
|
||||||
|
// The per-slot filament arrays stay label-consistent whether or not the stitched plan
|
||||||
|
// actually migrated a filament (one slot per filament, plus one per extra variant).
|
||||||
|
REQUIRE(print.config().filament_extruder_variant.values.size() == print.config().filament_self_index.values.size());
|
||||||
|
REQUIRE(print.config().filament_self_index.values.size() >= print.config().filament_map.values.size());
|
||||||
|
|
||||||
// Export must consume the cached plans and produce g-code without throwing.
|
// Export must consume the cached plans and produce g-code without throwing.
|
||||||
boost::filesystem::path gcode_path = boost::filesystem::temp_directory_path() / "orca_seq_dynamic_publish_test.gcode";
|
boost::filesystem::path gcode_path = boost::filesystem::temp_directory_path() / "orca_seq_dynamic_publish_test.gcode";
|
||||||
@@ -662,3 +667,198 @@ TEST_CASE("Sequential selector prints publish a stitched result and cache the pl
|
|||||||
REQUIRE(boost::filesystem::exists(gcode_path));
|
REQUIRE(boost::filesystem::exists(gcode_path));
|
||||||
boost::filesystem::remove(gcode_path);
|
boost::filesystem::remove(gcode_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Per-variant expansion gives migrating filaments one slot per variant", "[PrintConfig][H2C][Dynamic]")
|
||||||
|
{
|
||||||
|
// The selector write-back rebuilds the filament arrays from the grouping result: a filament
|
||||||
|
// that prints through several (extruder x volume type) variants keeps one slot per variant,
|
||||||
|
// and every key grows in lockstep with the self-index / variant labels.
|
||||||
|
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
||||||
|
// Extruder 1 Standard, extruder 2 Hybrid (Standard + High Flow): 3 nozzle slots, 2 extruders.
|
||||||
|
config.option<ConfigOptionEnumsGeneric>("extruder_type", true)->values = {etDirectDrive, etDirectDrive};
|
||||||
|
config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type", true)->values = {nvtStandard, nvtHybrid};
|
||||||
|
config.option<ConfigOptionStrings>("extruder_variant_list", true)->values = {"Direct Drive Standard,Direct Drive High Flow",
|
||||||
|
"Direct Drive Standard,Direct Drive High Flow"};
|
||||||
|
config.option<ConfigOptionInts>("print_extruder_id", true)->values = {1, 1, 2, 2};
|
||||||
|
config.option<ConfigOptionStrings>("print_extruder_variant", true)->values = {"Direct Drive Standard", "Direct Drive High Flow",
|
||||||
|
"Direct Drive Standard", "Direct Drive High Flow"};
|
||||||
|
// Two filaments with superset arrays: one column per (filament x variant).
|
||||||
|
config.option<ConfigOptionInts>("filament_map", true)->values = {1, 2};
|
||||||
|
config.option<ConfigOptionInts>("filament_volume_map", true)->values = {(int) nvtStandard, (int) nvtHighFlow};
|
||||||
|
config.option<ConfigOptionInts>("filament_self_index", true)->values = {1, 1, 2, 2};
|
||||||
|
config.option<ConfigOptionStrings>("filament_extruder_variant", true)->values = {"Direct Drive Standard", "Direct Drive High Flow",
|
||||||
|
"Direct Drive Standard", "Direct Drive High Flow"};
|
||||||
|
config.option<ConfigOptionInts>("nozzle_temperature", true)->values = {220, 230, 240, 250};
|
||||||
|
|
||||||
|
std::set<std::string> key_set = {"filament_self_index", "filament_extruder_variant", "nozzle_temperature"};
|
||||||
|
|
||||||
|
auto make_use = [](ExtruderType et, NozzleVolumeType nvt, int extruder_id) {
|
||||||
|
FilamentVariantUse use;
|
||||||
|
use.extruder_type = et;
|
||||||
|
use.nozzle_volume_type = nvt;
|
||||||
|
use.extruder_id = extruder_id;
|
||||||
|
return use;
|
||||||
|
};
|
||||||
|
|
||||||
|
SECTION("a migrating filament expands, machine slots track each output slot") {
|
||||||
|
std::unordered_map<int, std::vector<FilamentVariantUse>> uses;
|
||||||
|
uses[0] = {make_use(etDirectDrive, nvtStandard, 0), make_use(etDirectDrive, nvtHighFlow, 1)};
|
||||||
|
uses[1] = {make_use(etDirectDrive, nvtHighFlow, 1)};
|
||||||
|
std::vector<int> slot_machine_indices;
|
||||||
|
config.update_filament_config_values_for_multiple_extruders(config, uses, 2, 3, key_set,
|
||||||
|
"filament_self_index", "filament_extruder_variant",
|
||||||
|
&slot_machine_indices);
|
||||||
|
REQUIRE(config.option<ConfigOptionInts>("filament_self_index")->values == std::vector<int>{1, 1, 2});
|
||||||
|
REQUIRE(config.option<ConfigOptionStrings>("filament_extruder_variant")->values ==
|
||||||
|
std::vector<std::string>({"Direct Drive Standard", "Direct Drive High Flow", "Direct Drive High Flow"}));
|
||||||
|
REQUIRE(config.option<ConfigOptionInts>("nozzle_temperature")->values == std::vector<int>{220, 230, 250});
|
||||||
|
// Slot 0 backs onto extruder 1 Standard; slots 1-2 onto extruder 2 High Flow.
|
||||||
|
REQUIRE(slot_machine_indices == std::vector<int>{0, 3, 3});
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("filaments absent from the uses fall back to their static assignment") {
|
||||||
|
std::unordered_map<int, std::vector<FilamentVariantUse>> uses;
|
||||||
|
uses[0] = {make_use(etDirectDrive, nvtStandard, 0)};
|
||||||
|
// Filament 1 unrouted: filament_map -> extruder 2 (Hybrid) -> volume map -> High Flow.
|
||||||
|
config.update_filament_config_values_for_multiple_extruders(config, uses, 2, 3, key_set,
|
||||||
|
"filament_self_index", "filament_extruder_variant");
|
||||||
|
REQUIRE(config.option<ConfigOptionInts>("filament_self_index")->values == std::vector<int>{1, 2});
|
||||||
|
REQUIRE(config.option<ConfigOptionInts>("nozzle_temperature")->values == std::vector<int>{220, 250});
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("a mis-sized filament_volume_map is ignored") {
|
||||||
|
config.option<ConfigOptionInts>("filament_volume_map", true)->values = {(int) nvtHighFlow};
|
||||||
|
std::unordered_map<int, std::vector<FilamentVariantUse>> uses;
|
||||||
|
uses[0] = {make_use(etDirectDrive, nvtStandard, 0)};
|
||||||
|
// Unrouted filament 1 keeps the extruder's own typing (Hybrid folds to Standard).
|
||||||
|
config.update_filament_config_values_for_multiple_extruders(config, uses, 2, 3, key_set,
|
||||||
|
"filament_self_index", "filament_extruder_variant");
|
||||||
|
REQUIRE(config.option<ConfigOptionInts>("nozzle_temperature")->values == std::vector<int>{220, 240});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Selector write-back expands migrating filaments and survives re-apply", "[Print][H2C][Dynamic]")
|
||||||
|
{
|
||||||
|
// A filament the per-layer plan moves between nozzle variants must end up with one config
|
||||||
|
// slot per variant (so per-layer temperatures/retractions resolve correctly), the extruder
|
||||||
|
// retract overrides must key each slot to its own variant's machine value, and an unchanged
|
||||||
|
// re-apply must reproduce the expansion instead of trimming it back to one slot per
|
||||||
|
// filament — a trim-back would diff the freshly written values and invalidate the result.
|
||||||
|
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
||||||
|
config.option<ConfigOptionFloats>("nozzle_diameter", true)->values = {0.4, 0.4};
|
||||||
|
config.option<ConfigOptionStrings>("extruder_nozzle_stats", true)->values = {"Standard#1", "Standard#1|High Flow#2"};
|
||||||
|
config.option<ConfigOptionEnumsGeneric>("extruder_type", true)->values = {etDirectDrive, etDirectDrive};
|
||||||
|
config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type", true)->values = {nvtStandard, nvtHybrid};
|
||||||
|
config.option<ConfigOptionStrings>("extruder_variant_list", true)->values = {"Direct Drive Standard,Direct Drive High Flow",
|
||||||
|
"Direct Drive Standard,Direct Drive High Flow"};
|
||||||
|
config.option<ConfigOptionInts>("print_extruder_id", true)->values = {1, 1, 2, 2};
|
||||||
|
config.option<ConfigOptionStrings>("print_extruder_variant", true)->values = {"Direct Drive Standard", "Direct Drive High Flow",
|
||||||
|
"Direct Drive Standard", "Direct Drive High Flow"};
|
||||||
|
// Three filaments: 0 -> extruder 1 (Std), 1 -> extruder 2 (Std), 2 -> extruder 2, migrating
|
||||||
|
// Standard -> High Flow between layers. Superset arrays: one column per (filament x variant).
|
||||||
|
// filament_type must be sized to the filament count: the variant-use collection (like the
|
||||||
|
// full-config producers) keys the per-filament loop on it.
|
||||||
|
config.option<ConfigOptionStrings>("filament_type", true)->values = {"PLA", "PLA", "PLA"};
|
||||||
|
config.option<ConfigOptionFloats>("filament_diameter", true)->values = {1.75, 1.75, 1.75};
|
||||||
|
config.option<ConfigOptionStrings>("filament_colour", true)->values = {"#FF0000", "#00FF00", "#0000FF"};
|
||||||
|
config.option<ConfigOptionInts>("filament_map", true)->values = {1, 2, 2};
|
||||||
|
config.option<ConfigOptionInts>("filament_volume_map", true)->values = {(int) nvtStandard, (int) nvtStandard, (int) nvtStandard};
|
||||||
|
config.option<ConfigOptionInts>("filament_self_index", true)->values = {1, 1, 2, 2, 3, 3};
|
||||||
|
config.option<ConfigOptionStrings>("filament_extruder_variant", true)->values = {"Direct Drive Standard", "Direct Drive High Flow",
|
||||||
|
"Direct Drive Standard", "Direct Drive High Flow",
|
||||||
|
"Direct Drive Standard", "Direct Drive High Flow"};
|
||||||
|
config.option<ConfigOptionInts>("nozzle_temperature", true)->values = {200, 210, 220, 230, 240, 250};
|
||||||
|
// The migrating filament's Standard column is nil, so the override merge must fall back to
|
||||||
|
// the machine value of the Standard slot (not the High Flow one).
|
||||||
|
config.option<ConfigOptionFloatsNullable>("filament_retraction_length", true)->values =
|
||||||
|
{0.5, 0.5, 0.6, 0.6, ConfigOptionFloatsNullable::nil_value(), 1.2};
|
||||||
|
config.option<ConfigOptionFloats>("retraction_length", true)->values = {0.8, 0.9, 1.0, 1.1};
|
||||||
|
|
||||||
|
Model model;
|
||||||
|
model.add_object("cube", "", make_cube(20, 20, 20))->add_instance();
|
||||||
|
|
||||||
|
Print print;
|
||||||
|
print.apply(model, config);
|
||||||
|
|
||||||
|
// Stub grouping result: nozzles as in the resolver test; filament 2 prints on the Standard
|
||||||
|
// nozzle at layer 0 and on the High Flow nozzle at layer 1.
|
||||||
|
std::vector<NozzleInfo> nozzle_list;
|
||||||
|
{
|
||||||
|
NozzleInfo n;
|
||||||
|
n.diameter = "0.4";
|
||||||
|
n.volume_type = nvtStandard; n.extruder_id = 0; n.group_id = 0; nozzle_list.push_back(n);
|
||||||
|
n.volume_type = nvtStandard; n.extruder_id = 1; n.group_id = 1; nozzle_list.push_back(n);
|
||||||
|
n.volume_type = nvtHighFlow; n.extruder_id = 1; n.group_id = 2; nozzle_list.push_back(n);
|
||||||
|
}
|
||||||
|
std::vector<std::vector<int>> layer_maps = {{0, 1, 1}, {0, 1, 2}};
|
||||||
|
std::vector<std::vector<unsigned int>> layer_seqs = {{0, 1, 2}, {0, 1, 2}};
|
||||||
|
auto group = LayeredNozzleGroupResult::create(layer_maps, nozzle_list, {0, 1, 2}, layer_seqs);
|
||||||
|
REQUIRE(group.has_value());
|
||||||
|
REQUIRE(group->is_support_dynamic_nozzle_map());
|
||||||
|
print.set_nozzle_group_result(std::make_shared<LayeredNozzleGroupResult>(*group));
|
||||||
|
|
||||||
|
print.update_to_config_by_nozzle_group_result(*group);
|
||||||
|
|
||||||
|
// Filament 2 holds two slots (Standard + High Flow), everything in lockstep.
|
||||||
|
REQUIRE(print.config().filament_map.values == group->get_extruder_map(false));
|
||||||
|
REQUIRE(print.config().filament_self_index.values == std::vector<int>{1, 2, 3, 3});
|
||||||
|
REQUIRE(print.config().nozzle_temperature.values == std::vector<int>{200, 220, 240, 250});
|
||||||
|
// The layer-aware resolver picks the slot matching each layer's variant.
|
||||||
|
REQUIRE(print.get_filament_config_indx(2, 0) == 2);
|
||||||
|
REQUIRE(print.get_filament_config_indx(2, 1) == 3);
|
||||||
|
// Retract overrides: non-nil slots take the filament value; the nil Standard slot of the
|
||||||
|
// migrating filament falls back to its own variant's machine value.
|
||||||
|
const auto &machine_retract = print.full_print_config().option<ConfigOptionFloats>("retraction_length")->values;
|
||||||
|
int f2_std_machine_slot = print.full_print_config().get_index_for_extruder(2, "print_extruder_id", etDirectDrive, nvtStandard,
|
||||||
|
"print_extruder_variant");
|
||||||
|
REQUIRE(f2_std_machine_slot >= 0);
|
||||||
|
const std::vector<double> merged_retract = print.config().retraction_length.values;
|
||||||
|
REQUIRE(merged_retract.size() == 4);
|
||||||
|
REQUIRE_THAT(merged_retract[0], Catch::Matchers::WithinAbs(0.5, 1e-9));
|
||||||
|
REQUIRE_THAT(merged_retract[1], Catch::Matchers::WithinAbs(0.6, 1e-9));
|
||||||
|
REQUIRE_THAT(merged_retract[2], Catch::Matchers::WithinAbs(machine_retract[f2_std_machine_slot], 1e-9));
|
||||||
|
REQUIRE_THAT(merged_retract[3], Catch::Matchers::WithinAbs(1.2, 1e-9));
|
||||||
|
|
||||||
|
// Re-apply the unchanged config: the persisted result must reproduce the exact expansion.
|
||||||
|
auto status = print.apply(model, config);
|
||||||
|
REQUIRE(status != PrintBase::APPLY_STATUS_INVALIDATED);
|
||||||
|
REQUIRE(print.config().filament_self_index.values == std::vector<int>{1, 2, 3, 3});
|
||||||
|
REQUIRE(print.config().nozzle_temperature.values == std::vector<int>{200, 220, 240, 250});
|
||||||
|
REQUIRE(print.config().retraction_length.values == merged_retract);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Selector slicing keeps the result valid across re-apply", "[Print][H2C][Dynamic]")
|
||||||
|
{
|
||||||
|
// The dynamic counterpart of the static re-apply test above: a full process() run through
|
||||||
|
// the selector branch (whatever grouping it settles on) must leave the config in a state
|
||||||
|
// the next apply reproduces without invalidating the freshly sliced result.
|
||||||
|
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
||||||
|
config.option<ConfigOptionFloats>("nozzle_diameter", true)->values = {0.4, 0.4};
|
||||||
|
config.option<ConfigOptionStrings>("extruder_nozzle_stats", true)->values = {"Standard#1", "Standard#1|High Flow#2"};
|
||||||
|
config.option<ConfigOptionEnumsGeneric>("extruder_type", true)->values = {etDirectDrive, etDirectDrive};
|
||||||
|
config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type", true)->values = {nvtStandard, nvtHybrid};
|
||||||
|
config.option<ConfigOptionStrings>("extruder_variant_list", true)->values = {"Direct Drive Standard,Direct Drive High Flow",
|
||||||
|
"Direct Drive Standard,Direct Drive High Flow"};
|
||||||
|
config.option<ConfigOptionInts>("print_extruder_id", true)->values = {1, 1, 2, 2};
|
||||||
|
config.option<ConfigOptionStrings>("print_extruder_variant", true)->values = {"Direct Drive Standard", "Direct Drive High Flow",
|
||||||
|
"Direct Drive Standard", "Direct Drive High Flow"};
|
||||||
|
config.option<ConfigOptionFloats>("filament_diameter", true)->values = {1.75, 1.75, 1.75};
|
||||||
|
config.option<ConfigOptionStrings>("filament_colour", true)->values = {"#FF0000", "#00FF00", "#0000FF"};
|
||||||
|
config.option<ConfigOptionInts>("filament_map", true)->values = {1, 2, 2};
|
||||||
|
config.option<ConfigOptionInts>("filament_volume_map", true)->values = {(int) nvtStandard, (int) nvtStandard, (int) nvtHighFlow};
|
||||||
|
config.set_key_value("enable_filament_dynamic_map", new ConfigOptionBool(true));
|
||||||
|
config.option<ConfigOptionEnum<FilamentMapMode>>("filament_map_mode", true)->value = FilamentMapMode::fmmAutoForFlush;
|
||||||
|
|
||||||
|
Model model;
|
||||||
|
ModelObject *object = model.add_object("cube", "", make_cube(20, 20, 20));
|
||||||
|
object->add_instance()->set_offset(Vec3d(100., 100., 0.));
|
||||||
|
|
||||||
|
Print print;
|
||||||
|
print.apply(model, config);
|
||||||
|
print.process();
|
||||||
|
REQUIRE(print.is_step_done(psSlicingFinished));
|
||||||
|
|
||||||
|
auto status = print.apply(model, config);
|
||||||
|
REQUIRE(status != PrintBase::APPLY_STATUS_INVALIDATED);
|
||||||
|
REQUIRE(print.is_step_done(psSlicingFinished));
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user