BBL Port Color Mix Base

This commit is contained in:
Ian Bassi
2026-08-23 22:11:48 +08:00
committed by SoftFever
parent 550e234a37
commit 8fea099d99
75 changed files with 34174 additions and 51 deletions
+122 -9
View File
@@ -1,6 +1,7 @@
#include "ClipperUtils.hpp"
#include "Model.hpp"
#include "Print.hpp"
#include "FilamentMixer.hpp"
#include <boost/log/trivial.hpp>
#include <cfloat>
@@ -886,7 +887,12 @@ bool verify_update_print_object_regions(
size_t hash = regions[i]->config_hash();
size_t j = i;
for (++ j; j < regions.size() && regions[j]->config_hash() == hash; ++ j)
if (regions[i]->config() == regions[j]->config()) {
// Same config but different gradient_volume_id is intentional (per-part gradient
// splitting) and must NOT be flagged as a merge. When per-part is off all regions
// carry an invalid (default) gradient_volume_id, so the AND condition is always
// true and behavior matches the legacy check.
if (regions[i]->config() == regions[j]->config()
&& regions[i]->gradient_volume_id() == regions[j]->gradient_volume_id()) {
// Regions were merged. We need to reslice.
return false;
}
@@ -978,7 +984,10 @@ static PrintObjectRegions* generate_print_object_regions(
const float xy_contour_compensation,
const std::vector<unsigned int> &painting_extruders,
std::vector<int> &variant_index,
const bool has_painted_fuzzy_skin)
const bool has_painted_fuzzy_skin,
// Per-part gradient: slot_per_part_enabled[s-1] is true when mixed slot s has
// filament_mixed_gradient_per_part on. Empty / all-false preserves legacy behavior.
const std::vector<bool> &slot_per_part_enabled = {})
{
// Reuse the old object or generate a new one.
auto out = print_object_regions_old ? std::unique_ptr<PrintObjectRegions>(print_object_regions_old) : std::make_unique<PrintObjectRegions>();
@@ -1013,19 +1022,71 @@ static PrintObjectRegions* generate_print_object_regions(
update_volume_bboxes(layer_ranges_regions, out->cached_volume_ids, model_volumes, out->trafo_bboxes, is_mm_painted ? 0.f : std::max(0.f, xy_contour_compensation));
std::vector<PrintRegion*> region_set;
auto get_create_region = [&region_set, &all_regions](PrintRegionConfig &&config) -> PrintRegion* {
// Look up or create a PrintRegion. The optional volume_tag, when valid (non-zero ObjectID),
// keys the region to one ModelVolume so two volumes with identical settings still get
// separate regions — needed so each part can run its own gradient. A default (invalid)
// tag reproduces the previous lookup exactly.
auto get_create_region = [&region_set, &all_regions](PrintRegionConfig &&config, ObjectID volume_tag = ObjectID()) -> PrintRegion* {
size_t hash = config.hash();
auto it = Slic3r::lower_bound_by_predicate(region_set.begin(), region_set.end(), [&config, hash](const PrintRegion* l) {
return l->config_hash() < hash || (l->config_hash() == hash && l->config() < config); });
if (it != region_set.end() && (*it)->config_hash() == hash && (*it)->config() == config)
auto it = Slic3r::lower_bound_by_predicate(region_set.begin(), region_set.end(), [&config, hash, volume_tag](const PrintRegion* l) {
return l->config_hash() < hash || (l->config_hash() == hash && l->config() < config)
|| (l->config_hash() == hash && l->config() == config && l->gradient_volume_id() < volume_tag); });
if (it != region_set.end() && (*it)->config_hash() == hash && (*it)->config() == config
&& (*it)->gradient_volume_id() == volume_tag)
return *it;
// Insert into a sorted array, it has O(n) complexity, but the calling algorithm has an O(n^2*log(n)) complexity anyways.
all_regions.emplace_back(std::make_unique<PrintRegion>(std::move(config), hash, int(all_regions.size())));
all_regions.emplace_back(std::make_unique<PrintRegion>(std::move(config), hash, int(all_regions.size()), volume_tag));
PrintRegion *region = all_regions.back().get();
region_set.emplace(it, region);
return region;
};
// Per-part gradient: count how many model-part volumes in this object use each
// per-part-enabled gradient slot. Only slots with at least 2 users get their volumes
// tagged — a single-user slot gains nothing from per-volume splitting and would only
// inflate the region count. Empty slot_per_part_enabled leaves this empty, so
// compute_volume_tag below always returns an invalid tag and nothing changes.
std::vector<int> per_part_volume_users;
if (!slot_per_part_enabled.empty()) {
per_part_volume_users.assign(slot_per_part_enabled.size(), 0);
for (const ModelVolume *mv : model_volumes) {
if (! mv->is_model_part())
continue;
const DynamicPrintConfig *range_cfg = layer_ranges_regions.empty() ? nullptr : layer_ranges_regions.front().config;
PrintRegionConfig vol_cfg = region_config_from_model_volume(default_region_config, range_cfg, *mv, num_extruders, variant_index);
for (unsigned int s_1based : { (unsigned int)vol_cfg.outer_wall_filament_id.value,
(unsigned int)vol_cfg.inner_wall_filament_id.value,
(unsigned int)vol_cfg.sparse_infill_filament_id.value,
(unsigned int)vol_cfg.internal_solid_filament_id.value,
(unsigned int)vol_cfg.top_surface_filament_id.value,
(unsigned int)vol_cfg.bottom_surface_filament_id.value }) {
if (s_1based >= 1
&& size_t(s_1based - 1) < slot_per_part_enabled.size()
&& slot_per_part_enabled[s_1based - 1])
++per_part_volume_users[s_1based - 1];
}
}
}
auto compute_volume_tag = [&](const PrintRegionConfig &cfg, const ModelVolume &mv) -> ObjectID {
if (per_part_volume_users.empty())
return ObjectID();
auto qualifies = [&](unsigned int s_1based) {
return s_1based >= 1
&& size_t(s_1based - 1) < slot_per_part_enabled.size()
&& slot_per_part_enabled[s_1based - 1]
&& per_part_volume_users[s_1based - 1] >= 2;
};
if (qualifies((unsigned int)cfg.outer_wall_filament_id.value)
|| qualifies((unsigned int)cfg.inner_wall_filament_id.value)
|| qualifies((unsigned int)cfg.sparse_infill_filament_id.value)
|| qualifies((unsigned int)cfg.internal_solid_filament_id.value)
|| qualifies((unsigned int)cfg.top_surface_filament_id.value)
|| qualifies((unsigned int)cfg.bottom_surface_filament_id.value)) {
return mv.id();
}
return ObjectID();
};
// Chain the regions in the order they are stored in the volumes list.
for (int volume_id = 0; volume_id < int(model_volumes.size()); ++ volume_id) {
const ModelVolume &volume = *model_volumes[volume_id];
@@ -1034,9 +1095,11 @@ static PrintObjectRegions* generate_print_object_regions(
if (const PrintObjectRegions::BoundingBox *bbox = find_volume_extents(layer_range, volume); bbox) {
if (volume.is_model_part()) {
// Add a model volume, assign an existing region or generate a new one.
PrintRegionConfig vol_cfg = region_config_from_model_volume(default_region_config, layer_range.config, volume, num_extruders, variant_index);
ObjectID volume_tag = compute_volume_tag(vol_cfg, volume);
layer_range.volume_regions.push_back({
&volume, -1,
get_create_region(region_config_from_model_volume(default_region_config, layer_range.config, volume, num_extruders, variant_index)),
get_create_region(std::move(vol_cfg), volume_tag),
bbox
});
} else if (volume.is_negative_volume()) {
@@ -1121,6 +1184,12 @@ static PrintObjectRegions* generate_print_object_regions(
}
}
// Save the slot_per_part_enabled bit vector that produced these regions, so the guard in
// Print::apply can detect changes on the next call even when PrintRegionConfig did not
// change. Always written — including an empty vector — so the snapshot always reflects
// the exact input used to generate the current regions.
out->last_slot_per_part_enabled = slot_per_part_enabled;
return out.release();
}
@@ -1141,6 +1210,17 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
std::vector <unsigned int> used_filaments = this->extruders(true);
std::unordered_set <unsigned int> used_filament_set(used_filaments.begin(), used_filaments.end());
// A mixed slot is virtual: the filaments actually consumed are its components, so add them
// to the used set or they would be treated as unused and stripped from the config.
{
auto* is_mixed_opt = new_full_config.option<ConfigOptionBools>("filament_is_mixed");
auto* comp_strs_opt = new_full_config.option<ConfigOptionStrings>("filament_mixed_components");
if (is_mixed_opt && comp_strs_opt && has_any_mixed_filament(is_mixed_opt->values)) {
auto expanded = expand_mixed_filaments(used_filaments, is_mixed_opt->values, comp_strs_opt->values);
used_filament_set.insert(expanded.begin(), expanded.end());
}
}
//new_full_config.normalize_fdm(used_filaments);
new_full_config.normalize_fdm_1();
t_config_option_keys changed_keys = new_full_config.normalize_fdm_2(objects().size(), used_filaments.size());
@@ -1802,6 +1882,29 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
update_filament_self_index_cache();
}
// Per-part gradient: compute the per-slot enable bit vector once for this Print::apply pass.
// Used by generate_print_object_regions to decide which volumes deserve their own PrintRegion.
std::vector<bool> slot_per_part_enabled;
{
const auto &is_mixed_vec = m_config.filament_is_mixed.values;
const auto &grad_vec = m_config.filament_mixed_gradient.values;
const auto &per_part_vec = m_config.filament_mixed_gradient_per_part.values;
const auto &components_vec = m_config.filament_mixed_components.values;
slot_per_part_enabled.assign(is_mixed_vec.size(), false);
for (size_t i = 0; i < is_mixed_vec.size(); ++i) {
if (! is_mixed_vec[i])
continue;
std::vector<unsigned int> comps = parse_mixed_components(i < components_vec.size() ? components_vec[i] : "");
if (comps.size() != 2)
continue;
if (i >= grad_vec.size() || ! grad_vec[i])
continue;
if (i >= per_part_vec.size() || ! per_part_vec[i])
continue;
slot_per_part_enabled[i] = true;
}
}
// All regions now have distinct settings.
// Check whether applying the new region config defaults we would get different regions,
// update regions or create regions from scratch.
@@ -1862,6 +1965,15 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
update_apply_status((*it)->invalidate_state_by_config_options(old_config, new_config, diff_keys));
},
print_variant_index)) {
// Per-part gradient: PrintRegionConfig alone cannot reveal a change in which slots
// have per-part enabled, so compare against the snapshot taken when these regions
// were generated and regenerate on any difference (slot toggled, per-part moved
// between slots, eligibility changed via components / gradient / is_mixed).
if (print_object_regions->last_slot_per_part_enabled != slot_per_part_enabled) {
invalidate();
model_object_status.print_object_regions_status = ModelObjectStatus::PrintObjectRegionsStatus::PartiallyValid;
print_regions_reshuffled = true;
}
// Regions are valid, just keep them.
} else {
// Regions were reshuffled.
@@ -1884,7 +1996,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
print_object.is_mm_painted() ? 0.f : float(print_object.config().xy_contour_compensation.value),
painting_extruders,
print_variant_index,
print_object.is_fuzzy_skin_painted());
print_object.is_fuzzy_skin_painted(),
slot_per_part_enabled);
}
for (auto it = it_print_object; it != it_print_object_end; ++it)
if ((*it)->m_shared_regions) {