Enhance mixed filament functionality: Introduce support for gradient component IDs and weights in mixed filaments, allowing for more complex color mixing configurations. Update parsing logic to accommodate new gradient definitions and ensure backward compatibility. Implement pointillism distribution mode for same-layer mixing, enhancing user control over filament blending. Improve GUI elements to facilitate gradient weight adjustments and multi-color previews, enriching the user experience in mixed filament management.

This commit is contained in:
Rad
2026-02-12 02:29:34 +01:00
parent 0178ad32ad
commit c414e377a0
10 changed files with 2889 additions and 72 deletions

View File

@@ -3,6 +3,7 @@
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdlib>
#include <fstream>
#include <iomanip>
#include <numeric>
@@ -1048,6 +1049,554 @@ static std::vector<double> build_local_z_pass_heights(double base_height,
return build_uniform_local_z_pass_heights(base_height, lo, hi);
}
static std::vector<unsigned int> decode_manual_pattern_sequence(const MixedFilament &mf, size_t num_physical)
{
std::vector<unsigned int> sequence;
if (mf.manual_pattern.empty())
return sequence;
sequence.reserve(mf.manual_pattern.size());
for (const char token : mf.manual_pattern) {
unsigned int extruder_id = 0;
if (token == '1')
extruder_id = mf.component_a;
else if (token == '2')
extruder_id = mf.component_b;
else if (token >= '3' && token <= '9')
extruder_id = unsigned(token - '0');
if (extruder_id >= 1 && extruder_id <= num_physical)
sequence.emplace_back(extruder_id);
}
return sequence;
}
static std::vector<unsigned int> decode_gradient_component_ids(const MixedFilament &mf, size_t num_physical)
{
std::vector<unsigned int> ids;
if (mf.gradient_component_ids.empty() || num_physical == 0)
return ids;
bool seen[10] = { false };
ids.reserve(mf.gradient_component_ids.size());
for (const char c : mf.gradient_component_ids) {
if (c < '1' || c > '9')
continue;
const unsigned int id = unsigned(c - '0');
if (id == 0 || id > num_physical || seen[id])
continue;
seen[id] = true;
ids.emplace_back(id);
}
return ids;
}
static std::vector<int> decode_gradient_component_weights(const MixedFilament &mf, size_t expected_components)
{
std::vector<int> out;
if (mf.gradient_component_weights.empty() || expected_components == 0)
return out;
std::string token;
for (const char c : mf.gradient_component_weights) {
if (c >= '0' && c <= '9') {
token.push_back(c);
continue;
}
if (!token.empty()) {
out.emplace_back(std::max(0, std::atoi(token.c_str())));
token.clear();
}
}
if (!token.empty())
out.emplace_back(std::max(0, std::atoi(token.c_str())));
if (out.size() != expected_components)
return {};
int sum = 0;
for (const int v : out)
sum += std::max(0, v);
if (sum <= 0)
return {};
return out;
}
static std::vector<unsigned int> build_weighted_gradient_sequence(const std::vector<unsigned int> &ids,
const std::vector<int> &weights)
{
if (ids.empty())
return {};
std::vector<unsigned int> filtered_ids;
std::vector<int> counts;
filtered_ids.reserve(ids.size());
counts.reserve(ids.size());
for (size_t i = 0; i < ids.size(); ++i) {
const int w = (i < weights.size()) ? std::max(0, weights[i]) : 0;
if (w <= 0)
continue;
filtered_ids.emplace_back(ids[i]);
counts.emplace_back(w);
}
if (filtered_ids.empty()) {
filtered_ids = ids;
counts.assign(ids.size(), 1);
}
int g = 0;
for (const int c : counts)
g = std::gcd(g, std::max(1, c));
if (g > 1) {
for (int &c : counts)
c = std::max(1, c / g);
}
int cycle = std::accumulate(counts.begin(), counts.end(), 0);
constexpr int k_max_cycle = 48;
if (cycle > k_max_cycle) {
const double scale = double(k_max_cycle) / double(cycle);
for (int &c : counts)
c = std::max(1, int(std::round(double(c) * scale)));
cycle = std::accumulate(counts.begin(), counts.end(), 0);
while (cycle > k_max_cycle) {
auto it = std::max_element(counts.begin(), counts.end());
if (it == counts.end() || *it <= 1)
break;
--(*it);
--cycle;
}
}
if (cycle <= 0)
return {};
std::vector<unsigned int> sequence;
sequence.reserve(size_t(cycle));
std::vector<int> emitted(counts.size(), 0);
for (int pos = 0; pos < cycle; ++pos) {
size_t best_idx = 0;
double best_score = -1e9;
for (size_t i = 0; i < counts.size(); ++i) {
const double target = double((pos + 1) * counts[i]) / double(cycle);
const double score = target - double(emitted[i]);
if (score > best_score) {
best_score = score;
best_idx = i;
}
}
++emitted[best_idx];
sequence.emplace_back(filtered_ids[best_idx]);
}
return sequence;
}
static std::vector<unsigned int> pointillism_sequence_for_row(const MixedFilament &mf, size_t num_physical)
{
if (!mf.enabled || num_physical == 0)
return {};
if (mf.distribution_mode != int(MixedFilament::SameLayerPointillisme))
return {};
if (!mf.manual_pattern.empty())
return decode_manual_pattern_sequence(mf, num_physical);
const std::vector<unsigned int> selected_gradient_ids = decode_gradient_component_ids(mf, num_physical);
if (selected_gradient_ids.size() >= 2) {
const std::vector<int> selected_gradient_weights = decode_gradient_component_weights(mf, selected_gradient_ids.size());
const std::vector<unsigned int> weighted_sequence =
build_weighted_gradient_sequence(selected_gradient_ids,
selected_gradient_weights.empty() ? std::vector<int>(selected_gradient_ids.size(), 1) : selected_gradient_weights);
if (!weighted_sequence.empty())
return weighted_sequence;
}
if (mf.component_a < 1 || mf.component_a > num_physical ||
mf.component_b < 1 || mf.component_b > num_physical ||
mf.component_a == mf.component_b)
return {};
int ratio_a = std::max(0, mf.ratio_a);
int ratio_b = std::max(0, mf.ratio_b);
if (ratio_a == 0 && ratio_b == 0)
ratio_a = 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;
}
}
constexpr int k_max_cycle = 24;
if (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)));
}
const int cycle = std::max(1, ratio_a + ratio_b);
std::vector<unsigned int> sequence;
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) ? mf.component_b : mf.component_a);
}
bool seen_a = false;
bool seen_b = false;
for (const unsigned int extruder_id : sequence) {
seen_a = seen_a || (extruder_id == mf.component_a);
seen_b = seen_b || (extruder_id == mf.component_b);
if (seen_a && seen_b)
break;
}
if (!seen_a || !seen_b)
return {};
return sequence;
}
static size_t unique_extruder_count(const std::vector<unsigned int> &sequence, size_t num_physical)
{
if (sequence.empty() || num_physical == 0)
return 0;
std::vector<bool> seen(num_physical + 1, false);
size_t unique_count = 0;
for (const unsigned int extruder_id : sequence) {
if (extruder_id == 0 || extruder_id > num_physical)
continue;
if (!seen[extruder_id]) {
seen[extruder_id] = true;
++unique_count;
}
}
return unique_count;
}
static bool split_masks_pointillism_stripes(const ExPolygons &source_masks,
const std::vector<unsigned int> &sequence,
size_t num_physical,
size_t layer_id,
coord_t stripe_pitch,
bool flip_orientation,
std::vector<ExPolygons> &out_by_extruder)
{
if (source_masks.empty() || sequence.empty() || num_physical == 0 || stripe_pitch <= 0)
return false;
const BoundingBox bbox = get_extents(source_masks);
if (!bbox.defined || bbox.min.x() >= bbox.max.x() || bbox.min.y() >= bbox.max.y())
return false;
out_by_extruder.assign(num_physical, ExPolygons());
const size_t slot_count = sequence.size();
const size_t phase = slot_count > 0 ? (layer_id % slot_count) : 0;
auto align_down_to_grid = [stripe_pitch](coord_t value) {
coord_t rem = value % stripe_pitch;
if (rem < 0)
rem += stripe_pitch;
return value - rem;
};
std::vector<Polygons> stripe_polygons_by_slot(slot_count);
const bool vertical_base = (bbox.max.x() - bbox.min.x()) >= (bbox.max.y() - bbox.min.y());
// Alternate stripe orientation every layer so different faces of the model
// receive mixed-color variation instead of long single-direction bands.
const bool layer_alternates = (layer_id & 1) != 0;
bool vertical = layer_alternates ? !vertical_base : vertical_base;
if (flip_orientation)
vertical = !vertical;
if (vertical) {
const coord_t y0 = bbox.min.y();
const coord_t y1 = bbox.max.y();
const coord_t x_start_aligned = align_down_to_grid(bbox.min.x());
size_t stripe_idx = 0;
for (coord_t x = x_start_aligned; x < bbox.max.x(); x += stripe_pitch, ++stripe_idx) {
const coord_t x0 = std::max(x, bbox.min.x());
const coord_t x1 = std::min<coord_t>(x + stripe_pitch, bbox.max.x());
if (x1 <= x0)
continue;
const size_t slot = (stripe_idx + phase) % slot_count;
stripe_polygons_by_slot[slot].emplace_back(BoundingBox(Point(x0, y0), Point(x1, y1)).polygon());
}
} else {
const coord_t x0 = bbox.min.x();
const coord_t x1 = bbox.max.x();
const coord_t y_start_aligned = align_down_to_grid(bbox.min.y());
size_t stripe_idx = 0;
for (coord_t y = y_start_aligned; y < bbox.max.y(); y += stripe_pitch, ++stripe_idx) {
const coord_t y0 = std::max(y, bbox.min.y());
const coord_t y1 = std::min<coord_t>(y + stripe_pitch, bbox.max.y());
if (y1 <= y0)
continue;
const size_t slot = (stripe_idx + phase) % slot_count;
stripe_polygons_by_slot[slot].emplace_back(BoundingBox(Point(x0, y0), Point(x1, y1)).polygon());
}
}
unsigned int fallback_extruder = 0;
for (const unsigned int extruder_id : sequence) {
if (extruder_id >= 1 && extruder_id <= num_physical) {
fallback_extruder = extruder_id;
break;
}
}
if (fallback_extruder == 0)
return false;
for (size_t slot = 0; slot < slot_count; ++slot) {
const unsigned int extruder_id = sequence[slot];
if (extruder_id == 0 || extruder_id > num_physical || stripe_polygons_by_slot[slot].empty())
continue;
ExPolygons clipped = intersection_ex(source_masks, stripe_polygons_by_slot[slot], ApplySafetyOffset::Yes);
if (!clipped.empty())
append(out_by_extruder[extruder_id - 1], std::move(clipped));
}
ExPolygons assigned_union;
for (ExPolygons &masks : out_by_extruder) {
if (masks.size() > 1)
masks = union_ex(masks);
append(assigned_union, masks);
}
if (assigned_union.empty()) {
append(out_by_extruder[fallback_extruder - 1], source_masks);
return true;
}
if (assigned_union.size() > 1)
assigned_union = union_ex(assigned_union);
ExPolygons remainder = diff_ex(source_masks, assigned_union, ApplySafetyOffset::Yes);
if (!remainder.empty()) {
append(out_by_extruder[fallback_extruder - 1], std::move(remainder));
ExPolygons &fallback_masks = out_by_extruder[fallback_extruder - 1];
if (fallback_masks.size() > 1)
fallback_masks = union_ex(fallback_masks);
}
return true;
}
static size_t non_empty_mask_count(const std::vector<ExPolygons> &masks_by_extruder)
{
size_t count = 0;
for (const ExPolygons &masks : masks_by_extruder)
if (!masks.empty())
++count;
return count;
}
template<typename ThrowOnCancel>
static bool apply_pointillism_mixed_segmentation(PrintObject &print_object, std::vector<std::vector<ExPolygons>> &segmentation, ThrowOnCancel throw_on_cancel)
{
const Print *print = print_object.print();
if (print == nullptr || segmentation.empty())
return false;
const PrintConfig &print_cfg = print->config();
const size_t num_physical = print_cfg.filament_colour.size();
if (num_physical < 2)
return false;
const MixedFilamentManager &mixed_mgr = print->mixed_filament_manager();
const auto &mixed_rows = mixed_mgr.mixed_filaments();
if (mixed_rows.empty())
return false;
const size_t num_channels = segmentation.front().size();
if (num_channels <= num_physical)
return false;
const double nozzle = print_cfg.nozzle_diameter.values.empty() ? 0.4 : print_cfg.nozzle_diameter.get_at(0);
// Keep stripe width at or above roughly one printable line to avoid
// non-printable slivers that can get dropped later and create holes.
const double stripe_pitch_mm = std::max(0.25, 1.10 * nozzle);
const coord_t stripe_pitch = std::max<coord_t>(scale_(0.25), scale_(stripe_pitch_mm));
std::vector<std::vector<unsigned int>> same_layer_sequences(mixed_rows.size());
std::vector<bool> same_layer_row_active(mixed_rows.size(), false);
std::vector<size_t> same_layer_row_indices;
for (size_t mixed_idx = 0; mixed_idx < mixed_rows.size(); ++mixed_idx) {
const MixedFilament &mf = mixed_rows[mixed_idx];
if (!mf.enabled || mf.distribution_mode != int(MixedFilament::SameLayerPointillisme))
continue;
same_layer_sequences[mixed_idx] = pointillism_sequence_for_row(mf, num_physical);
if (unique_extruder_count(same_layer_sequences[mixed_idx], num_physical) >= 2) {
same_layer_row_active[mixed_idx] = true;
same_layer_row_indices.emplace_back(mixed_idx);
}
}
auto find_sequence_override = [&](size_t mixed_idx) -> const std::vector<unsigned int> * {
if (mixed_idx >= mixed_rows.size())
return nullptr;
if (same_layer_row_active[mixed_idx])
return &same_layer_sequences[mixed_idx];
const MixedFilament &src = mixed_rows[mixed_idx];
for (size_t idx : same_layer_row_indices) {
if (idx >= mixed_rows.size())
continue;
const MixedFilament &candidate = mixed_rows[idx];
if ((candidate.component_a == src.component_a && candidate.component_b == src.component_b) ||
(candidate.component_a == src.component_b && candidate.component_b == src.component_a))
return &same_layer_sequences[idx];
}
if (same_layer_row_indices.size() == 1)
return &same_layer_sequences[same_layer_row_indices.front()];
return nullptr;
};
size_t same_layer_rows = 0;
for (size_t mixed_idx = 0; mixed_idx < mixed_rows.size(); ++mixed_idx) {
const MixedFilament &mf = mixed_rows[mixed_idx];
if (!same_layer_row_active[mixed_idx])
continue;
const std::vector<unsigned int> &seq = same_layer_sequences[mixed_idx];
const size_t unique = unique_extruder_count(seq, num_physical);
BOOST_LOG_TRIVIAL(debug) << "Same-layer pointillisme row"
<< " mixed_idx=" << mixed_idx
<< " component_a=" << mf.component_a
<< " component_b=" << mf.component_b
<< " mix_b_percent=" << mf.mix_b_percent
<< " manual_pattern_len=" << mf.manual_pattern.size()
<< " gradient_components=" << mf.gradient_component_ids
<< " sequence_len=" << seq.size()
<< " unique_extruders=" << unique;
if (unique >= 2)
++same_layer_rows;
}
size_t transformed_layers = 0;
size_t transformed_states = 0;
size_t transformed_masks = 0;
size_t skipped_states = 0;
size_t retried_states = 0;
size_t weak_split_states = 0;
size_t pair_override_states = 0;
size_t global_override_states = 0;
for (size_t layer_id = 0; layer_id < segmentation.size(); ++layer_id) {
throw_on_cancel();
if (segmentation[layer_id].size() != num_channels) {
++skipped_states;
continue;
}
bool layer_transformed = false;
std::vector<bool> touched_physical(num_physical, false);
for (size_t channel_idx = num_physical; channel_idx < num_channels; ++channel_idx) {
ExPolygons &state_masks = segmentation[layer_id][channel_idx];
if (state_masks.empty())
continue;
const unsigned int state_id = unsigned(channel_idx + 1);
const int mixed_idx = mixed_mgr.mixed_index_from_filament_id(state_id, num_physical);
if (mixed_idx < 0 || size_t(mixed_idx) >= mixed_rows.size()) {
++skipped_states;
continue;
}
const MixedFilament &mf = mixed_rows[size_t(mixed_idx)];
const std::vector<unsigned int> *sequence_ptr = find_sequence_override(size_t(mixed_idx));
if (sequence_ptr == nullptr || sequence_ptr->empty() || unique_extruder_count(*sequence_ptr, num_physical) < 2) {
++skipped_states;
continue;
}
if (!same_layer_row_active[size_t(mixed_idx)]) {
bool pair_match = false;
for (size_t idx : same_layer_row_indices) {
const MixedFilament &candidate = mixed_rows[idx];
if ((candidate.component_a == mf.component_a && candidate.component_b == mf.component_b) ||
(candidate.component_a == mf.component_b && candidate.component_b == mf.component_a)) {
pair_match = true;
break;
}
}
if (pair_match)
++pair_override_states;
else if (same_layer_row_indices.size() == 1)
++global_override_states;
}
std::vector<ExPolygons> split_by_extruder;
if (!split_masks_pointillism_stripes(state_masks, *sequence_ptr, num_physical, layer_id, stripe_pitch, false, split_by_extruder)) {
++skipped_states;
continue;
}
size_t split_unique = non_empty_mask_count(split_by_extruder);
if (split_unique < 2) {
std::vector<ExPolygons> retry_split;
if (split_masks_pointillism_stripes(state_masks, *sequence_ptr, num_physical, layer_id, stripe_pitch, true, retry_split)) {
const size_t retry_unique = non_empty_mask_count(retry_split);
if (retry_unique > split_unique) {
split_by_extruder = std::move(retry_split);
split_unique = retry_unique;
}
++retried_states;
}
}
if (split_unique < 2)
++weak_split_states;
for (size_t extruder_idx = 0; extruder_idx < num_physical; ++extruder_idx) {
if (split_by_extruder[extruder_idx].empty())
continue;
append(segmentation[layer_id][extruder_idx], std::move(split_by_extruder[extruder_idx]));
touched_physical[extruder_idx] = true;
}
transformed_masks += state_masks.size();
state_masks.clear();
layer_transformed = true;
++transformed_states;
}
if (layer_transformed) {
++transformed_layers;
for (size_t extruder_idx = 0; extruder_idx < num_physical; ++extruder_idx) {
if (!touched_physical[extruder_idx] || segmentation[layer_id][extruder_idx].size() <= 1)
continue;
segmentation[layer_id][extruder_idx] = union_ex(segmentation[layer_id][extruder_idx]);
}
}
}
if (transformed_states > 0) {
BOOST_LOG_TRIVIAL(warning) << "Mixed interleaved-stripe segmentation applied"
<< " object=" << (print_object.model_object() ? print_object.model_object()->name : std::string("<unknown>"))
<< " same_layer_rows=" << same_layer_rows
<< " transformed_layers=" << transformed_layers
<< " transformed_states=" << transformed_states
<< " transformed_masks=" << transformed_masks
<< " retried_states=" << retried_states
<< " weak_split_states=" << weak_split_states
<< " pair_override_states=" << pair_override_states
<< " global_override_states=" << global_override_states
<< " stripe_pitch_mm=" << stripe_pitch_mm
<< " skipped_states=" << skipped_states;
return true;
}
if (same_layer_rows > 0) {
BOOST_LOG_TRIVIAL(warning) << "Same-layer pointillisme requested but produced no transformed states"
<< " object=" << (print_object.model_object() ? print_object.model_object()->name : std::string("<unknown>"))
<< " same_layer_rows=" << same_layer_rows
<< " stripe_pitch_mm=" << stripe_pitch_mm
<< " skipped_states=" << skipped_states;
}
return false;
}
static ExPolygons collect_layer_region_slices(const Layer &layer)
{
ExPolygons out;
@@ -1190,6 +1739,22 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
return;
}
const MixedFilamentManager &mixed_mgr = print->mixed_filament_manager();
const auto &mixed_rows = mixed_mgr.mixed_filaments();
size_t pointillism_rows = 0;
for (const MixedFilament &mf : mixed_rows) {
const std::vector<unsigned int> sequence = pointillism_sequence_for_row(mf, num_physical);
if (unique_extruder_count(sequence, num_physical) >= 2)
++pointillism_rows;
}
if (pointillism_rows > 0) {
BOOST_LOG_TRIVIAL(warning) << "Local-Z plan skipped: interleaved stripe mixed pattern active"
<< " object=" << object_name
<< " interleaved_rows=" << pointillism_rows;
return;
}
BOOST_LOG_TRIVIAL(debug) << "Local-Z plan start"
<< " object=" << object_name
<< " layers=" << print_object.layer_count()
@@ -1199,7 +1764,6 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
<< " preferred_b=" << preferred_b
<< " physical_filaments=" << num_physical;
const MixedFilamentManager &mixed_mgr = print->mixed_filament_manager();
std::vector<LocalZInterval> intervals;
std::vector<SubLayerPlan> plans;
intervals.reserve(print_object.layer_count());
@@ -1223,7 +1787,6 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
double locked_gradient_h_a = 0.0;
double locked_gradient_h_b = 0.0;
bool locked_gradient_valid = false;
const auto &mixed_rows = mixed_mgr.mixed_filaments();
int cadence_index = 0;
for (size_t layer_id = 0; layer_id < print_object.layer_count(); ++layer_id) {
@@ -1257,7 +1820,8 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
const double mixed_area = std::abs(area(state_masks));
if (mixed_area > dominant_mixed_area) {
dominant_mixed_area = mixed_area;
dominant_mixed_idx = state_id > num_physical ? size_t(state_id - num_physical - 1) : size_t(-1);
const int resolved_mixed_idx = mixed_mgr.mixed_index_from_filament_id(state_id, num_physical);
dominant_mixed_idx = resolved_mixed_idx >= 0 ? size_t(resolved_mixed_idx) : size_t(-1);
}
}
}
@@ -1360,12 +1924,12 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
if (!mixed_mgr.is_mixed(state_id, num_physical))
continue;
++forced_height_resolve_calls;
const size_t mixed_idx = state_id > num_physical ? size_t(state_id - num_physical - 1) : size_t(-1);
if (mixed_idx >= mixed_rows.size() || !mixed_rows[mixed_idx].custom)
const int mixed_idx = mixed_mgr.mixed_index_from_filament_id(state_id, num_physical);
if (mixed_idx < 0 || size_t(mixed_idx) >= mixed_rows.size() || !mixed_rows[size_t(mixed_idx)].custom)
++forced_height_resolve_non_custom_calls;
unsigned int target_extruder = 0;
if (mixed_idx < mixed_rows.size()) {
const MixedFilament &mf = mixed_rows[mixed_idx];
if (mixed_idx >= 0 && size_t(mixed_idx) < mixed_rows.size()) {
const MixedFilament &mf = mixed_rows[size_t(mixed_idx)];
if (mf.component_a > 0 && mf.component_a <= num_physical &&
mf.component_b > 0 && mf.component_b <= num_physical) {
// Enforce strict per-pass alternation inside split local-Z intervals.
@@ -1426,8 +1990,8 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
if (!mixed_mgr.is_mixed(state_id, num_physical))
continue;
++forced_height_resolve_calls;
const size_t mixed_idx = state_id > num_physical ? size_t(state_id - num_physical - 1) : size_t(-1);
if (mixed_idx >= mixed_rows.size() || !mixed_rows[mixed_idx].custom)
const int mixed_idx = mixed_mgr.mixed_index_from_filament_id(state_id, num_physical);
if (mixed_idx < 0 || size_t(mixed_idx) >= mixed_rows.size() || !mixed_rows[size_t(mixed_idx)].custom)
++forced_height_resolve_non_custom_calls;
const unsigned int target_extruder =
mixed_mgr.resolve(state_id, num_physical, cadence_index, float(plan.print_z), float(plan.flow_height), true);
@@ -1540,6 +2104,8 @@ static inline void apply_mm_segmentation(PrintObject &print_object, std::vector<
by_extruder.assign(num_extruders, ByExtruder());
by_region.assign(layer.region_count(), ByRegion());
bool layer_split = false;
size_t missing_target_regions = 0;
std::vector<int> missing_target_extruders;
for (size_t extruder_id = 0; extruder_id < num_extruders; ++ extruder_id) {
ByExtruder &region = by_extruder[extruder_id];
append(region.expolygons, std::move(segmentation[layer_id][extruder_id]));
@@ -1581,28 +2147,42 @@ static inline void apply_mm_segmentation(PrintObject &print_object, std::vector<
const BoundingBox parent_layer_region_bbox = get_extents(parent_layer_region.slices.surfaces);
bool self_trimmed = false;
int self_extruder_id = -1; // 1-based extruder ID
if (const int cfg_wall = parent_print_region.config().wall_filament.value;
cfg_wall >= 1 && cfg_wall <= int(by_extruder.size()))
self_extruder_id = cfg_wall;
std::vector<bool> assigned_extruder(by_extruder.size(), false);
std::vector<int> alias_to_self_extruders;
for (int extruder_id = 1; extruder_id <= int(by_extruder.size()); ++extruder_id) {
const ByExtruder &segmented = by_extruder[extruder_id - 1];
if (!segmented.bbox.defined || !parent_layer_region_bbox.overlap(segmented.bbox))
continue;
// Find the first target region iterator.
auto it_target_region = std::find_if(it_painted_region_begin, layer_range.painted_regions.cend(), [extruder_id](const auto &painted_region) {
return int(painted_region.extruder_id) >= extruder_id;
// Find the matching target region for this parent and extruder ID.
auto it_target_region = std::find_if(it_painted_region_begin, layer_range.painted_regions.cend(), [&layer_range, &parent_print_region, extruder_id](const auto &painted_region) {
return layer_range.volume_regions[painted_region.parent].region == &parent_print_region &&
int(painted_region.extruder_id) == extruder_id;
});
assert(it_target_region != layer_range.painted_regions.end());
assert(layer_range.volume_regions[it_target_region->parent].region == &parent_print_region && int(it_target_region->extruder_id) == extruder_id);
if (it_target_region == layer_range.painted_regions.cend()) {
++missing_target_regions;
missing_target_extruders.emplace_back(extruder_id);
continue;
}
// Update the beginning PaintedRegion iterator for the next iteration.
it_painted_region_begin = it_target_region;
// FIXME: Don't trim by self, it is not reliable.
if (it_target_region->region == &parent_print_region) {
self_extruder_id = extruder_id;
if (self_extruder_id < 0)
self_extruder_id = extruder_id;
if (extruder_id != self_extruder_id)
alias_to_self_extruders.emplace_back(extruder_id);
continue;
}
assigned_extruder[size_t(extruder_id - 1)] = true;
// Steal from this region.
int target_region_id = it_target_region->region->print_object_region_id();
ExPolygons stolen = intersection_ex(parent_layer_region.slices.surfaces, segmented.expolygons);
@@ -1620,8 +2200,11 @@ static inline void apply_mm_segmentation(PrintObject &print_object, std::vector<
if (!self_trimmed) {
// Trim slices of this LayerRegion with all the MM regions.
Polygons mine = to_polygons(parent_layer_region.slices.surfaces);
for (auto &segmented : by_extruder) {
if (&segmented - by_extruder.data() + 1 != self_extruder_id && segmented.bbox.defined && parent_layer_region_bbox.overlap(segmented.bbox)) {
for (size_t extruder_idx = 0; extruder_idx < by_extruder.size(); ++extruder_idx) {
const ByExtruder &segmented = by_extruder[extruder_idx];
if (!assigned_extruder[extruder_idx])
continue;
if (int(extruder_idx + 1) != self_extruder_id && segmented.bbox.defined && parent_layer_region_bbox.overlap(segmented.bbox)) {
mine = diff(mine, segmented.expolygons);
if (mine.empty())
break;
@@ -1647,6 +2230,41 @@ static inline void apply_mm_segmentation(PrintObject &print_object, std::vector<
}
}
}
if (!alias_to_self_extruders.empty()) {
std::sort(alias_to_self_extruders.begin(), alias_to_self_extruders.end());
alias_to_self_extruders.erase(std::unique(alias_to_self_extruders.begin(), alias_to_self_extruders.end()), alias_to_self_extruders.end());
std::string alias_ids;
for (size_t i = 0; i < alias_to_self_extruders.size(); ++i) {
if (i > 0)
alias_ids += ",";
alias_ids += std::to_string(alias_to_self_extruders[i]);
}
BOOST_LOG_TRIVIAL(warning) << "MM segmentation alias-to-parent channels ignored"
<< " object=" << (print_object.model_object() ? print_object.model_object()->name : std::string("<unknown>"))
<< " layer_id=" << layer_id
<< " parent_region_id=" << parent_print_region.print_object_region_id()
<< " self_extruder_id=" << self_extruder_id
<< " alias_extruders=[" << alias_ids << "]";
}
}
if (missing_target_regions > 0) {
std::sort(missing_target_extruders.begin(), missing_target_extruders.end());
missing_target_extruders.erase(std::unique(missing_target_extruders.begin(), missing_target_extruders.end()), missing_target_extruders.end());
std::string missing_ids;
for (size_t i = 0; i < missing_target_extruders.size(); ++i) {
if (i > 0)
missing_ids += ",";
missing_ids += std::to_string(missing_target_extruders[i]);
}
BOOST_LOG_TRIVIAL(warning) << "MM segmentation missing painted target regions"
<< " object=" << (print_object.model_object() ? print_object.model_object()->name : std::string("<unknown>"))
<< " layer_id=" << layer_id
<< " missing_targets=" << missing_target_regions
<< " missing_extruders=[" << missing_ids << "]"
<< " segmentation_channels=" << num_extruders
<< " painted_regions=" << layer_range.painted_regions.size();
}
// Re-create Surfaces of LayerRegions.
@@ -1856,6 +2474,9 @@ void PrintObject::slice_volumes()
BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - MMU segmentation";
std::vector<std::vector<ExPolygons>> mm_segmentation = multi_material_segmentation_by_painting(*this, [print]() { print->throw_if_canceled(); });
// Same-layer pointillisme is applied in G-code path domain (segment-level assignment),
// not by XY state mask splitting, to avoid boolean-induced voids.
BOOST_LOG_TRIVIAL(info) << "Same-layer pointillisme uses path-domain G-code segmentation";
build_local_z_plan(*this, mm_segmentation, [print]() { print->throw_if_canceled(); });
apply_mm_segmentation(*this, std::move(mm_segmentation), [print]() { print->throw_if_canceled(); });
}