Refactor custom GCode handling and mixed filament management

- Updated `custom_tool_changes` function to use filament IDs instead of extruder IDs, ensuring better compatibility with mixed filament configurations.
- Enhanced `MixedFilament` structure to include a `stable_id` for persistent identity across mixed filament entries, improving remapping during updates.
- Introduced new methods for stable ID allocation and normalization in `MixedFilamentManager`.
- Updated serialization and loading functions to maintain stable IDs, ensuring consistent behavior when mixed filament entries are modified.
- Added tests to verify the correct handling of tool changes and stable ID remapping in mixed filament scenarios.
This commit is contained in:
Rad
2026-03-02 16:05:41 +01:00
parent 21d9bffb45
commit db0ccb1210
13 changed files with 389 additions and 117 deletions

View File

@@ -57,16 +57,17 @@ extern void check_mode_for_custom_gcode_per_print_z(Info& info)
info.mode = is_single_extruder ? SingleExtruder : MultiExtruder;
}
// Return pairs of <print_z, 1-based extruder ID> sorted by increasing print_z from custom_gcode_per_print_z.
// print_z corresponds to the first layer printed with the new extruder.
std::vector<std::pair<double, unsigned int>> custom_tool_changes(const Info& custom_gcode_per_print_z, size_t num_extruders)
// Return pairs of <print_z, 1-based filament ID> sorted by increasing print_z
// from custom_gcode_per_print_z.
std::vector<std::pair<double, unsigned int>> custom_tool_changes(const Info& custom_gcode_per_print_z, size_t num_filaments)
{
std::vector<std::pair<double, unsigned int>> custom_tool_changes;
for (const Item& custom_gcode : custom_gcode_per_print_z.gcodes)
if (custom_gcode.type == ToolChange) {
// If extruder count in PrinterSettings was changed, use default (0) extruder for extruders, more than num_extruders
// If available filament slots changed, fall back to filament 1 for
// stale IDs beyond the current physical+mixed range.
assert(custom_gcode.extruder >= 0);
custom_tool_changes.emplace_back(custom_gcode.print_z, static_cast<unsigned int>(size_t(custom_gcode.extruder) > num_extruders ? 1 : custom_gcode.extruder));
custom_tool_changes.emplace_back(custom_gcode.print_z, static_cast<unsigned int>(size_t(custom_gcode.extruder) > num_filaments ? 1 : custom_gcode.extruder));
}
return custom_tool_changes;
}

View File

@@ -120,9 +120,10 @@ struct Info
// So, we should set CustomGCode::Info.mode should be updated considering code values from items.
extern void check_mode_for_custom_gcode_per_print_z(Info& info);
// Return pairs of <print_z, 1-based extruder ID> sorted by increasing print_z from custom_gcode_per_print_z.
// print_z corresponds to the first layer printed with the new extruder.
std::vector<std::pair<double, unsigned int>> custom_tool_changes(const Info& custom_gcode_per_print_z, size_t num_extruders);
// Return pairs of <print_z, 1-based filament ID> sorted by increasing print_z
// from custom_gcode_per_print_z. The filament count may include mixed virtual
// filaments in addition to physical ones.
std::vector<std::pair<double, unsigned int>> custom_tool_changes(const Info& custom_gcode_per_print_z, size_t num_filaments);
} // namespace CustomGCode

View File

