Implement advanced mixed filament features: Add gradient settings for mixed filaments, including height-weighted cadence and custom filament definitions. Enhance MixedFilamentManager to support custom rows and apply gradient settings during filament generation. Update PrintConfig and GUI to accommodate new configuration options for mixed filament management.

This commit is contained in:
Rad
2026-02-10 19:40:22 +01:00
parent e6cfadddca
commit 494835c5ba
14 changed files with 1796 additions and 137 deletions

View File

@@ -31,6 +31,8 @@ unsigned int resolve_mixed_with_layer_heights(const MixedFilamentManager *mixed_
size_t num_physical,
unsigned int filament_id_1based,
int layer_index,
float layer_print_z,
float layer_height,
float layer_height_a,
float layer_height_b,
float base_layer_height)
@@ -38,15 +40,17 @@ unsigned int resolve_mixed_with_layer_heights(const MixedFilamentManager *mixed_
if (!(mixed_mgr && mixed_mgr->is_mixed(filament_id_1based, num_physical)))
return filament_id_1based;
if (layer_height_a > 0.f || layer_height_b > 0.f) {
const size_t idx = static_cast<size_t>(filament_id_1based - num_physical - 1);
const auto &mixed = mixed_mgr->mixed_filaments();
const bool is_custom_mixed = idx < mixed.size() && mixed[idx].custom;
if (!is_custom_mixed && (layer_height_a > 0.f || layer_height_b > 0.f)) {
const float safe_base = std::max<float>(0.01f, base_layer_height);
const int ratio_a = std::max(1, int(std::lround((layer_height_a > 0.f ? layer_height_a : safe_base) / safe_base)));
const int ratio_b = std::max(1, int(std::lround((layer_height_b > 0.f ? layer_height_b : safe_base) / safe_base)));
const int cycle = ratio_a + ratio_b;
if (cycle > 0) {
const size_t idx = static_cast<size_t>(filament_id_1based - num_physical - 1);
const auto &mixed = mixed_mgr->mixed_filaments();
if (idx < mixed.size()) {
const int pos = ((layer_index % cycle) + cycle) % cycle;
return pos < ratio_a ? mixed[idx].component_a : mixed[idx].component_b;
@@ -54,7 +58,7 @@ unsigned int resolve_mixed_with_layer_heights(const MixedFilamentManager *mixed_
}
}
return mixed_mgr->resolve(filament_id_1based, num_physical, layer_index);
return mixed_mgr->resolve(filament_id_1based, num_physical, layer_index, layer_print_z, layer_height);
}
} // namespace
@@ -188,6 +192,8 @@ unsigned int LayerTools::resolve_mixed_1based(unsigned int filament_id) const
num_physical,
filament_id,
this->layer_index,
float(this->print_z),
float(this->layer_height),
mixed_layer_height_a,
mixed_layer_height_b,
mixed_base_layer_height);
@@ -571,11 +577,18 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto
// Collect the support extruders.
for (auto support_layer : object.support_layers()) {
LayerTools &layer_tools = this->tools_for_layer(support_layer->print_z);
layer_tools.layer_height = support_layer->height;
ExtrusionRole role = support_layer->support_fills.role();
bool has_support = role == erMixed || role == erSupportMaterial || role == erSupportTransition;
bool has_interface = role == erMixed || role == erSupportMaterialInterface;
unsigned int extruder_support = resolve_mixed(object.config().support_filament.value, layer_tools.layer_index);
unsigned int extruder_interface = resolve_mixed(object.config().support_interface_filament.value, layer_tools.layer_index);
unsigned int extruder_support = resolve_mixed(object.config().support_filament.value,
layer_tools.layer_index,
float(support_layer->print_z),
float(support_layer->height));
unsigned int extruder_interface = resolve_mixed(object.config().support_interface_filament.value,
layer_tools.layer_index,
float(support_layer->print_z),
float(support_layer->height));
if (has_support)
layer_tools.extruders.push_back(extruder_support);
if (has_interface)
@@ -600,7 +613,8 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto
for (auto layer : object.layers()) {
LayerTools &layer_tools = this->tools_for_layer(layer->print_z);
// Store the sequential layer index and mixed-filament context for resolution.
layer_tools.layer_index = layerCount;
layer_tools.layer_index = layerCount;
layer_tools.layer_height = layer->height;
// Override extruder with the next
for (; it_per_layer_extruder_override != per_layer_extruder_switches.end() && it_per_layer_extruder_override->first < layer->print_z + EPSILON; ++ it_per_layer_extruder_override)
@@ -625,7 +639,7 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto
if (something_nonoverriddable){
unsigned int wall_ext = (extruder_override == 0) ? region.config().wall_filament.value : extruder_override;
wall_ext = resolve_mixed(wall_ext, layerCount);
wall_ext = resolve_mixed(wall_ext, layerCount, float(layer->print_z), float(layer->height));
layer_tools.extruders.emplace_back(wall_ext);
if (layerCount == 0) {
firstLayerExtruders.emplace_back(wall_ext);
@@ -656,11 +670,20 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto
if (something_nonoverriddable || !m_print_config_ptr) {
if (extruder_override == 0) {
if (has_solid_infill)
layer_tools.extruders.emplace_back(resolve_mixed(region.config().solid_infill_filament, layerCount));
layer_tools.extruders.emplace_back(resolve_mixed(region.config().solid_infill_filament,
layerCount,
float(layer->print_z),
float(layer->height)));
if (has_infill)
layer_tools.extruders.emplace_back(resolve_mixed(region.config().sparse_infill_filament, layerCount));
layer_tools.extruders.emplace_back(resolve_mixed(region.config().sparse_infill_filament,
layerCount,
float(layer->print_z),
float(layer->height)));
} else if (has_solid_infill || has_infill)
layer_tools.extruders.emplace_back(resolve_mixed(extruder_override, layerCount));
layer_tools.extruders.emplace_back(resolve_mixed(extruder_override,
layerCount,
float(layer->print_z),
float(layer->height)));
}
if (has_solid_infill || has_infill)
layer_tools.has_object = true;
@@ -1513,12 +1536,17 @@ int WipingExtrusions::get_support_interface_extruder_overrides(const PrintObject
}
// Resolve a 1-based filament ID through the mixed-filament manager.
unsigned int ToolOrdering::resolve_mixed(unsigned int filament_id_1based, int layer_index) const
unsigned int ToolOrdering::resolve_mixed(unsigned int filament_id_1based,
int layer_index,
float layer_print_z,
float layer_height) const
{
return resolve_mixed_with_layer_heights(m_mixed_mgr,
m_num_physical,
filament_id_1based,
layer_index,
layer_print_z,
layer_height,
m_mixed_layer_height_a,
m_mixed_layer_height_b,
m_mixed_base_layer_height);

View File

@@ -122,6 +122,8 @@ public:
unsigned int extruder_override = 0;
// Sequential layer index (0-based), used by mixed-filament resolution.
int layer_index = 0;
// Actual layer height for this print_z where available.
coordf_t layer_height = 0.;
// Should a skirt be printed at this layer?
// Layers are marked for infinite skirt aka draft shield. Not all the layers have to be printed.
bool has_skirt = false;
@@ -220,7 +222,10 @@ private:
// Resolve a 1-based filament ID through the mixed-filament manager.
// Returns the resolved physical extruder (1-based). If the ID is not a
// mixed filament or no manager is set, returns the input unchanged.
unsigned int resolve_mixed(unsigned int filament_id_1based, int layer_index) const;
unsigned int resolve_mixed(unsigned int filament_id_1based,
int layer_index,
float layer_print_z = 0.f,
float layer_height = 0.f) const;
std::vector<LayerTools> m_layer_tools;
// First printing extruder, including the multi-material priming sequence.

View File

@@ -5,6 +5,7 @@
#include <cstdio>
#include <sstream>
#include <iomanip>
#include <numeric>
namespace Slic3r {
@@ -146,6 +147,148 @@ static std::string rgb_to_hex(const RGB &c)
return std::string(buf);
}
static int clamp_int(int v, int lo, int hi)
{
return std::max(lo, std::min(hi, v));
}
static int safe_ratio_from_height(float h, float unit)
{
if (unit <= 1e-6f)
return 1;
return std::max(0, int(std::lround(h / unit)));
}
static void compute_gradient_heights(const MixedFilament &mf, float lower_bound, float upper_bound, float &h_a, float &h_b)
{
const int mix_b = clamp_int(mf.mix_b_percent, 0, 100);
const float pct_b = float(mix_b) / 100.f;
const float pct_a = 1.f - pct_b;
const float lo = std::max(0.01f, lower_bound);
const float hi = std::max(lo, upper_bound);
h_a = lo + pct_a * (hi - lo);
h_b = lo + pct_b * (hi - lo);
}
static void normalize_ratio_pair(int &a, int &b)
{
a = std::max(0, a);
b = std::max(0, b);
if (a == 0 && b == 0) {
a = 1;
return;
}
if (a > 0 && b > 0) {
const int g = std::gcd(a, b);
if (g > 1) {
a /= g;
b /= g;
}
}
}
static void compute_gradient_ratios(MixedFilament &mf, int gradient_mode, float lower_bound, float upper_bound, int cycle_layers)
{
if (gradient_mode == 1) {
// Height-weighted mode:
// map blend to [lower, upper], then convert relative heights to an integer cadence.
float h_a = 0.f;
float h_b = 0.f;
compute_gradient_heights(mf, lower_bound, upper_bound, h_a, h_b);
// Use lower-bound as quantization unit so this mode differs clearly from layer-cycle mode.
const float unit = std::max(0.01f, std::min(h_a, h_b));
mf.ratio_a = std::max(1, safe_ratio_from_height(h_a, unit));
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.
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;
}
normalize_ratio_pair(mf.ratio_a, mf.ratio_b);
}
static int safe_mod(int x, int m)
{
if (m <= 0)
return 0;
int r = x % m;
return (r < 0) ? (r + m) : r;
}
static int dithering_phase_step(int cycle)
{
if (cycle <= 1)
return 0;
int step = cycle / 2 + 1;
while (std::gcd(step, cycle) != 1)
++step;
return step % cycle;
}
static bool use_component_b_advanced_dither(int layer_index, int ratio_a, int ratio_b)
{
ratio_a = std::max(0, ratio_a);
ratio_b = std::max(0, ratio_b);
const int cycle = ratio_a + ratio_b;
if (cycle <= 0 || ratio_b <= 0)
return false;
if (ratio_a <= 0)
return true;
// Base ordered pattern: as evenly distributed as possible for ratio_b/cycle.
const int pos = safe_mod(layer_index, cycle);
const int cycle_idx = (layer_index - pos) / cycle;
// Rotate each cycle to avoid visible long-period vertical striping.
const int phase = safe_mod(cycle_idx * dithering_phase_step(cycle), cycle);
const int p = safe_mod(pos + phase, cycle);
const int b_before = (p * ratio_b) / cycle;
const int b_after = ((p + 1) * ratio_b) / cycle;
return b_after > b_before;
}
static bool parse_row_definition(const std::string &row,
unsigned int &a,
unsigned int &b,
bool &enabled,
bool &custom,
int &mix_b_percent)
{
std::vector<int> values;
std::stringstream ss(row);
std::string token;
while (std::getline(ss, token, ',')) {
if (token.empty())
return false;
try {
values.push_back(std::stoi(token));
} catch (...) {
return false;
}
}
if (values.size() != 4 && values.size() != 5)
return false;
if (values[0] <= 0 || values[1] <= 0)
return false;
a = unsigned(values[0]);
b = unsigned(values[1]);
enabled = (values[2] != 0);
custom = (values.size() == 5) ? (values[3] != 0) : true;
mix_b_percent = clamp_int(values.size() == 5 ? values[4] : values[3], 0, 100);
return true;
}
// ---------------------------------------------------------------------------
// MixedFilamentManager
// ---------------------------------------------------------------------------
@@ -153,7 +296,7 @@ static std::string rgb_to_hex(const RGB &c)
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
// enabled flags.
// enabled flags and custom rows.
std::vector<MixedFilament> old = std::move(m_mixed);
m_mixed.clear();
@@ -161,6 +304,16 @@ void MixedFilamentManager::auto_generate(const std::vector<std::string> &filamen
if (n < 2)
return;
std::vector<MixedFilament> custom_rows;
custom_rows.reserve(old.size());
for (const MixedFilament &prev : old) {
if (!prev.custom)
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);
}
// Generate all C(N,2) pairwise combinations.
for (size_t i = 0; i < n; ++i) {
for (size_t j = i + 1; j < n; ++j) {
@@ -169,25 +322,27 @@ void MixedFilamentManager::auto_generate(const std::vector<std::string> &filamen
mf.component_b = static_cast<unsigned int>(j + 1);
mf.ratio_a = 1;
mf.ratio_b = 1;
mf.mix_b_percent = 50;
mf.enabled = true;
mf.custom = false;
// Try to preserve previous settings.
for (const auto &prev : old) {
if (prev.component_a == mf.component_a &&
if (!prev.custom &&
prev.component_a == mf.component_a &&
prev.component_b == mf.component_b) {
mf.ratio_a = prev.ratio_a;
mf.ratio_b = prev.ratio_b;
mf.enabled = prev.enabled;
break;
}
}
mf.display_color = blend_color(filament_colours[i],
filament_colours[j],
mf.ratio_a, mf.ratio_b);
m_mixed.push_back(mf);
}
}
for (MixedFilament &mf : custom_rows)
m_mixed.push_back(std::move(mf));
refresh_display_colors(filament_colours);
}
void MixedFilamentManager::remove_physical_filament(unsigned int deleted_filament_id)
@@ -211,9 +366,127 @@ void MixedFilamentManager::remove_physical_filament(unsigned int deleted_filamen
m_mixed = std::move(filtered);
}
void MixedFilamentManager::add_custom_filament(unsigned int component_a,
unsigned int component_b,
int mix_b_percent,
const std::vector<std::string> &filament_colours)
{
const size_t n = filament_colours.size();
if (n < 2)
return;
component_a = std::max<unsigned int>(1, std::min<unsigned int>(component_a, unsigned(n)));
component_b = std::max<unsigned int>(1, std::min<unsigned int>(component_b, unsigned(n)));
if (component_a == component_b) {
component_b = (component_a == 1) ? 2 : 1;
}
MixedFilament mf;
mf.component_a = component_a;
mf.component_b = component_b;
mf.mix_b_percent = clamp_int(mix_b_percent, 0, 100);
mf.ratio_a = 1;
mf.ratio_b = 1;
mf.enabled = true;
mf.custom = true;
m_mixed.push_back(std::move(mf));
refresh_display_colors(filament_colours);
}
void MixedFilamentManager::clear_custom_entries()
{
m_mixed.erase(std::remove_if(m_mixed.begin(), m_mixed.end(), [](const MixedFilament &mf) { return mf.custom; }), m_mixed.end());
}
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) {
if (!mf.custom) {
mf.ratio_a = 1;
mf.ratio_b = 1;
continue;
}
compute_gradient_ratios(mf, m_gradient_mode, m_height_lower_bound, m_height_upper_bound, m_cycle_layers);
}
}
std::string MixedFilamentManager::serialize_custom_entries() const
{
std::ostringstream ss;
bool first = true;
for (const MixedFilament &mf : m_mixed) {
if (!first)
ss << ';';
first = false;
ss << mf.component_a << ','
<< mf.component_b << ','
<< (mf.enabled ? 1 : 0) << ','
<< (mf.custom ? 1 : 0) << ','
<< clamp_int(mf.mix_b_percent, 0, 100);
}
return ss.str();
}
void MixedFilamentManager::load_custom_entries(const std::string &serialized, const std::vector<std::string> &filament_colours)
{
const size_t n = filament_colours.size();
if (serialized.empty() || n < 2)
return;
std::stringstream all(serialized);
std::string row;
while (std::getline(all, row, ';')) {
if (row.empty())
continue;
unsigned int a = 0;
unsigned int b = 0;
bool enabled = true;
bool custom = true;
int mix = 50;
if (!parse_row_definition(row, a, b, enabled, custom, mix))
continue;
if (a == 0 || b == 0 || a > n || b > n || a == b)
continue;
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->mix_b_percent = mix;
continue;
}
}
MixedFilament mf;
mf.component_a = a;
mf.component_b = b;
mf.mix_b_percent = mix;
mf.ratio_a = 1;
mf.ratio_b = 1;
mf.enabled = enabled;
mf.custom = custom;
m_mixed.push_back(std::move(mf));
}
refresh_display_colors(filament_colours);
}
unsigned int MixedFilamentManager::resolve(unsigned int filament_id,
size_t num_physical,
int layer_index) const
int layer_index,
float layer_print_z,
float layer_height) const
{
if (!is_mixed(filament_id, num_physical))
return filament_id;
@@ -223,10 +496,30 @@ unsigned int MixedFilamentManager::resolve(unsigned int filament_id,
return 1; // fallback to first extruder
const MixedFilament &mf = m_mixed[idx];
// Height-weighted cadence for custom rows uses Z-height windows rather
// than integer layer counts.
if (m_gradient_mode == 1 && mf.custom) {
float h_a = 0.f;
float h_b = 0.f;
compute_gradient_heights(mf, m_height_lower_bound, m_height_upper_bound, h_a, h_b);
const float cycle_h = std::max(0.01f, h_a + h_b);
const float z_anchor = (layer_height > 1e-6f)
? std::max(0.f, layer_print_z - 0.5f * layer_height)
: std::max(0.f, layer_print_z);
float phase = std::fmod(z_anchor, cycle_h);
if (phase < 0.f)
phase += cycle_h;
return (phase < h_a) ? mf.component_a : mf.component_b;
}
const int cycle = mf.ratio_a + mf.ratio_b;
if (cycle <= 0)
return mf.component_a;
if (m_gradient_mode == 0 && m_advanced_dithering && mf.custom)
return use_component_b_advanced_dither(layer_index, mf.ratio_a, mf.ratio_b) ? mf.component_b : mf.component_a;
const int pos = ((layer_index % cycle) + cycle) % cycle; // safe modulo for negatives
return (pos < mf.ratio_a) ? mf.component_a : mf.component_b;
}
@@ -265,6 +558,23 @@ std::string MixedFilamentManager::blend_color(const std::string &color_a,
return rgb_to_hex(to_rgb8(rgb_out));
}
void MixedFilamentManager::refresh_display_colors(const std::vector<std::string> &filament_colours)
{
for (MixedFilament &mf : m_mixed) {
if (mf.component_a == 0 || mf.component_b == 0 ||
mf.component_a > filament_colours.size() || mf.component_b > filament_colours.size()) {
mf.display_color = "#26A69A";
continue;
}
const int ratio_a = std::max(0, 100 - clamp_int(mf.mix_b_percent, 0, 100));
const int ratio_b = clamp_int(mf.mix_b_percent, 0, 100);
mf.display_color = blend_color(
filament_colours[mf.component_a - 1],
filament_colours[mf.component_b - 1],
ratio_a, ratio_b);
}
}
size_t MixedFilamentManager::enabled_count() const
{
size_t count = 0;

View File

@@ -23,9 +23,15 @@ struct MixedFilament
int ratio_a = 1;
int ratio_b = 1;
// Blend percentage of component B in [0..100].
int mix_b_percent = 50;
// Whether this mixed filament is enabled (available for assignment).
bool enabled = true;
// True when this row was user-created (custom) instead of auto-generated.
bool custom = false;
// Computed display colour as "#RRGGBB".
std::string display_color;
@@ -35,7 +41,9 @@ struct MixedFilament
component_b == rhs.component_b &&
ratio_a == rhs.ratio_a &&
ratio_b == rhs.ratio_b &&
enabled == rhs.enabled;
mix_b_percent == rhs.mix_b_percent &&
enabled == rhs.enabled &&
custom == rhs.custom;
}
bool operator!=(const MixedFilament &rhs) const { return !(*this == rhs); }
};
@@ -67,6 +75,24 @@ public:
// Remaining component IDs are shifted down to stay aligned with physical IDs.
void remove_physical_filament(unsigned int deleted_filament_id);
// Add a custom mixed filament.
void add_custom_filament(unsigned int component_a, unsigned int component_b, int mix_b_percent, const std::vector<std::string> &filament_colours);
// Remove all custom rows, keep auto-generated ones.
void clear_custom_entries();
// Recompute cadence ratios from gradient settings.
// gradient_mode: 0 = Layer cycle weighted, 1 = Height weighted.
void apply_gradient_settings(int gradient_mode,
float lower_bound,
float upper_bound,
int cycle_layers,
bool advanced_dithering = false);
// Persist only custom rows.
std::string serialize_custom_entries() const;
void load_custom_entries(const std::string &serialized, const std::vector<std::string> &filament_colours);
// ---- Queries --------------------------------------------------------
// True when `filament_id` (1-based) refers to a mixed filament.
@@ -76,9 +102,13 @@ public:
}
// Resolve a mixed filament ID to a physical extruder (1-based) for the
// given `layer_index`. Returns `filament_id` unchanged when it is not a
// given layer context. Returns `filament_id` unchanged when it is not a
// mixed filament.
unsigned int resolve(unsigned int filament_id, size_t num_physical, int layer_index) const;
unsigned int resolve(unsigned int filament_id,
size_t num_physical,
int layer_index,
float layer_print_z = 0.f,
float layer_height = 0.f) const;
// Compute a display colour by blending in RYB pigment space.
static std::string blend_color(const std::string &color_a,
@@ -105,7 +135,14 @@ private:
return static_cast<size_t>(filament_id - num_physical - 1);
}
void refresh_display_colors(const std::vector<std::string> &filament_colours);
std::vector<MixedFilament> m_mixed;
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;
};
} // namespace Slic3r

