Add release notes for v0.92 and refactor mixed filament gradient handling

- Introduced a new `RELEASE_NOTES_v0.92.md` file detailing highlights such as gradual gradient behavior, mixed filament indexing fixes, and editable automatic mixed filaments.
- Refactored `compute_gradient_ratios` to remove the cycle layers parameter, implementing a new gradual integer cadence for gradient transitions.
- Updated `MixedFilament` structure to include an `origin_auto` flag for better management of auto-generated entries.
- Adjusted related parsing, serialization, and UI handling to reflect these changes, ensuring improved user experience and color fidelity in mixed filament rendering.
This commit is contained in:
Rad
2026-02-23 21:47:26 +01:00
parent effe346814
commit f758028de0
14 changed files with 591 additions and 186 deletions
+112 -28
View File
@@ -10,6 +10,7 @@
#include <sstream>
#include <iomanip>
#include <numeric>
#include <set>
namespace Slic3r {
@@ -231,7 +232,7 @@ static void normalize_ratio_pair(int &a, int &b)
}
}
static void compute_gradient_ratios(MixedFilament &mf, int gradient_mode, float lower_bound, float upper_bound, int cycle_layers)
static void compute_gradient_ratios(MixedFilament &mf, int gradient_mode, float lower_bound, float upper_bound)
{
if (gradient_mode == 1) {
// Height-weighted mode:
@@ -245,12 +246,25 @@ static void compute_gradient_ratios(MixedFilament &mf, int gradient_mode, float
mf.ratio_b = std::max(1, safe_ratio_from_height(h_b, unit));
} else {
// Layer-cycle mode:
// distribute an integer cycle directly by blend percentages.
// derive a gradual integer cadence directly from the blend ratio
// by fixing the minority side to one layer and scaling the majority.
const int mix_b = clamp_int(mf.mix_b_percent, 0, 100);
const float pct_b = float(mix_b) / 100.f;
const int cycle = std::max(2, cycle_layers);
mf.ratio_b = clamp_int(int(std::lround(pct_b * cycle)), 0, cycle);
mf.ratio_a = cycle - mf.ratio_b;
if (mix_b <= 0) {
mf.ratio_a = 1;
mf.ratio_b = 0;
} else if (mix_b >= 100) {
mf.ratio_a = 0;
mf.ratio_b = 1;
} else {
const int pct_b = mix_b;
const int pct_a = 100 - pct_b;
const bool b_is_major = pct_b >= pct_a;
const int major_pct = b_is_major ? pct_b : pct_a;
const int minor_pct = b_is_major ? pct_a : pct_b;
const int major_layers = std::max(1, int(std::lround(double(major_pct) / double(std::max(1, minor_pct)))));
mf.ratio_a = b_is_major ? 1 : major_layers;
mf.ratio_b = b_is_major ? major_layers : 1;
}
}
normalize_ratio_pair(mf.ratio_a, mf.ratio_b);
@@ -303,6 +317,7 @@ static bool parse_row_definition(const std::string &row,
unsigned int &b,
bool &enabled,
bool &custom,
bool &origin_auto,
int &mix_b_percent,
bool &pointillism_all_filaments,
std::string &gradient_component_ids,
@@ -368,6 +383,7 @@ static bool parse_row_definition(const std::string &row,
b = unsigned(values[1]);
enabled = (values[2] != 0);
custom = (tokens.size() == 4) ? true : (values[3] != 0);
origin_auto = !custom;
mix_b_percent = clamp_int(values[4], 0, 100);
pointillism_all_filaments = false;
gradient_component_ids.clear();
@@ -418,6 +434,12 @@ static bool parse_row_definition(const std::string &row,
deleted = parsed_deleted != 0;
continue;
}
if (tok[0] == 'o' || tok[0] == 'O') {
int parsed_origin_auto = origin_auto ? 1 : 0;
if (parse_int_token(tok.substr(1), parsed_origin_auto))
origin_auto = parsed_origin_auto != 0;
continue;
}
manual_pattern = tok;
}
@@ -696,6 +718,7 @@ void MixedFilamentManager::auto_generate(const std::vector<std::string> &filamen
mf.enabled = true;
mf.deleted = false;
mf.custom = false;
mf.origin_auto = true;
// Try to preserve previous settings.
for (const auto &prev : old) {
@@ -769,6 +792,7 @@ void MixedFilamentManager::add_custom_filament(unsigned int component_a,
mf.enabled = true;
mf.deleted = false;
mf.custom = true;
mf.origin_auto = false;
m_mixed.push_back(std::move(mf));
refresh_display_colors(filament_colours);
}
@@ -799,13 +823,11 @@ std::string MixedFilamentManager::normalize_manual_pattern(const std::string &pa
void MixedFilamentManager::apply_gradient_settings(int gradient_mode,
float lower_bound,
float upper_bound,
int cycle_layers,
bool advanced_dithering)
{
m_gradient_mode = (gradient_mode != 0) ? 1 : 0;
m_height_lower_bound = std::max(0.01f, lower_bound);
m_height_upper_bound = std::max(m_height_lower_bound, upper_bound);
m_cycle_layers = std::max(2, cycle_layers);
m_advanced_dithering = advanced_dithering;
for (MixedFilament &mf : m_mixed) {
@@ -814,7 +836,7 @@ void MixedFilamentManager::apply_gradient_settings(int gradient_mode,
mf.ratio_b = 1;
continue;
}
compute_gradient_ratios(mf, m_gradient_mode, m_height_lower_bound, m_height_upper_bound, m_cycle_layers);
compute_gradient_ratios(mf, m_gradient_mode, m_height_lower_bound, m_height_upper_bound);
}
}
@@ -837,7 +859,8 @@ std::string MixedFilamentManager::serialize_custom_entries() const
<< 'g' << normalized_ids << ','
<< 'w' << normalized_weights << ','
<< 'm' << clamp_int(mf.distribution_mode, int(MixedFilament::LayerCycle), int(MixedFilament::Simple)) << ','
<< 'd' << (mf.deleted ? 1 : 0);
<< 'd' << (mf.deleted ? 1 : 0) << ','
<< 'o' << (mf.origin_auto ? 1 : 0);
const std::string normalized_pattern = normalize_manual_pattern(mf.manual_pattern);
if (!normalized_pattern.empty())
ss << ',' << normalized_pattern;
@@ -858,8 +881,24 @@ void MixedFilamentManager::load_custom_entries(const std::string &serialized, co
size_t parsed_rows = 0;
size_t loaded_rows = 0;
size_t updated_auto = 0;
size_t appended_auto = 0;
size_t skipped_rows = 0;
auto canonical_pair = [](unsigned int a, unsigned int b) {
return std::make_pair(std::min(a, b), std::max(a, b));
};
std::vector<MixedFilament> auto_rows;
auto_rows.reserve(m_mixed.size());
for (const MixedFilament &mf : m_mixed) {
if (!mf.custom)
auto_rows.push_back(mf);
}
std::vector<MixedFilament> rebuilt;
rebuilt.reserve(m_mixed.size() + 8);
std::set<std::pair<unsigned int, unsigned int>> consumed_auto_pairs;
std::stringstream all(serialized);
std::string row;
while (std::getline(all, row, ';')) {
@@ -870,6 +909,7 @@ void MixedFilamentManager::load_custom_entries(const std::string &serialized, co
unsigned int b = 0;
bool enabled = true;
bool custom = true;
bool origin_auto = false;
int mix = 50;
bool pointillism_all_filaments = false;
std::string gradient_component_ids;
@@ -877,7 +917,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, mix, pointillism_all_filaments,
if (!parse_row_definition(row, a, b, 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;
@@ -894,24 +934,49 @@ void MixedFilamentManager::load_custom_entries(const std::string &serialized, co
}
if (!custom) {
auto it_auto = std::find_if(m_mixed.begin(), m_mixed.end(), [a, b](const MixedFilament &mf) {
return !mf.custom && mf.component_a == a && mf.component_b == b;
});
if (it_auto != m_mixed.end()) {
it_auto->enabled = enabled;
it_auto->pointillism_all_filaments = pointillism_all_filaments;
it_auto->gradient_component_ids = normalize_gradient_component_ids(gradient_component_ids);
it_auto->gradient_component_weights =
normalize_gradient_component_weights(gradient_component_weights, it_auto->gradient_component_ids.size());
it_auto->manual_pattern = normalize_manual_pattern(manual_pattern);
it_auto->distribution_mode = clamp_int(distribution_mode, int(MixedFilament::LayerCycle), int(MixedFilament::Simple));
it_auto->mix_b_percent = it_auto->manual_pattern.empty() ? mix : mix_percent_from_normalized_pattern(it_auto->manual_pattern);
it_auto->deleted = deleted;
if (it_auto->deleted)
it_auto->enabled = false;
++updated_auto;
const auto key = canonical_pair(a, b);
if (consumed_auto_pairs.count(key) != 0) {
++skipped_rows;
BOOST_LOG_TRIVIAL(warning) << "MixedFilamentManager::load_custom_entries duplicate auto row"
<< ", row=" << row
<< ", a=" << key.first
<< ", b=" << key.second;
continue;
}
auto it_auto = std::find_if(auto_rows.begin(), auto_rows.end(), [key, canonical_pair](const MixedFilament &mf) {
return canonical_pair(mf.component_a, mf.component_b) == key;
});
if (it_auto == auto_rows.end()) {
++skipped_rows;
BOOST_LOG_TRIVIAL(warning) << "MixedFilamentManager::load_custom_entries auto row missing after regenerate"
<< ", row=" << row
<< ", a=" << key.first
<< ", b=" << key.second;
continue;
}
MixedFilament mf = *it_auto;
mf.component_a = key.first;
mf.component_b = key.second;
mf.enabled = enabled;
mf.pointillism_all_filaments = pointillism_all_filaments;
mf.gradient_component_ids = normalize_gradient_component_ids(gradient_component_ids);
mf.gradient_component_weights =
normalize_gradient_component_weights(gradient_component_weights, mf.gradient_component_ids.size());
mf.manual_pattern = normalize_manual_pattern(manual_pattern);
mf.distribution_mode = clamp_int(distribution_mode, int(MixedFilament::LayerCycle), int(MixedFilament::Simple));
mf.mix_b_percent = mf.manual_pattern.empty() ? mix : mix_percent_from_normalized_pattern(mf.manual_pattern);
mf.deleted = deleted;
if (mf.deleted)
mf.enabled = false;
mf.custom = false;
mf.origin_auto = true;
rebuilt.push_back(std::move(mf));
consumed_auto_pairs.insert(key);
++updated_auto;
continue;
}
MixedFilament mf;
@@ -933,15 +998,34 @@ void MixedFilamentManager::load_custom_entries(const std::string &serialized, co
if (mf.deleted)
mf.enabled = false;
mf.custom = custom;
m_mixed.push_back(std::move(mf));
mf.origin_auto = origin_auto;
rebuilt.push_back(std::move(mf));
++loaded_rows;
}
// Keep any newly generated auto rows that were not present in serialized
// definitions and append them at the end to preserve existing virtual IDs.
for (const MixedFilament &auto_mf : auto_rows) {
const auto key = canonical_pair(auto_mf.component_a, auto_mf.component_b);
if (consumed_auto_pairs.count(key) != 0)
continue;
MixedFilament mf = auto_mf;
mf.component_a = key.first;
mf.component_b = key.second;
mf.custom = false;
mf.origin_auto = true;
rebuilt.push_back(std::move(mf));
++appended_auto;
}
m_mixed = std::move(rebuilt);
refresh_display_colors(filament_colours);
BOOST_LOG_TRIVIAL(info) << "MixedFilamentManager::load_custom_entries"
<< ", physical_count=" << n
<< ", parsed_rows=" << parsed_rows
<< ", loaded_rows=" << loaded_rows
<< ", updated_auto_rows=" << updated_auto
<< ", appended_auto_rows=" << appended_auto
<< ", skipped_rows=" << skipped_rows
<< ", mixed_total=" << m_mixed.size();
}
+7 -3
View File
@@ -65,6 +65,11 @@ struct MixedFilament
// True when this row was user-created (custom) instead of auto-generated.
bool custom = false;
// True when this row originated from an auto-generated pair. This remains
// true even after editing so delete logic can keep the base auto pair
// tombstoned instead of letting regeneration resurrect it.
bool origin_auto = false;
// Computed display colour as "#RRGGBB".
std::string display_color;
@@ -82,7 +87,8 @@ struct MixedFilament
distribution_mode == rhs.distribution_mode &&
enabled == rhs.enabled &&
deleted == rhs.deleted &&
custom == rhs.custom;
custom == rhs.custom &&
origin_auto == rhs.origin_auto;
}
bool operator!=(const MixedFilament &rhs) const { return !(*this == rhs); }
};
@@ -125,7 +131,6 @@ public:
void apply_gradient_settings(int gradient_mode,
float lower_bound,
float upper_bound,
int cycle_layers,
bool advanced_dithering = false);
// Persist only custom rows.
@@ -196,7 +201,6 @@ private:
int m_gradient_mode = 0;
float m_height_lower_bound = 0.04f;
float m_height_upper_bound = 0.16f;
int m_cycle_layers = 4;
bool m_advanced_dithering = false;
};
+70 -31
View File
@@ -45,7 +45,6 @@ static std::vector<std::string> s_project_options {
"mixed_filament_gradient_mode",
"mixed_filament_height_lower_bound",
"mixed_filament_height_upper_bound",
"mixed_filament_cycle_layers",
"mixed_filament_advanced_dithering",
"mixed_filament_surface_indentation",
"mixed_filament_definitions",
@@ -1869,7 +1868,7 @@ void PresetBundle::update_num_filaments(unsigned int to_del_filament_id)
ams_multi_color_filment.resize(to_del_filament_id);
}
update_multi_material_filament_presets(to_del_filament_id);
update_multi_material_filament_presets(to_del_filament_id, old_filament_count);
}
void PresetBundle::set_num_filaments(unsigned int n, std::vector<std::string> new_colors) {
@@ -1890,7 +1889,7 @@ void PresetBundle::set_num_filaments(unsigned int n, std::vector<std::string> ne
}
}
}
update_multi_material_filament_presets();
update_multi_material_filament_presets(size_t(-1), size_t(old_filament_count));
}
void PresetBundle::set_num_filaments(unsigned int n, std::string new_color)
{
@@ -1914,7 +1913,7 @@ void PresetBundle::set_num_filaments(unsigned int n, std::string new_color)
}
}
update_multi_material_filament_presets();
update_multi_material_filament_presets(size_t(-1), size_t(old_filament_count));
}
unsigned int PresetBundle::sync_ams_list(unsigned int &unknowns)
@@ -3226,7 +3225,7 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_vendor_configs_
return std::make_pair(std::move(substitutions), presets_loaded);
}
void PresetBundle::update_multi_material_filament_presets(size_t to_delete_filament_id)
void PresetBundle::update_multi_material_filament_presets(size_t to_delete_filament_id, size_t old_num_filaments_arg)
{
if (printers.get_edited_preset().printer_technology() != ptFFF)
return;
@@ -3245,7 +3244,9 @@ void PresetBundle::update_multi_material_filament_presets(size_t to_delete_filam
size_t num_filaments = this->filament_presets.size();
#endif
const bool deleting_filament = (to_delete_filament_id != size_t(-1));
const size_t old_num_filaments = deleting_filament ? (num_filaments + 1) : num_filaments;
const size_t old_num_filaments = (old_num_filaments_arg != size_t(-1))
? old_num_filaments_arg
: (deleting_filament ? (num_filaments + 1) : num_filaments);
const std::vector<MixedFilament> old_mixed = this->mixed_filaments.mixed_filaments();
m_last_filament_id_remap.clear();
@@ -3288,13 +3289,6 @@ void PresetBundle::update_multi_material_filament_presets(size_t to_delete_filam
ConfigOptionStrings *color_opt = this->project_config.option<ConfigOptionStrings>("filament_colour");
if (color_opt) {
DynamicPrintConfig &print_cfg = this->prints.get_edited_preset().config;
auto get_mixed_int = [this, &print_cfg](const std::string &key, int fallback) {
if (this->project_config.has(key))
return this->project_config.opt_int(key);
if (print_cfg.has(key))
return print_cfg.opt_int(key);
return fallback;
};
auto get_mixed_bool = [this, &print_cfg](const std::string &key, bool fallback) {
if (const ConfigOptionBool *opt = this->project_config.option<ConfigOptionBool>(key))
return opt->value;
@@ -3354,16 +3348,14 @@ void PresetBundle::update_multi_material_filament_presets(size_t to_delete_filam
int gradient_mode = get_mixed_mode(false) ? 1 : 0;
float lower_bound = get_mixed_float("mixed_filament_height_lower_bound", 0.04f);
float upper_bound = get_mixed_float("mixed_filament_height_upper_bound", 0.16f);
int cycle_layers = get_mixed_int("mixed_filament_cycle_layers", 4);
bool advanced_dithering = get_mixed_bool("mixed_filament_advanced_dithering", false);
gradient_mode = std::clamp(gradient_mode, 0, 1);
lower_bound = std::max(0.01f, lower_bound);
upper_bound = std::max(lower_bound, upper_bound);
cycle_layers = std::max(2, cycle_layers);
this->mixed_filaments.clear_custom_entries();
this->mixed_filaments.load_custom_entries(get_mixed_string("mixed_filament_definitions"), color_opt->values);
this->mixed_filaments.apply_gradient_settings(gradient_mode, lower_bound, upper_bound, cycle_layers, advanced_dithering);
this->mixed_filaments.apply_gradient_settings(gradient_mode, lower_bound, upper_bound, advanced_dithering);
const std::string serialized = this->mixed_filaments.serialize_custom_entries();
set_mixed_string("mixed_filament_definitions", serialized);
@@ -3371,9 +3363,10 @@ void PresetBundle::update_multi_material_filament_presets(size_t to_delete_filam
}
// Build old->new filament ID remap for painted facet data normalization.
if (deleting_filament) {
const unsigned int deleted_1based = unsigned(to_delete_filament_id + 1);
// 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)
@@ -3383,20 +3376,30 @@ void PresetBundle::update_multi_material_filament_presets(size_t to_delete_filam
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) {
if (old_id == deleted_1based)
m_last_filament_id_remap[old_id] = 0;
else
m_last_filament_id_remap[old_id] = old_id > deleted_1based ? old_id - 1 : 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;
}
m_last_filament_id_remap[old_id] = mapped;
}
std::map<std::pair<unsigned int, unsigned int>, unsigned int> new_pair_to_id;
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_id[{mf.component_a, mf.component_b}] = 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;
unsigned int old_virtual_id = unsigned(old_num_filaments + 1);
for (const auto &mf : old_mixed) {
if (!mf.enabled)
@@ -3407,15 +3410,51 @@ void PresetBundle::update_multi_material_filament_presets(size_t to_delete_filam
if (a == deleted_1based || b == deleted_1based) {
m_last_filament_id_remap[old_virtual_id] = 0;
} else {
if (a > deleted_1based)
--a;
if (b > deleted_1based)
--b;
auto it = new_pair_to_id.find({a, b});
m_last_filament_id_remap[old_virtual_id] = (it == new_pair_to_id.end()) ? 0 : it->second;
if (deleting_filament) {
if (a > deleted_1based)
--a;
if (b > deleted_1based)
--b;
}
const auto key = canonical_pair(a, b);
auto it = new_pair_to_ids.find(key);
if (it == new_pair_to_ids.end()) {
m_last_filament_id_remap[old_virtual_id] = 0;
} else {
size_t &used = used_per_pair[key];
if (used >= it->second.size()) {
m_last_filament_id_remap[old_virtual_id] = 0;
} else {
m_last_filament_id_remap[old_virtual_id] = it->second[used++];
}
}
}
++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);
}
}
+9 -2
View File
@@ -252,10 +252,17 @@ public:
// Read out the number of extruders from an active printer preset,
// update size and content of filament_presets.
void update_multi_material_filament_presets(size_t to_delete_filament_id = size_t(-1));
// Mapping generated during the latest filament deletion.
void update_multi_material_filament_presets(size_t to_delete_filament_id = size_t(-1),
size_t old_num_filaments = size_t(-1));
// 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; }
std::vector<unsigned int> consume_last_filament_id_remap()
{
std::vector<unsigned int> out = std::move(m_last_filament_id_remap);
m_last_filament_id_remap.clear();
return out;
}
// Update the is_compatible flag of all print and filament presets depending on whether they are marked
// as compatible with the currently selected printer (and print in case of filament presets).
-1
View File
@@ -251,7 +251,6 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|| opt_key == "mixed_filament_gradient_mode"
|| opt_key == "mixed_filament_height_lower_bound"
|| opt_key == "mixed_filament_height_upper_bound"
|| opt_key == "mixed_filament_cycle_layers"
|| opt_key == "mixed_filament_advanced_dithering"
|| opt_key == "mixed_filament_surface_indentation"
|| opt_key == "mixed_filament_definitions"
-9
View File
@@ -1173,7 +1173,6 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
new_full_config.option("mixed_filament_gradient_mode", true);
new_full_config.option("mixed_filament_height_lower_bound", true);
new_full_config.option("mixed_filament_height_upper_bound", true);
new_full_config.option("mixed_filament_cycle_layers", true);
new_full_config.option("mixed_filament_advanced_dithering", true);
new_full_config.option("mixed_filament_pointillism_pixel_size", true);
new_full_config.option("mixed_filament_pointillism_line_gap", true);
@@ -1185,7 +1184,6 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
m_config.option("mixed_filament_gradient_mode", true);
m_config.option("mixed_filament_height_lower_bound", true);
m_config.option("mixed_filament_height_upper_bound", true);
m_config.option("mixed_filament_cycle_layers", true);
m_config.option("mixed_filament_advanced_dithering", true);
m_config.option("mixed_filament_pointillism_pixel_size", true);
m_config.option("mixed_filament_pointillism_line_gap", true);
@@ -1197,7 +1195,6 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
m_default_object_config.option("mixed_filament_gradient_mode", true);
m_default_object_config.option("mixed_filament_height_lower_bound", true);
m_default_object_config.option("mixed_filament_height_upper_bound", true);
m_default_object_config.option("mixed_filament_cycle_layers", true);
m_default_object_config.option("mixed_filament_advanced_dithering", true);
m_default_object_config.option("mixed_filament_pointillism_pixel_size", true);
m_default_object_config.option("mixed_filament_pointillism_line_gap", true);
@@ -1303,7 +1300,6 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
int mixed_gradient_mode = 0;
float mixed_height_lower = 0.04f;
float mixed_height_upper = 0.16f;
int mixed_cycle_layers = 4;
bool mixed_advanced_dither = false;
float mixed_pointillism_pixel_size = 0.f;
float mixed_pointillism_line_gap = 0.f;
@@ -1319,8 +1315,6 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
mixed_height_lower = float(new_full_config.opt_float("mixed_filament_height_lower_bound"));
if (new_full_config.has("mixed_filament_height_upper_bound"))
mixed_height_upper = float(new_full_config.opt_float("mixed_filament_height_upper_bound"));
if (new_full_config.has("mixed_filament_cycle_layers"))
mixed_cycle_layers = new_full_config.opt_int("mixed_filament_cycle_layers");
if (new_full_config.has("mixed_filament_advanced_dithering")) {
if (const ConfigOptionBool *opt = new_full_config.option<ConfigOptionBool>("mixed_filament_advanced_dithering"))
mixed_advanced_dither = opt->value;
@@ -1339,7 +1333,6 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
mixed_gradient_mode = std::clamp(mixed_gradient_mode, 0, 1);
mixed_height_lower = std::max(0.01f, mixed_height_lower);
mixed_height_upper = std::max(mixed_height_lower, mixed_height_upper);
mixed_cycle_layers = std::max(2, mixed_cycle_layers);
mixed_pointillism_pixel_size = std::max(0.f, mixed_pointillism_pixel_size);
mixed_pointillism_line_gap = std::max(0.f, mixed_pointillism_line_gap);
mixed_surface_indentation = std::clamp(mixed_surface_indentation, -2.f, 2.f);
@@ -1348,7 +1341,6 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
<< ", gradient_mode=" << mixed_gradient_mode
<< ", lower=" << mixed_height_lower
<< ", upper=" << mixed_height_upper
<< ", cycle_layers=" << mixed_cycle_layers
<< ", advanced_dither=" << (mixed_advanced_dither ? 1 : 0)
<< ", pointillism_pixel_size=" << mixed_pointillism_pixel_size
<< ", pointillism_line_gap=" << mixed_pointillism_line_gap
@@ -1366,7 +1358,6 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
m_mixed_filament_mgr.apply_gradient_settings(mixed_gradient_mode,
mixed_height_lower,
mixed_height_upper,
mixed_cycle_layers,
mixed_advanced_dither);
size_t mixed_custom_count = 0;
for (const auto &mf : m_mixed_filament_mgr.mixed_filaments())
-9
View File
@@ -4174,15 +4174,6 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(0.16));
def = this->add("mixed_filament_cycle_layers", coInt);
def->label = L("Mixed filament layer cycle");
def->category = L("Others");
def->tooltip = L("Number of layers in one alternation cycle for layer-cycle mixed filament mode.\n\n"
"Detailed mixed filament setting explanations will be published once the project wiki is available.");
def->min = 2;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionInt(4));
def = this->add("mixed_filament_advanced_dithering", coBool);
def->label = L("Advanced dithering");
def->category = L("Others");
-1
View File
@@ -1356,7 +1356,6 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
((ConfigOptionBool, mixed_filament_gradient_mode))
((ConfigOptionFloat, mixed_filament_height_lower_bound))
((ConfigOptionFloat, mixed_filament_height_upper_bound))
((ConfigOptionInt, mixed_filament_cycle_layers))
((ConfigOptionBool, mixed_filament_advanced_dithering))
((ConfigOptionFloat, mixed_filament_pointillism_pixel_size))
((ConfigOptionFloat, mixed_filament_pointillism_line_gap))
-1
View File
@@ -971,7 +971,6 @@ bool PrintObject::invalidate_state_by_config_options(
opt_key == "mixed_filament_gradient_mode"
|| opt_key == "mixed_filament_height_lower_bound"
|| opt_key == "mixed_filament_height_upper_bound"
|| opt_key == "mixed_filament_cycle_layers"
|| opt_key == "mixed_filament_advanced_dithering"
|| opt_key == "mixed_filament_surface_indentation"
|| opt_key == "mixed_filament_definitions") {
+40 -9
View File
@@ -900,12 +900,14 @@ static bool apply_mixed_surface_indentation(PrintObject &print_object, std::vect
size_t changed_states = 0;
size_t emptied_states = 0;
size_t overlap_clipped_states = 0;
size_t outside_trimmed_states = 0;
for (size_t layer_id = 0; layer_id < segmentation.size(); ++layer_id) {
if (segmentation[layer_id].size() != num_channels)
continue;
bool layer_changed = false;
ExPolygons outside_trim_band;
ExPolygons occupied;
if (expand_outward) {
for (size_t channel_idx = 0; channel_idx < num_channels; ++channel_idx) {
@@ -919,6 +921,25 @@ static bool apply_mixed_surface_indentation(PrintObject &print_object, std::vect
}
if (occupied.size() > 1)
occupied = union_ex(occupied);
} else {
ExPolygons layer_masks;
for (size_t channel_idx = 0; channel_idx < num_channels; ++channel_idx) {
const ExPolygons &state_masks = segmentation[layer_id][channel_idx];
if (!state_masks.empty())
append(layer_masks, state_masks);
}
if (!layer_masks.empty()) {
if (layer_masks.size() > 1)
layer_masks = union_ex(layer_masks);
ExPolygons layer_inner = offset_ex(layer_masks, -delta_scaled);
if (!layer_inner.empty() && layer_inner.size() > 1)
layer_inner = union_ex(layer_inner);
outside_trim_band = layer_inner.empty() ? layer_masks : diff_ex(layer_masks, layer_inner, ApplySafetyOffset::Yes);
if (!outside_trim_band.empty() && outside_trim_band.size() > 1)
outside_trim_band = union_ex(outside_trim_band);
}
}
for (size_t channel_idx = num_physical; channel_idx < num_channels; ++channel_idx) {
@@ -930,15 +951,24 @@ static bool apply_mixed_surface_indentation(PrintObject &print_object, std::vect
if (!mixed_mgr.is_mixed(state_id, num_physical))
continue;
ExPolygons adjusted = offset_ex(state_masks, expand_outward ? delta_scaled : -delta_scaled);
if (!adjusted.empty() && adjusted.size() > 1)
adjusted = union_ex(adjusted);
ExPolygons adjusted;
if (expand_outward) {
adjusted = offset_ex(state_masks, delta_scaled);
if (!adjusted.empty() && adjusted.size() > 1)
adjusted = union_ex(adjusted);
if (expand_outward && !adjusted.empty() && !occupied.empty()) {
ExPolygons clipped = diff_ex(adjusted, occupied, ApplySafetyOffset::Yes);
if (std::abs(area(clipped)) + EPSILON < std::abs(area(adjusted)))
++overlap_clipped_states;
adjusted = std::move(clipped);
if (!adjusted.empty() && !occupied.empty()) {
ExPolygons clipped = diff_ex(adjusted, occupied, ApplySafetyOffset::Yes);
if (std::abs(area(clipped)) + EPSILON < std::abs(area(adjusted)))
++overlap_clipped_states;
adjusted = std::move(clipped);
if (!adjusted.empty() && adjusted.size() > 1)
adjusted = union_ex(adjusted);
}
} else {
adjusted = outside_trim_band.empty() ? state_masks : diff_ex(state_masks, outside_trim_band, ApplySafetyOffset::Yes);
if (std::abs(area(adjusted)) + EPSILON < std::abs(area(state_masks)))
++outside_trimmed_states;
if (!adjusted.empty() && adjusted.size() > 1)
adjusted = union_ex(adjusted);
}
@@ -970,7 +1000,8 @@ static bool apply_mixed_surface_indentation(PrintObject &print_object, std::vect
<< " changed_layers=" << changed_layers
<< " changed_states=" << changed_states
<< " emptied_states=" << emptied_states
<< " overlap_clipped_states=" << overlap_clipped_states;
<< " overlap_clipped_states=" << overlap_clipped_states
<< " outside_trimmed_states=" << outside_trimmed_states;
return true;
}