@@ -366,12 +366,13 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool
std::vector<std::pair<double, unsigned int>> per_layer_extruder_switches;
// BBS
if (auto num_filaments = unsigned(print.config().filament_diameter.size());
num_filaments > 1 && print.object_extruders().size() == 1 && // the current Print's configuration is CustomGCode::MultiAsSingle
if (auto num_physical = unsigned(print.config().filament_diameter.size());
num_physical > 1 && print.object_extruders().size() == 1 && // the current Print's configuration is CustomGCode::MultiAsSingle
//BBS: replace model custom gcode with current plate custom gcode
print.model().get_curr_plate_custom_gcodes().mode == CustomGCode::MultiAsSingle) {
// Printing a single extruder platter on a printer with more than 1 extruder (or single-extruder multi-material).
// There may be custom per-layer tool changes available at the model.
const size_t num_filaments = (m_mixed_mgr == nullptr) ? num_physical : m_mixed_mgr->total_filaments(num_physical);
per_layer_extruder_switches = custom_tool_changes(print.model().get_curr_plate_custom_gcodes(), num_filaments);
}

View File

@@ -315,6 +315,7 @@ static bool use_component_b_advanced_dither(int layer_index, int ratio_a, int ra
static bool parse_row_definition(const std::string &row,
unsigned int &a,
unsigned int &b,
uint64_t &stable_id,
bool &enabled,
bool &custom,
bool &origin_auto,
@@ -352,13 +353,29 @@ static bool parse_row_definition(const std::string &row,
}
};
auto parse_uint64_token = [&trim_copy](const std::string &tok, uint64_t &out) {
const std::string t = trim_copy(tok);
if (t.empty())
return false;
try {
size_t consumed = 0;
const unsigned long long v = std::stoull(t, &consumed);
if (consumed != t.size())
return false;
out = uint64_t(v);
return true;
} catch (...) {
return false;
}
};
std::vector<std::string> tokens;
std::stringstream ss(row);
std::string token;
while (std::getline(ss, token, ','))
tokens.emplace_back(trim_copy(token));
if (tokens.size() < 4 || tokens.size() > 12)
if (tokens.size() < 4 || tokens.size() > 13)
return false;
int values[5] = { 0, 0, 1, 1, 50 };
@@ -381,6 +398,7 @@ static bool parse_row_definition(const std::string &row,
a = unsigned(values[0]);
b = unsigned(values[1]);
stable_id = 0;
enabled = (values[2] != 0);
custom = (tokens.size() == 4) ? true : (values[3] != 0);
origin_auto = !custom;
@@ -440,6 +458,12 @@ static bool parse_row_definition(const std::string &row,
origin_auto = parsed_origin_auto != 0;
continue;
}
if (tok[0] == 'u' || tok[0] == 'U') {
uint64_t parsed_stable_id = stable_id;
if (parse_uint64_token(tok.substr(1), parsed_stable_id))
stable_id = parsed_stable_id;
continue;
}
manual_pattern = tok;
}
@@ -685,6 +709,22 @@ static std::vector<unsigned int> build_weighted_gradient_sequence(const std::vec
// MixedFilamentManager
// ---------------------------------------------------------------------------
uint64_t MixedFilamentManager::allocate_stable_id()
{
const uint64_t stable_id = std::max<uint64_t>(1, m_next_stable_id);
m_next_stable_id = stable_id + 1;
return stable_id;
}
uint64_t MixedFilamentManager::normalize_stable_id(uint64_t stable_id)
{
if (stable_id == 0)
return allocate_stable_id();
if (stable_id >= m_next_stable_id)
m_next_stable_id = stable_id + 1;
return stable_id;
}
void MixedFilamentManager::auto_generate(const std::vector<std::string> &filament_colours)
{
// Keep a copy of the old list so we can preserve user-modified ratios and
@@ -703,7 +743,9 @@ void MixedFilamentManager::auto_generate(const std::vector<std::string> &filamen
continue;
if (prev.component_a == 0 || prev.component_b == 0 || prev.component_a > n || prev.component_b > n || prev.component_a == prev.component_b)
continue;
custom_rows.push_back(prev);
MixedFilament custom = prev;
custom.stable_id = normalize_stable_id(custom.stable_id);
custom_rows.push_back(std::move(custom));
}
// Generate all C(N,2) pairwise combinations.
@@ -727,11 +769,13 @@ void MixedFilamentManager::auto_generate(const std::vector<std::string> &filamen
prev.component_b == mf.component_b) {
mf.enabled = prev.enabled;
mf.deleted = prev.deleted;
mf.stable_id = prev.stable_id;
if (mf.deleted)
mf.enabled = false;
break;
}
}
mf.stable_id = normalize_stable_id(mf.stable_id);
m_mixed.push_back(mf);
}
}
@@ -781,6 +825,7 @@ void MixedFilamentManager::add_custom_filament(unsigned int component_a,
MixedFilament mf;
mf.component_a = component_a;
mf.component_b = component_b;
mf.stable_id = allocate_stable_id();
mf.mix_b_percent = clamp_int(mix_b_percent, 0, 100);
mf.ratio_a = 1;
mf.ratio_b = 1;
@@ -840,14 +885,15 @@ void MixedFilamentManager::apply_gradient_settings(int gradient_mode,
}
}
std::string MixedFilamentManager::serialize_custom_entries() const
std::string MixedFilamentManager::serialize_custom_entries()
{
std::ostringstream ss;
bool first = true;
for (const MixedFilament &mf : m_mixed) {
for (MixedFilament &mf : m_mixed) {
if (!first)
ss << ';';
first = false;
mf.stable_id = normalize_stable_id(mf.stable_id);
const std::string normalized_ids = normalize_gradient_component_ids(mf.gradient_component_ids);
const std::string normalized_weights = normalize_gradient_component_weights(mf.gradient_component_weights, normalized_ids.size());
ss << mf.component_a << ','
@@ -860,7 +906,8 @@ std::string MixedFilamentManager::serialize_custom_entries() const
<< 'w' << normalized_weights << ','
<< 'm' << clamp_int(mf.distribution_mode, int(MixedFilament::LayerCycle), int(MixedFilament::Simple)) << ','
<< 'd' << (mf.deleted ? 1 : 0) << ','
<< 'o' << (mf.origin_auto ? 1 : 0);
<< 'o' << (mf.origin_auto ? 1 : 0) << ','
<< 'u' << mf.stable_id;
const std::string normalized_pattern = normalize_manual_pattern(mf.manual_pattern);
if (!normalized_pattern.empty())
ss << ',' << normalized_pattern;
@@ -898,6 +945,15 @@ void MixedFilamentManager::load_custom_entries(const std::string &serialized, co
std::vector<MixedFilament> rebuilt;
rebuilt.reserve(m_mixed.size() + 8);
std::set<std::pair<unsigned int, unsigned int>> consumed_auto_pairs;
std::set<uint64_t> used_stable_ids;
auto dedupe_stable_id = [this, &used_stable_ids](uint64_t stable_id) {
stable_id = normalize_stable_id(stable_id);
if (used_stable_ids.insert(stable_id).second)
return stable_id;
uint64_t replacement = allocate_stable_id();
used_stable_ids.insert(replacement);
return replacement;
};
std::stringstream all(serialized);
std::string row;
@@ -907,6 +963,7 @@ void MixedFilamentManager::load_custom_entries(const std::string &serialized, co
++parsed_rows;
unsigned int a = 0;
unsigned int b = 0;
uint64_t stable_id = 0;
bool enabled = true;
bool custom = true;
bool origin_auto = false;
@@ -917,7 +974,7 @@ void MixedFilamentManager::load_custom_entries(const std::string &serialized, co
std::string manual_pattern;
int distribution_mode = int(MixedFilament::Simple);
bool deleted = false;
if (!parse_row_definition(row, a, b, enabled, custom, origin_auto, mix, pointillism_all_filaments,
if (!parse_row_definition(row, a, b, stable_id, enabled, custom, origin_auto, mix, pointillism_all_filaments,
gradient_component_ids, gradient_component_weights, manual_pattern, distribution_mode, deleted)) {
++skipped_rows;
BOOST_LOG_TRIVIAL(warning) << "MixedFilamentManager::load_custom_entries invalid row format: " << row;
@@ -959,6 +1016,7 @@ void MixedFilamentManager::load_custom_entries(const std::string &serialized, co
MixedFilament mf = *it_auto;
mf.component_a = key.first;
mf.component_b = key.second;
mf.stable_id = dedupe_stable_id(stable_id != 0 ? stable_id : mf.stable_id);
mf.enabled = enabled;
mf.pointillism_all_filaments = pointillism_all_filaments;
mf.gradient_component_ids = normalize_gradient_component_ids(gradient_component_ids);
@@ -982,6 +1040,7 @@ void MixedFilamentManager::load_custom_entries(const std::string &serialized, co
MixedFilament mf;
mf.component_a = a;
mf.component_b = b;
mf.stable_id = dedupe_stable_id(stable_id);
mf.mix_b_percent = mix;
mf.ratio_a = 1;
mf.ratio_b = 1;
@@ -1012,6 +1071,7 @@ void MixedFilamentManager::load_custom_entries(const std::string &serialized, co
MixedFilament mf = auto_mf;
mf.component_a = key.first;
mf.component_b = key.second;
mf.stable_id = dedupe_stable_id(mf.stable_id);
mf.custom = false;
mf.origin_auto = true;
rebuilt.push_back(std::move(mf));

View File

@@ -27,6 +27,10 @@ struct MixedFilament
unsigned int component_a = 1;
unsigned int component_b = 2;
// Persistent row identity used to keep painted virtual-tool assignments
// stable even when the visible mixed-filament list is rebuilt.
uint64_t stable_id = 0;
// Layer-alternation ratio. With ratio_a = 2, ratio_b = 1 the cycle is
// A, A, B, A, A, B, ...
int ratio_a = 1;
@@ -77,6 +81,7 @@ struct MixedFilament
{
return component_a == rhs.component_a &&
component_b == rhs.component_b &&
stable_id == rhs.stable_id &&
ratio_a == rhs.ratio_a &&
ratio_b == rhs.ratio_b &&
mix_b_percent == rhs.mix_b_percent &&
@@ -133,8 +138,9 @@ public:
float upper_bound,
bool advanced_dithering = false);
// Persist only custom rows.
std::string serialize_custom_entries() const;
// Persist mixed rows, including auto/deleted state, into the compact
// project-settings string.
std::string serialize_custom_entries();
void load_custom_entries(const std::string &serialized, const std::vector<std::string> &filament_colours);
// Normalize a manual mixed-pattern string into compact token form.
@@ -196,12 +202,15 @@ private:
}
void refresh_display_colors(const std::vector<std::string> &filament_colours);
uint64_t allocate_stable_id();
uint64_t normalize_stable_id(uint64_t stable_id);
std::vector<MixedFilament> m_mixed;
int m_gradient_mode = 0;
float m_height_lower_bound = 0.04f;
float m_height_upper_bound = 0.16f;
bool m_advanced_dithering = false;
uint64_t m_next_stable_id = 1;
};
} // namespace Slic3r

View File

@@ -11,6 +11,7 @@
#include <algorithm>
#include <set>
#include <fstream>
#include <unordered_map>
#include <unordered_set>
#include <boost/filesystem.hpp>
#include <boost/algorithm/clamp.hpp>
@@ -3365,51 +3366,84 @@ void PresetBundle::update_multi_material_filament_presets(size_t to_delete_filam
// Build old->new filament ID remap for painted facet data normalization.
// This is needed for both deletion and addition of physical filaments so
// painted mixed states keep pointing at the same virtual mixed entries.
if (old_num_filaments != num_filaments || deleting_filament) {
const unsigned int deleted_1based = deleting_filament ? unsigned(to_delete_filament_id + 1) : 0u;
size_t old_enabled_mixed = 0;
for (const auto &mf : old_mixed)
if (mf.enabled)
++old_enabled_mixed;
if (old_num_filaments != num_filaments || deleting_filament)
build_filament_id_remap(old_mixed, old_num_filaments, num_filaments, deleting_filament,
deleting_filament ? unsigned(to_delete_filament_id + 1) : 0u);
}
const size_t old_total_filaments = old_num_filaments + old_enabled_mixed;
m_last_filament_id_remap.assign(old_total_filaments + 1, 0);
void PresetBundle::update_mixed_filament_id_remap(const std::vector<MixedFilament> &old_mixed,
size_t old_num_filaments,
size_t new_num_filaments)
{
build_filament_id_remap(old_mixed, old_num_filaments, new_num_filaments, false, 0u);
}
for (unsigned int old_id = 1; old_id <= unsigned(old_num_filaments); ++old_id) {
unsigned int mapped = 0;
if (deleting_filament && old_id == deleted_1based) {
mapped = 0;
} else if (old_id <= unsigned(num_filaments)) {
mapped = old_id;
if (deleting_filament && old_id > deleted_1based)
--mapped;
void PresetBundle::build_filament_id_remap(const std::vector<MixedFilament> &old_mixed,
size_t old_num_filaments,
size_t new_num_filaments,
bool deleting_filament,
unsigned int deleted_1based)
{
size_t old_enabled_mixed = 0;
for (const auto &mf : old_mixed)
if (mf.enabled)
++old_enabled_mixed;
const size_t old_total_filaments = old_num_filaments + old_enabled_mixed;
m_last_filament_id_remap.assign(old_total_filaments + 1, 0);
for (unsigned int old_id = 1; old_id <= unsigned(old_num_filaments); ++old_id) {
unsigned int mapped = 0;
if (deleting_filament && old_id == deleted_1based) {
mapped = 0;
} else if (old_id <= unsigned(new_num_filaments)) {
mapped = old_id;
if (deleting_filament && old_id > deleted_1based)
--mapped;
}
m_last_filament_id_remap[old_id] = mapped;
}
auto canonical_pair = [](unsigned int a, unsigned int b) {
return std::make_pair(std::min(a, b), std::max(a, b));
};
std::unordered_map<uint64_t, unsigned int> new_stable_id_to_virtual_id;
std::map<std::pair<unsigned int, unsigned int>, std::vector<unsigned int>> new_pair_to_ids;
unsigned int next_virtual_id = unsigned(new_num_filaments + 1);
for (const auto &mf : this->mixed_filaments.mixed_filaments()) {
if (!mf.enabled)
continue;
if (mf.stable_id != 0)
new_stable_id_to_virtual_id.emplace(mf.stable_id, next_virtual_id);
new_pair_to_ids[canonical_pair(mf.component_a, mf.component_b)].push_back(next_virtual_id++);
}
std::map<std::pair<unsigned int, unsigned int>, size_t> used_per_pair;
size_t stable_id_hits = 0;
size_t fallback_pair_hits = 0;
size_t missing_hits = 0;
unsigned int old_virtual_id = unsigned(old_num_filaments + 1);
for (const auto &mf : old_mixed) {
if (!mf.enabled)
continue;
unsigned int a = mf.component_a;
unsigned int b = mf.component_b;
if (a == deleted_1based || b == deleted_1based) {
m_last_filament_id_remap[old_virtual_id] = 0;
++missing_hits;
} else {
bool mapped_by_stable_id = false;
if (mf.stable_id != 0) {
auto it_stable = new_stable_id_to_virtual_id.find(mf.stable_id);
if (it_stable != new_stable_id_to_virtual_id.end()) {
m_last_filament_id_remap[old_virtual_id] = it_stable->second;
mapped_by_stable_id = true;
++stable_id_hits;
}
}
m_last_filament_id_remap[old_id] = mapped;
}
auto canonical_pair = [](unsigned int a, unsigned int b) {
return std::make_pair(std::min(a, b), std::max(a, b));
};
std::map<std::pair<unsigned int, unsigned int>, std::vector<unsigned int>> new_pair_to_ids;
unsigned int next_virtual_id = unsigned(num_filaments + 1);
for (const auto &mf : this->mixed_filaments.mixed_filaments()) {
if (!mf.enabled)
continue;
new_pair_to_ids[canonical_pair(mf.component_a, mf.component_b)].push_back(next_virtual_id++);
}
std::map<std::pair<unsigned int, unsigned int>, size_t> used_per_pair;
unsigned int old_virtual_id = unsigned(old_num_filaments + 1);
for (const auto &mf : old_mixed) {
if (!mf.enabled)
continue;
unsigned int a = mf.component_a;
unsigned int b = mf.component_b;
if (a == deleted_1based || b == deleted_1based) {
m_last_filament_id_remap[old_virtual_id] = 0;
} else {
if (!mapped_by_stable_id) {
if (deleting_filament) {
if (a > deleted_1based)
--a;
@@ -3420,42 +3454,48 @@ void PresetBundle::update_multi_material_filament_presets(size_t to_delete_filam
auto it = new_pair_to_ids.find(key);
if (it == new_pair_to_ids.end()) {
m_last_filament_id_remap[old_virtual_id] = 0;
++missing_hits;
} else {
size_t &used = used_per_pair[key];
if (used >= it->second.size()) {
m_last_filament_id_remap[old_virtual_id] = 0;
++missing_hits;
} else {
m_last_filament_id_remap[old_virtual_id] = it->second[used++];
++fallback_pair_hits;
}
}
}
++old_virtual_id;
}
auto summarize_uint_vector = [](const std::vector<unsigned int> &values, size_t max_items = 24) {
std::string out = "[";
const size_t n = std::min(values.size(), max_items);
for (size_t i = 0; i < n; ++i) {
if (i > 0)
out += ",";
out += std::to_string(values[i]);
}
if (values.size() > n)
out += ",...";
out += "]";
return out;
};
BOOST_LOG_TRIVIAL(warning) << "MF_REMAP preset_bundle"
<< " old_physical=" << old_num_filaments
<< " new_physical=" << num_filaments
<< " deleting=" << (deleting_filament ? 1 : 0)
<< " deleted_id=" << deleted_1based
<< " old_mixed_enabled=" << old_enabled_mixed
<< " new_mixed_enabled=" << this->mixed_filaments.enabled_count()
<< " remap_size=" << m_last_filament_id_remap.size()
<< " remap=" << summarize_uint_vector(m_last_filament_id_remap);
++old_virtual_id;
}
auto summarize_uint_vector = [](const std::vector<unsigned int> &values, size_t max_items = 24) {
std::string out = "[";
const size_t n = std::min(values.size(), max_items);
for (size_t i = 0; i < n; ++i) {
if (i > 0)
out += ",";
out += std::to_string(values[i]);
}
if (values.size() > n)
out += ",...";
out += "]";
return out;
};
BOOST_LOG_TRIVIAL(warning) << "MF_REMAP preset_bundle"
<< " old_physical=" << old_num_filaments
<< " new_physical=" << new_num_filaments
<< " deleting=" << (deleting_filament ? 1 : 0)
<< " deleted_id=" << deleted_1based
<< " old_mixed_enabled=" << old_enabled_mixed
<< " new_mixed_enabled=" << this->mixed_filaments.enabled_count()
<< " stable_id_hits=" << stable_id_hits
<< " fallback_pair_hits=" << fallback_pair_hits
<< " missing_hits=" << missing_hits
<< " remap_size=" << m_last_filament_id_remap.size()
<< " remap=" << summarize_uint_vector(m_last_filament_id_remap);
}
void PresetBundle::update_compatible(PresetSelectCompatibleType select_other_print_if_incompatible, PresetSelectCompatibleType select_other_filament_if_incompatible)

View File

@@ -254,6 +254,11 @@ public:
// update size and content of filament_presets.
void update_multi_material_filament_presets(size_t to_delete_filament_id = size_t(-1),
size_t old_num_filaments = size_t(-1));
// Rebuild old->new virtual filament mapping after mixed-row enable/delete
// changes when the physical filament count itself did not change.
void update_mixed_filament_id_remap(const std::vector<MixedFilament> &old_mixed,
size_t old_num_filaments,
size_t new_num_filaments);
// Mapping generated during the latest filament count change.
// Index is old 1-based filament ID, value is new 1-based filament ID (0 = removed).
const std::vector<unsigned int>& last_filament_id_remap() const { return m_last_filament_id_remap; }
@@ -312,6 +317,11 @@ private:
std::pair<PresetsConfigSubstitutions, std::string> load_system_presets_from_json(ForwardCompatibilitySubstitutionRule compatibility_rule);
// Merge one vendor's presets with the other vendor's presets, report duplicates.
std::vector<std::string> merge_presets(PresetBundle &&other);
void build_filament_id_remap(const std::vector<MixedFilament> &old_mixed,
size_t old_num_filaments,
size_t new_num_filaments,
bool deleting_filament,
unsigned int deleted_1based);
// Update renamed_from and alias maps of system profiles.
void update_system_maps();

View File

@@ -484,10 +484,11 @@ std::vector<unsigned int> Print::extruders(bool conside_custom_gcode) const
if (conside_custom_gcode) {
//BBS
int num_extruders = m_config.filament_colour.size();
const size_t num_physical = m_config.filament_colour.size();
const size_t num_filaments = m_mixed_filament_mgr.total_filaments(num_physical);
if (m_model.plates_custom_gcodes.find(m_model.curr_plate_index) != m_model.plates_custom_gcodes.end()) {
for (auto item : m_model.plates_custom_gcodes.at(m_model.curr_plate_index).gcodes) {
if (item.type == CustomGCode::Type::ToolChange && item.extruder <= num_extruders)
if (item.type == CustomGCode::Type::ToolChange && item.extruder <= int(num_filaments))
extruders.push_back((unsigned int)(item.extruder - 1));
}
}

View File

@@ -1439,9 +1439,10 @@ std::vector<int> PartPlate::get_extruders(bool conside_custom_gcode) const
int nums_extruders = 0;
if (const ConfigOptionStrings *color_option = dynamic_cast<const ConfigOptionStrings *>(wxGetApp().preset_bundle->project_config.option("filament_colour"))) {
nums_extruders = color_option->values.size();
const size_t total_filaments = wxGetApp().preset_bundle->mixed_filaments.total_filaments(size_t(nums_extruders));
if (m_model->plates_custom_gcodes.find(m_plate_index) != m_model->plates_custom_gcodes.end()) {
for (auto item : m_model->plates_custom_gcodes.at(m_plate_index).gcodes) {
if (item.type == CustomGCode::Type::ToolChange && item.extruder <= nums_extruders)
if (item.type == CustomGCode::Type::ToolChange && item.extruder <= int(total_filaments))
plate_extruders.push_back(item.extruder);
}
}
@@ -1561,9 +1562,12 @@ std::vector<int> PartPlate::get_extruders_under_cli(bool conside_custom_gcode, D
int nums_extruders = 0;
if (const ConfigOptionStrings *color_option = dynamic_cast<const ConfigOptionStrings *>(full_config.option("filament_colour"))) {
nums_extruders = color_option->values.size();
size_t total_filaments = size_t(nums_extruders);
if (wxGetApp().preset_bundle != nullptr)
total_filaments = wxGetApp().preset_bundle->mixed_filaments.total_filaments(total_filaments);
if (m_model->plates_custom_gcodes.find(m_plate_index) != m_model->plates_custom_gcodes.end()) {
for (auto item : m_model->plates_custom_gcodes.at(m_plate_index).gcodes) {
if (item.type == CustomGCode::Type::ToolChange && item.extruder <= nums_extruders)
if (item.type == CustomGCode::Type::ToolChange && item.extruder <= int(total_filaments))
plate_extruders.push_back(item.extruder);
}
}
@@ -1606,9 +1610,10 @@ std::vector<int> PartPlate::get_extruders_without_support(bool conside_custom_gc
int nums_extruders = 0;
if (const ConfigOptionStrings* color_option = dynamic_cast<const ConfigOptionStrings*>(wxGetApp().preset_bundle->project_config.option("filament_colour"))) {
nums_extruders = color_option->values.size();
const size_t total_filaments = wxGetApp().preset_bundle->mixed_filaments.total_filaments(size_t(nums_extruders));
if (m_model->plates_custom_gcodes.find(m_plate_index) != m_model->plates_custom_gcodes.end()) {
for (auto item : m_model->plates_custom_gcodes.at(m_plate_index).gcodes) {
if (item.type == CustomGCode::Type::ToolChange && item.extruder <= nums_extruders)
if (item.type == CustomGCode::Type::ToolChange && item.extruder <= int(total_filaments))
plate_extruders.push_back(item.extruder);
}
}

View File

@@ -4400,7 +4400,10 @@ void Sidebar::update_mixed_filament_panel()
return wxString::Format("(F%u + F%u)", unsigned(entry.component_a), unsigned(entry.component_b));
};
auto apply_mixed_entry_changes = [this, preset_bundle, print_cfg](size_t mixed_id, const MixedFilament &updated_mf, bool preserve_enabled = false) {
auto apply_mixed_entry_changes = [this, preset_bundle, print_cfg, num_physical](size_t mixed_id,
const MixedFilament &updated_mf,
bool preserve_enabled = false,
bool rebuild_virtual_id_remap = false) {
if (!preset_bundle)
return;
@@ -4409,6 +4412,7 @@ void Sidebar::update_mixed_filament_panel()
if (mixed_id >= mfs.size())
return;
const std::vector<MixedFilament> old_mixed = rebuild_virtual_id_remap ? mfs : std::vector<MixedFilament>();
MixedFilament merged = updated_mf;
if (preserve_enabled)
merged.enabled = mfs[mixed_id].enabled;
@@ -4435,6 +4439,9 @@ void Sidebar::update_mixed_filament_panel()
if (wxGetApp().plater())
wxGetApp().plater()->update_project_dirty_from_presets();
if (rebuild_virtual_id_remap)
preset_bundle->update_mixed_filament_id_remap(old_mixed, num_physical, num_physical);
int mode = 0;
if (const ConfigOptionBool *opt = preset_bundle->project_config.option<ConfigOptionBool>("mixed_filament_gradient_mode"))
mode = opt->value ? 1 : 0;
@@ -4452,6 +4459,9 @@ void Sidebar::update_mixed_filament_panel()
hi = std::max(lo, hi);
mgr.apply_gradient_settings(mode, lo, hi, advanced);
update_dynamic_filament_list();
if (rebuild_virtual_id_remap && wxGetApp().plater())
wxGetApp().plater()->on_filaments_change(num_physical);
};
size_t visible_mixed_idx = 0;
@@ -4507,7 +4517,7 @@ void Sidebar::update_mixed_filament_panel()
return;
MixedFilament updated = mfs[mixed_id];
updated.enabled = enabled_chk->GetValue();
apply_mixed_entry_changes(mixed_id, updated, false);
apply_mixed_entry_changes(mixed_id, updated, false, true);
});
auto *del_btn = new ScalableButton(header_panel, wxID_ANY, "cross");
@@ -4519,6 +4529,7 @@ void Sidebar::update_mixed_filament_panel()
auto &mgr = wxGetApp().preset_bundle->mixed_filaments;
auto &mfs = mgr.mixed_filaments();
if (mixed_id < mfs.size()) {
const std::vector<MixedFilament> old_mixed = mfs;
auto canonical_pair = [](unsigned int a, unsigned int b) {
return std::make_pair(std::min(a, b), std::max(a, b));
};
@@ -4571,10 +4582,12 @@ void Sidebar::update_mixed_filament_panel()
}
p->m_expanded_mixed_filament_rows.clear();
set_mixed_string("mixed_filament_definitions", mgr.serialize_custom_entries());
wxGetApp().preset_bundle->update_mixed_filament_id_remap(old_mixed, num_physical, num_physical);
notify_mixed_change();
if (wxGetApp().plater())
wxGetApp().plater()->update_project_dirty_from_presets();
update_mixed_filament_panel();
if (wxGetApp().plater())
wxGetApp().plater()->on_filaments_change(num_physical);
}
}
});
@@ -16894,30 +16907,6 @@ void Plater::on_filaments_change(size_t num_filaments)
}
}
// Add-flow is deterministic: old virtual filament IDs must shift by
// +delta_physical because physical IDs were prepended.
// Keep this rule authoritative to avoid stale/partial remaps.
if (num_filaments > old_num_filaments) {
const size_t delta = num_filaments - old_num_filaments;
const size_t old_first_virtual = old_num_filaments + 1;
size_t old_total = 0;
if (!id_remap.empty() && id_remap.size() > 1)
old_total = std::min(id_remap.size() - 1, state_map.size() - 1);
else
old_total = std::min((total_filaments >= delta ? total_filaments - delta : 0), state_map.size() - 1);
if (old_total >= old_first_virtual) {
should_remap_states = true;
for (size_t i = old_first_virtual; i <= old_total; ++i) {
const size_t mapped = i + delta;
if (mapped >= state_map.size() || mapped > total_filaments)
state_map[i] = EnforcerBlockerType::NONE;
else
state_map[i] = EnforcerBlockerType(mapped);
}
}
}
size_t changed_entries = 0;
std::string changed_map_preview = "[";
for (size_t i = 1; i < state_map.size(); ++i) {