Add mixed filament support and improve filament handling

- Introduced functions to calculate the physical filament count and maximum supported filament ID from project configuration, enhancing mixed filament management.
- Updated GUI components to reflect total filament counts, including mixed filaments, ensuring accurate display and interaction.
- Refactored filament initialization logic to streamline color assignment and naming for both physical and mixed filaments.
- Enhanced the sidebar update mechanism to refresh model canvas colors when mixed filaments are modified, improving visual feedback in the UI.
This commit is contained in:
Rad
2026-03-02 19:08:23 +01:00
parent db0ccb1210
commit d4063efc87
4 changed files with 189 additions and 40 deletions

View File

@@ -1,6 +1,7 @@
#include "../libslic3r.h" #include "../libslic3r.h"
#include "../Exception.hpp" #include "../Exception.hpp"
#include "../Model.hpp" #include "../Model.hpp"
#include "../MixedFilament.hpp"
#include "../Preset.hpp" #include "../Preset.hpp"
#include "../Utils.hpp" #include "../Utils.hpp"
#include "../LocalesUtils.hpp" #include "../LocalesUtils.hpp"
@@ -574,6 +575,51 @@ bool bbs_is_valid_object_type(const std::string& type)
namespace Slic3r { namespace Slic3r {
static size_t physical_filament_count_from_project_config(const DynamicPrintConfig &config)
{
if (const auto *opt = config.option<ConfigOptionStrings>("filament_colour"); opt != nullptr && !opt->values.empty())
return opt->values.size();
if (const auto *opt = config.option<ConfigOptionStrings>("filament_settings_id"); opt != nullptr && !opt->values.empty())
return opt->values.size();
if (const auto *opt = config.option<ConfigOptionStrings>("filament_ids"); opt != nullptr && !opt->values.empty())
return opt->values.size();
if (const auto *opt = config.option<ConfigOptionStrings>("default_filament_colour"); opt != nullptr && !opt->values.empty())
return opt->values.size();
if (const auto *opt = config.option<ConfigOptionFloats>("nozzle_diameter"); opt != nullptr && !opt->values.empty())
return opt->values.size();
return 0;
}
static int max_supported_filament_id_from_project_config(const DynamicPrintConfig &config)
{
const size_t physical_count = physical_filament_count_from_project_config(config);
if (physical_count == 0)
return std::numeric_limits<int>::max();
std::vector<std::string> physical_colors;
if (const auto *opt = config.option<ConfigOptionStrings>("filament_colour"); opt != nullptr)
physical_colors = opt->values;
else if (const auto *opt = config.option<ConfigOptionStrings>("default_filament_colour"); opt != nullptr)
physical_colors = opt->values;
if (physical_colors.size() < physical_count)
physical_colors.resize(physical_count, "#FFFFFF");
else if (physical_colors.size() > physical_count)
physical_colors.resize(physical_count);
size_t max_filament_id = physical_count;
if (physical_count >= 2) {
if (const auto *mixed_defs_opt = config.option<ConfigOptionString>("mixed_filament_definitions");
mixed_defs_opt != nullptr && !mixed_defs_opt->value.empty()) {
MixedFilamentManager mixed_mgr;
mixed_mgr.auto_generate(physical_colors);
mixed_mgr.load_custom_entries(mixed_defs_opt->value, physical_colors);
max_filament_id = mixed_mgr.total_filaments(physical_count);
}
}
return max_filament_id >= size_t(std::numeric_limits<int>::max()) ? std::numeric_limits<int>::max() : int(max_filament_id);
}
void PlateData::parse_filament_info(GCodeProcessorResult *result) void PlateData::parse_filament_info(GCodeProcessorResult *result)
{ {
if (!result) return; if (!result) return;
@@ -1787,7 +1833,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
// extract slic3r print config file // extract slic3r print config file
// _extract_print_config_from_archive(archive, stat, config, config_substitutions, filename); // _extract_print_config_from_archive(archive, stat, config, config_substitutions, filename);
//} else //} else
if (!dont_load_config && boost::algorithm::iequals(name, BBS_PROJECT_CONFIG_FILE)) { if (boost::algorithm::iequals(name, BBS_PROJECT_CONFIG_FILE)) {
// extract slic3r print config file // extract slic3r print config file
_extract_project_config_from_archive(archive, stat, config, config_substitutions, model); _extract_project_config_from_archive(archive, stat, config, config_substitutions, model);
} }
@@ -2085,8 +2131,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
++object_idx; ++object_idx;
} }
const ConfigOptionStrings* filament_ids_opt = config.option<ConfigOptionStrings>("filament_settings_id"); const int max_filament_id = max_supported_filament_id_from_project_config(config);
int max_filament_id = filament_ids_opt ? filament_ids_opt->size() : std::numeric_limits<int>::max();
for (ModelObject* mo : m_model->objects) { for (ModelObject* mo : m_model->objects) {
const ConfigOptionInt* extruder_opt = dynamic_cast<const ConfigOptionInt*>(mo->config.option("extruder")); const ConfigOptionInt* extruder_opt = dynamic_cast<const ConfigOptionInt*>(mo->config.option("extruder"));
int extruder_id = 0; int extruder_id = 0;

View File

@@ -68,15 +68,20 @@ static DynamicPrintConfig& printer_config()
return wxGetApp().preset_bundle->printers.get_edited_preset().config; return wxGetApp().preset_bundle->printers.get_edited_preset().config;
} }
static size_t total_filaments_count(size_t physical_count)
{
if (wxGetApp().preset_bundle == nullptr)
return physical_count;
return wxGetApp().preset_bundle->mixed_filaments.total_filaments(physical_count);
}
static int filaments_count() static int filaments_count()
{ {
if (wxGetApp().preset_bundle == nullptr) if (wxGetApp().preset_bundle == nullptr)
return 0; return 0;
int physical = wxGetApp().filaments_cnt(); return static_cast<int>(total_filaments_count(size_t(std::max(wxGetApp().filaments_cnt(), 0))));
// Include enabled mixed (virtual) filaments in the count.
const auto &mixed_mgr = wxGetApp().preset_bundle->mixed_filaments;
return static_cast<int>(mixed_mgr.total_filaments(physical));
} }
static void take_snapshot(const std::string& snapshot_name) static void take_snapshot(const std::string& snapshot_name)
@@ -959,6 +964,7 @@ void ObjectList::update_objects_list_filament_column_when_delete_filament(size_t
int replace_filament_id) int replace_filament_id)
{ {
m_prevent_update_filament_in_config = true; m_prevent_update_filament_in_config = true;
const size_t total_filaments = total_filaments_count(filaments_count);
// BBS: update extruder values even when filaments_count is 1, because it may be reduced from value greater than 1 // BBS: update extruder values even when filaments_count is 1, because it may be reduced from value greater than 1
if (m_objects) if (m_objects)
@@ -967,7 +973,7 @@ void ObjectList::update_objects_list_filament_column_when_delete_filament(size_t
update_filament_colors(); update_filament_colors();
// set show/hide for this column // set show/hide for this column
set_filament_column_hidden(filaments_count == 1); set_filament_column_hidden(total_filaments == 1);
// a workaround for a wrong last column width updating under OSX // a workaround for a wrong last column width updating under OSX
GetColumn(colEditing)->SetWidth(25); GetColumn(colEditing)->SetWidth(25);
@@ -977,6 +983,7 @@ void ObjectList::update_objects_list_filament_column_when_delete_filament(size_t
void ObjectList::update_objects_list_filament_column(size_t filaments_count) void ObjectList::update_objects_list_filament_column(size_t filaments_count)
{ {
assert(filaments_count >= 1); assert(filaments_count >= 1);
const size_t total_filaments = total_filaments_count(filaments_count);
if (printer_technology() == ptSLA) if (printer_technology() == ptSLA)
filaments_count = 1; filaments_count = 1;
@@ -985,12 +992,12 @@ void ObjectList::update_objects_list_filament_column(size_t filaments_count)
// BBS: update extruder values even when filaments_count is 1, because it may be reduced from value greater than 1 // BBS: update extruder values even when filaments_count is 1, because it may be reduced from value greater than 1
if (m_objects) if (m_objects)
update_filament_values_for_items(filaments_count); update_filament_values_for_items(printer_technology() == ptSLA ? 1 : total_filaments);
update_filament_colors(); update_filament_colors();
// set show/hide for this column // set show/hide for this column
set_filament_column_hidden(filaments_count == 1); set_filament_column_hidden((printer_technology() == ptSLA ? 1 : total_filaments) == 1);
//a workaround for a wrong last column width updating under OSX //a workaround for a wrong last column width updating under OSX
auto em = em_unit(this); auto em = em_unit(this);
GetColumn(colEditing)->SetWidth(m_columns_width[colEditing]*em); GetColumn(colEditing)->SetWidth(m_columns_width[colEditing]*em);

View File

@@ -2805,50 +2805,48 @@ int ObjectTablePanel::init_bitmap()
int ObjectTablePanel::init_filaments_and_colors() int ObjectTablePanel::init_filaments_and_colors()
{ {
//DynamicPrintConfig& global_config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
const DynamicPrintConfig* global_config = m_plater->config();
const std::vector<std::string> filament_presets = wxGetApp().preset_bundle->filament_presets; const std::vector<std::string> filament_presets = wxGetApp().preset_bundle->filament_presets;
m_filaments_count = filament_presets.size(); const std::vector<std::string> filament_colors = wxGetApp().plater()->get_extruder_colors_from_plater_config();
m_filaments_count = filament_colors.size();
if (m_filaments_count <= 0) { if (m_filaments_count <= 0) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", can not get filaments, count: %1%, set to default") %m_filaments_count; BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", can not get filaments, count: %1%, set to default") %m_filaments_count;
set_default_filaments_and_colors(); set_default_filaments_and_colors();
return -1; return -1;
} }
const ConfigOptionStrings* filament_opt = dynamic_cast<const ConfigOptionStrings*>(global_config->option("filament_colour"));
if (filament_opt == nullptr) {
set_default_filaments_and_colors();
return -1;
}
m_filaments_colors.resize(m_filaments_count); m_filaments_colors.resize(m_filaments_count);
m_filaments_name.resize(m_filaments_count); m_filaments_name.resize(m_filaments_count);
unsigned int color_count = filament_opt->values.size(); const size_t physical_count = filament_presets.size();
if (color_count != m_filaments_count) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", invalid color count:%1%, extruder count: %2%") %color_count %m_filaments_count;
}
unsigned int i = 0;
ColorRGB rgb; ColorRGB rgb;
while (i < m_filaments_count) {
const std::string& txt_color = global_config->opt_string("filament_colour", i); for (int i = 0; i < m_filaments_count; ++i) {
if (i < color_count) { if (size_t(i) < filament_colors.size() && decode_color(filament_colors[size_t(i)], rgb))
if (decode_color(txt_color, rgb)) m_filaments_colors[i] = wxColour(rgb.r_uchar(), rgb.g_uchar(), rgb.b_uchar());
{ else
m_filaments_colors[i] = wxColour(rgb.r_uchar(), rgb.g_uchar(), rgb.b_uchar());
}
else
{
m_filaments_colors[i] = *wxGREEN;
}
}
else {
m_filaments_colors[i] = *wxGREEN; m_filaments_colors[i] = *wxGREEN;
if (size_t(i) < physical_count) {
m_filaments_name[i] = wxString(std::to_string(i + 1) + ": " + filament_presets[size_t(i)]);
continue;
} }
//parse the filaments size_t mixed_offset = 0;
m_filaments_name[i] = wxString(std::to_string(i+1) + ": " + filament_presets[i]); for (const MixedFilament &mf : wxGetApp().preset_bundle->mixed_filaments.mixed_filaments()) {
if (!mf.enabled || mf.deleted)
continue;
if (size_t(i) != physical_count + mixed_offset) {
++mixed_offset;
continue;
}
i++; m_filaments_name[i] = wxString::Format("%d: Mixed Filament %d (F%u + F%u)",
i + 1, i + 1,
unsigned(mf.component_a), unsigned(mf.component_b));
break;
}
if (m_filaments_name[i].empty())
m_filaments_name[i] = wxString::Format("%d: Filament %d", i + 1, i + 1);
} }
return 0; return 0;

View File

@@ -3873,6 +3873,22 @@ void Sidebar::update_mixed_filament_panel()
if (!p->m_panel_mixed_filaments_title || !p->m_panel_mixed_filaments_content) if (!p->m_panel_mixed_filaments_title || !p->m_panel_mixed_filaments_content)
return; return;
auto refresh_model_canvas_colors = []() {
Plater *plater = wxGetApp().plater();
if (plater == nullptr)
return;
auto refresh_canvas = [](GLCanvas3D *canvas) {
if (canvas == nullptr || !canvas->is_initialized())
return;
canvas->update_volumes_colors_by_extruder();
canvas->render();
};
refresh_canvas(plater->get_view3D_canvas3D());
refresh_canvas(plater->get_assmeble_canvas3D());
};
int prev_rows_view_y = 0; int prev_rows_view_y = 0;
for (wxWindow *child : p->m_panel_mixed_filaments_content->GetChildren()) { for (wxWindow *child : p->m_panel_mixed_filaments_content->GetChildren()) {
if (auto *scrolled = dynamic_cast<wxScrolledWindow*>(child)) { if (auto *scrolled = dynamic_cast<wxScrolledWindow*>(child)) {
@@ -4325,6 +4341,7 @@ void Sidebar::update_mixed_filament_panel()
p->m_panel_mixed_filaments_title->Hide(); p->m_panel_mixed_filaments_title->Hide();
p->m_panel_mixed_filaments_content->Hide(); p->m_panel_mixed_filaments_content->Hide();
Layout(); Layout();
refresh_model_canvas_colors();
return; return;
} }
@@ -4740,6 +4757,7 @@ void Sidebar::update_mixed_filament_panel()
p->m_panel_mixed_filaments_content->Layout(); p->m_panel_mixed_filaments_content->Layout();
m_scrolled_sizer->Layout(); m_scrolled_sizer->Layout();
Layout(); Layout();
refresh_model_canvas_colors();
} }
void Sidebar::add_filament() { void Sidebar::add_filament() {
@@ -7142,6 +7160,32 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
<< boost::format(", plate_data.size %1%, project_preset.size %2%, is_bbs_3mf %3%, file_version %4% \n") % plate_data.size() % << boost::format(", plate_data.size %1%, project_preset.size %2%, is_bbs_3mf %3%, file_version %4% \n") % plate_data.size() %
project_presets.size() % (en_3mf_file_type == En3mfType::From_BBS) % file_version.to_string(); project_presets.size() % (en_3mf_file_type == En3mfType::From_BBS) % file_version.to_string();
auto imported_string_count = [&config_loaded](const char *key) -> size_t {
if (const auto *opt = config_loaded.option<ConfigOptionStrings>(key))
return opt->values.size();
return 0;
};
auto imported_float_count = [&config_loaded](const char *key) -> size_t {
if (const auto *opt = config_loaded.option<ConfigOptionFloats>(key))
return opt->values.size();
return 0;
};
std::vector<std::string> imported_filament_colors;
size_t imported_physical_filaments = 0;
if (const auto *filament_colors_opt = config_loaded.option<ConfigOptionStrings>("filament_colour")) {
imported_filament_colors = filament_colors_opt->values;
imported_physical_filaments = imported_filament_colors.size();
}
if (imported_physical_filaments == 0)
imported_physical_filaments = imported_string_count("filament_settings_id");
if (imported_physical_filaments == 0)
imported_physical_filaments = imported_string_count("filament_ids");
if (imported_physical_filaments == 0)
imported_physical_filaments = imported_string_count("default_filament_colour");
if (imported_physical_filaments == 0)
imported_physical_filaments = imported_float_count("nozzle_diameter");
// 1. add extruder for prusa model if the number of existing extruders is not enough // 1. add extruder for prusa model if the number of existing extruders is not enough
// 2. add extruder for BBS or Other model if only import geometry // 2. add extruder for BBS or Other model if only import geometry
if (en_3mf_file_type == En3mfType::From_Prusa || (load_model && !load_config)) { if (en_3mf_file_type == En3mfType::From_Prusa || (load_model && !load_config)) {
@@ -7154,6 +7198,57 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
} }
} }
int size = extruderIds.size() == 0 ? 0 : *(extruderIds.rbegin()); int size = extruderIds.size() == 0 ? 0 : *(extruderIds.rbegin());
const bool geometry_only_project_import = load_model && !load_config && imported_physical_filaments > 0;
const size_t desired_physical_filaments = geometry_only_project_import ?
std::min(imported_physical_filaments, size_t(MAXIMUM_EXTRUDER_NUMBER)) : 0;
BOOST_LOG_TRIVIAL(info) << "3MF geometry import filament detection"
<< " imported_physical=" << imported_physical_filaments
<< " imported_colors=" << imported_filament_colors.size()
<< " config_filament_settings_id=" << imported_string_count("filament_settings_id")
<< " config_filament_ids=" << imported_string_count("filament_ids")
<< " config_default_filament_colour=" << imported_string_count("default_filament_colour")
<< " config_nozzle_diameter=" << imported_float_count("nozzle_diameter")
<< " model_max_extruder=" << size
<< " geometry_only_project_import=" << (geometry_only_project_import ? 1 : 0);
if (geometry_only_project_import)
size = int(desired_physical_filaments);
PresetBundle *preset_bundle = wxGetApp().preset_bundle;
if (geometry_only_project_import && preset_bundle != nullptr) {
const size_t current_num_filaments = preset_bundle->filament_presets.size();
const bool current_project_empty = this->model.objects.empty();
if (current_project_empty) {
static const t_config_option_keys imported_project_option_keys = {
"filament_colour",
"mixed_filament_definitions",
"mixed_filament_gradient_mode",
"mixed_filament_height_lower_bound",
"mixed_filament_height_upper_bound",
"mixed_filament_advanced_dithering",
"mixed_filament_pointillism_pixel_size",
"mixed_filament_pointillism_line_gap",
"mixed_filament_surface_indentation"
};
preset_bundle->project_config.apply_only(config_loaded, imported_project_option_keys, true);
if (current_num_filaments != desired_physical_filaments)
preset_bundle->set_num_filaments(unsigned(desired_physical_filaments));
else
preset_bundle->update_multi_material_filament_presets();
BOOST_LOG_TRIVIAL(info) << "3MF geometry import applied imported project config"
<< " current_num_filaments=" << current_num_filaments
<< " desired_physical_filaments=" << desired_physical_filaments
<< " mixed_enabled=" << preset_bundle->mixed_filaments.enabled_count();
wxGetApp().plater()->on_filaments_change(desired_physical_filaments);
} else if (current_num_filaments < desired_physical_filaments) {
std::vector<std::string> new_colors;
if (imported_filament_colors.size() > current_num_filaments) {
new_colors.assign(imported_filament_colors.begin() + current_num_filaments,
imported_filament_colors.begin() + desired_physical_filaments);
}
preset_bundle->set_num_filaments(unsigned(desired_physical_filaments), new_colors);
wxGetApp().plater()->on_filaments_change(desired_physical_filaments);
}
}
int filament_size = sidebar->combos_filament().size(); int filament_size = sidebar->combos_filament().size();
while (filament_size < MAXIMUM_EXTRUDER_NUMBER && filament_size < size) { while (filament_size < MAXIMUM_EXTRUDER_NUMBER && filament_size < size) {
@@ -7497,6 +7592,10 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
// BBS: add preset combo box re-active logic // BBS: add preset combo box re-active logic
// currently found only needs re-active here // currently found only needs re-active here
wxGetApp().load_current_presets(false, false); wxGetApp().load_current_presets(false, false);
// Some preset-tab refresh paths rebuild printer/filament UI from the
// active presets but do not preserve the mixed manager instance.
// Rebuild it explicitly from project_config before clamping object IDs.
preset_bundle->update_multi_material_filament_presets();
// Update filament colors for the MM-printer profile in the full config // Update filament colors for the MM-printer profile in the full config
// to avoid black (default) colors for Extruders in the ObjectList, // to avoid black (default) colors for Extruders in the ObjectList,
// when for extruder colors are used filament colors // when for extruder colors are used filament colors