mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-07-28 05:12:27 +00:00
added a unified color helper so that every created colors and cached colors and preview are the same
This commit is contained in:
6
.github/workflows/build_all.yml
vendored
6
.github/workflows/build_all.yml
vendored
@@ -77,10 +77,10 @@ jobs:
|
|||||||
|
|
||||||
notes_content=""
|
notes_content=""
|
||||||
for notes_file in \
|
for notes_file in \
|
||||||
"doc/changelogs/CHANGELOG_v${version}.md" \
|
|
||||||
"doc/changelogs/RELEASE_NOTES_v${version}.md" \
|
"doc/changelogs/RELEASE_NOTES_v${version}.md" \
|
||||||
"CHANGELOG_v${version}.md" \
|
"doc/changelogs/CHANGELOG_v${version}.md" \
|
||||||
"RELEASE_NOTES_v${version}.md"; do
|
"RELEASE_NOTES_v${version}.md" \
|
||||||
|
"CHANGELOG_v${version}.md"; do
|
||||||
if [ -f "$notes_file" ]; then
|
if [ -f "$notes_file" ]; then
|
||||||
notes_content=$(cat "$notes_file")
|
notes_content=$(cat "$notes_file")
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -861,6 +861,601 @@ static std::vector<unsigned int> build_weighted_gradient_sequence(const std::vec
|
|||||||
return sequence;
|
return sequence;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static unsigned int decode_manual_pattern_preview_token(char token, unsigned int component_a, unsigned int component_b, size_t num_physical)
|
||||||
|
{
|
||||||
|
unsigned int extruder_id = 0;
|
||||||
|
if (token == '1')
|
||||||
|
extruder_id = component_a;
|
||||||
|
else if (token == '2')
|
||||||
|
extruder_id = component_b;
|
||||||
|
else if (token >= '3' && token <= '9')
|
||||||
|
extruder_id = unsigned(token - '0');
|
||||||
|
|
||||||
|
return (extruder_id >= 1 && extruder_id <= num_physical) ? extruder_id : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<unsigned int> build_grouped_manual_pattern_preview_sequence(const std::string &pattern,
|
||||||
|
unsigned int component_a,
|
||||||
|
unsigned int component_b,
|
||||||
|
size_t num_physical,
|
||||||
|
size_t wall_loops)
|
||||||
|
{
|
||||||
|
std::vector<unsigned int> sequence;
|
||||||
|
if (num_physical == 0)
|
||||||
|
return sequence;
|
||||||
|
|
||||||
|
const std::string normalized = MixedFilamentManager::normalize_manual_pattern(pattern);
|
||||||
|
if (normalized.empty())
|
||||||
|
return sequence;
|
||||||
|
|
||||||
|
const std::vector<std::string> groups = split_manual_pattern_groups(normalized);
|
||||||
|
if (groups.empty())
|
||||||
|
return sequence;
|
||||||
|
|
||||||
|
if (groups.size() == 1) {
|
||||||
|
sequence.reserve(normalized.size());
|
||||||
|
for (const char token : normalized) {
|
||||||
|
const unsigned int extruder_id =
|
||||||
|
decode_manual_pattern_preview_token(token, component_a, component_b, num_physical);
|
||||||
|
if (extruder_id != 0)
|
||||||
|
sequence.emplace_back(extruder_id);
|
||||||
|
}
|
||||||
|
return sequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr size_t k_max_preview_cycle = 48;
|
||||||
|
size_t cycle = 1;
|
||||||
|
for (const std::string &group : groups) {
|
||||||
|
if (group.empty())
|
||||||
|
continue;
|
||||||
|
cycle = std::lcm(cycle, group.size());
|
||||||
|
if (cycle >= k_max_preview_cycle) {
|
||||||
|
cycle = k_max_preview_cycle;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t preview_wall_loops = std::max<size_t>(1, wall_loops == 0 ? groups.size() : wall_loops);
|
||||||
|
sequence.reserve(preview_wall_loops * cycle);
|
||||||
|
for (size_t layer_idx = 0; layer_idx < cycle; ++layer_idx) {
|
||||||
|
for (size_t wall_idx = 0; wall_idx < preview_wall_loops; ++wall_idx) {
|
||||||
|
const std::string &group = groups[std::min(wall_idx, groups.size() - 1)];
|
||||||
|
if (group.empty())
|
||||||
|
continue;
|
||||||
|
const char token = group[layer_idx % group.size()];
|
||||||
|
const unsigned int extruder_id =
|
||||||
|
decode_manual_pattern_preview_token(token, component_a, component_b, num_physical);
|
||||||
|
if (extruder_id != 0)
|
||||||
|
sequence.emplace_back(extruder_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::pair<int, int> effective_pair_preview_ratios(int percent_b)
|
||||||
|
{
|
||||||
|
const int mix_b = std::clamp(percent_b, 0, 100);
|
||||||
|
int ratio_a = 1;
|
||||||
|
int ratio_b = 0;
|
||||||
|
|
||||||
|
if (mix_b >= 100) {
|
||||||
|
ratio_a = 0;
|
||||||
|
ratio_b = 1;
|
||||||
|
} else if (mix_b > 0) {
|
||||||
|
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)))));
|
||||||
|
ratio_a = b_is_major ? 1 : major_layers;
|
||||||
|
ratio_b = b_is_major ? major_layers : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ratio_a > 0 && ratio_b > 0) {
|
||||||
|
const int g = std::gcd(ratio_a, ratio_b);
|
||||||
|
if (g > 1) {
|
||||||
|
ratio_a /= g;
|
||||||
|
ratio_b /= g;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { std::max(0, ratio_a), std::max(0, ratio_b) };
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<unsigned int> build_effective_pair_preview_sequence(unsigned int component_a,
|
||||||
|
unsigned int component_b,
|
||||||
|
int percent_b,
|
||||||
|
bool limit_cycle)
|
||||||
|
{
|
||||||
|
std::vector<unsigned int> sequence;
|
||||||
|
if (component_a == 0 || component_b == 0 || component_a == component_b)
|
||||||
|
return sequence;
|
||||||
|
|
||||||
|
auto [ratio_a, ratio_b] = effective_pair_preview_ratios(percent_b);
|
||||||
|
constexpr int k_max_cycle = 24;
|
||||||
|
if (limit_cycle && ratio_a > 0 && ratio_b > 0 && ratio_a + ratio_b > k_max_cycle) {
|
||||||
|
const double scale = double(k_max_cycle) / double(ratio_a + ratio_b);
|
||||||
|
ratio_a = std::max(1, int(std::round(double(ratio_a) * scale)));
|
||||||
|
ratio_b = std::max(1, int(std::round(double(ratio_b) * scale)));
|
||||||
|
}
|
||||||
|
if (ratio_a == 0 && ratio_b == 0)
|
||||||
|
ratio_a = 1;
|
||||||
|
|
||||||
|
const int cycle = std::max(1, ratio_a + ratio_b);
|
||||||
|
sequence.reserve(size_t(cycle));
|
||||||
|
for (int pos = 0; pos < cycle; ++pos) {
|
||||||
|
const int b_before = (pos * ratio_b) / cycle;
|
||||||
|
const int b_after = ((pos + 1) * ratio_b) / cycle;
|
||||||
|
sequence.emplace_back((b_after > b_before) ? component_b : component_a);
|
||||||
|
}
|
||||||
|
return sequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string blend_display_color_from_sequence(const std::vector<std::string> &colors,
|
||||||
|
size_t num_physical,
|
||||||
|
const std::vector<unsigned int> &sequence,
|
||||||
|
const std::string &fallback)
|
||||||
|
{
|
||||||
|
if (colors.empty() || sequence.empty() || num_physical == 0)
|
||||||
|
return fallback;
|
||||||
|
|
||||||
|
std::vector<size_t> counts(num_physical + 1, size_t(0));
|
||||||
|
size_t total = 0;
|
||||||
|
for (const unsigned int id : sequence) {
|
||||||
|
if (id == 0 || id > num_physical)
|
||||||
|
continue;
|
||||||
|
++counts[id];
|
||||||
|
++total;
|
||||||
|
}
|
||||||
|
if (total == 0)
|
||||||
|
return fallback;
|
||||||
|
|
||||||
|
std::vector<std::pair<std::string, int>> color_percents;
|
||||||
|
color_percents.reserve(num_physical);
|
||||||
|
for (size_t id = 1; id <= num_physical; ++id) {
|
||||||
|
if (counts[id] == 0 || id > colors.size())
|
||||||
|
continue;
|
||||||
|
color_percents.emplace_back(colors[id - 1], int(counts[id]));
|
||||||
|
}
|
||||||
|
if (color_percents.empty())
|
||||||
|
return fallback;
|
||||||
|
|
||||||
|
if (color_percents.size() == 1)
|
||||||
|
return color_percents.front().first;
|
||||||
|
|
||||||
|
return MixedFilamentManager::blend_color_multi(color_percents);
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<double> build_local_z_preview_pass_heights(double nominal_layer_height,
|
||||||
|
double lower_bound,
|
||||||
|
double upper_bound,
|
||||||
|
double preferred_a_height,
|
||||||
|
double preferred_b_height,
|
||||||
|
int mix_b_percent,
|
||||||
|
int max_sublayers_limit)
|
||||||
|
{
|
||||||
|
if (nominal_layer_height <= EPSILON)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
const double base_height = nominal_layer_height;
|
||||||
|
const double lo = std::max<double>(0.01, lower_bound);
|
||||||
|
const double hi = std::max<double>(lo, upper_bound);
|
||||||
|
const size_t max_passes_limit = max_sublayers_limit >= 2 ? size_t(max_sublayers_limit) : size_t(0);
|
||||||
|
|
||||||
|
auto fit_pass_heights_to_interval = [](std::vector<double> &passes, double total_height, double local_lo, double local_hi) {
|
||||||
|
if (passes.empty() || total_height <= EPSILON)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const auto within = [local_lo, local_hi](double value) {
|
||||||
|
return value >= local_lo - 1e-6 && value <= local_hi + 1e-6;
|
||||||
|
};
|
||||||
|
|
||||||
|
double sum = 0.0;
|
||||||
|
for (const double h : passes)
|
||||||
|
sum += h;
|
||||||
|
|
||||||
|
double delta = total_height - sum;
|
||||||
|
if (std::abs(delta) > 1e-6) {
|
||||||
|
if (delta > 0.0) {
|
||||||
|
for (double &h : passes) {
|
||||||
|
if (delta <= 1e-6)
|
||||||
|
break;
|
||||||
|
const double room = local_hi - h;
|
||||||
|
if (room <= 1e-6)
|
||||||
|
continue;
|
||||||
|
const double take = std::min(room, delta);
|
||||||
|
h += take;
|
||||||
|
delta -= take;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (auto it = passes.rbegin(); it != passes.rend() && delta < -1e-6; ++it) {
|
||||||
|
const double room = *it - local_lo;
|
||||||
|
if (room <= 1e-6)
|
||||||
|
continue;
|
||||||
|
const double take = std::min(room, -delta);
|
||||||
|
*it -= take;
|
||||||
|
delta += take;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std::abs(delta) > 1e-6)
|
||||||
|
return false;
|
||||||
|
return std::all_of(passes.begin(), passes.end(), within);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto build_uniform = [&fit_pass_heights_to_interval, base_height, lo, hi, max_passes_limit]() {
|
||||||
|
std::vector<double> out;
|
||||||
|
size_t min_passes = size_t(std::max<double>(1.0, std::ceil((base_height - EPSILON) / hi)));
|
||||||
|
size_t max_passes = size_t(std::max<double>(1.0, std::floor((base_height + EPSILON) / lo)));
|
||||||
|
size_t pass_count = min_passes;
|
||||||
|
|
||||||
|
if (max_passes >= min_passes) {
|
||||||
|
const double target_step = 0.5 * (lo + hi);
|
||||||
|
const size_t target_passes =
|
||||||
|
size_t(std::max<double>(1.0, std::llround(base_height / std::max<double>(target_step, EPSILON))));
|
||||||
|
pass_count = std::clamp(target_passes, min_passes, max_passes);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (max_passes_limit > 0 && pass_count > max_passes_limit)
|
||||||
|
pass_count = max_passes_limit;
|
||||||
|
|
||||||
|
if (pass_count == 1 && base_height >= 2.0 * lo - EPSILON && max_passes >= 2)
|
||||||
|
pass_count = 2;
|
||||||
|
|
||||||
|
if (pass_count <= 1) {
|
||||||
|
out.emplace_back(base_height);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
out.assign(pass_count, base_height / double(pass_count));
|
||||||
|
double accumulated = 0.0;
|
||||||
|
for (size_t i = 0; i + 1 < out.size(); ++i)
|
||||||
|
accumulated += out[i];
|
||||||
|
out.back() = std::max<double>(EPSILON, base_height - accumulated);
|
||||||
|
if (!fit_pass_heights_to_interval(out, base_height, lo, hi) && max_passes_limit == 0) {
|
||||||
|
out.assign(pass_count, base_height / double(pass_count));
|
||||||
|
accumulated = 0.0;
|
||||||
|
for (size_t i = 0; i + 1 < out.size(); ++i)
|
||||||
|
accumulated += out[i];
|
||||||
|
out.back() = std::max<double>(EPSILON, base_height - accumulated);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto build_alternating = [&build_uniform, &fit_pass_heights_to_interval, base_height, lo, hi, max_passes_limit](double gradient_h_a, double gradient_h_b) {
|
||||||
|
if (base_height < 2.0 * lo - EPSILON)
|
||||||
|
return std::vector<double>{ base_height };
|
||||||
|
|
||||||
|
const double cycle_h = std::max<double>(EPSILON, gradient_h_a + gradient_h_b);
|
||||||
|
const double ratio_a = std::clamp(gradient_h_a / cycle_h, 0.0, 1.0);
|
||||||
|
|
||||||
|
size_t min_passes = size_t(std::max<double>(2.0, std::ceil((base_height - EPSILON) / hi)));
|
||||||
|
if ((min_passes % 2) != 0)
|
||||||
|
++min_passes;
|
||||||
|
|
||||||
|
size_t max_passes = size_t(std::max<double>(2.0, std::floor((base_height + EPSILON) / lo)));
|
||||||
|
if ((max_passes % 2) != 0)
|
||||||
|
--max_passes;
|
||||||
|
if (max_passes_limit > 0) {
|
||||||
|
size_t capped_limit = std::max<size_t>(2, max_passes_limit);
|
||||||
|
if ((capped_limit % 2) != 0)
|
||||||
|
--capped_limit;
|
||||||
|
if (capped_limit >= 2)
|
||||||
|
max_passes = std::min(max_passes, capped_limit);
|
||||||
|
}
|
||||||
|
if (max_passes < 2)
|
||||||
|
return build_uniform();
|
||||||
|
if (min_passes > max_passes)
|
||||||
|
min_passes = max_passes;
|
||||||
|
if (min_passes < 2)
|
||||||
|
min_passes = 2;
|
||||||
|
if ((min_passes % 2) != 0)
|
||||||
|
++min_passes;
|
||||||
|
if (min_passes > max_passes)
|
||||||
|
return build_uniform();
|
||||||
|
|
||||||
|
const double target_step = 0.5 * (lo + hi);
|
||||||
|
size_t target_passes =
|
||||||
|
size_t(std::max<double>(2.0, std::llround(base_height / std::max<double>(target_step, EPSILON))));
|
||||||
|
if ((target_passes % 2) != 0) {
|
||||||
|
const size_t round_up = (target_passes < max_passes) ? (target_passes + 1) : max_passes;
|
||||||
|
const size_t round_down = (target_passes > min_passes) ? (target_passes - 1) : min_passes;
|
||||||
|
if (round_up > max_passes)
|
||||||
|
target_passes = round_down;
|
||||||
|
else if (round_down < min_passes)
|
||||||
|
target_passes = round_up;
|
||||||
|
else
|
||||||
|
target_passes = ((round_up - target_passes) <= (target_passes - round_down)) ? round_up : round_down;
|
||||||
|
}
|
||||||
|
target_passes = std::clamp(target_passes, min_passes, max_passes);
|
||||||
|
|
||||||
|
bool has_best = false;
|
||||||
|
std::vector<double> best_passes;
|
||||||
|
double best_ratio_error = 0.0;
|
||||||
|
size_t best_pass_distance = 0;
|
||||||
|
double best_max_height = 0.0;
|
||||||
|
size_t best_pass_count = 0;
|
||||||
|
|
||||||
|
for (size_t pass_count = min_passes; pass_count <= max_passes; pass_count += 2) {
|
||||||
|
const size_t pair_count = pass_count / 2;
|
||||||
|
if (pair_count == 0)
|
||||||
|
continue;
|
||||||
|
const double pair_h = base_height / double(pair_count);
|
||||||
|
|
||||||
|
const double h_a_min = std::max(lo, pair_h - hi);
|
||||||
|
const double h_a_max = std::min(hi, pair_h - lo);
|
||||||
|
if (h_a_min > h_a_max + EPSILON)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const double h_a = std::clamp(pair_h * ratio_a, h_a_min, h_a_max);
|
||||||
|
const double h_b = pair_h - h_a;
|
||||||
|
|
||||||
|
std::vector<double> out;
|
||||||
|
out.reserve(pass_count);
|
||||||
|
for (size_t pair_idx = 0; pair_idx < pair_count; ++pair_idx) {
|
||||||
|
out.emplace_back(h_a);
|
||||||
|
out.emplace_back(h_b);
|
||||||
|
}
|
||||||
|
if (!fit_pass_heights_to_interval(out, base_height, lo, hi))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const double ratio_actual = (h_a + h_b > EPSILON) ? (h_a / (h_a + h_b)) : 0.5;
|
||||||
|
const double ratio_error = std::abs(ratio_actual - ratio_a);
|
||||||
|
const size_t pass_distance =
|
||||||
|
(pass_count > target_passes) ? (pass_count - target_passes) : (target_passes - pass_count);
|
||||||
|
const double max_height = std::max(h_a, h_b);
|
||||||
|
|
||||||
|
const bool better_ratio = !has_best || (ratio_error + 1e-6 < best_ratio_error);
|
||||||
|
const bool similar_ratio = has_best && std::abs(ratio_error - best_ratio_error) <= 1e-6;
|
||||||
|
const bool better_distance = similar_ratio && (pass_distance < best_pass_distance);
|
||||||
|
const bool similar_distance = similar_ratio && (pass_distance == best_pass_distance);
|
||||||
|
const bool better_max_height = similar_distance && (max_height + 1e-6 < best_max_height);
|
||||||
|
const bool similar_max_height = similar_distance && std::abs(max_height - best_max_height) <= 1e-6;
|
||||||
|
const bool better_pass_count = similar_max_height && (pass_count > best_pass_count);
|
||||||
|
|
||||||
|
if (better_ratio || better_distance || better_max_height || better_pass_count) {
|
||||||
|
has_best = true;
|
||||||
|
best_passes = std::move(out);
|
||||||
|
best_ratio_error = ratio_error;
|
||||||
|
best_pass_distance = pass_distance;
|
||||||
|
best_max_height = max_height;
|
||||||
|
best_pass_count = pass_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return has_best ? best_passes : build_uniform();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (preferred_a_height > EPSILON || preferred_b_height > EPSILON) {
|
||||||
|
std::vector<double> cadence_unit;
|
||||||
|
if (preferred_a_height > EPSILON)
|
||||||
|
cadence_unit.push_back(std::clamp(preferred_a_height, lo, hi));
|
||||||
|
if (preferred_b_height > EPSILON)
|
||||||
|
cadence_unit.push_back(std::clamp(preferred_b_height, lo, hi));
|
||||||
|
|
||||||
|
if (!cadence_unit.empty()) {
|
||||||
|
std::vector<double> out;
|
||||||
|
out.reserve(size_t(std::ceil(base_height / lo)) + 2);
|
||||||
|
|
||||||
|
double z_used = 0.0;
|
||||||
|
size_t idx = 0;
|
||||||
|
size_t guard = 0;
|
||||||
|
while (z_used + cadence_unit[idx] < base_height - EPSILON && guard++ < 100000) {
|
||||||
|
out.push_back(cadence_unit[idx]);
|
||||||
|
z_used += cadence_unit[idx];
|
||||||
|
idx = (idx + 1) % cadence_unit.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
const double remainder = base_height - z_used;
|
||||||
|
if (remainder > EPSILON)
|
||||||
|
out.push_back(remainder);
|
||||||
|
|
||||||
|
if (fit_pass_heights_to_interval(out, base_height, lo, hi) &&
|
||||||
|
(max_passes_limit == 0 || out.size() <= max_passes_limit))
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preferred_a_height > EPSILON && preferred_b_height > EPSILON)
|
||||||
|
return build_alternating(preferred_a_height, preferred_b_height);
|
||||||
|
return build_uniform();
|
||||||
|
}
|
||||||
|
|
||||||
|
const int mix_b = std::clamp(mix_b_percent, 0, 100);
|
||||||
|
const double pct_b = double(mix_b) / 100.0;
|
||||||
|
const double pct_a = 1.0 - pct_b;
|
||||||
|
const double gradient_h_a = lo + pct_a * (hi - lo);
|
||||||
|
const double gradient_h_b = lo + pct_b * (hi - lo);
|
||||||
|
return build_alternating(gradient_h_a, gradient_h_b);
|
||||||
|
}
|
||||||
|
|
||||||
|
static double mixed_filament_reference_nozzle_mm(unsigned int component_a,
|
||||||
|
unsigned int component_b,
|
||||||
|
const std::vector<double> &nozzle_diameters)
|
||||||
|
{
|
||||||
|
std::vector<double> samples;
|
||||||
|
samples.reserve(2);
|
||||||
|
|
||||||
|
auto append_if_valid = [&samples, &nozzle_diameters](unsigned int component_id) {
|
||||||
|
if (component_id >= 1 && component_id <= nozzle_diameters.size())
|
||||||
|
samples.emplace_back(std::max(0.05, nozzle_diameters[size_t(component_id - 1)]));
|
||||||
|
};
|
||||||
|
|
||||||
|
append_if_valid(component_a);
|
||||||
|
append_if_valid(component_b);
|
||||||
|
|
||||||
|
if (samples.empty())
|
||||||
|
return 0.4;
|
||||||
|
return std::accumulate(samples.begin(), samples.end(), 0.0) / double(samples.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
int mixed_filament_effective_local_z_preview_mix_b_percent(const MixedFilament &mf,
|
||||||
|
const MixedFilamentPreviewSettings &preview_settings)
|
||||||
|
{
|
||||||
|
if (!preview_settings.local_z_mode)
|
||||||
|
return std::clamp(mf.mix_b_percent, 0, 100);
|
||||||
|
|
||||||
|
const std::string normalized_pattern = MixedFilamentManager::normalize_manual_pattern(mf.manual_pattern);
|
||||||
|
if (!normalized_pattern.empty() || mf.distribution_mode == int(MixedFilament::SameLayerPointillisme))
|
||||||
|
return std::clamp(mf.mix_b_percent, 0, 100);
|
||||||
|
|
||||||
|
const std::vector<unsigned int> gradient_ids = decode_gradient_component_ids(mf.gradient_component_ids, 9);
|
||||||
|
if (gradient_ids.size() >= 3)
|
||||||
|
return std::clamp(mf.mix_b_percent, 0, 100);
|
||||||
|
|
||||||
|
const std::vector<double> pass_heights = build_local_z_preview_pass_heights(preview_settings.nominal_layer_height,
|
||||||
|
preview_settings.mixed_lower_bound,
|
||||||
|
preview_settings.mixed_upper_bound,
|
||||||
|
preview_settings.preferred_a_height,
|
||||||
|
preview_settings.preferred_b_height,
|
||||||
|
mf.mix_b_percent,
|
||||||
|
0);
|
||||||
|
if (pass_heights.empty())
|
||||||
|
return std::clamp(mf.mix_b_percent, 0, 100);
|
||||||
|
|
||||||
|
double expected_h_a = preview_settings.preferred_a_height;
|
||||||
|
double expected_h_b = preview_settings.preferred_b_height;
|
||||||
|
if (expected_h_a <= EPSILON && expected_h_b <= EPSILON) {
|
||||||
|
const int mix_b = std::clamp(mf.mix_b_percent, 0, 100);
|
||||||
|
const double pct_b = double(mix_b) / 100.0;
|
||||||
|
const double pct_a = 1.0 - pct_b;
|
||||||
|
const double lo = std::max<double>(0.01, preview_settings.mixed_lower_bound);
|
||||||
|
const double hi = std::max<double>(lo, preview_settings.mixed_upper_bound);
|
||||||
|
expected_h_a = lo + pct_a * (hi - lo);
|
||||||
|
expected_h_b = lo + pct_b * (hi - lo);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto choose_start_with_component_a = [](const std::vector<double> &passes, double local_expected_h_a, double local_expected_h_b) {
|
||||||
|
double err_ab = 0.0;
|
||||||
|
double err_ba = 0.0;
|
||||||
|
for (size_t pass_i = 0; pass_i < passes.size(); ++pass_i) {
|
||||||
|
const double expected_ab = (pass_i % 2) == 0 ? local_expected_h_a : local_expected_h_b;
|
||||||
|
const double expected_ba = (pass_i % 2) == 0 ? local_expected_h_b : local_expected_h_a;
|
||||||
|
err_ab += std::abs(passes[pass_i] - expected_ab);
|
||||||
|
err_ba += std::abs(passes[pass_i] - expected_ba);
|
||||||
|
}
|
||||||
|
if (err_ab + 1e-6 < err_ba)
|
||||||
|
return true;
|
||||||
|
if (err_ba + 1e-6 < err_ab)
|
||||||
|
return false;
|
||||||
|
return local_expected_h_a >= local_expected_h_b;
|
||||||
|
};
|
||||||
|
|
||||||
|
const bool start_with_a = choose_start_with_component_a(pass_heights, expected_h_a, expected_h_b);
|
||||||
|
double total_a = 0.0;
|
||||||
|
double total_b = 0.0;
|
||||||
|
for (size_t pass_i = 0; pass_i < pass_heights.size(); ++pass_i) {
|
||||||
|
const bool even_pass = (pass_i % 2) == 0;
|
||||||
|
const bool pass_is_a = even_pass ? start_with_a : !start_with_a;
|
||||||
|
if (pass_is_a)
|
||||||
|
total_a += pass_heights[pass_i];
|
||||||
|
else
|
||||||
|
total_b += pass_heights[pass_i];
|
||||||
|
}
|
||||||
|
|
||||||
|
const double total = total_a + total_b;
|
||||||
|
if (total <= EPSILON)
|
||||||
|
return std::clamp(mf.mix_b_percent, 0, 100);
|
||||||
|
return std::clamp(int(std::lround(100.0 * total_b / total)), 0, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mixed_filament_supports_bias_apparent_color(const MixedFilament &mf,
|
||||||
|
const MixedFilamentPreviewSettings &preview_settings,
|
||||||
|
bool bias_mode_enabled)
|
||||||
|
{
|
||||||
|
if (!bias_mode_enabled)
|
||||||
|
return false;
|
||||||
|
if (preview_settings.local_z_mode)
|
||||||
|
return false;
|
||||||
|
if (mf.distribution_mode == int(MixedFilament::SameLayerPointillisme))
|
||||||
|
return false;
|
||||||
|
if (!MixedFilamentManager::normalize_manual_pattern(mf.manual_pattern).empty())
|
||||||
|
return false;
|
||||||
|
if (decode_gradient_component_ids(mf.gradient_component_ids, 9).size() >= 3)
|
||||||
|
return false;
|
||||||
|
return mf.component_a >= 1 && mf.component_b >= 1 && mf.component_a != mf.component_b;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<int, int> mixed_filament_apparent_pair_percentages(const MixedFilament &mf,
|
||||||
|
const MixedFilamentPreviewSettings &preview_settings,
|
||||||
|
const std::vector<double> &nozzle_diameters,
|
||||||
|
bool bias_mode_enabled)
|
||||||
|
{
|
||||||
|
const int base_b = mixed_filament_effective_local_z_preview_mix_b_percent(mf, preview_settings);
|
||||||
|
if (!mixed_filament_supports_bias_apparent_color(mf, preview_settings, bias_mode_enabled))
|
||||||
|
return { 100 - base_b, base_b };
|
||||||
|
|
||||||
|
const double reference_nozzle_mm = mixed_filament_reference_nozzle_mm(mf.component_a, mf.component_b, nozzle_diameters);
|
||||||
|
const int apparent_b = MixedFilamentManager::apparent_mix_b_percent(base_b,
|
||||||
|
mf.component_a_surface_offset,
|
||||||
|
mf.component_b_surface_offset,
|
||||||
|
float(reference_nozzle_mm));
|
||||||
|
return { 100 - apparent_b, apparent_b };
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string compute_mixed_filament_display_color(const MixedFilament &entry, const MixedFilamentDisplayContext &context)
|
||||||
|
{
|
||||||
|
constexpr const char *fallback = "#26A69A";
|
||||||
|
if (context.num_physical == 0 || context.physical_colors.empty())
|
||||||
|
return fallback;
|
||||||
|
|
||||||
|
if (mixed_filament_supports_bias_apparent_color(entry, context.preview_settings, context.component_bias_enabled) &&
|
||||||
|
entry.component_a >= 1 && entry.component_b >= 1 &&
|
||||||
|
entry.component_a <= context.num_physical && entry.component_b <= context.num_physical &&
|
||||||
|
entry.component_a <= context.physical_colors.size() && entry.component_b <= context.physical_colors.size()) {
|
||||||
|
const auto [apparent_pct_a, apparent_pct_b] =
|
||||||
|
mixed_filament_apparent_pair_percentages(entry, context.preview_settings, context.nozzle_diameters, context.component_bias_enabled);
|
||||||
|
return MixedFilamentManager::blend_color(
|
||||||
|
context.physical_colors[entry.component_a - 1],
|
||||||
|
context.physical_colors[entry.component_b - 1],
|
||||||
|
apparent_pct_a,
|
||||||
|
apparent_pct_b);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string normalized_pattern = MixedFilamentManager::normalize_manual_pattern(entry.manual_pattern);
|
||||||
|
if (!normalized_pattern.empty()) {
|
||||||
|
const std::vector<unsigned int> sequence = build_grouped_manual_pattern_preview_sequence(
|
||||||
|
normalized_pattern, entry.component_a, entry.component_b, context.num_physical, context.preview_settings.wall_loops);
|
||||||
|
if (!sequence.empty())
|
||||||
|
return blend_display_color_from_sequence(context.physical_colors, context.num_physical, sequence, fallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.distribution_mode != int(MixedFilament::Simple)) {
|
||||||
|
const std::vector<unsigned int> gradient_ids = decode_gradient_component_ids(entry.gradient_component_ids, context.num_physical);
|
||||||
|
if (gradient_ids.size() >= 3) {
|
||||||
|
const std::vector<int> gradient_weights =
|
||||||
|
decode_gradient_component_weights(entry.gradient_component_weights, gradient_ids.size());
|
||||||
|
const std::vector<unsigned int> sequence = build_weighted_gradient_sequence(
|
||||||
|
gradient_ids, gradient_weights.empty() ? std::vector<int>(gradient_ids.size(), 1) : gradient_weights);
|
||||||
|
if (!sequence.empty())
|
||||||
|
return blend_display_color_from_sequence(context.physical_colors, context.num_physical, sequence, fallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const int effective_mix_b = mixed_filament_effective_local_z_preview_mix_b_percent(entry, context.preview_settings);
|
||||||
|
const bool same_layer_mode = entry.distribution_mode == int(MixedFilament::SameLayerPointillisme);
|
||||||
|
const std::vector<unsigned int> pair_sequence =
|
||||||
|
build_effective_pair_preview_sequence(entry.component_a, entry.component_b, effective_mix_b, same_layer_mode);
|
||||||
|
if (!pair_sequence.empty())
|
||||||
|
return blend_display_color_from_sequence(context.physical_colors, context.num_physical, pair_sequence, fallback);
|
||||||
|
|
||||||
|
if (entry.component_a == 0 || entry.component_b == 0 ||
|
||||||
|
entry.component_a > context.num_physical || entry.component_b > context.num_physical ||
|
||||||
|
entry.component_a > context.physical_colors.size() || entry.component_b > context.physical_colors.size()) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int mix_b = std::clamp(entry.mix_b_percent, 0, 100);
|
||||||
|
return MixedFilamentManager::blend_color(
|
||||||
|
context.physical_colors[entry.component_a - 1],
|
||||||
|
context.physical_colors[entry.component_b - 1],
|
||||||
|
100 - mix_b,
|
||||||
|
mix_b);
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// MixedFilamentManager
|
// MixedFilamentManager
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -1651,54 +2246,16 @@ int MixedFilamentManager::apparent_mix_b_percent(int mix_b_percent,
|
|||||||
|
|
||||||
void MixedFilamentManager::refresh_display_colors(const std::vector<std::string> &filament_colours)
|
void MixedFilamentManager::refresh_display_colors(const std::vector<std::string> &filament_colours)
|
||||||
{
|
{
|
||||||
for (MixedFilament &mf : m_mixed) {
|
MixedFilamentDisplayContext context = m_display_context;
|
||||||
const std::vector<unsigned int> gradient_ids = decode_gradient_component_ids(mf.gradient_component_ids, filament_colours.size());
|
context.num_physical = filament_colours.size();
|
||||||
if (mf.distribution_mode != int(MixedFilament::Simple) && gradient_ids.size() >= 3) {
|
context.physical_colors = filament_colours;
|
||||||
const std::vector<int> gradient_weights =
|
if (context.preview_settings.wall_loops == 0)
|
||||||
decode_gradient_component_weights(mf.gradient_component_weights, gradient_ids.size());
|
context.preview_settings.wall_loops = 1;
|
||||||
const std::vector<unsigned int> gradient_sequence =
|
if (context.nozzle_diameters.size() < context.num_physical)
|
||||||
build_weighted_gradient_sequence(gradient_ids,
|
context.nozzle_diameters.resize(context.num_physical, 0.4);
|
||||||
gradient_weights.empty() ? std::vector<int>(gradient_ids.size(), 1) : gradient_weights);
|
|
||||||
if (gradient_sequence.empty()) {
|
|
||||||
mf.display_color = "#26A69A";
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<int> counts(gradient_ids.size(), 0);
|
for (MixedFilament &mf : m_mixed)
|
||||||
for (const unsigned int id : gradient_sequence) {
|
mf.display_color = compute_mixed_filament_display_color(mf, context);
|
||||||
auto it = std::find(gradient_ids.begin(), gradient_ids.end(), id);
|
|
||||||
if (it != gradient_ids.end())
|
|
||||||
++counts[size_t(it - gradient_ids.begin())];
|
|
||||||
}
|
|
||||||
std::vector<std::pair<std::string, int>> color_percents;
|
|
||||||
color_percents.reserve(gradient_ids.size());
|
|
||||||
for (size_t i = 0; i < gradient_ids.size(); ++i) {
|
|
||||||
const int wi = std::max(0, counts[i]);
|
|
||||||
if (wi == 0)
|
|
||||||
continue;
|
|
||||||
color_percents.emplace_back(filament_colours[gradient_ids[i] - 1], wi);
|
|
||||||
}
|
|
||||||
mf.display_color = blend_color_multi(color_percents);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
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 std::string normalized_pattern = normalize_manual_pattern(mf.manual_pattern);
|
|
||||||
const int ratio_b =
|
|
||||||
(mf.distribution_mode != int(MixedFilament::SameLayerPointillisme) &&
|
|
||||||
normalized_pattern.empty() &&
|
|
||||||
gradient_ids.size() < 3) ?
|
|
||||||
apparent_mix_b_percent(mf.mix_b_percent, mf.component_a_surface_offset, mf.component_b_surface_offset) :
|
|
||||||
clamp_int(mf.mix_b_percent, 0, 100);
|
|
||||||
const int ratio_a = std::max(0, 100 - ratio_b);
|
|
||||||
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 MixedFilamentManager::enabled_count() const
|
||||||
@@ -1719,4 +2276,17 @@ std::vector<std::string> MixedFilamentManager::display_colors() const
|
|||||||
return colors;
|
return colors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MixedFilamentManager::set_display_context(const MixedFilamentDisplayContext &context)
|
||||||
|
{
|
||||||
|
m_display_context = context;
|
||||||
|
if (m_display_context.num_physical == 0 || m_display_context.num_physical < m_display_context.physical_colors.size())
|
||||||
|
m_display_context.num_physical = m_display_context.physical_colors.size();
|
||||||
|
if (m_display_context.preview_settings.wall_loops == 0)
|
||||||
|
m_display_context.preview_settings.wall_loops = 1;
|
||||||
|
if (m_display_context.nozzle_diameters.size() < m_display_context.num_physical)
|
||||||
|
m_display_context.nozzle_diameters.resize(m_display_context.num_physical, 0.4);
|
||||||
|
if (!m_display_context.physical_colors.empty())
|
||||||
|
refresh_display_colors(m_display_context.physical_colors);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|||||||
@@ -112,6 +112,37 @@ struct MixedFilament
|
|||||||
bool operator!=(const MixedFilament &rhs) const { return !(*this == rhs); }
|
bool operator!=(const MixedFilament &rhs) const { return !(*this == rhs); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct MixedFilamentPreviewSettings
|
||||||
|
{
|
||||||
|
double nominal_layer_height { 0.2 };
|
||||||
|
double mixed_lower_bound { 0.04 };
|
||||||
|
double mixed_upper_bound { 0.16 };
|
||||||
|
double preferred_a_height { 0.0 };
|
||||||
|
double preferred_b_height { 0.0 };
|
||||||
|
bool local_z_mode { false };
|
||||||
|
size_t wall_loops { 1 };
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MixedFilamentDisplayContext
|
||||||
|
{
|
||||||
|
size_t num_physical { 0 };
|
||||||
|
std::vector<std::string> physical_colors;
|
||||||
|
std::vector<double> nozzle_diameters;
|
||||||
|
MixedFilamentPreviewSettings preview_settings;
|
||||||
|
bool component_bias_enabled { false };
|
||||||
|
};
|
||||||
|
|
||||||
|
int mixed_filament_effective_local_z_preview_mix_b_percent(const MixedFilament &mf,
|
||||||
|
const MixedFilamentPreviewSettings &preview_settings);
|
||||||
|
bool mixed_filament_supports_bias_apparent_color(const MixedFilament &mf,
|
||||||
|
const MixedFilamentPreviewSettings &preview_settings,
|
||||||
|
bool bias_mode_enabled);
|
||||||
|
std::pair<int, int> mixed_filament_apparent_pair_percentages(const MixedFilament &mf,
|
||||||
|
const MixedFilamentPreviewSettings &preview_settings,
|
||||||
|
const std::vector<double> &nozzle_diameters,
|
||||||
|
bool bias_mode_enabled);
|
||||||
|
std::string compute_mixed_filament_display_color(const MixedFilament &entry, const MixedFilamentDisplayContext &context);
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// MixedFilamentManager
|
// MixedFilamentManager
|
||||||
//
|
//
|
||||||
@@ -253,6 +284,7 @@ public:
|
|||||||
|
|
||||||
// Return the display colours of all enabled mixed filaments (in order).
|
// Return the display colours of all enabled mixed filaments (in order).
|
||||||
std::vector<std::string> display_colors() const;
|
std::vector<std::string> display_colors() const;
|
||||||
|
void set_display_context(const MixedFilamentDisplayContext &context);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Convert a 1-based virtual ID to a 0-based index into m_mixed.
|
// Convert a 1-based virtual ID to a 0-based index into m_mixed.
|
||||||
@@ -271,6 +303,7 @@ private:
|
|||||||
float m_height_upper_bound = 0.16f;
|
float m_height_upper_bound = 0.16f;
|
||||||
bool m_advanced_dithering = false;
|
bool m_advanced_dithering = false;
|
||||||
uint64_t m_next_stable_id = 1;
|
uint64_t m_next_stable_id = 1;
|
||||||
|
MixedFilamentDisplayContext m_display_context;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|||||||
@@ -244,6 +244,14 @@ struct MixedColorMatchRecipeResult
|
|||||||
MixedColorMatchRecipeResult prompt_best_color_match_recipe(wxWindow *parent,
|
MixedColorMatchRecipeResult prompt_best_color_match_recipe(wxWindow *parent,
|
||||||
const std::vector<std::string> &physical_colors,
|
const std::vector<std::string> &physical_colors,
|
||||||
const wxColour &initial_color);
|
const wxColour &initial_color);
|
||||||
|
double color_delta_e00(const wxColour &lhs, const wxColour &rhs);
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
MixedFilamentDisplayContext build_mixed_filament_display_context(const std::vector<std::string> &physical_colors);
|
||||||
|
wxColour compute_color_match_recipe_display_color(const MixedColorMatchRecipeResult &recipe, const MixedFilamentDisplayContext &context);
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
#define PRINTER_THUMBNAIL_SIZE (wxSize(FromDIP(48), FromDIP(48)))
|
#define PRINTER_THUMBNAIL_SIZE (wxSize(FromDIP(48), FromDIP(48)))
|
||||||
#define PRINTER_THUMBNAIL_SIZE_SMALL (wxSize(FromDIP(32), FromDIP(32)))
|
#define PRINTER_THUMBNAIL_SIZE_SMALL (wxSize(FromDIP(32), FromDIP(32)))
|
||||||
@@ -1561,7 +1569,9 @@ Sidebar::Sidebar(Plater *parent)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MixedFilamentDisplayContext display_context = build_mixed_filament_display_context(colors);
|
||||||
auto &mgr = wxGetApp().preset_bundle->mixed_filaments;
|
auto &mgr = wxGetApp().preset_bundle->mixed_filaments;
|
||||||
|
mgr.set_display_context(display_context);
|
||||||
mgr.add_custom_filament(recipe.component_a, recipe.component_b, recipe.mix_b_percent, colors);
|
mgr.add_custom_filament(recipe.component_a, recipe.component_b, recipe.mix_b_percent, colors);
|
||||||
auto &mfs = mgr.mixed_filaments();
|
auto &mfs = mgr.mixed_filaments();
|
||||||
if (!mfs.empty()) {
|
if (!mfs.empty()) {
|
||||||
@@ -1573,7 +1583,7 @@ Sidebar::Sidebar(Plater *parent)
|
|||||||
created.pointillism_all_filaments = false;
|
created.pointillism_all_filaments = false;
|
||||||
created.distribution_mode = recipe.gradient_component_ids.empty() ? int(MixedFilament::Simple) : int(MixedFilament::LayerCycle);
|
created.distribution_mode = recipe.gradient_component_ids.empty() ? int(MixedFilament::Simple) : int(MixedFilament::LayerCycle);
|
||||||
created.custom = true;
|
created.custom = true;
|
||||||
created.display_color = recipe.preview_color.GetAsString(wxC2S_HTML_SYNTAX).ToStdString();
|
created.display_color = compute_color_match_recipe_display_color(recipe, display_context).GetAsString(wxC2S_HTML_SYNTAX).ToStdString();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ConfigOptionString *opt = wxGetApp().preset_bundle->project_config.option<ConfigOptionString>("mixed_filament_definitions"))
|
if (ConfigOptionString *opt = wxGetApp().preset_bundle->project_config.option<ConfigOptionString>("mixed_filament_definitions"))
|
||||||
@@ -3785,6 +3795,7 @@ public:
|
|||||||
{
|
{
|
||||||
m_recipe_timer.SetOwner(this);
|
m_recipe_timer.SetOwner(this);
|
||||||
m_loading_timer.SetOwner(this);
|
m_loading_timer.SetOwner(this);
|
||||||
|
m_display_context = build_mixed_filament_display_context(m_physical_colors);
|
||||||
|
|
||||||
m_palette.reserve(m_physical_colors.size());
|
m_palette.reserve(m_physical_colors.size());
|
||||||
for (const std::string &hex : m_physical_colors)
|
for (const std::string &hex : m_physical_colors)
|
||||||
@@ -3983,6 +3994,16 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void sync_recipe_preview(MixedColorMatchRecipeResult &recipe, const wxColour *requested_target = nullptr)
|
||||||
|
{
|
||||||
|
if (!recipe.valid)
|
||||||
|
return;
|
||||||
|
|
||||||
|
recipe.preview_color = compute_color_match_recipe_display_color(recipe, m_display_context);
|
||||||
|
if (requested_target != nullptr && requested_target->IsOk())
|
||||||
|
recipe.delta_e = color_delta_e00(*requested_target, recipe.preview_color);
|
||||||
|
}
|
||||||
|
|
||||||
void update_range_label()
|
void update_range_label()
|
||||||
{
|
{
|
||||||
if (m_range_value)
|
if (m_range_value)
|
||||||
@@ -3995,6 +4016,8 @@ private:
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
m_presets = build_color_match_presets(m_physical_colors, m_min_component_percent);
|
m_presets = build_color_match_presets(m_physical_colors, m_min_component_percent);
|
||||||
|
for (MixedColorMatchRecipeResult &preset : m_presets)
|
||||||
|
sync_recipe_preview(preset);
|
||||||
|
|
||||||
m_presets_host->Freeze();
|
m_presets_host->Freeze();
|
||||||
while (m_presets_sizer->GetItemCount() > 0) {
|
while (m_presets_sizer->GetItemCount() > 0) {
|
||||||
@@ -4128,6 +4151,7 @@ private:
|
|||||||
|
|
||||||
m_has_recipe_result = true;
|
m_has_recipe_result = true;
|
||||||
m_selected_recipe = std::move(recipe);
|
m_selected_recipe = std::move(recipe);
|
||||||
|
sync_recipe_preview(m_selected_recipe, &requested_target);
|
||||||
set_recipe_loading(false, wxEmptyString);
|
set_recipe_loading(false, wxEmptyString);
|
||||||
|
|
||||||
if (m_selected_recipe.valid) {
|
if (m_selected_recipe.valid) {
|
||||||
@@ -4145,6 +4169,7 @@ private:
|
|||||||
void apply_preset(MixedColorMatchRecipeResult preset)
|
void apply_preset(MixedColorMatchRecipeResult preset)
|
||||||
{
|
{
|
||||||
preset.delta_e = 0.0;
|
preset.delta_e = 0.0;
|
||||||
|
sync_recipe_preview(preset);
|
||||||
++m_recipe_request_token;
|
++m_recipe_request_token;
|
||||||
m_requested_target = preset.preview_color;
|
m_requested_target = preset.preview_color;
|
||||||
m_selected_target = preset.preview_color;
|
m_selected_target = preset.preview_color;
|
||||||
@@ -4228,6 +4253,7 @@ private:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<std::string> m_physical_colors;
|
std::vector<std::string> m_physical_colors;
|
||||||
|
MixedFilamentDisplayContext m_display_context;
|
||||||
std::vector<wxColour> m_palette;
|
std::vector<wxColour> m_palette;
|
||||||
std::vector<MixedColorMatchRecipeResult> m_presets;
|
std::vector<MixedColorMatchRecipeResult> m_presets;
|
||||||
MixedFilamentColorMapPanel *m_color_map = nullptr;
|
MixedFilamentColorMapPanel *m_color_map = nullptr;
|
||||||
@@ -4558,17 +4584,6 @@ private:
|
|||||||
// Forward declaration for MixedMixPreview (defined below)
|
// Forward declaration for MixedMixPreview (defined below)
|
||||||
class MixedMixPreview;
|
class MixedMixPreview;
|
||||||
|
|
||||||
struct MixedFilamentPreviewSettings
|
|
||||||
{
|
|
||||||
double nominal_layer_height { 0.2 };
|
|
||||||
double mixed_lower_bound { 0.04 };
|
|
||||||
double mixed_upper_bound { 0.16 };
|
|
||||||
double preferred_a_height { 0.0 };
|
|
||||||
double preferred_b_height { 0.0 };
|
|
||||||
bool local_z_mode { false };
|
|
||||||
size_t wall_loops { 1 };
|
|
||||||
};
|
|
||||||
|
|
||||||
// Inline editor panel for configuring a single mixed filament
|
// Inline editor panel for configuring a single mixed filament
|
||||||
class MixedFilamentConfigPanel : public wxPanel
|
class MixedFilamentConfigPanel : public wxPanel
|
||||||
{
|
{
|
||||||
@@ -5532,88 +5547,7 @@ std::vector<double> MixedFilamentConfigPanel::build_local_z_preview_pass_heights
|
|||||||
int MixedFilamentConfigPanel::effective_local_z_preview_mix_b_percent(const MixedFilament &mf,
|
int MixedFilamentConfigPanel::effective_local_z_preview_mix_b_percent(const MixedFilament &mf,
|
||||||
const MixedFilamentPreviewSettings &preview_settings)
|
const MixedFilamentPreviewSettings &preview_settings)
|
||||||
{
|
{
|
||||||
if (!preview_settings.local_z_mode)
|
return Slic3r::mixed_filament_effective_local_z_preview_mix_b_percent(mf, preview_settings);
|
||||||
return std::clamp(mf.mix_b_percent, 0, 100);
|
|
||||||
|
|
||||||
const std::string normalized_pattern = MixedFilamentManager::normalize_manual_pattern(mf.manual_pattern);
|
|
||||||
if (!normalized_pattern.empty() || mf.distribution_mode == int(MixedFilament::SameLayerPointillisme))
|
|
||||||
return std::clamp(mf.mix_b_percent, 0, 100);
|
|
||||||
|
|
||||||
const std::vector<unsigned int> gradient_ids = decode_gradient_ids(mf.gradient_component_ids);
|
|
||||||
if (gradient_ids.size() >= 3)
|
|
||||||
return std::clamp(mf.mix_b_percent, 0, 100);
|
|
||||||
|
|
||||||
const std::vector<double> pass_heights = build_local_z_preview_pass_heights(preview_settings.nominal_layer_height,
|
|
||||||
preview_settings.mixed_lower_bound,
|
|
||||||
preview_settings.mixed_upper_bound,
|
|
||||||
preview_settings.preferred_a_height,
|
|
||||||
preview_settings.preferred_b_height,
|
|
||||||
mf.mix_b_percent,
|
|
||||||
0);
|
|
||||||
if (pass_heights.empty())
|
|
||||||
return std::clamp(mf.mix_b_percent, 0, 100);
|
|
||||||
|
|
||||||
double expected_h_a = preview_settings.preferred_a_height;
|
|
||||||
double expected_h_b = preview_settings.preferred_b_height;
|
|
||||||
if (expected_h_a <= EPSILON && expected_h_b <= EPSILON) {
|
|
||||||
const int mix_b = std::clamp(mf.mix_b_percent, 0, 100);
|
|
||||||
const double pct_b = double(mix_b) / 100.0;
|
|
||||||
const double pct_a = 1.0 - pct_b;
|
|
||||||
const double lo = std::max<double>(0.01, preview_settings.mixed_lower_bound);
|
|
||||||
const double hi = std::max<double>(lo, preview_settings.mixed_upper_bound);
|
|
||||||
expected_h_a = lo + pct_a * (hi - lo);
|
|
||||||
expected_h_b = lo + pct_b * (hi - lo);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto choose_start_with_component_a = [](const std::vector<double> &passes, double local_expected_h_a, double local_expected_h_b) {
|
|
||||||
double err_ab = 0.0;
|
|
||||||
double err_ba = 0.0;
|
|
||||||
for (size_t pass_i = 0; pass_i < passes.size(); ++pass_i) {
|
|
||||||
const double expected_ab = (pass_i % 2) == 0 ? local_expected_h_a : local_expected_h_b;
|
|
||||||
const double expected_ba = (pass_i % 2) == 0 ? local_expected_h_b : local_expected_h_a;
|
|
||||||
err_ab += std::abs(passes[pass_i] - expected_ab);
|
|
||||||
err_ba += std::abs(passes[pass_i] - expected_ba);
|
|
||||||
}
|
|
||||||
if (err_ab + 1e-6 < err_ba)
|
|
||||||
return true;
|
|
||||||
if (err_ba + 1e-6 < err_ab)
|
|
||||||
return false;
|
|
||||||
return local_expected_h_a >= local_expected_h_b;
|
|
||||||
};
|
|
||||||
|
|
||||||
const bool start_with_a = choose_start_with_component_a(pass_heights, expected_h_a, expected_h_b);
|
|
||||||
double total_a = 0.0;
|
|
||||||
double total_b = 0.0;
|
|
||||||
for (size_t pass_i = 0; pass_i < pass_heights.size(); ++pass_i) {
|
|
||||||
const bool even_pass = (pass_i % 2) == 0;
|
|
||||||
const bool pass_is_a = even_pass ? start_with_a : !start_with_a;
|
|
||||||
if (pass_is_a)
|
|
||||||
total_a += pass_heights[pass_i];
|
|
||||||
else
|
|
||||||
total_b += pass_heights[pass_i];
|
|
||||||
}
|
|
||||||
|
|
||||||
const double total = total_a + total_b;
|
|
||||||
if (total <= EPSILON)
|
|
||||||
return std::clamp(mf.mix_b_percent, 0, 100);
|
|
||||||
return std::clamp(int(std::lround(100.0 * total_b / total)), 0, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool mixed_filament_supports_bias_apparent_color(const MixedFilament &mf,
|
|
||||||
const MixedFilamentPreviewSettings &preview_settings,
|
|
||||||
bool bias_mode_enabled)
|
|
||||||
{
|
|
||||||
if (!bias_mode_enabled)
|
|
||||||
return false;
|
|
||||||
if (preview_settings.local_z_mode)
|
|
||||||
return false;
|
|
||||||
if (mf.distribution_mode == int(MixedFilament::SameLayerPointillisme))
|
|
||||||
return false;
|
|
||||||
if (!MixedFilamentManager::normalize_manual_pattern(mf.manual_pattern).empty())
|
|
||||||
return false;
|
|
||||||
if (mf.gradient_component_ids.size() >= 3)
|
|
||||||
return false;
|
|
||||||
return mf.component_a >= 1 && mf.component_b >= 1 && mf.component_a != mf.component_b;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static double mixed_filament_reference_nozzle_mm(unsigned int component_a,
|
static double mixed_filament_reference_nozzle_mm(unsigned int component_a,
|
||||||
@@ -5660,35 +5594,18 @@ static std::pair<float, float> mixed_filament_single_surface_offset_pair(const M
|
|||||||
return MixedFilamentManager::surface_offset_pair_from_signed_bias(value, float(reference_nozzle_mm));
|
return MixedFilamentManager::surface_offset_pair_from_signed_bias(value, float(reference_nozzle_mm));
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::pair<int, int> mixed_filament_apparent_pair_percentages(const MixedFilament &mf,
|
|
||||||
const MixedFilamentPreviewSettings &preview_settings,
|
|
||||||
const std::vector<double> &nozzle_diameters,
|
|
||||||
bool bias_mode_enabled)
|
|
||||||
{
|
|
||||||
const int base_b = MixedFilamentConfigPanel::effective_local_z_preview_mix_b_percent(mf, preview_settings);
|
|
||||||
if (!mixed_filament_supports_bias_apparent_color(mf, preview_settings, bias_mode_enabled))
|
|
||||||
return { 100 - base_b, base_b };
|
|
||||||
|
|
||||||
const double reference_nozzle_mm = mixed_filament_reference_nozzle_mm(mf.component_a, mf.component_b, nozzle_diameters);
|
|
||||||
const int apparent_b = MixedFilamentManager::apparent_mix_b_percent(base_b,
|
|
||||||
mf.component_a_surface_offset,
|
|
||||||
mf.component_b_surface_offset,
|
|
||||||
float(reference_nozzle_mm));
|
|
||||||
return { 100 - apparent_b, apparent_b };
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::string mixed_filament_apparent_pair_summary(const MixedFilament &mf,
|
static std::string mixed_filament_apparent_pair_summary(const MixedFilament &mf,
|
||||||
const MixedFilamentPreviewSettings &preview_settings,
|
const MixedFilamentPreviewSettings &preview_settings,
|
||||||
const std::vector<double> &nozzle_diameters,
|
const std::vector<double> &nozzle_diameters,
|
||||||
bool bias_mode_enabled)
|
bool bias_mode_enabled)
|
||||||
{
|
{
|
||||||
if (!mixed_filament_supports_bias_apparent_color(mf, preview_settings, bias_mode_enabled))
|
if (!Slic3r::mixed_filament_supports_bias_apparent_color(mf, preview_settings, bias_mode_enabled))
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
const int base_b = MixedFilamentConfigPanel::effective_local_z_preview_mix_b_percent(mf, preview_settings);
|
const int base_b = MixedFilamentConfigPanel::effective_local_z_preview_mix_b_percent(mf, preview_settings);
|
||||||
const int base_a = 100 - base_b;
|
const int base_a = 100 - base_b;
|
||||||
const auto [apparent_a, apparent_b] =
|
const auto [apparent_a, apparent_b] =
|
||||||
mixed_filament_apparent_pair_percentages(mf, preview_settings, nozzle_diameters, bias_mode_enabled);
|
Slic3r::mixed_filament_apparent_pair_percentages(mf, preview_settings, nozzle_diameters, bias_mode_enabled);
|
||||||
|
|
||||||
if (std::abs(mf.component_a_surface_offset - mf.component_b_surface_offset) > 1e-4f &&
|
if (std::abs(mf.component_a_surface_offset - mf.component_b_surface_offset) > 1e-4f &&
|
||||||
(apparent_a != base_a || apparent_b != base_b)) {
|
(apparent_a != base_a || apparent_b != base_b)) {
|
||||||
@@ -5702,6 +5619,202 @@ static std::string mixed_filament_apparent_pair_summary(const MixedFilament
|
|||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MixedFilamentDisplayContext build_mixed_filament_display_context(const std::vector<std::string> &physical_colors)
|
||||||
|
{
|
||||||
|
MixedFilamentDisplayContext context;
|
||||||
|
context.num_physical = physical_colors.size();
|
||||||
|
context.physical_colors = physical_colors;
|
||||||
|
context.nozzle_diameters.assign(context.num_physical, 0.4);
|
||||||
|
|
||||||
|
auto *preset_bundle = wxGetApp().preset_bundle;
|
||||||
|
if (preset_bundle == nullptr)
|
||||||
|
return context;
|
||||||
|
|
||||||
|
DynamicPrintConfig *print_cfg = &preset_bundle->prints.get_edited_preset().config;
|
||||||
|
if (const ConfigOptionFloats *opt = preset_bundle->printers.get_edited_preset().config.option<ConfigOptionFloats>("nozzle_diameter")) {
|
||||||
|
const size_t opt_count = opt->values.size();
|
||||||
|
if (opt_count > 0) {
|
||||||
|
for (size_t i = 0; i < context.num_physical; ++i)
|
||||||
|
context.nozzle_diameters[i] = std::max(0.05, opt->get_at(unsigned(std::min(i, opt_count - 1))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto get_mixed_bool = [preset_bundle, print_cfg](const std::string &key, bool fallback) {
|
||||||
|
if (const ConfigOptionBool *opt = preset_bundle->project_config.option<ConfigOptionBool>(key))
|
||||||
|
return opt->value;
|
||||||
|
if (const ConfigOptionInt *opt = preset_bundle->project_config.option<ConfigOptionInt>(key))
|
||||||
|
return opt->value != 0;
|
||||||
|
if (print_cfg != nullptr) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
return fallback;
|
||||||
|
};
|
||||||
|
auto get_mixed_float = [preset_bundle, print_cfg](const std::string &key, float fallback) {
|
||||||
|
if (preset_bundle->project_config.has(key))
|
||||||
|
return float(preset_bundle->project_config.opt_float(key));
|
||||||
|
if (print_cfg != nullptr && print_cfg->has(key))
|
||||||
|
return float(print_cfg->opt_float(key));
|
||||||
|
return fallback;
|
||||||
|
};
|
||||||
|
|
||||||
|
context.preview_settings.mixed_lower_bound = std::max(0.01, double(get_mixed_float("mixed_filament_height_lower_bound", 0.04f)));
|
||||||
|
context.preview_settings.mixed_upper_bound = std::max(context.preview_settings.mixed_lower_bound,
|
||||||
|
double(get_mixed_float("mixed_filament_height_upper_bound", 0.16f)));
|
||||||
|
context.preview_settings.preferred_a_height = std::max(0.0, double(get_mixed_float("mixed_color_layer_height_a", 0.f)));
|
||||||
|
context.preview_settings.preferred_b_height = std::max(0.0, double(get_mixed_float("mixed_color_layer_height_b", 0.f)));
|
||||||
|
context.preview_settings.nominal_layer_height = 0.2;
|
||||||
|
if (print_cfg != nullptr && print_cfg->has("layer_height"))
|
||||||
|
context.preview_settings.nominal_layer_height = std::max(0.01, print_cfg->opt_float("layer_height"));
|
||||||
|
if (print_cfg != nullptr && print_cfg->has("wall_loops"))
|
||||||
|
context.preview_settings.wall_loops = std::max<size_t>(1, size_t(std::max(1, print_cfg->opt_int("wall_loops"))));
|
||||||
|
context.preview_settings.local_z_mode = get_mixed_bool("dithering_local_z_mode", false);
|
||||||
|
context.component_bias_enabled = get_mixed_bool("mixed_filament_component_bias_enabled", false);
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<unsigned int> build_display_weighted_multi_sequence(const std::vector<unsigned int> &ids,
|
||||||
|
const std::vector<int> &weights,
|
||||||
|
size_t max_cycle_limit = 0)
|
||||||
|
{
|
||||||
|
if (ids.empty())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
std::vector<unsigned int> filtered_ids;
|
||||||
|
std::vector<int> counts;
|
||||||
|
filtered_ids.reserve(ids.size());
|
||||||
|
counts.reserve(ids.size());
|
||||||
|
|
||||||
|
const std::vector<int> normalized = normalize_color_match_weights(weights, ids.size());
|
||||||
|
for (size_t idx = 0; idx < ids.size(); ++idx) {
|
||||||
|
const int weight = idx < normalized.size() ? std::max(0, normalized[idx]) : 0;
|
||||||
|
if (weight <= 0)
|
||||||
|
continue;
|
||||||
|
filtered_ids.emplace_back(ids[idx]);
|
||||||
|
counts.emplace_back(weight);
|
||||||
|
}
|
||||||
|
if (filtered_ids.empty()) {
|
||||||
|
filtered_ids = ids;
|
||||||
|
counts.assign(ids.size(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int g = 0;
|
||||||
|
for (const int count : counts)
|
||||||
|
g = std::gcd(g, std::max(1, count));
|
||||||
|
if (g > 1) {
|
||||||
|
for (int &count : counts)
|
||||||
|
count = std::max(1, count / g);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr size_t k_max_cycle = 48;
|
||||||
|
const size_t effective_cycle_limit =
|
||||||
|
max_cycle_limit > 0 ? std::min(k_max_cycle, std::max<size_t>(1, max_cycle_limit)) : k_max_cycle;
|
||||||
|
reduce_weight_counts_to_cycle_limit(counts, effective_cycle_limit);
|
||||||
|
|
||||||
|
std::vector<unsigned int> reduced_ids;
|
||||||
|
std::vector<int> reduced_counts;
|
||||||
|
reduced_ids.reserve(filtered_ids.size());
|
||||||
|
reduced_counts.reserve(counts.size());
|
||||||
|
for (size_t idx = 0; idx < counts.size(); ++idx) {
|
||||||
|
if (counts[idx] <= 0)
|
||||||
|
continue;
|
||||||
|
reduced_ids.emplace_back(filtered_ids[idx]);
|
||||||
|
reduced_counts.emplace_back(counts[idx]);
|
||||||
|
}
|
||||||
|
if (reduced_ids.empty())
|
||||||
|
return {};
|
||||||
|
filtered_ids = std::move(reduced_ids);
|
||||||
|
counts = std::move(reduced_counts);
|
||||||
|
|
||||||
|
const int total = std::accumulate(counts.begin(), counts.end(), 0);
|
||||||
|
if (total <= 0)
|
||||||
|
return std::vector<unsigned int>(filtered_ids.begin(), filtered_ids.end());
|
||||||
|
|
||||||
|
const size_t cycle = size_t(total);
|
||||||
|
|
||||||
|
std::vector<unsigned int> sequence;
|
||||||
|
sequence.reserve(cycle);
|
||||||
|
std::vector<int> emitted(counts.size(), 0);
|
||||||
|
for (size_t pos = 0; pos < cycle; ++pos) {
|
||||||
|
size_t best_idx = 0;
|
||||||
|
double best_score = -1e9;
|
||||||
|
for (size_t idx = 0; idx < counts.size(); ++idx) {
|
||||||
|
const double target = double(pos + 1) * double(counts[idx]) / double(total);
|
||||||
|
const double score = target - double(emitted[idx]);
|
||||||
|
if (score > best_score) {
|
||||||
|
best_score = score;
|
||||||
|
best_idx = idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
++emitted[best_idx];
|
||||||
|
sequence.emplace_back(filtered_ids[best_idx]);
|
||||||
|
}
|
||||||
|
if (sequence.empty())
|
||||||
|
sequence = filtered_ids;
|
||||||
|
return sequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string blend_display_color_from_sequence(const std::vector<std::string> &colors,
|
||||||
|
size_t num_physical,
|
||||||
|
const std::vector<unsigned int> &sequence,
|
||||||
|
const std::string &fallback)
|
||||||
|
{
|
||||||
|
if (colors.empty() || sequence.empty() || num_physical == 0)
|
||||||
|
return fallback;
|
||||||
|
|
||||||
|
std::vector<size_t> counts(num_physical + 1, size_t(0));
|
||||||
|
size_t total = 0;
|
||||||
|
for (const unsigned int id : sequence) {
|
||||||
|
if (id == 0 || id > num_physical)
|
||||||
|
continue;
|
||||||
|
++counts[id];
|
||||||
|
++total;
|
||||||
|
}
|
||||||
|
if (total == 0)
|
||||||
|
return fallback;
|
||||||
|
|
||||||
|
unsigned int first_id = 0;
|
||||||
|
for (size_t id = 1; id <= num_physical; ++id) {
|
||||||
|
if (counts[id] > 0) {
|
||||||
|
first_id = unsigned(id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (first_id == 0 || first_id > colors.size())
|
||||||
|
return fallback;
|
||||||
|
|
||||||
|
std::string blended = colors[first_id - 1];
|
||||||
|
int accumulated = int(counts[first_id]);
|
||||||
|
for (size_t id = size_t(first_id + 1); id <= num_physical; ++id) {
|
||||||
|
if (counts[id] == 0 || id > colors.size())
|
||||||
|
continue;
|
||||||
|
blended = MixedFilamentManager::blend_color(blended, colors[id - 1], accumulated, int(counts[id]));
|
||||||
|
accumulated += int(counts[id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return blended;
|
||||||
|
}
|
||||||
|
|
||||||
|
wxColour compute_color_match_recipe_display_color(const MixedColorMatchRecipeResult &recipe, const MixedFilamentDisplayContext &context)
|
||||||
|
{
|
||||||
|
if (!recipe.valid)
|
||||||
|
return recipe.preview_color.IsOk() ? recipe.preview_color : wxColour("#26A69A");
|
||||||
|
|
||||||
|
MixedFilament entry;
|
||||||
|
entry.component_a = recipe.component_a;
|
||||||
|
entry.component_b = recipe.component_b;
|
||||||
|
entry.mix_b_percent = recipe.mix_b_percent;
|
||||||
|
entry.manual_pattern = recipe.manual_pattern;
|
||||||
|
entry.gradient_component_ids = recipe.gradient_component_ids;
|
||||||
|
entry.gradient_component_weights = recipe.gradient_component_weights;
|
||||||
|
entry.distribution_mode = recipe.gradient_component_ids.empty() ? int(MixedFilament::Simple) : int(MixedFilament::LayerCycle);
|
||||||
|
|
||||||
|
return parse_mixed_color(compute_mixed_filament_display_color(entry, context));
|
||||||
|
}
|
||||||
|
|
||||||
std::string MixedFilamentConfigPanel::summarize_sequence(const std::vector<unsigned int> &seq)
|
std::string MixedFilamentConfigPanel::summarize_sequence(const std::vector<unsigned int> &seq)
|
||||||
{
|
{
|
||||||
if (seq.empty()) return "";
|
if (seq.empty()) return "";
|
||||||
@@ -6367,11 +6480,11 @@ void MixedFilamentConfigPanel::build_ui()
|
|||||||
m_blend_selector->set_multi_preview(corner_colors, *m_selected_weight_state);
|
m_blend_selector->set_multi_preview(corner_colors, *m_selected_weight_state);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mixed_filament_supports_bias_apparent_color(m_mf, m_preview_settings, m_bias_mode_enabled) &&
|
if (Slic3r::mixed_filament_supports_bias_apparent_color(m_mf, m_preview_settings, m_bias_mode_enabled) &&
|
||||||
m_mf.component_a >= 1 && m_mf.component_b >= 1 &&
|
m_mf.component_a >= 1 && m_mf.component_b >= 1 &&
|
||||||
m_mf.component_a <= m_physical_colors.size() && m_mf.component_b <= m_physical_colors.size()) {
|
m_mf.component_a <= m_physical_colors.size() && m_mf.component_b <= m_physical_colors.size()) {
|
||||||
const auto [apparent_pct_a, apparent_pct_b] =
|
const auto [apparent_pct_a, apparent_pct_b] =
|
||||||
mixed_filament_apparent_pair_percentages(m_mf, m_preview_settings, m_nozzle_diameters, m_bias_mode_enabled);
|
Slic3r::mixed_filament_apparent_pair_percentages(m_mf, m_preview_settings, m_nozzle_diameters, m_bias_mode_enabled);
|
||||||
m_mf.display_color = MixedFilamentManager::blend_color(
|
m_mf.display_color = MixedFilamentManager::blend_color(
|
||||||
m_physical_colors[size_t(m_mf.component_a - 1)],
|
m_physical_colors[size_t(m_mf.component_a - 1)],
|
||||||
m_physical_colors[size_t(m_mf.component_b - 1)],
|
m_physical_colors[size_t(m_mf.component_b - 1)],
|
||||||
@@ -6680,11 +6793,11 @@ void MixedFilamentConfigPanel::update_preview()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (m_mix_preview) {
|
if (m_mix_preview) {
|
||||||
if (mixed_filament_supports_bias_apparent_color(m_mf, m_preview_settings, m_bias_mode_enabled) &&
|
if (Slic3r::mixed_filament_supports_bias_apparent_color(m_mf, m_preview_settings, m_bias_mode_enabled) &&
|
||||||
m_mf.component_a >= 1 && m_mf.component_b >= 1 &&
|
m_mf.component_a >= 1 && m_mf.component_b >= 1 &&
|
||||||
m_mf.component_a <= m_physical_colors.size() && m_mf.component_b <= m_physical_colors.size()) {
|
m_mf.component_a <= m_physical_colors.size() && m_mf.component_b <= m_physical_colors.size()) {
|
||||||
const auto [apparent_pct_a, apparent_pct_b] =
|
const auto [apparent_pct_a, apparent_pct_b] =
|
||||||
mixed_filament_apparent_pair_percentages(m_mf, m_preview_settings, m_nozzle_diameters, m_bias_mode_enabled);
|
Slic3r::mixed_filament_apparent_pair_percentages(m_mf, m_preview_settings, m_nozzle_diameters, m_bias_mode_enabled);
|
||||||
m_mf.display_color = MixedFilamentManager::blend_color(
|
m_mf.display_color = MixedFilamentManager::blend_color(
|
||||||
m_physical_colors[size_t(m_mf.component_a - 1)],
|
m_physical_colors[size_t(m_mf.component_a - 1)],
|
||||||
m_physical_colors[size_t(m_mf.component_b - 1)],
|
m_physical_colors[size_t(m_mf.component_b - 1)],
|
||||||
@@ -7187,6 +7300,13 @@ void Sidebar::update_mixed_filament_panel(bool sync_manager)
|
|||||||
local_z_mode,
|
local_z_mode,
|
||||||
wall_loops
|
wall_loops
|
||||||
};
|
};
|
||||||
|
const MixedFilamentDisplayContext display_context {
|
||||||
|
num_physical,
|
||||||
|
physical_colors,
|
||||||
|
nozzle_diameters,
|
||||||
|
preview_settings,
|
||||||
|
component_bias_enabled
|
||||||
|
};
|
||||||
auto summarize_sequence = [num_physical](const std::vector<unsigned int> &sequence) {
|
auto summarize_sequence = [num_physical](const std::vector<unsigned int> &sequence) {
|
||||||
if (sequence.empty() || num_physical == 0)
|
if (sequence.empty() || num_physical == 0)
|
||||||
return std::string();
|
return std::string();
|
||||||
@@ -7213,97 +7333,12 @@ void Sidebar::update_mixed_filament_panel(bool sync_manager)
|
|||||||
}
|
}
|
||||||
return ss.str();
|
return ss.str();
|
||||||
};
|
};
|
||||||
auto blend_from_sequence = [num_physical](const std::vector<std::string> &colors, const std::vector<unsigned int> &sequence, const std::string &fallback) {
|
auto compute_entry_display_color = [display_context](const MixedFilament &entry) {
|
||||||
if (colors.empty() || sequence.empty() || num_physical == 0)
|
return compute_mixed_filament_display_color(entry, display_context);
|
||||||
return fallback;
|
|
||||||
std::vector<size_t> counts(num_physical + 1, size_t(0));
|
|
||||||
size_t total = 0;
|
|
||||||
for (const unsigned int id : sequence) {
|
|
||||||
if (id == 0 || id > num_physical)
|
|
||||||
continue;
|
|
||||||
++counts[id];
|
|
||||||
++total;
|
|
||||||
}
|
|
||||||
if (total == 0)
|
|
||||||
return fallback;
|
|
||||||
|
|
||||||
unsigned int first_id = 0;
|
|
||||||
for (size_t id = 1; id <= num_physical; ++id) {
|
|
||||||
if (counts[id] > 0) {
|
|
||||||
first_id = unsigned(id);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (first_id == 0 || first_id > colors.size())
|
|
||||||
return fallback;
|
|
||||||
|
|
||||||
std::string blended = colors[first_id - 1];
|
|
||||||
int acc = int(counts[first_id]);
|
|
||||||
for (size_t id = size_t(first_id + 1); id <= num_physical; ++id) {
|
|
||||||
if (counts[id] == 0 || id > colors.size())
|
|
||||||
continue;
|
|
||||||
blended = MixedFilamentManager::blend_color(blended, colors[id - 1], acc, int(counts[id]));
|
|
||||||
acc += int(counts[id]);
|
|
||||||
}
|
|
||||||
return blended;
|
|
||||||
};
|
|
||||||
auto build_entry_preview_sequence = [decode_manual_pattern_ids, decode_gradient_ids, decode_gradient_weights,
|
|
||||||
build_weighted_multi_sequence, preview_settings](const MixedFilament &entry) {
|
|
||||||
const std::string normalized_pattern = MixedFilamentManager::normalize_manual_pattern(entry.manual_pattern);
|
|
||||||
if (!normalized_pattern.empty())
|
|
||||||
return decode_manual_pattern_ids(normalized_pattern,
|
|
||||||
entry.component_a,
|
|
||||||
entry.component_b,
|
|
||||||
preview_settings.wall_loops);
|
|
||||||
|
|
||||||
const bool simple_mode = entry.distribution_mode == int(MixedFilament::Simple);
|
|
||||||
if (!simple_mode) {
|
|
||||||
const std::vector<unsigned int> gradient_ids = decode_gradient_ids(entry.gradient_component_ids);
|
|
||||||
if (gradient_ids.size() >= 3) {
|
|
||||||
const std::vector<int> gradient_weights =
|
|
||||||
decode_gradient_weights(entry.gradient_component_weights, gradient_ids.size());
|
|
||||||
return build_weighted_multi_sequence(gradient_ids, gradient_weights, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const int effective_mix_b = MixedFilamentConfigPanel::effective_local_z_preview_mix_b_percent(entry, preview_settings);
|
|
||||||
const bool same_layer_mode = entry.distribution_mode == int(MixedFilament::SameLayerPointillisme);
|
|
||||||
return build_effective_pair_preview_sequence(entry.component_a, entry.component_b, effective_mix_b, same_layer_mode);
|
|
||||||
};
|
|
||||||
auto compute_entry_display_color = [num_physical, &physical_colors, &nozzle_diameters, blend_from_sequence, build_entry_preview_sequence,
|
|
||||||
preview_settings, component_bias_enabled](const MixedFilament &entry) {
|
|
||||||
if (mixed_filament_supports_bias_apparent_color(entry, preview_settings, component_bias_enabled) &&
|
|
||||||
entry.component_a >= 1 && entry.component_b >= 1 &&
|
|
||||||
entry.component_a <= num_physical && entry.component_b <= num_physical &&
|
|
||||||
entry.component_a <= physical_colors.size() && entry.component_b <= physical_colors.size()) {
|
|
||||||
const auto [apparent_pct_a, apparent_pct_b] =
|
|
||||||
mixed_filament_apparent_pair_percentages(entry, preview_settings, nozzle_diameters, component_bias_enabled);
|
|
||||||
return MixedFilamentManager::blend_color(
|
|
||||||
physical_colors[entry.component_a - 1],
|
|
||||||
physical_colors[entry.component_b - 1],
|
|
||||||
apparent_pct_a,
|
|
||||||
apparent_pct_b);
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::vector<unsigned int> sequence = build_entry_preview_sequence(entry);
|
|
||||||
if (!sequence.empty())
|
|
||||||
return blend_from_sequence(physical_colors, sequence, "#26A69A");
|
|
||||||
|
|
||||||
if (entry.component_a == 0 || entry.component_b == 0 ||
|
|
||||||
entry.component_a > num_physical || entry.component_b > num_physical ||
|
|
||||||
entry.component_a > physical_colors.size() || entry.component_b > physical_colors.size()) {
|
|
||||||
return std::string("#26A69A");
|
|
||||||
}
|
|
||||||
|
|
||||||
const int mix_b = std::clamp(entry.mix_b_percent, 0, 100);
|
|
||||||
return MixedFilamentManager::blend_color(
|
|
||||||
physical_colors[entry.component_a - 1],
|
|
||||||
physical_colors[entry.component_b - 1],
|
|
||||||
100 - mix_b,
|
|
||||||
mix_b);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
auto &mixed_mgr = preset_bundle->mixed_filaments;
|
auto &mixed_mgr = preset_bundle->mixed_filaments;
|
||||||
|
mixed_mgr.set_display_context(display_context);
|
||||||
if (sync_manager) {
|
if (sync_manager) {
|
||||||
mixed_mgr.auto_generate(physical_colors);
|
mixed_mgr.auto_generate(physical_colors);
|
||||||
mixed_mgr.clear_custom_entries();
|
mixed_mgr.clear_custom_entries();
|
||||||
|
|||||||
Reference in New Issue
Block a user