Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions src/libslic3r/MixedFilament.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2646,6 +2646,68 @@ void MixedFilamentManager::load_custom_entries(const std::string &serialized, co
<< ", mixed_total=" << m_mixed.size();
}

std::string MixedFilamentManager::clamp_serialized_entries_to_physical_count(const std::string &serialized, size_t num_physical)
{
if (serialized.empty() || num_physical < 2)
return {};

std::ostringstream out;
bool first = true;
size_t kept = 0;
size_t dropped = 0;
std::stringstream all(serialized);
std::string row;
while (std::getline(all, row, ';')) {
if (row.empty())
continue;

unsigned int a = 0;
unsigned int b = 0;
uint64_t stable_id = 0;
bool enabled = true;
bool custom = true;
bool origin_auto = false;
int mix = 50;
bool pointillism_all_filaments = false;
std::string gradient_component_ids;
std::string gradient_component_weights;
std::string manual_pattern;
int distribution_mode = int(MixedFilament::Simple);
int local_z_max_sublayers = 0;
float component_a_surface_offset = 0.f;
float component_b_surface_offset = 0.f;
bool deleted = false;
bool gradient_enabled = false;
float gradient_start = 0.8f;
float gradient_end = 0.2f;
int cm_mode = -1;
std::string image_fill_ref;
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,
local_z_max_sublayers, component_a_surface_offset, component_b_surface_offset, deleted,
gradient_enabled, gradient_start, gradient_end, cm_mode, image_fill_ref) ||
a == 0 || b == 0 || a > num_physical || b > num_physical || a == b ||
mixed_filament_references_exceed_physical(gradient_component_ids, manual_pattern, num_physical)) {
++dropped;
continue;
}

if (!first)
out << ';';
first = false;
out << row;
++kept;
}

if (dropped > 0)
BOOST_LOG_TRIVIAL(info) << "MixedFilamentManager::clamp_serialized_entries_to_physical_count"
<< ", physical_count=" << num_physical
<< ", kept_rows=" << kept
<< ", dropped_rows=" << dropped;

return out.str();
}

unsigned int MixedFilamentManager::resolve(unsigned int filament_id,
size_t num_physical,
int layer_index,
Expand Down
8 changes: 8 additions & 0 deletions src/libslic3r/MixedFilament.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,14 @@ class MixedFilamentManager
std::string serialize_custom_entries();
void load_custom_entries(const std::string &serialized, const std::vector<std::string> &filament_colours);

// Drop serialized mixed rows whose components (or gradient/pattern tokens)
// reference physical IDs beyond num_physical. Valid custom rows that still
// fit are kept verbatim. Empty input, or fewer than two physical slots,
// yields an empty string — mixed pairs cannot exist in that state.
// Edge analog of Orca #15728 resize_mixed_filament_metadata (truncate stale
// tails before grow); this project does not use filament_is_mixed arrays.
static std::string clamp_serialized_entries_to_physical_count(const std::string &serialized, size_t num_physical);

// ---- Pattern string functions -------------------------------------------
// Normalize a manual mixed-pattern string into canonical form.
// Format: digits 1-9 for IDs 1-9, [N] for IDs >= 10, comma for group separator.
Expand Down
75 changes: 65 additions & 10 deletions src/libslic3r/PresetBundle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,26 @@ void EraseFilamentColorFields(DynamicPrintConfig &config, size_t index)
EnsureFilamentColorFieldsAligned(config);
}

// Clamp mixed_filament_definitions in project (and optional print) config to the
// current physical slot count. Orphan tails that reference IDs beyond that count
// are discarded so a later grow cannot resurrect them as phantom mixed rows.
void normalize_mixed_filament_definitions(DynamicPrintConfig &project_config,
DynamicPrintConfig *print_cfg,
size_t physical_count)
{
auto clamp_opt = [physical_count](DynamicPrintConfig &cfg) {
if (ConfigOptionString *opt = cfg.option<ConfigOptionString>("mixed_filament_definitions")) {
const std::string clamped =
MixedFilamentManager::clamp_serialized_entries_to_physical_count(opt->value, physical_count);
if (clamped != opt->value)
opt->value = clamped;
}
};
clamp_opt(project_config);
if (print_cfg != nullptr)
clamp_opt(*print_cfg);
}

} // namespace

static std::vector<std::string> s_project_options {
Expand Down Expand Up @@ -2178,8 +2198,18 @@ void PresetBundle::update_num_filaments(unsigned int to_del_filament_id)
update_multi_material_filament_presets(to_del_filament_id, old_filament_count);
}

size_t PresetBundle::num_physical_filaments() const
{
if (const auto *colors = project_config.option<ConfigOptionStrings>("filament_colour")) {
if (!colors->values.empty())
return colors->values.size();
}
return filament_presets.size();
}