View File

@@ -3274,8 +3274,74 @@ 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 (print_cfg.has(key))
return print_cfg.opt_int(key);
return this->project_config.has(key) ? this->project_config.opt_int(key) : fallback;
};
auto get_mixed_bool = [this, &print_cfg](const std::string &key, bool fallback) {
if (const ConfigOptionBool *opt = print_cfg.option<ConfigOptionBool>(key))
return opt->value;
if (const ConfigOptionInt *opt = print_cfg.option<ConfigOptionInt>(key))
return opt->value != 0;
if (const ConfigOptionBool *opt = this->project_config.option<ConfigOptionBool>(key))
return opt->value;
if (const ConfigOptionInt *opt = this->project_config.option<ConfigOptionInt>(key))
return opt->value != 0;
return fallback;
};
auto get_mixed_mode = [this, &print_cfg](bool fallback) {
if (const ConfigOptionBool *opt = print_cfg.option<ConfigOptionBool>("mixed_filament_gradient_mode"))
return opt->value;
if (const ConfigOptionInt *opt = print_cfg.option<ConfigOptionInt>("mixed_filament_gradient_mode"))
return opt->value != 0;
if (const ConfigOptionBool *opt = this->project_config.option<ConfigOptionBool>("mixed_filament_gradient_mode"))
return opt->value;
if (const ConfigOptionInt *opt = this->project_config.option<ConfigOptionInt>("mixed_filament_gradient_mode"))
return opt->value != 0;
return fallback;
};
auto get_mixed_float = [this, &print_cfg](const std::string &key, float fallback) {
if (print_cfg.has(key))
return float(print_cfg.opt_float(key));
return this->project_config.has(key) ? float(this->project_config.opt_float(key)) : fallback;
};
auto get_mixed_string = [this, &print_cfg](const std::string &key) {
if (print_cfg.has(key))
return print_cfg.opt_string(key);
return this->project_config.has(key) ? this->project_config.opt_string(key) : std::string();
};
auto set_mixed_string = [this, &print_cfg](const std::string &key, const std::string &value) {
if (ConfigOptionString *opt = print_cfg.option<ConfigOptionString>(key))
opt->value = value;
else
print_cfg.set_key_value(key, new ConfigOptionString(value));
if (ConfigOptionString *opt = this->project_config.option<ConfigOptionString>(key))
opt->value = value;
else
this->project_config.set_key_value(key, new ConfigOptionString(value));
};
color_opt->values.resize(num_filaments, "#26A69A");
this->mixed_filaments.auto_generate(color_opt->values);
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);
const std::string serialized = this->mixed_filaments.serialize_custom_entries();
set_mixed_string("mixed_filament_definitions", serialized);
}
}

