Update from FS at b3c41fda4.

Slicing
  - align merge_segmented_layers shape with FS apply_mm_segmentation
    (size = num_facets_states, loop from 0, no -1 shift); painted mixed
    regions were previously attributed to filament_id-1 of intent.
    apply_fuzzy_skin_segmentation reads channel 1;
    apply_mixed_surface_indentation uses segmentation_channel_filament_id
  - port apply_mixed_surface_indentation, apply_mixed_component_surface_offsets,
    apply_mixed_region_surface_offsets, apply_surface_emboss_mixed_region_override,
    plus surface_emboss_mixed_* debug subsystem
  - refactor apply_mm_segmentation (by-value MM, bias_mode, surface-type-
    preserving intersection, region normalization, post-MM dump); hoist MM
    segmentation into slice_volumes so mixed apply_* flow can mutate it
  - restore clear_local_z_plan() invalidation hooks
    (PrintObject.cpp:805/1264/1286)

  GCode
  - add LayerTools::preserve_extruder_order, honored by collect_extruders,
    both reorder_extruders overloads, and
    reorder_filaments_for_minimum_flush_volume; helpers
    append_unique_preserve_order / remove_duplicates_preserve_order
  - wire MixedFilamentManager::ordered_perimeter_extruders for grouped
    manual-pattern walls; set preserve_extruder_order when >= 2
  - mixed-aware support: layer_height set for support-only layers,
    ExtrusionRole-based has_support/has_interface with erMixed short-
    circuit, support_filament / support_interface_filament routed through
    resolve_mixed

  Print
  - materialize mixed_filament_pointillism_{pixel_size,line_gap} in
    PrintApply's option-tracking block so in-session edits diff correctly

  GUI
  - Tab::on_value_change: dithering_local_z_mode cascading clears, 17-key
    project_config sync, update_mixed_filament_panel(false) on
    mixed_filament_component_bias_enabled change
  - GUI_Factories: physical_filaments_count, ui_ordered_filament_ids,
    filament_menu_item_name; filaments_count includes enabled mixed
    virtuals; right-click 'Change filament' submenus iterate UI-ordered IDs

  Tests
  - sentinel asserting MultiMaterialSegmentation uses FS-aligned shape
    (39 -> 40)
This commit is contained in:
SoftFever
2026-04-29 19:22:29 +08:00
parent 9c8caf121e
commit 103cf247e5
10 changed files with 1516 additions and 184 deletions

View File