void PresetBundle::set_num_filaments(unsigned int n, std::vector<std::string> new_colors) {
int old_filament_count = this->filament_presets.size();
const unsigned old_filament_count = unsigned(this->filament_presets.size());
const size_t old_slot_count = this->num_physical_filaments();
if (n > old_filament_count && old_filament_count != 0)
filament_presets.resize(n, filament_presets.back());
else {
Expand All @@ -2190,21 +2220,27 @@ void PresetBundle::set_num_filaments(unsigned int n, std::vector<std::string> ne
ams_multi_color_filment.resize(n);
EnsureFilamentColorFieldsAligned(project_config);
// BBS set new filament color to new_color
if (old_filament_count < n) {
if (old_slot_count < n) {
if (!new_colors.empty()) {
ConfigOptionStrings *multi_colors = project_config.option<ConfigOptionStrings>("filament_multi_colors", true);
for (int i = old_filament_count; i < n; i++) {
filament_color->values[i] = new_colors[i - old_filament_count];
multi_colors->values[i] = new_colors[i - old_filament_count];
for (size_t i = old_slot_count; i < n; i++) {
filament_color->values[i] = new_colors[i - old_slot_count];
multi_colors->values[i] = new_colors[i - old_slot_count];
}
EnsureFilamentColorFieldsAligned(project_config);
}
}
update_multi_material_filament_presets(size_t(-1), size_t(old_filament_count));
// Palette may already have been written (batch-match / #866); presets still hold the old
// physical count and must drive remap. Otherwise colours are the configured slot count.
const size_t remap_old = (old_slot_count > old_filament_count && old_filament_count != 0)
? size_t(old_filament_count)
: old_slot_count;
update_multi_material_filament_presets(size_t(-1), remap_old);
}
void PresetBundle::set_num_filaments(unsigned int n, std::string new_color)
{
int old_filament_count = this->filament_presets.size();
const unsigned old_filament_count = unsigned(this->filament_presets.size());
const size_t old_slot_count = this->num_physical_filaments();
if (n > old_filament_count && old_filament_count != 0)
filament_presets.resize(n, filament_presets.back());
else {
Expand All @@ -2217,18 +2253,21 @@ void PresetBundle::set_num_filaments(unsigned int n, std::string new_color)
EnsureFilamentColorFieldsAligned(project_config);

//BBS set new filament color to new_color
if (old_filament_count < n) {
if (old_slot_count < n) {
if (!new_color.empty()) {
ConfigOptionStrings *multi_colors = project_config.option<ConfigOptionStrings>("filament_multi_colors", true);
for (int i = old_filament_count; i < n; i++) {
for (size_t i = old_slot_count; i < n; i++) {
filament_color->values[i] = new_color;
multi_colors->values[i] = new_color;
}
EnsureFilamentColorFieldsAligned(project_config);
}
}

update_multi_material_filament_presets(size_t(-1), size_t(old_filament_count));
const size_t remap_old = (old_slot_count > old_filament_count && old_filament_count != 0)
? size_t(old_filament_count)
: old_slot_count;
update_multi_material_filament_presets(size_t(-1), remap_old);
}

unsigned int PresetBundle::sync_ams_list(unsigned int &unknowns)
Expand Down Expand Up @@ -3025,6 +3064,12 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
// 4) Load the project config values (the per extruder wipe matrix etc).
this->project_config.apply_only(config, s_project_options);
EnsureFilamentColorFieldsAligned(this->project_config);
// Older projects can carry mixed_filament_definitions that no longer match the
// physical colour slots (short, missing, or a stale tail from a larger slot count).
// Clamp now so a later add-filament grow cannot resurrect orphan mixed rows.
normalize_mixed_filament_definitions(this->project_config,
&this->prints.get_edited_preset().config,
num_filaments);

break;
}
Expand Down Expand Up @@ -3796,6 +3841,16 @@ void PresetBundle::update_multi_material_filament_presets(size_t to_delete_filam
lower_bound = std::max(0.01f, lower_bound);
upper_bound = std::max(lower_bound, upper_bound);

// Grow must clamp mixed defs to the *old* physical count first: a stale tail
// that named the not-yet-added slot would otherwise be accepted as a custom row.
// Shrink/same clamp to the current count so orphan IDs are dropped.
if (!deleting_filament) {
const size_t mixed_defs_limit = (num_filaments > old_num_filaments)
? old_num_filaments
: num_filaments;
normalize_mixed_filament_definitions(this->project_config, &print_cfg, mixed_defs_limit);
}

this->mixed_filaments.clear_custom_entries();
this->mixed_filaments.load_custom_entries(
deleting_filament ? post_delete_mixed_defs : get_mixed_string("mixed_filament_definitions"),
Expand Down
3 changes: 3 additions & 0 deletions src/libslic3r/PresetBundle.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ class PresetBundle
void set_num_filaments(unsigned int n, std::string new_col = "");
void set_num_filaments(unsigned int n, std::vector<std::string> new_colors);
void update_num_filaments(unsigned int to_del_filament_id);
// Physical slot count. filament_colour is the source of truth; mixed_filament_definitions
// and a topped-up filament_presets list must not invent extra slots (Orca #15728 adapt).
size_t num_physical_filaments() const;
unsigned int sync_ams_list(unsigned int & unknowns);
//BBS: check whether this is the only edited filament
bool is_the_only_edited_filament(unsigned int filament_index);
Expand Down
25 changes: 16 additions & 9 deletions src/slic3r/GUI/Plater.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3254,12 +3254,15 @@ Sidebar::Sidebar(Plater *parent)
ScalableButton* add_btn = new ScalableButton(p->m_panel_physical_filaments_title, wxID_ANY, "add_filament");
add_btn->SetToolTip(_L("Add one filament"));
add_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e){
if (p->combos_filament.size() >= MAXIMUM_EXTRUDER_NUMBER)
return;
PresetBundle* pb = wxGetApp().preset_bundle;
if (!pb || pb->mixed_filaments.total_filaments(p->combos_filament.size()) >= MAXIMUM_FILAMENT_NUMBER)
// Colour slots are the physical count; combos can lag the extruder-count spinner
// and mixed_filament_definitions must not invent extra slots (Orca #15728 adapt).
const size_t physical_count = pb ? pb->num_physical_filaments() : p->combos_filament.size();
if (physical_count >= MAXIMUM_EXTRUDER_NUMBER)
return;
if (!pb || pb->mixed_filaments.total_filaments(physical_count) >= MAXIMUM_FILAMENT_NUMBER)
return;
int filament_count = p->combos_filament.size() + 1;
int filament_count = int(physical_count) + 1;
wxGetApp().plater()->confirm_auto_generated_gradients(filament_count);
wxColour new_col = Plater::get_next_color_for_filament();
std::string new_color = new_col.GetAsString(wxC2S_HTML_SYNTAX).ToStdString();
Expand Down Expand Up @@ -8426,9 +8429,10 @@ PlaterPresetComboBox* Sidebar::combo_printer() { return p->combo_printer; }
PlaterPresetComboBox* Sidebar::combo_print() { return p->combo_print; }

void Sidebar::add_filament() {
if (p->combos_filament.size() >= MAXIMUM_EXTRUDER_NUMBER) return;
PresetBundle* pb = wxGetApp().preset_bundle;
if (!pb || pb->mixed_filaments.total_filaments(p->combos_filament.size()) >= MAXIMUM_FILAMENT_NUMBER) return;
const size_t physical_count = pb ? pb->num_physical_filaments() : p->combos_filament.size();
if (physical_count >= MAXIMUM_EXTRUDER_NUMBER) return;
if (!pb || pb->mixed_filaments.total_filaments(physical_count) >= MAXIMUM_FILAMENT_NUMBER) return;
wxColour new_col = Plater::get_next_color_for_filament();
add_custom_filament(new_col);
// Reveal the just-added filament: it is appended at the end of the (height-capped,
Expand Down Expand Up @@ -9319,11 +9323,14 @@ void Sidebar::cleanup_unused_filaments_after_batch_match(const BatchMatchResult
}

void Sidebar::add_custom_filament(wxColour new_col) {
if (p->combos_filament.size() >= MAXIMUM_EXTRUDER_NUMBER) return;
PresetBundle* pb = wxGetApp().preset_bundle;
if (!pb || pb->mixed_filaments.total_filaments(p->combos_filament.size()) >= MAXIMUM_FILAMENT_NUMBER) return;
// Count configured colour slots, not the combo widgets or mixed definitions:
// the extruder-count spinner can reach this before the sidebar has rebuilt.
const size_t physical_count = pb ? pb->num_physical_filaments() : p->combos_filament.size();
if (physical_count >= MAXIMUM_EXTRUDER_NUMBER) return;
if (!pb || pb->mixed_filaments.total_filaments(physical_count) >= MAXIMUM_FILAMENT_NUMBER) return;

int filament_count = p->combos_filament.size() + 1;
int filament_count = int(physical_count) + 1;
wxGetApp().plater()->confirm_auto_generated_gradients(filament_count);
std::string new_color = new_col.GetAsString(wxC2S_HTML_SYNTAX).ToStdString();
pb->set_num_filaments(filament_count, new_color);
Expand Down
Loading