View File

@@ -247,6 +247,12 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|| opt_key == "precise_z_height"
|| opt_key == "dithering_z_step_size"
|| opt_key == "dithering_step_painted_zones_only"
|| 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_definitions"
// Spiral Vase forces different kind of slicing than the normal model:
// In Spiral Vase mode, holes are closed and only the largest area contour is kept at each layer.
// Therefore toggling the Spiral Vase on / off requires complete reslicing.

View File

@@ -1095,10 +1095,28 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
// Ensure newly introduced dithering keys are present so in-session updates are detected.
new_full_config.option("dithering_z_step_size", true);
new_full_config.option("dithering_step_painted_zones_only", true);
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_definitions", true);
m_config.option("dithering_z_step_size", true);
m_config.option("dithering_step_painted_zones_only", true);
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_definitions", true);
m_default_object_config.option("dithering_z_step_size", true);
m_default_object_config.option("dithering_step_painted_zones_only", true);
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_definitions", true);
// BBS
int used_filaments = this->extruders(true).size();
@@ -1196,10 +1214,50 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
}
}
// Regenerate mixed (virtual) filaments from physical filament colours only.
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;
std::string mixed_custom_definitions;
if (new_full_config.has("mixed_filament_gradient_mode")) {
if (const ConfigOptionBool *opt = new_full_config.option<ConfigOptionBool>("mixed_filament_gradient_mode"))
mixed_gradient_mode = opt->value ? 1 : 0;
else
mixed_gradient_mode = new_full_config.opt_int("mixed_filament_gradient_mode");
}
if (new_full_config.has("mixed_filament_height_lower_bound"))
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;
else
mixed_advanced_dither = (new_full_config.opt_int("mixed_filament_advanced_dithering") != 0);
}
if (new_full_config.has("mixed_filament_definitions"))
mixed_custom_definitions = new_full_config.opt_string("mixed_filament_definitions");
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);
// Regenerate mixed (virtual) filaments from physical filament colours and
// re-apply user custom mixed definitions.
std::vector<std::string> physical_filament_colors = m_config.filament_colour.values;
physical_filament_colors.resize(num_extruders, "#26A69A");
m_mixed_filament_mgr.clear_custom_entries();
m_mixed_filament_mgr.auto_generate(physical_filament_colors);
m_mixed_filament_mgr.load_custom_entries(mixed_custom_definitions, physical_filament_colors);
m_mixed_filament_mgr.apply_gradient_settings(mixed_gradient_mode,
mixed_height_lower,
mixed_height_upper,
mixed_cycle_layers,
mixed_advanced_dither);
// Total filaments = physical extruders + enabled mixed (virtual) filaments.
// Used for extruder ID clamping so that virtual IDs are accepted.
size_t num_total_filaments = m_mixed_filament_mgr.total_filaments(num_extruders);