@@ -90,6 +90,12 @@ bool has_grouped_manual_pattern(const MixedFilamentManager *mixed_mgr,
return normalized.find(',') != std::string::npos;
}
void append_unique_preserve_order(std::vector<unsigned int> &dst, unsigned int value)
{
if (std::find(dst.begin(), dst.end(), value) == dst.end())
dst.emplace_back(value);
}
bool internal_solid_infill_uses_sparse_filament(const PrintRegion &region, ExtrusionRole role)
{
return role == erSolidInfill && std::abs(region.config().sparse_infill_density.value - 100.) < EPSILON;
@@ -141,6 +147,15 @@ unsigned int grouped_manual_pattern_infill_filament_1based(const LayerTools& la
float(layer_tools.layer_height));
}
void remove_duplicates_preserve_order(std::vector<unsigned int> &values)
{
std::vector<unsigned int> ordered;
ordered.reserve(values.size());
for (unsigned int value : values)
append_unique_preserve_order(ordered, value);
values = std::move(ordered);
}
} // anonymous namespace
// ---------------------------------------------------------------------------
@@ -323,27 +338,29 @@ void ToolOrdering::handle_dontcare_extruder(const std::vector<unsigned int>& too
// Reorder the extruders of first layer
{
LayerTools& lt = m_layer_tools[0];
std::vector<unsigned int> layer0_extruders = lt.extruders;
lt.extruders.clear();
for (unsigned int extruder_id : tool_order_layer0) {
auto iter = std::find(layer0_extruders.begin(), layer0_extruders.end(), extruder_id);
if (iter != layer0_extruders.end()) {
lt.extruders.push_back(extruder_id);
*iter = (unsigned int)-1;
if (!lt.preserve_extruder_order) {
std::vector<unsigned int> layer0_extruders = lt.extruders;
lt.extruders.clear();
for (unsigned int extruder_id : tool_order_layer0) {
auto iter = std::find(layer0_extruders.begin(), layer0_extruders.end(), extruder_id);
if (iter != layer0_extruders.end()) {
lt.extruders.push_back(extruder_id);
*iter = (unsigned int)-1;
}
}
}
for (unsigned int extruder_id : layer0_extruders) {
if (extruder_id == 0)
continue;
for (unsigned int extruder_id : layer0_extruders) {
if (extruder_id == 0)
continue;
if (extruder_id != (unsigned int)-1)
lt.extruders.push_back(extruder_id);
}
if (extruder_id != (unsigned int)-1)
lt.extruders.push_back(extruder_id);
}
// all extruders are zero
if (lt.extruders.empty()) {
lt.extruders.push_back(tool_order_layer0[0]);
// all extruders are zero
if (lt.extruders.empty()) {
lt.extruders.push_back(tool_order_layer0[0]);
}
}
}
@@ -359,6 +376,10 @@ void ToolOrdering::handle_dontcare_extruder(const std::vector<unsigned int>& too
if (lt.extruders.front() == 0)
// Pop the "don't care" extruder, the "don't care" region will be merged with the next one.
lt.extruders.erase(lt.extruders.begin());
if (lt.preserve_extruder_order) {
last_extruder_id = lt.extruders.back();
continue;
}
// Reorder the extruders to start with the last one.
for (size_t i = 1; i < lt.extruders.size(); ++i)
if (lt.extruders[i] == last_extruder_id) {
@@ -413,6 +434,10 @@ void ToolOrdering::handle_dontcare_extruder(unsigned int last_extruder_id)
if (lt.extruders.front() == 0)
// Pop the "don't care" extruder, the "don't care" region will be merged with the next one.
lt.extruders.erase(lt.extruders.begin());
if (lt.preserve_extruder_order) {
last_extruder_id = lt.extruders.back();
continue;
}
// Reorder the extruders to start with the last one.
for (size_t i = 1; i < lt.extruders.size(); ++ i)
if (lt.extruders[i] == last_extruder_id) {
@@ -874,19 +899,40 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto
}
if (something_nonoverriddable){
if (extruder_override == 0) {
layer_tools.extruders.emplace_back(layer_tools.wall_filament(region) + 1);
if (layerCount == 0) {
firstLayerExtruders.emplace_back(layer_tools.wall_filament(region) + 1);
const unsigned int configured_wall = (extruder_override == 0)
? region.config().wall_filament.value
: extruder_override;
unsigned int wall_ext = resolve_mixed(configured_wall,
layerCount,
float(layer->print_z),
float(layer->height));
const unsigned int grouped_id =
grouped_manual_pattern_mixed_filament_id_for_layer(layer_tools, configured_wall);
if (grouped_id != 0) {
const std::vector<unsigned int> ordered =
m_mixed_mgr->ordered_perimeter_extruders(grouped_id,
m_num_physical,
layerCount,
float(layer->print_z),
float(layer->height));
if (!ordered.empty()) {
if (ordered.size() >= 2)
layer_tools.preserve_extruder_order = true;
for (unsigned int extruder_id : ordered) {
layer_tools.extruders.emplace_back(extruder_id);
if (layerCount == 0 &&
std::find(firstLayerExtruders.begin(), firstLayerExtruders.end(), int(extruder_id)) == firstLayerExtruders.end())
firstLayerExtruders.emplace_back(int(extruder_id));
}
} else {
layer_tools.extruders.emplace_back(wall_ext);
if (layerCount == 0)
firstLayerExtruders.emplace_back(wall_ext);
}
} else {
const unsigned int resolved = resolve_mixed(extruder_override,
layerCount,
float(layer->print_z),
float(layer->height));
layer_tools.extruders.emplace_back(resolved);
layer_tools.extruders.emplace_back(wall_ext);
if (layerCount == 0)
firstLayerExtruders.emplace_back(resolved);
firstLayerExtruders.emplace_back(wall_ext);
}
}
@@ -935,19 +981,24 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto
// Collect the support extruders.
for (auto support_layer : object.support_layers()) {
LayerTools &layer_tools = this->tools_for_layer(support_layer->print_z);
ExtrusionRole role = support_layer->support_fills.role();
bool has_support = false;
bool has_interface = false;
for (const ExtrusionEntity *ee : support_layer->support_fills.entities) {
ExtrusionRole er = ee->role();
if (er == erSupportMaterial || er == erSupportTransition) has_support = true;
if (er == erSupportMaterialInterface) has_interface = true;
if (has_support && has_interface) break;
}
unsigned int extruder_support = object.config().support_filament.value;
unsigned int extruder_interface = object.config().support_interface_filament.value;
LayerTools &layer_tools = this->tools_for_layer(support_layer->print_z);
layer_tools.layer_height = support_layer->height;
ExtrusionRole role = support_layer->support_fills.role();
bool has_support = role == erMixed || role == erSupportMaterial || role == erSupportTransition;
bool has_interface = role == erMixed || role == erSupportMaterialInterface;
unsigned int extruder_support = resolve_mixed(object.config().support_filament.value,
layer_tools.layer_index,
float(support_layer->print_z),
float(support_layer->height));
unsigned int extruder_interface = resolve_mixed(object.config().support_interface_filament.value,
layer_tools.layer_index,
float(support_layer->print_z),
float(support_layer->height));
if (has_support) {
// BP-only fallback: when support_filament is unset and an interface
// exists, pick the lowest-flush non-soluble body extruder.
if (extruder_support > 0 || !has_interface || extruder_interface == 0 || layer_tools.has_object)
layer_tools.extruders.push_back(extruder_support);
else {
@@ -956,7 +1007,6 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto
std::vector<float> flush_matrix(
cast<float>(get_flush_volumes_matrix(object.print()->config().flush_volumes_matrix.values, 0, object.print()->config().nozzle_diameter.values.size())));
const unsigned int number_of_extruders = (unsigned int) (sqrt(flush_matrix.size()) + EPSILON);
// Extract purging volumes for each extruder pair:
std::vector<std::vector<float>> wipe_volumes;
for (unsigned int i = 0; i < number_of_extruders; ++i)
wipe_volumes.push_back(std::vector<float>(flush_matrix.begin() + i * number_of_extruders, flush_matrix.begin() + (i + 1) * number_of_extruders));
@@ -983,8 +1033,10 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto
}
for (auto& layer : m_layer_tools) {
// Sort and remove duplicates
sort_remove_duplicates(layer.extruders);
if (layer.preserve_extruder_order)
remove_duplicates_preserve_order(layer.extruders);
else
sort_remove_duplicates(layer.extruders);
// make sure that there are some tools for each object layer (e.g. tall wiping object will result in empty extruders vector)
if (layer.extruders.empty() && layer.has_object)
@@ -1539,6 +1591,9 @@ void ToolOrdering::reorder_extruders_for_minimum_flush_volume(bool reorder_first
&filament_sequences
);
// TODO(fs-port): for layers with preserve_extruder_order=true the stats
// here reflect the optimized sequence; the guarded writeback below keeps
// the original ordering. UI-only divergence — see line ~1610 below.
auto curr_flush_info = calc_filament_change_info_by_toolorder(print_config, filament_maps, nozzle_flush_mtx, filament_sequences);
if (nozzle_nums <= 1)
m_stats_by_single_extruder = curr_flush_info;
@@ -1580,8 +1635,19 @@ void ToolOrdering::reorder_extruders_for_minimum_flush_volume(bool reorder_first
}
}
for (size_t i = 0; i < filament_sequences.size(); ++i)
for (size_t i = 0; i < filament_sequences.size(); ++i) {
// FS preserve_extruder_order guard: keep the layer's existing extruder
// ordering by skipping writeback of the optimized sequence.
//
// Note: BP runs the optimizer (and updates stats) BEFORE this writeback
// because the optimizer is a free function in ToolOrderUtils.cpp without
// LayerTools access. Stats may diverge from g-code reality on
// preserve-order layers. See FS ToolOrdering.cpp:1156-1159 for the
// upstream behavior that guards earlier in the call chain.
if (m_layer_tools[i].preserve_extruder_order)
continue;
m_layer_tools[i].extruders = std::move(filament_sequences[i]);
}
}
// Layers are marked for infinite skirt aka draft shield. Not all the layers have to be printed.
void ToolOrdering::mark_skirt_layers(const PrintConfig &config, coordf_t max_layer_height)

View File

@@ -157,6 +157,9 @@ public:
bool has_support = false;
// Zero based extruder IDs, ordered to minimize tool switches.
std::vector<unsigned int> extruders;
// When set, downstream reorder passes leave this layer's extruder
// sequence in place (used by grouped manual mixed-filament patterns).
bool preserve_extruder_order = false;
// If per layer extruder switches are inserted by the G-code preview slider, this value contains the new (1 based) extruder, with which the whole object layer is being printed with.
// If not overriden, it is set to 0.
unsigned int extruder_override = 0;