View File

@@ -4125,7 +4125,8 @@ void PrintConfigDef::init_fff_params()
def->label = L("Dithering cadence height A");
def->category = L("Others");
def->tooltip = L("Layer height contribution of component A for dithering virtual filaments. "
"Set to 0 to use normal 1-layer A / 1-layer B alternation.");
"Set to 0 to use normal 1-layer A / 1-layer B alternation.\n\n"
"Detailed mixed filament setting explanations will be published once the project wiki is available.");
def->sidetext = "mm";
def->min = 0.;
def->mode = comAdvanced;
@@ -4135,17 +4136,78 @@ void PrintConfigDef::init_fff_params()
def->label = L("Dithering cadence height B");
def->category = L("Others");
def->tooltip = L("Layer height contribution of component B for dithering virtual filaments. "
"Set to 0 to use normal 1-layer A / 1-layer B alternation.");
"Set to 0 to use normal 1-layer A / 1-layer B alternation.\n\n"
"Detailed mixed filament setting explanations will be published once the project wiki is available.");
def->sidetext = "mm";
def->min = 0.;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(0.0));
def = this->add("mixed_filament_gradient_mode", coBool);
def->label = L("Height-weighted cadence");
def->category = L("Others");
def->tooltip = L("Enable height-weighted cadence for mixed filaments. "
"Limitation: only one height-weighted mixed color should be present at a given Z plane, "
"because independent per-color layer heights are not supported and the resulting layer height applies to the whole plane. "
"When disabled, layer-cycle cadence is used.\n\n"
"Detailed mixed filament setting explanations will be published once the project wiki is available.");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(false));
def = this->add("mixed_filament_height_lower_bound", coFloat);
def->label = L("Mixed filament lower height bound");
def->category = L("Others");
def->tooltip = L("Lower bound used by the height-weighted mixed filament gradient mode.\n\n"
"Detailed mixed filament setting explanations will be published once the project wiki is available.");
def->sidetext = "mm";
def->min = 0.01;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(0.04));
def = this->add("mixed_filament_height_upper_bound", coFloat);
def->label = L("Mixed filament upper height bound");
def->category = L("Others");
def->tooltip = L("Upper bound used by the height-weighted mixed filament gradient mode.\n\n"
"Detailed mixed filament setting explanations will be published once the project wiki is available.");
def->sidetext = "mm";
def->min = 0.01;
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");
def->tooltip = L("Distribute mixed filament layer-cycle cadence using an advanced ordered dithering pattern "
"instead of a simple contiguous A-then-B run. This can reduce visible striping for some hues.\n\n"
"This is an even more experimental mode and the perceived color may differ from normal dithering "
"for the same filament pair and ratio.\n\n"
"Detailed mixed filament setting explanations will be published once the project wiki is available.");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(false));
def = this->add("mixed_filament_definitions", coString);
def->label = L("Mixed filament custom definitions");
def->tooltip = L("Serialized custom mixed filament rows.\n\n"
"Detailed mixed filament setting explanations will be published once the project wiki is available.");
def->gui_flags = "serialized";
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionString(""));
def = this->add("dithering_z_step_size", coFloat);
def->label = L("Dithering Z step size");
def->category = L("Others");
def->tooltip = L("Layer height used in Z zones painted with dithering (mixed virtual filaments). "
"Set to 0 to keep normal layer height in those zones.");
"Set to 0 to keep normal layer height in those zones.\n\n"
"Detailed mixed filament setting explanations will be published once the project wiki is available.");
def->sidetext = "mm";
def->min = 0.;
def->mode = comAdvanced;
@@ -4155,7 +4217,8 @@ void PrintConfigDef::init_fff_params()
def->label = L("Use step size in painted zones only");
def->category = L("Others");
def->tooltip = L("When enabled, dithering Z step size is applied only where mixed filament is painted. "
"Unpainted zones keep their original layer height.");
"Unpainted zones keep their original layer height.\n\n"
"Detailed mixed filament setting explanations will be published once the project wiki is available.");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(true));

View File

@@ -1353,6 +1353,12 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
((ConfigOptionStrings, post_process))
((ConfigOptionFloat, mixed_color_layer_height_a))
((ConfigOptionFloat, mixed_color_layer_height_b))
((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))
((ConfigOptionString, mixed_filament_definitions))
((ConfigOptionFloat, dithering_z_step_size))
((ConfigOptionBool, dithering_step_painted_zones_only))
((ConfigOptionString, printer_model))

View File

@@ -964,6 +964,17 @@ bool PrintObject::invalidate_state_by_config_options(
|| opt_key == "interlocking_depth"
|| opt_key == "interlocking_boundary_avoidance"
|| opt_key == "interlocking_beam_width") {
steps.emplace_back(posSlice);
} else if (
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_definitions") {
// Mixed filament gradient controls affect layer cadence and virtual
// tool distribution, so force a re-slice prompt like other
// layer-structure settings.
steps.emplace_back(posSlice);
} else if (
opt_key == "elefant_foot_compensation"
@@ -3341,6 +3352,18 @@ struct LayerHeightRangeOverride {
coordf_t height { 0.f };
};
struct MixedStateZRanges {
size_t state_id { 0 };
std::vector<t_layer_height_range> ranges;
};
struct MixedStateCadence {
size_t state_id { 0 };
coordf_t height_a { 0.f };
coordf_t height_b { 0.f };
std::vector<t_layer_height_range> ranges;
};
static void sort_and_merge_layer_ranges(std::vector<t_layer_height_range> &ranges)
{
if (ranges.empty())
@@ -3368,11 +3391,8 @@ static void sort_and_merge_layer_ranges(std::vector<t_layer_height_range> &range
static std::vector<t_layer_height_range> collect_mixed_painted_z_ranges(const PrintObject &print_object, coordf_t object_height)
{
std::vector<t_layer_height_range> mixed_ranges;
if (object_height <= EPSILON)
return mixed_ranges;
const Print *print = print_object.print();
if (print == nullptr)
if (object_height <= EPSILON || print == nullptr)
return mixed_ranges;
const size_t num_physical = print->config().filament_colour.size();
@@ -3381,6 +3401,7 @@ static std::vector<t_layer_height_range> collect_mixed_painted_z_ranges(const Pr
return mixed_ranges;
const size_t max_state = std::min(num_total, size_t(EnforcerBlockerType::ExtruderMax));
std::vector<std::vector<t_layer_height_range>> per_state(max_state + 1);
const Transform3d object_to_print = print_object.trafo_centered();
for (const ModelVolume *mv : print_object.model_object()->volumes) {
@@ -3401,6 +3422,7 @@ static std::vector<t_layer_height_range> collect_mixed_painted_z_ranges(const Pr
if (facets.indices.empty() || facets.vertices.empty())
continue;
auto &state_ranges = per_state[state_idx];
for (const auto &face : facets.indices) {
double tri_z_min = DBL_MAX;
double tri_z_max = -DBL_MAX;
@@ -3424,15 +3446,98 @@ static std::vector<t_layer_height_range> collect_mixed_painted_z_ranges(const Pr
hi = std::min<coordf_t>(object_height, center + thin_band * 0.5f);
}
if (lo + EPSILON < hi)
mixed_ranges.emplace_back(lo, hi);
state_ranges.emplace_back(lo, hi);
}
}
}
for (size_t state_idx = num_physical + 1; state_idx <= max_state; ++state_idx) {
auto &state_ranges = per_state[state_idx];
if (state_ranges.empty())
continue;
sort_and_merge_layer_ranges(state_ranges);
mixed_ranges.insert(mixed_ranges.end(), state_ranges.begin(), state_ranges.end());
}
sort_and_merge_layer_ranges(mixed_ranges);
return mixed_ranges;
}
static std::vector<MixedStateZRanges> collect_mixed_painted_z_ranges_by_state(const PrintObject &print_object, coordf_t object_height)
{
std::vector<MixedStateZRanges> out;
const Print *print = print_object.print();
if (object_height <= EPSILON || print == nullptr)
return out;
const size_t num_physical = print->config().filament_colour.size();
const size_t num_total = print->mixed_filament_manager().total_filaments(num_physical);
if (num_total <= num_physical)
return out;
const size_t max_state = std::min(num_total, size_t(EnforcerBlockerType::ExtruderMax));
std::vector<std::vector<t_layer_height_range>> per_state(max_state + 1);
const Transform3d object_to_print = print_object.trafo_centered();
for (const ModelVolume *mv : print_object.model_object()->volumes) {
if (mv == nullptr || !mv->is_model_part() || mv->mmu_segmentation_facets.empty())
continue;
const auto &used_states = mv->mmu_segmentation_facets.get_data().used_states;
if (used_states.empty())
continue;
const Transform3d volume_to_print = object_to_print * mv->get_matrix();
constexpr coordf_t thin_band = 0.01f;
for (size_t state_idx = num_physical + 1; state_idx <= max_state; ++state_idx) {
if (state_idx >= used_states.size() || !used_states[state_idx])
continue;
const auto facets = mv->mmu_segmentation_facets.get_facets_strict(*mv, static_cast<EnforcerBlockerType>(state_idx));
if (facets.indices.empty() || facets.vertices.empty())
continue;
auto &state_ranges = per_state[state_idx];
for (const auto &face : facets.indices) {
double tri_z_min = DBL_MAX;
double tri_z_max = -DBL_MAX;
for (int i = 0; i < 3; ++i) {
const size_t vertex_idx = size_t(face[i]);
if (vertex_idx >= facets.vertices.size())
continue;
const Vec3d p = volume_to_print * facets.vertices[vertex_idx].cast<double>();
tri_z_min = std::min(tri_z_min, p.z());
tri_z_max = std::max(tri_z_max, p.z());
}
if (tri_z_min == DBL_MAX || tri_z_max == -DBL_MAX)
continue;
coordf_t lo = std::max<coordf_t>(0.f, coordf_t(tri_z_min));
coordf_t hi = std::min<coordf_t>(object_height, coordf_t(tri_z_max));
if (hi <= lo + EPSILON) {
const coordf_t center = std::max<coordf_t>(0.f, std::min<coordf_t>(object_height, lo));
lo = std::max<coordf_t>(0.f, center - thin_band * 0.5f);
hi = std::min<coordf_t>(object_height, center + thin_band * 0.5f);
}
if (lo + EPSILON < hi)
state_ranges.emplace_back(lo, hi);
}
}
}
out.reserve(max_state > num_physical ? max_state - num_physical : 0);
for (size_t state_idx = num_physical + 1; state_idx <= max_state; ++state_idx) {
auto &state_ranges = per_state[state_idx];
if (state_ranges.empty())
continue;
sort_and_merge_layer_ranges(state_ranges);
if (!state_ranges.empty())
out.push_back({ state_idx, std::move(state_ranges) });
}
return out;
}
static std::vector<LayerHeightRangeOverride> base_layer_height_overrides(const t_layer_config_ranges &ranges, coordf_t object_height)
{
std::vector<LayerHeightRangeOverride> out;
@@ -3550,6 +3655,167 @@ static t_layer_config_ranges layer_ranges_with_dithering(const t_layer_config_ra
return out;
}
static bool mixed_state_heights(const MixedFilamentManager &mixed_mgr,
size_t num_physical,
size_t state_id,
coordf_t lower_bound,
coordf_t upper_bound,
coordf_t &height_a,
coordf_t &height_b)
{
if (state_id <= num_physical)
return false;
const size_t idx = state_id - num_physical - 1;
const auto &mixed = mixed_mgr.mixed_filaments();
if (idx >= mixed.size())
return false;
const int mix_b = std::clamp(mixed[idx].mix_b_percent, 0, 100);
const coordf_t pct_b = coordf_t(mix_b) / coordf_t(100.f);
const coordf_t pct_a = coordf_t(1.f) - pct_b;
const coordf_t lo = std::max<coordf_t>(0.01f, lower_bound);
const coordf_t hi = std::max<coordf_t>(lo, upper_bound);
height_a = std::max<coordf_t>(0.01f, lo + pct_a * (hi - lo));
height_b = std::max<coordf_t>(0.01f, lo + pct_b * (hi - lo));
return true;
}
static coordf_t mixed_state_height_at_z(const MixedStateCadence &state, coordf_t z)
{
const coordf_t cycle = std::max<coordf_t>(0.01f, state.height_a + state.height_b);
coordf_t phase = std::fmod(std::max<coordf_t>(0.f, z), cycle);
if (phase < 0.f)
phase += cycle;
return (phase < state.height_a) ? state.height_a : state.height_b;
}
static void append_state_cadence_boundaries(std::vector<coordf_t> &boundaries,
const t_layer_height_range &range,
coordf_t height_a,
coordf_t height_b)
{
const coordf_t cycle = height_a + height_b;
if (cycle <= EPSILON || range.second <= range.first + EPSILON)
return;
const int k_start = int(std::floor(range.first / cycle)) - 1;
coordf_t boundary = coordf_t(k_start) * cycle;
size_t guard = 0;
while (boundary <= range.second + cycle + EPSILON && guard++ < 200000) {
if (boundary > range.first + EPSILON && boundary < range.second - EPSILON)
boundaries.emplace_back(boundary);
const coordf_t split = boundary + height_a;
if (split > range.first + EPSILON && split < range.second - EPSILON)
boundaries.emplace_back(split);
boundary += cycle;
}
}
static t_layer_config_ranges layer_ranges_with_height_weighted_mixed(
const t_layer_config_ranges &base_ranges_map,
coordf_t object_height,
coordf_t default_layer_height,
const std::vector<MixedStateZRanges> &mixed_state_ranges,
const MixedFilamentManager &mixed_mgr,
size_t num_physical,
coordf_t lower_bound,
coordf_t upper_bound)
{
if (object_height <= EPSILON || mixed_state_ranges.empty())
return base_ranges_map;
const std::vector<LayerHeightRangeOverride> base_ranges = base_layer_height_overrides(base_ranges_map, object_height);
std::vector<MixedStateCadence> states;
states.reserve(mixed_state_ranges.size());
for (const MixedStateZRanges &state_ranges : mixed_state_ranges) {
coordf_t height_a = 0.f;
coordf_t height_b = 0.f;
if (!mixed_state_heights(mixed_mgr, num_physical, state_ranges.state_id, lower_bound, upper_bound, height_a, height_b))
continue;
states.push_back({ state_ranges.state_id, height_a, height_b, state_ranges.ranges });
}
if (states.empty())
return base_ranges_map;
std::vector<coordf_t> boundaries;
boundaries.reserve(2 + base_ranges.size() * 2 + states.size() * 64);
boundaries.emplace_back(0.f);
boundaries.emplace_back(object_height);
for (const LayerHeightRangeOverride &range : base_ranges) {
boundaries.emplace_back(range.lo);
boundaries.emplace_back(range.hi);
}
for (const MixedStateCadence &state : states) {
for (const t_layer_height_range &range : state.ranges) {
boundaries.emplace_back(range.first);
boundaries.emplace_back(range.second);
append_state_cadence_boundaries(boundaries, range, state.height_a, state.height_b);
}
}
std::sort(boundaries.begin(), boundaries.end());
boundaries.erase(std::unique(boundaries.begin(), boundaries.end(), [](coordf_t a, coordf_t b) {
return std::abs(a - b) <= EPSILON;
}),
boundaries.end());
std::vector<LayerHeightRangeOverride> merged_ranges;
merged_ranges.reserve(boundaries.size());
for (size_t i = 1; i < boundaries.size(); ++i) {
const coordf_t lo = boundaries[i - 1];
const coordf_t hi = boundaries[i];
if (hi <= lo + EPSILON)
continue;
const coordf_t z_mid = 0.5f * (lo + hi);
coordf_t target_height = default_layer_height;
coordf_t base_height = 0.f;
const bool has_base_override = get_override_height(base_ranges, z_mid, base_height);
if (has_base_override)
target_height = base_height;
bool has_mixed = false;
coordf_t mixed_height = target_height;
for (const MixedStateCadence &state : states) {
if (!contains_z(state.ranges, z_mid))
continue;
const coordf_t state_height = mixed_state_height_at_z(state, z_mid);
if (!has_mixed) {
mixed_height = state_height;
has_mixed = true;
} else {
mixed_height = std::min(mixed_height, state_height);
}
}
if (has_mixed)
target_height = mixed_height;
if (std::abs(target_height - default_layer_height) <= EPSILON)
continue;
if (!merged_ranges.empty() &&
std::abs(merged_ranges.back().height - target_height) <= EPSILON &&
std::abs(merged_ranges.back().hi - lo) <= EPSILON) {
merged_ranges.back().hi = hi;
} else {
merged_ranges.push_back({ lo, hi, target_height });
}
}
t_layer_config_ranges out;
for (const LayerHeightRangeOverride &range : merged_ranges) {
if (range.hi <= range.lo + EPSILON)
continue;
ModelConfig cfg;
cfg.set_key_value("layer_height", new ConfigOptionFloat(range.height));
out.emplace(t_layer_height_range(range.lo, range.hi), std::move(cfg));
}
return out;
}
} // namespace
bool PrintObject::update_layer_height_profile(const ModelObject &model_object,
@@ -3560,9 +3826,45 @@ bool PrintObject::update_layer_height_profile(const ModelObject &model_
bool updated = false;
const t_layer_config_ranges *ranges_to_use = &model_object.layer_config_ranges;
t_layer_config_ranges mixed_gradient_ranges;
t_layer_config_ranges dithering_ranges;
if (print_object != nullptr && print_object->print() != nullptr) {
const DynamicPrintConfig &full_cfg = print_object->print()->full_print_config();
const PrintConfig &print_cfg = print_object->print()->config();
bool height_weighted_mode = print_cfg.mixed_filament_gradient_mode.value;
if (full_cfg.has("mixed_filament_gradient_mode")) {
if (const ConfigOptionBool *opt = full_cfg.option<ConfigOptionBool>("mixed_filament_gradient_mode"))
height_weighted_mode = opt->value;
else if (const ConfigOptionInt *opt = full_cfg.option<ConfigOptionInt>("mixed_filament_gradient_mode"))
height_weighted_mode = (opt->value != 0);
}
coordf_t mixed_lower = coordf_t(print_cfg.mixed_filament_height_lower_bound.value);
coordf_t mixed_upper = coordf_t(print_cfg.mixed_filament_height_upper_bound.value);
if (full_cfg.has("mixed_filament_height_lower_bound"))
mixed_lower = coordf_t(full_cfg.opt_float("mixed_filament_height_lower_bound"));
if (full_cfg.has("mixed_filament_height_upper_bound"))
mixed_upper = coordf_t(full_cfg.opt_float("mixed_filament_height_upper_bound"));
mixed_lower = std::max<coordf_t>(0.01f, mixed_lower);
mixed_upper = std::max<coordf_t>(mixed_lower, mixed_upper);
if (height_weighted_mode) {
const coordf_t object_height = slicing_parameters.object_print_z_uncompensated_height();
const auto mixed_states = collect_mixed_painted_z_ranges_by_state(*print_object, object_height);
if (!mixed_states.empty()) {
mixed_gradient_ranges = layer_ranges_with_height_weighted_mixed(*ranges_to_use,
object_height,
slicing_parameters.layer_height,
mixed_states,
print_object->print()->mixed_filament_manager(),
print_cfg.filament_colour.size(),
mixed_lower,
mixed_upper);
ranges_to_use = &mixed_gradient_ranges;
}
}
coordf_t dithering_step = coordf_t(print_object->print()->config().dithering_z_step_size.value);
bool painted_zones_only = print_object->print()->config().dithering_step_painted_zones_only.value;
if (full_cfg.has("dithering_z_step_size"))
@@ -3570,7 +3872,7 @@ bool PrintObject::update_layer_height_profile(const ModelObject &model_
if (full_cfg.has("dithering_step_painted_zones_only"))
painted_zones_only = full_cfg.opt_bool("dithering_step_painted_zones_only");
if (dithering_step > EPSILON) {
if (!height_weighted_mode && dithering_step > EPSILON) {
const coordf_t object_height = slicing_parameters.object_print_z_uncompensated_height();
std::vector<t_layer_height_range> mixed_ranges;
if (painted_zones_only)
@@ -3579,7 +3881,7 @@ bool PrintObject::update_layer_height_profile(const ModelObject &model_
mixed_ranges.emplace_back(0.f, object_height);
if (!mixed_ranges.empty()) {
dithering_ranges = layer_ranges_with_dithering(model_object.layer_config_ranges,
dithering_ranges = layer_ranges_with_dithering(*ranges_to_use,
object_height,
slicing_parameters.layer_height,
mixed_ranges,