OrcaSlicer latest
OrcaSlicer
Loading...
Searching...
No Matches
Slic3r::ToolOrdering Class Reference

#include </home/runner/work/OrcaSlicer/OrcaSlicer/src/libslic3r/GCode/ToolOrdering.hpp>

Collaboration diagram for Slic3r::ToolOrdering:

Public Types

enum  FilamentChangeMode { SingleExt , MultiExtBest , MultiExtCurr }

Public Member Functions

 ToolOrdering ()=default
 ToolOrdering (const PrintObject &object, unsigned int first_extruder, bool prime_multi_material=false)
 ToolOrdering (const Print &print, unsigned int first_extruder, bool prime_multi_material=false)
void handle_dontcare_extruder (const std::vector< unsigned int > &first_layer_tool_order)
void handle_dontcare_extruder (unsigned int first_extruder)
void sort_and_build_data (const PrintObject &object, unsigned int first_extruder, bool prime_multi_material=false)
void sort_and_build_data (const Print &print, unsigned int first_extruder, bool prime_multi_material=false)
void clear ()
void assign_custom_gcodes (const Print &print)
unsigned int first_extruder () const
unsigned int last_extruder () const
const std::vector< unsigned int > & all_extruders () const
const std::vector< unsigned int > & used_mixed_filaments () const
const LayerTools & tools_for_layer (coordf_t print_z) const
LayerTools & tools_for_layer (coordf_t print_z)
const LayerTools & front () const
const LayerTools & back () const
std::vector< LayerTools >::const_iterator begin () const
std::vector< LayerTools >::const_iterator end () const
bool empty () const
std::vector< LayerTools > & layer_tools ()
bool has_wipe_tower () const
int get_most_used_extruder () const
const MultiNozzleUtils::LayeredNozzleGroupResult & get_layered_nozzle_group_result () const
const MultiNozzleUtils::NozzleStatusRecorder & get_nozzle_status () const
void set_nozzle_status (const MultiNozzleUtils::NozzleStatusRecorder &status)
FilamentChangeStats get_filament_change_stats (FilamentChangeMode mode)
void cal_most_used_extruder (const PrintConfig &config)
float cal_max_additional_fan (const PrintConfig &config)
bool cal_non_support_filaments (const PrintConfig &config, unsigned int &first_non_support_filament, std::vector< int > &initial_non_support_filaments, std::vector< int > &initial_filaments)
bool has_non_support_filament (const PrintConfig &config)
bool is_last_extrusion_layer (coordf_t print_z, unsigned int extruder_id) const

Static Public Member Functions

static MultiNozzleUtils::LayeredNozzleGroupResult get_recommended_filament_maps (const std::vector< std::vector< unsigned int > > &layer_filaments, const Print *print, const FilamentMapMode mode, const std::vector< std::set< int > > &physical_unprintables, const std::vector< std::set< int > > &geometric_unprintables, const std::map< int, std::set< NozzleVolumeType > > &unprintable_volumes={}, const std::unordered_map< int, int > &nozzle_status={})
static MultiNozzleUtils::LayeredNozzleGroupResult build_sequential_group_result (Print *print, std::vector< std::vector< int > > nozzle_map_per_layer, const std::vector< std::vector< unsigned int > > &layer_filaments, const std::vector< std::vector< unsigned int > > &layer_sequences, const std::vector< unsigned int > &used_filaments, const std::vector< std::set< int > > &physical_unprintables, const std::vector< std::set< int > > &geometric_unprintables, const std::map< int, std::set< NozzleVolumeType > > &unprintable_volumes)

Private Member Functions

void initialize_layers (std::vector< coordf_t > &zs)
void collect_extruders (const PrintObject &object, const std::vector< std::pair< double, unsigned int > > &per_layer_extruder_switches)
void fill_wipe_tower_partitions (const PrintConfig &config, coordf_t object_bottom_z, coordf_t max_layer_height)
bool insert_wipe_tower_extruder ()
void mark_skirt_layers (const PrintConfig &config, coordf_t max_layer_height)
void collect_extruder_statistics (bool prime_multi_material)
void reorder_extruders_for_minimum_flush_volume (bool reorder_first_layer)
void resolve_mixed_filaments (const PrintConfig &config)
void enforce_mixed_component_order ()
std::vector< unsigned int > generate_first_layer_tool_order (const Print &print)
std::vector< unsigned int > generate_first_layer_tool_order (const PrintObject &object)

Private Attributes

std::vector< LayerTools > m_layer_tools
unsigned int m_first_printing_extruder = (unsigned int)-1
unsigned int m_last_printing_extruder = (unsigned int)-1
std::vector< unsigned int > m_all_printing_extruders
std::vector< unsigned int > m_used_mixed_filaments
const DynamicPrintConfig * m_print_full_config = nullptr
const PrintConfig * m_print_config_ptr = nullptr
std::map< unsigned int, std::map< const PrintObject *, std::vector< size_t > > > m_mixed_object_layers
std::map< const PrintObject *, std::vector< size_t > > m_object_all_layer_indices
std::map< unsigned int, std::map< LayerTools::MixedSubLayerGroup::VolumeKey, std::vector< size_t > > > m_gradient_volume_layers
std::map< unsigned int, coordf_t > m_last_layer_per_extruder
const PrintObject * m_print_object_ptr = nullptr
Print * m_print
bool m_sorted = false
FilamentChangeStats m_stats_by_single_extruder
FilamentChangeStats m_stats_by_multi_extruder_curr
FilamentChangeStats m_stats_by_multi_extruder_best
MultiNozzleUtils::LayeredNozzleGroupResult m_nozzle_group_result
MultiNozzleUtils::NozzleStatusRecorder m_initial_nozzle_status
MultiNozzleUtils::NozzleStatusRecorder m_nozzle_status
int most_used_extruder

Detailed Description

Member Enumeration Documentation

◆ FilamentChangeMode

Enumerator
SingleExt 
MultiExtBest 
MultiExtCurr 
265 {
266 SingleExt,
269 };
@ SingleExt
Definition ToolOrdering.hpp:266
@ MultiExtCurr
Definition ToolOrdering.hpp:268
@ MultiExtBest
Definition ToolOrdering.hpp:267

Constructor & Destructor Documentation

◆ ToolOrdering() [1/3]

Slic3r::ToolOrdering::ToolOrdering ( )
default

References first_extruder().

Here is the call graph for this function:

◆ ToolOrdering() [2/3]

Slic3r::ToolOrdering::ToolOrdering ( const PrintObject & object,
unsigned int first_extruder,
bool prime_multi_material = false )
507{
508 m_print_full_config = &object.print()->full_print_config();
509 m_print_config_ptr = &object.print()->config();
510 m_print_object_ptr = &object;
511 m_print = const_cast<Print*>(object.print());
512 if (object.layers().empty())
513 return;
514
515 // Initialize the print layers for just a single object.
516 {
517 // construct layer tools by z height
518 std::vector<coordf_t> zs;
519 zs.reserve(zs.size() + object.layers().size() + object.support_layers().size());
520 for (auto layer : object.layers())
521 zs.emplace_back(layer->print_z);
522 for (auto layer : object.support_layers())
523 zs.emplace_back(layer->print_z);
524 // Belt brim apron bands sit below the object's first layer and have no
525 // layer of their own, but tools_for_layer() asserts an exact Z match, so
526 // their print_z must be part of the ordering.
527 for (const BeltBrimBand &band : object.belt_brim_prologue())
528 zs.emplace_back(band.print_z);
529 this->initialize_layers(zs);
530 }
531
532 // Collect extruders reuqired to print the layers. Add dontcare extruders
533 this->collect_extruders(object, std::vector<std::pair<double, unsigned int>>());
534
535 // BBS
536 // Reorder the extruders to minimize tool switches.
537 std::vector<unsigned int> first_layer_tool_order;
538 if (first_extruder == (unsigned int) -1) {
539 first_layer_tool_order = generate_first_layer_tool_order(object);
540 }
541
542 if (!first_layer_tool_order.empty()) {
543 this->handle_dontcare_extruder(first_layer_tool_order);
544 } else {
546 }
547
548 this->collect_extruder_statistics(prime_multi_material);
549
550 double max_layer_height = calc_max_layer_height(object.print()->config(), object.config().layer_height);
551
552 this->mark_skirt_layers(object.print()->config(), max_layer_height);
553}
void collect_extruders(const PrintObject &object, const std::vector< std::pair< double, unsigned int > > &per_layer_extruder_switches)
Definition ToolOrdering.cpp:781
const PrintConfig * m_print_config_ptr
Definition ToolOrdering.hpp:399
void mark_skirt_layers(const PrintConfig &config, coordf_t max_layer_height)
Definition ToolOrdering.cpp:3207
std::vector< unsigned int > generate_first_layer_tool_order(const Print &print)
Definition ToolOrdering.cpp:637
void collect_extruder_statistics(bool prime_multi_material)
Definition ToolOrdering.cpp:1245
void initialize_layers(std::vector< coordf_t > &zs)
Definition ToolOrdering.cpp:765
Print * m_print
Definition ToolOrdering.hpp:420
void handle_dontcare_extruder(const std::vector< unsigned int > &first_layer_tool_order)
Definition ToolOrdering.cpp:254
unsigned int first_extruder() const
Definition ToolOrdering.hpp:300
const PrintObject * m_print_object_ptr
Definition ToolOrdering.hpp:419
bool empty() const
Definition ToolOrdering.hpp:319
const DynamicPrintConfig * m_print_full_config
Definition ToolOrdering.hpp:398
layer_height((ConfigOptionInt, faded_layers))((ConfigOptionFloat
static double calc_max_layer_height(const PrintConfig &config, double max_object_layer_height)
Definition ToolOrdering.cpp:180

References Slic3r::calc_max_layer_height(), collect_extruder_statistics(), collect_extruders(), empty(), first_extruder(), generate_first_layer_tool_order(), handle_dontcare_extruder(), initialize_layers(), Slic3r::layer_height(), m_print, m_print_config_ptr, m_print_full_config, m_print_object_ptr, and mark_skirt_layers().

Here is the call graph for this function:

◆ ToolOrdering() [3/3]

Slic3r::ToolOrdering::ToolOrdering ( const Print & print,
unsigned int first_extruder,
bool prime_multi_material = false )
558{
559 m_print_full_config = &print.full_print_config();
560 m_print = const_cast<Print *>(&print); // for update the context of print
561 m_print_config_ptr = &print.config();
562
563 // Initialize the print layers for all objects and all layers.
564 coordf_t max_layer_height = 0.;
565 {
566 std::vector<coordf_t> zs;
567 for (auto object : print.objects()) {
568 zs.reserve(zs.size() + object->layers().size() + object->support_layers().size());
569 for (auto layer : object->layers())
570 zs.emplace_back(layer->print_z);
571 for (auto layer : object->support_layers())
572 zs.emplace_back(layer->print_z);
573 // See the single-object ctor: belt brim apron bands need their own
574 // ordering entries or tools_for_layer() will assert.
575 for (const BeltBrimBand &band : object->belt_brim_prologue())
576 zs.emplace_back(band.print_z);
577
578 max_layer_height = std::max(max_layer_height, object->config().layer_height.value);
579 }
580 this->initialize_layers(zs);
581 }
582 max_layer_height = calc_max_layer_height(print.config(), max_layer_height);
583
584 // Use the extruder switches from Model::custom_gcode_per_print_z to override the extruder to print the object.
585 // Do it only if all the objects were configured to be printed with a single extruder.
586 std::vector<std::pair<double, unsigned int>> per_layer_extruder_switches;
587
588 // BBS
589 if (auto num_filaments = unsigned(print.config().filament_diameter.size());
590 num_filaments > 1 && print.object_extruders().size() == 1 && // the current Print's configuration is CustomGCode::MultiAsSingle
591 //BBS: replace model custom gcode with current plate custom gcode
592 print.model().get_curr_plate_custom_gcodes().mode == CustomGCode::MultiAsSingle) {
593 // Printing a single extruder platter on a printer with more than 1 extruder (or single-extruder multi-material).
594 // There may be custom per-layer tool changes available at the model.
595 per_layer_extruder_switches = custom_tool_changes(print.model().get_curr_plate_custom_gcodes(), num_filaments);
596 }
597
598 // Collect extruders reuqired to print the layers.
599 for (auto object : print.objects())
600 this->collect_extruders(*object, per_layer_extruder_switches);
601
602 // Reorder the extruders to minimize tool switches.
603 std::vector<unsigned int> first_layer_tool_order;
604 if (first_extruder == (unsigned int)-1) {
605 first_layer_tool_order = generate_first_layer_tool_order(print);
606 }
607
608 if(!first_layer_tool_order.empty())
609 this->handle_dontcare_extruder(first_layer_tool_order);
610 else
612
613 this->collect_extruder_statistics(prime_multi_material);
614
615 this->mark_skirt_layers(print.config(), max_layer_height);
616}
std::vector< std::pair< double, unsigned int > > custom_tool_changes(const Info &custom_gcode_per_print_z, size_t num_extruders)
Definition CustomGCode.cpp:103
@ MultiAsSingle
Definition CustomGCode.hpp:57
double coordf_t
Definition GUI_ObjectList.hpp:46

References Slic3r::calc_max_layer_height(), collect_extruder_statistics(), collect_extruders(), Slic3r::Print::config(), Slic3r::CustomGCode::custom_tool_changes(), first_extruder(), Slic3r::PrintBase::full_print_config(), generate_first_layer_tool_order(), Slic3r::Model::get_curr_plate_custom_gcodes(), handle_dontcare_extruder(), initialize_layers(), m_print, m_print_config_ptr, m_print_full_config, mark_skirt_layers(), Slic3r::CustomGCode::Info::mode, Slic3r::PrintBase::model(), Slic3r::CustomGCode::MultiAsSingle, Slic3r::Print::object_extruders(), and Slic3r::Print::objects().

Here is the call graph for this function:

Member Function Documentation

◆ all_extruders()

const std::vector< unsigned int > & Slic3r::ToolOrdering::all_extruders ( ) const
inline
306{ return m_all_printing_extruders; }
std::vector< unsigned int > m_all_printing_extruders
Definition ToolOrdering.hpp:396

References m_all_printing_extruders.

Referenced by Slic3r::GCode::_do_export().

Here is the caller graph for this function:

◆ assign_custom_gcodes()

void Slic3r::ToolOrdering::assign_custom_gcodes ( const Print & print)
3255{
3256 // Only valid for non-sequential print.
3257 assert(print.config().print_sequence == PrintSequence::ByLayer);
3258
3259 custom_gcode_per_print_z = print.model().get_curr_plate_custom_gcodes();
3260 if (custom_gcode_per_print_z.gcodes.empty())
3261 return;
3262
3263 // BBS
3264 auto num_filaments = unsigned(print.config().filament_diameter.size());
3265 CustomGCode::Mode mode =
3266 (num_filaments == 1) ? CustomGCode::SingleExtruder :
3267 print.object_extruders().size() == 1 ? CustomGCode::MultiAsSingle : CustomGCode::MultiExtruder;
3268 CustomGCode::Mode model_mode = print.model().get_curr_plate_custom_gcodes().mode;
3269 std::vector<unsigned char> extruder_printing_above(num_filaments, false);
3270 auto custom_gcode_it = custom_gcode_per_print_z.gcodes.rbegin();
3271 // Tool changes and color changes will be ignored, if the model's tool/color changes were entered in mm mode and the print is in non mm mode
3272 // or vice versa.
3273 bool ignore_tool_and_color_changes = (mode == CustomGCode::MultiExtruder) != (model_mode == CustomGCode::MultiExtruder);
3274 // If printing on a single extruder machine, make the tool changes trigger color change (M600) events.
3275 bool tool_changes_as_color_changes = mode == CustomGCode::SingleExtruder && model_mode == CustomGCode::MultiAsSingle;
3276
3277 // From the last layer to the first one:
3278 coordf_t print_z_above = std::numeric_limits<coordf_t>::lowest();
3279 for (auto it_lt = m_layer_tools.rbegin(); it_lt != m_layer_tools.rend(); ++ it_lt) {
3280 LayerTools &lt = *it_lt;
3281 // Add the extruders of the current layer to the set of extruders printing at and above this print_z.
3282 for (unsigned int i : lt.extruders)
3283 extruder_printing_above[i] = true;
3284 // Skip all custom G-codes above this layer and skip all extruder switches.
3285 for (; custom_gcode_it != custom_gcode_per_print_z.gcodes.rend() && (
3286 (print_z_above > lt.print_z && custom_gcode_it->print_z > 0.5 * (lt.print_z + print_z_above))
3287 || custom_gcode_it->type == CustomGCode::ToolChange); ++ custom_gcode_it) {}
3288 print_z_above = lt.print_z;
3289 if (custom_gcode_it == custom_gcode_per_print_z.gcodes.rend())
3290 // Custom G-codes were processed.
3291 break;
3292 // Some custom G-code is configured for this layer or a layer below.
3293 const CustomGCode::Item &custom_gcode = *custom_gcode_it;
3294 // print_z of the layer below the current layer.
3295 coordf_t print_z_below = 0.;
3296 if (auto it_lt_below = it_lt; ++ it_lt_below != m_layer_tools.rend())
3297 print_z_below = it_lt_below->print_z;
3298 if (custom_gcode.print_z > 0.5 * (print_z_below + lt.print_z)) {
3299 // The custom G-code applies to the current layer.
3300 bool color_change = custom_gcode.type == CustomGCode::ColorChange;
3301 bool tool_change = custom_gcode.type == CustomGCode::ToolChange;
3302 bool pause_or_custom_gcode = ! color_change && ! tool_change;
3303 bool apply_color_change = ! ignore_tool_and_color_changes &&
3304 // If it is color change, it will actually be useful as the exturder above will print.
3305 // BBS
3306 (color_change ?
3307 mode == CustomGCode::SingleExtruder ||
3308 (custom_gcode.extruder <= int(num_filaments) && extruder_printing_above[unsigned(custom_gcode.extruder - 1)]) :
3309 tool_change && tool_changes_as_color_changes);
3310 if (pause_or_custom_gcode || apply_color_change)
3311 lt.custom_gcode = &custom_gcode;
3312 // Consume that custom G-code event.
3313 ++ custom_gcode_it;
3314 }
3315 }
3316}
std::vector< LayerTools > m_layer_tools
Definition ToolOrdering.hpp:390
Mode
Definition CustomGCode.hpp:54
@ SingleExtruder
Definition CustomGCode.hpp:56
@ MultiExtruder
Definition CustomGCode.hpp:60
@ ToolChange
Definition CustomGCode.hpp:21
@ ColorChange
Definition CustomGCode.hpp:19
static CustomGCode::Info custom_gcode_per_print_z
Definition ToolOrdering.cpp:3253
@ ByLayer
Definition PrintConfig.hpp:311
@ CustomGCode
Definition Types.hpp:129
std::vector< Item > gcodes
Definition CustomGCode.hpp:71

References assign_custom_gcodes(), Slic3r::ByLayer, Slic3r::CustomGCode::ColorChange, Slic3r::Print::config(), Slic3r::LayerTools::custom_gcode, Slic3r::custom_gcode_per_print_z, Slic3r::CustomGCode::Item::extruder, Slic3r::LayerTools::extruders, Slic3r::Model::get_curr_plate_custom_gcodes(), m_layer_tools, Slic3r::CustomGCode::Info::mode, Slic3r::PrintBase::model(), Slic3r::CustomGCode::MultiAsSingle, Slic3r::CustomGCode::MultiExtruder, Slic3r::Print::object_extruders(), Slic3r::CustomGCode::Item::print_z, Slic3r::LayerTools::print_z, Slic3r::CustomGCode::SingleExtruder, Slic3r::CustomGCode::ToolChange, and Slic3r::CustomGCode::Item::type.

Referenced by Slic3r::GCode::_do_export(), and assign_custom_gcodes().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ back()

const LayerTools & Slic3r::ToolOrdering::back ( ) const
inline
316{ return m_layer_tools.back(); }

References m_layer_tools.

◆ begin()

std::vector< LayerTools >::const_iterator Slic3r::ToolOrdering::begin ( ) const
inline
317{ return m_layer_tools.begin(); }

References m_layer_tools.

◆ build_sequential_group_result()

MultiNozzleUtils::LayeredNozzleGroupResult Slic3r::ToolOrdering::build_sequential_group_result ( Print * print,
std::vector< std::vector< int > > nozzle_map_per_layer,
const std::vector< std::vector< unsigned int > > & layer_filaments,
const std::vector< std::vector< unsigned int > > & layer_sequences,
const std::vector< unsigned int > & used_filaments,
const std::vector< std::set< int > > & physical_unprintables,
const std::vector< std::set< int > > & geometric_unprintables,
const std::map< int, std::set< NozzleVolumeType > > & unprintable_volumes )
static
2219{
2220 MultiNozzleUtils::normalize_nozzle_map_per_layer(nozzle_map_per_layer, layer_filaments);
2221 auto context = build_filament_group_context(print, layer_filaments, physical_unprintables, geometric_unprintables,
2222 unprintable_volumes, FilamentMapMode::fmmAutoForFlush, {});
2223 auto result = MultiNozzleUtils::LayeredNozzleGroupResult::create(nozzle_map_per_layer, context.nozzle_info.nozzle_list,
2224 used_filaments, layer_sequences);
2225 return result ? *result : MultiNozzleUtils::LayeredNozzleGroupResult();
2226}
static std::optional< LayeredNozzleGroupResult > create(const std::vector< int > &filament_nozzle_map, const std::vector< NozzleInfo > &nozzle_list, const std::vector< unsigned int > &used_filaments)
Definition MultiNozzleUtils.cpp:142
void normalize_nozzle_map_per_layer(std::vector< std::vector< int > > &layer_filament_nozzle_maps, const std::vector< std::vector< unsigned int > > &layer_filaments)
Definition MultiNozzleUtils.cpp:65
static FilamentGroupContext build_filament_group_context(const Print *print, const std::vector< std::vector< unsigned int > > &layer_filaments, const std::vector< std::set< int > > &physical_unprintables, const std::vector< std::set< int > > &geometric_unprintables, const std::map< int, std::set< NozzleVolumeType > > &unprintable_volumes, FilamentMapMode mode, const std::unordered_map< int, int > &nozzle_status)
Definition ToolOrdering.cpp:1537
@ fmmAutoForFlush
Definition PrintConfig.hpp:633

References Slic3r::build_filament_group_context(), build_sequential_group_result(), Slic3r::MultiNozzleUtils::LayeredNozzleGroupResult::create(), Slic3r::fmmAutoForFlush, and Slic3r::MultiNozzleUtils::normalize_nozzle_map_per_layer().

Referenced by build_sequential_group_result().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ cal_max_additional_fan()

float Slic3r::ToolOrdering::cal_max_additional_fan ( const PrintConfig & config)
1315{
1316 std::set<unsigned int> used_filaments;
1317 for (const LayerTools &layer_tools : m_layer_tools)
1318 used_filaments.insert(layer_tools.extruders.begin(), layer_tools.extruders.end());
1319 if (used_filaments.empty())
1320 return 0;
1321
1322 // Orca: additional_cooling_fan_speed can hold one value per extruder variant a filament prints with;
1323 // filament_self_index maps such a column to its filament.
1324 const std::vector<int> &self_index = config.filament_self_index.values;
1325 const size_t columns = std::max(config.additional_cooling_fan_speed.size(), size_t(*used_filaments.rbegin()) + 1);
1326 float max_fan = 0;
1327 for (size_t column = 0; column < columns; ++column) {
1328 const unsigned int filament_id = self_index.size() == columns ? self_index[column] - 1 : column;
1329 if (used_filaments.count(filament_id) && max_fan < config.additional_cooling_fan_speed.get_at(column))
1330 max_fan = config.additional_cooling_fan_speed.get_at(column);
1331 }
1332 return max_fan;
1333}
std::vector< LayerTools > & layer_tools()
Definition ToolOrdering.hpp:320

References cal_max_additional_fan(), layer_tools(), and m_layer_tools.

Referenced by Slic3r::GCode::_do_export(), and cal_max_additional_fan().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ cal_most_used_extruder()

void Slic3r::ToolOrdering::cal_most_used_extruder ( const PrintConfig & config)
1287{
1288 // record
1289 std::vector<int> extruder_count;
1290 extruder_count.resize(config.nozzle_diameter.size(), 0);
1291 for (LayerTools &layer_tools : m_layer_tools) {
1292 std::vector<unsigned int> filaments = layer_tools.extruders;
1293 std::set<int> layer_extruder_count;
1294 //count once only
1295 for (unsigned int &filament : filaments) {
1296 layer_extruder_count.insert(config.filament_map.values[filament] - 1);
1297 }
1298
1299 //record
1300 for (int extruder_id : layer_extruder_count) {
1301 extruder_count[extruder_id]++;
1302 }
1303 }
1304
1305 // set key for most used extruder
1306 // count most used extruder
1308 for (int extruder_id = 1; extruder_id < extruder_count.size(); extruder_id++) {
1309 if (extruder_count[extruder_id] >= extruder_count[most_used_extruder])
1310 most_used_extruder = extruder_id;
1311 }
1312}
int most_used_extruder
Definition ToolOrdering.hpp:434

References cal_most_used_extruder(), layer_tools(), m_layer_tools, and most_used_extruder.

Referenced by Slic3r::GCode::_do_export(), and cal_most_used_extruder().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ cal_non_support_filaments()

bool Slic3r::ToolOrdering::cal_non_support_filaments ( const PrintConfig & config,
unsigned int & first_non_support_filament,
std::vector< int > & initial_non_support_filaments,
std::vector< int > & initial_filaments )
1341{
1342 int find_count = 0;
1343 int find_first_filaments_count = 0;
1344 bool has_non_support = has_non_support_filament(config);
1345 // The selector can move a filament between extruders per layer; resolve the extruder from
1346 // the published result then, so the first filament attributed to an extruder is one it
1347 // actually prints there. Static results keep the cross-layer filament_map arithmetic.
1348 const bool use_dynamic_map = m_nozzle_group_result.is_support_dynamic_nozzle_map() && m_nozzle_group_result.get_layer_count() > 0;
1349 auto extruder_for_filament = [&](unsigned int filament, size_t layer_idx) -> int {
1350 if (use_dynamic_map)
1351 return m_nozzle_group_result.get_extruder_id(static_cast<int>(filament), static_cast<int>(layer_idx));
1352 return config.filament_map.values[filament] - 1;
1353 };
1354 for (size_t layer_idx = 0; layer_idx < m_layer_tools.size(); ++layer_idx) {
1355 for (const unsigned int &filament : m_layer_tools[layer_idx].extruders) {
1356 //check first filament
1357 if (!config.filament_map.values.empty()) {
1358 const int extruder_id = extruder_for_filament(filament, layer_idx);
1359 if (extruder_id >= 0 && extruder_id < static_cast<int>(initial_filaments.size()) && initial_filaments[extruder_id] == -1) {
1360 initial_filaments[extruder_id] = filament;
1361 find_first_filaments_count++;
1362 }
1363 }
1364
1365 if (has_non_support) {
1366 // check first non support filaments
1367 if (config.filament_is_support.get_at(filament))
1368 continue;
1369
1370 if (first_non_support_filament == (unsigned int) -1) first_non_support_filament = filament;
1371
1372 // params missing, add protection
1373 // filament map missing means single nozzle, no need to set initial_non_support_filaments
1374 if (config.filament_map.values.empty())
1375 return true;
1376
1377 const int extruder_id = extruder_for_filament(filament, layer_idx);
1378 if (extruder_id >= 0 && extruder_id < static_cast<int>(initial_non_support_filaments.size()) && initial_non_support_filaments[extruder_id] == -1) {
1379 initial_non_support_filaments[extruder_id] = filament;
1380 find_count++;
1381 }
1382
1383 if (find_count == initial_non_support_filaments.size())
1384 return true;
1385 } else if (find_first_filaments_count == initial_filaments.size() || config.filament_map.values.empty()){
1386 return false;
1387 }
1388
1389 }
1390 }
1391
1392 return false;
1393}
bool has_non_support_filament(const PrintConfig &config)
Definition ToolOrdering.cpp:1395
MultiNozzleUtils::LayeredNozzleGroupResult m_nozzle_group_result
Definition ToolOrdering.hpp:426

References cal_non_support_filaments(), has_non_support_filament(), m_layer_tools, and m_nozzle_group_result.

Referenced by Slic3r::GCode::_do_export(), and cal_non_support_filaments().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ clear()

void Slic3r::ToolOrdering::clear ( )
inline
286 {
287 m_layer_tools.clear();
291 }
FilamentChangeStats m_stats_by_single_extruder
Definition ToolOrdering.hpp:423
FilamentChangeStats m_stats_by_multi_extruder_best
Definition ToolOrdering.hpp:425
FilamentChangeStats m_stats_by_multi_extruder_curr
Definition ToolOrdering.hpp:424

References m_layer_tools, m_stats_by_multi_extruder_best, m_stats_by_multi_extruder_curr, and m_stats_by_single_extruder.

◆ collect_extruder_statistics()

void Slic3r::ToolOrdering::collect_extruder_statistics ( bool prime_multi_material)
private
1246{
1247 m_first_printing_extruder = (unsigned int)-1;
1248 for (const auto &lt : m_layer_tools)
1249 if (! lt.extruders.empty()) {
1250 m_first_printing_extruder = lt.extruders.front();
1251 break;
1252 }
1253
1254 m_last_printing_extruder = (unsigned int)-1;
1255 for (auto lt_it = m_layer_tools.rbegin(); lt_it != m_layer_tools.rend(); ++ lt_it)
1256 if (! lt_it->extruders.empty()) {
1257 m_last_printing_extruder = lt_it->extruders.back();
1258 break;
1259 }
1260
1262 for (const auto &lt : m_layer_tools) {
1263 append(m_all_printing_extruders, lt.extruders);
1265 }
1266
1268 for (const LayerTools& lt : m_layer_tools) {
1269 for (unsigned int ext : lt.extruders) {
1270 m_last_layer_per_extruder[ext] = lt.print_z;
1271 }
1272 }
1273
1274 if (prime_multi_material && ! m_all_printing_extruders.empty()) {
1275 // Reorder m_all_printing_extruders in the sequence they will be primed, the last one will be m_first_printing_extruder.
1276 // Then set m_first_printing_extruder to the 1st extruder primed.
1278 std::remove_if(m_all_printing_extruders.begin(), m_all_printing_extruders.end(),
1279 [ this ](const unsigned int eid) { return eid == m_first_printing_extruder; }),
1283 }
1284}
unsigned int m_last_printing_extruder
Definition ToolOrdering.hpp:394
std::map< unsigned int, coordf_t > m_last_layer_per_extruder
Definition ToolOrdering.hpp:418
unsigned int m_first_printing_extruder
Definition ToolOrdering.hpp:392
void sort_remove_duplicates(std::vector< T > &vec)
Definition libslic3r.h:231
void append(std::vector< T, Alloc > &dest, const std::vector< T, Alloc2 > &src)
Definition libslic3r.h:157

References Slic3r::append(), collect_extruder_statistics(), m_all_printing_extruders, m_first_printing_extruder, m_last_layer_per_extruder, m_last_printing_extruder, m_layer_tools, and Slic3r::sort_remove_duplicates().

Referenced by ToolOrdering(), ToolOrdering(), collect_extruder_statistics(), sort_and_build_data(), and sort_and_build_data().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ collect_extruders()

void Slic3r::ToolOrdering::collect_extruders ( const PrintObject & object,
const std::vector< std::pair< double, unsigned int > > & per_layer_extruder_switches )
private
782{
783 // Extruder overrides are ordered by print_z.
784 std::vector<std::pair<double, unsigned int>>::const_iterator it_per_layer_extruder_override;
785 it_per_layer_extruder_override = per_layer_extruder_switches.begin();
786 unsigned int extruder_override = 0;
787
788 // Pre-compute 1-based IDs of mixed filament slots for per-object tracking.
789 // mixed_slots_1based covers ALL mixed slots (needed by calc_slot_lh for
790 // accurate layer height when a slot skips layers). gradient_slots_1based
791 // and per_part_slots_1based are subsets for gradient-specific logic.
792 std::set<unsigned int> mixed_slots_1based;
793 std::set<unsigned int> gradient_slots_1based;
794 std::set<unsigned int> per_part_slots_1based;
795 {
796 const PrintConfig &cfg = object.print()->config();
797 const auto &is_mixed = cfg.filament_is_mixed.values;
798 const auto &grad_flags = cfg.filament_mixed_gradient.values;
799 const auto &per_part_flags = cfg.filament_mixed_gradient_per_part.values;
800 const auto &comp_strs = cfg.filament_mixed_components.values;
801 for (size_t i = 0; i < is_mixed.size(); ++i) {
802 if (!is_mixed[i])
803 continue;
804 auto comps = parse_mixed_components(i < comp_strs.size() ? comp_strs[i] : "");
805 if (comps.size() < 2)
806 continue;
807 mixed_slots_1based.insert(static_cast<unsigned int>(i + 1));
808 // Gradient/per-part are only defined for 2-component slots; keep their
809 // tracking limited to them (mirrors the is_gradient guard at resolve time).
810 if (comps.size() != 2)
811 continue;
812 if (i >= grad_flags.size() || !grad_flags[i])
813 continue;
814 gradient_slots_1based.insert(static_cast<unsigned int>(i + 1));
815 if (i < per_part_flags.size() && per_part_flags[i])
816 per_part_slots_1based.insert(static_cast<unsigned int>(i + 1));
817 }
818 }
819
820 // BBS: collect first layer extruders of an object's wall, which will be used by brim generator
821 int layerCount = 0;
822 std::vector<int> firstLayerExtruders;
823 firstLayerExtruders.clear();
824
825 // Collect the object extruders.
826 for (auto layer : object.layers()) {
827 LayerTools &layer_tools = this->tools_for_layer(layer->print_z);
828
829 m_object_all_layer_indices[&object].push_back(
830 static_cast<size_t>(&layer_tools - m_layer_tools.data()));
831
832 // Override extruder with the next
833 for (; it_per_layer_extruder_override != per_layer_extruder_switches.end() && it_per_layer_extruder_override->first < layer->print_z + EPSILON; ++ it_per_layer_extruder_override)
834 extruder_override = (int)it_per_layer_extruder_override->second;
835
836 // Store the current extruder override (set to zero if no overriden), so that layer_tools.wiping_extrusions().is_overridable_and_mark() will use it.
837 layer_tools.extruder_override = extruder_override;
838
839 // Snapshot extruders before this object's regions to track new additions.
840 const size_t ext_snapshot = layer_tools.extruders.size();
841
842 // What extruders are required to print this object layer?
843 for (const LayerRegion *layerm : layer->regions()) {
844 const PrintRegion &region = layerm->region();
845
846 if (! layerm->perimeters.entities.empty()) {
847 bool something_nonoverriddable = true;
848
849 if (m_print_config_ptr) { // in this case print->config().print_sequence != PrintSequence::ByObject (see ToolOrdering constructors)
850 something_nonoverriddable = false;
851 for (const auto& eec : layerm->perimeters.entities) // let's check if there are nonoverriddable entities
852 if (!layer_tools.wiping_extrusions().is_overriddable_and_mark(dynamic_cast<const ExtrusionEntityCollection&>(*eec), *m_print_config_ptr, object, region))
853 something_nonoverriddable = true;
854 }
855
856 if (something_nonoverriddable){
857 layer_tools.extruders.emplace_back((extruder_override == 0) ? region.config().outer_wall_filament_id.value : extruder_override);
858 const ExtrusionEntitiesPtr &islands = layerm->perimeters.entities;
859 const bool has_inner_wall = std::any_of(islands.begin(), islands.end(), [](const ExtrusionEntity *island) {
860 const ExtrusionEntitiesPtr &walls = static_cast<const ExtrusionEntityCollection*>(island)->entities;
861 return std::any_of(walls.begin(), walls.end(), [](const ExtrusionEntity *ee) { return is_inner_wall(*ee); });
862 });
863 if (extruder_override == 0 && region.config().wall_loops.value > 1 && has_inner_wall)
864 layer_tools.extruders.emplace_back(region.config().inner_wall_filament_id.value);
865 if (layerCount == 0) {
866 firstLayerExtruders.emplace_back((extruder_override == 0) ? region.config().outer_wall_filament_id.value : extruder_override);
867 }
868 }
869
870 layer_tools.has_object = true;
871 }
872
873 std::vector<unsigned int> fill_filaments;
874 bool something_nonoverriddable = false;
875 for (const ExtrusionEntity *ee : layerm->fills.entities) {
876 // fill represents infill extrusions of a single island.
877 const auto *fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
878 if (! fill->entities.empty())
879 fill_filaments.emplace_back(region_filament(*fill, region.config()));
880
881 if (m_print_config_ptr) {
882 if (! layer_tools.wiping_extrusions().is_overriddable_and_mark(*fill, *m_print_config_ptr, object, region))
883 something_nonoverriddable = true;
884 }
885 }
886
887 if (something_nonoverriddable || !m_print_config_ptr) {
888 if (extruder_override == 0)
889 append(layer_tools.extruders, fill_filaments);
890 else if (! fill_filaments.empty())
891 layer_tools.extruders.emplace_back(extruder_override);
892 }
893 if (! fill_filaments.empty())
894 layer_tools.has_object = true;
895 }
896
897 // Record mixed slot usage for this object at this layer.
898 // All mixed slots are tracked (not just gradient) so that calc_slot_lh
899 // can compute accurate layer heights even when a slot skips layers.
900 if (!mixed_slots_1based.empty()) {
901 size_t layer_idx = static_cast<size_t>(&layer_tools - m_layer_tools.data());
902 std::set<unsigned int> seen;
903 for (size_t ei = ext_snapshot; ei < layer_tools.extruders.size(); ++ei) {
904 unsigned int ext_1based = layer_tools.extruders[ei];
905 if (mixed_slots_1based.count(ext_1based) && seen.insert(ext_1based).second)
906 m_mixed_object_layers[ext_1based - 1][&object].push_back(layer_idx);
907 }
908 }
909
910 // Per-part gradient: walk LayerRegions and record which (slot, ModelVolume) pairs
911 // contributed to this layer. Only regions tagged by PrintApply.cpp's get_create_region
912 // (i.e. gradient_volume_id().valid()) are considered, so this loop is a strict no-op
913 // unless per_part_gradient is enabled for at least one slot AND the corresponding
914 // ModelObject has >=2 model-part volumes using that slot. The per-object pass above is
915 // unaffected — both run the same layer's data through orthogonal containers.
916 if (!per_part_slots_1based.empty()) {
917 size_t layer_idx = static_cast<size_t>(&layer_tools - m_layer_tools.data());
918 std::set<std::pair<unsigned int, ObjectID>> vol_seen;
919 for (const LayerRegion *layerm : layer->regions()) {
920 if (layerm->slices.empty())
921 continue;
922 const PrintRegion &region = layerm->region();
923 ObjectID vol_id = region.gradient_volume_id();
924 if (! vol_id.valid())
925 continue;
926 const PrintRegionConfig &rcfg = region.config();
927 // Orca splits BBS's three role slots into five; cover them all so a mixed
928 // slot used by any role is tracked.
929 const unsigned int role_slots[5] = {
930 static_cast<unsigned int>(rcfg.outer_wall_filament_id.value),
931 static_cast<unsigned int>(rcfg.inner_wall_filament_id.value),
932 static_cast<unsigned int>(rcfg.sparse_infill_filament_id.value),
933 static_cast<unsigned int>(rcfg.top_surface_filament_id.value),
934 static_cast<unsigned int>(rcfg.bottom_surface_filament_id.value),
935 };
936 for (unsigned int ext_1based : role_slots) {
937 if (ext_1based >= 1
938 && per_part_slots_1based.count(ext_1based)
939 && vol_seen.insert({ext_1based, vol_id}).second)
940 m_gradient_volume_layers[ext_1based - 1][{&object, vol_id}].push_back(layer_idx);
941 }
942 }
943 }
944 layerCount++;
945 }
946
947 sort_remove_duplicates(firstLayerExtruders);
948 const_cast<PrintObject&>(object).object_first_layer_wall_extruders = firstLayerExtruders;
949
950 // Collect the support extruders.
951 for (auto support_layer : object.support_layers()) {
952 LayerTools &layer_tools = this->tools_for_layer(support_layer->print_z);
953 ExtrusionRole role = support_layer->support_fills.role();
954 bool has_support = false;
955 bool has_interface = false;
956 for (const ExtrusionEntity *ee : support_layer->support_fills.entities) {
957 ExtrusionRole er = ee->role();
958 if (er == erSupportMaterial || er == erSupportTransition) has_support = true;
959 if (er == erSupportMaterialInterface) has_interface = true;
960 if (has_support && has_interface) break;
961 }
962 unsigned int extruder_support = object.config().support_filament.value;
963 unsigned int extruder_interface = object.config().support_interface_filament.value;
964 if (has_support) {
965 if (extruder_support > 0 || !has_interface || extruder_interface == 0 || layer_tools.has_object)
966 layer_tools.extruders.push_back(extruder_support);
967 else {
968 auto all_extruders = object.print()->extruders();
969 auto get_next_extruder = [&](int current_extruder, const std::vector<unsigned int> &extruders) {
970 std::vector<float> flush_matrix(
971 cast<float>(get_flush_volumes_matrix(object.print()->config().flush_volumes_matrix.values, 0, object.print()->config().nozzle_diameter.values.size())));
972 const unsigned int number_of_extruders = (unsigned int) (sqrt(flush_matrix.size()) + EPSILON);
973 // Extract purging volumes for each extruder pair:
974 std::vector<std::vector<float>> wipe_volumes;
975 for (unsigned int i = 0; i < number_of_extruders; ++i)
976 wipe_volumes.push_back(std::vector<float>(flush_matrix.begin() + i * number_of_extruders, flush_matrix.begin() + (i + 1) * number_of_extruders));
977 int next_extruder = current_extruder;
978 float min_flush = std::numeric_limits<float>::max();
979 for (auto extruder_id : extruders) {
980 if (object.print()->config().filament_soluble.get_at(extruder_id) || extruder_id == current_extruder) continue;
981 if (wipe_volumes[extruder_interface - 1][extruder_id] < min_flush) {
982 next_extruder = extruder_id;
983 min_flush = wipe_volumes[extruder_interface - 1][extruder_id];
984 }
985 }
986 return next_extruder;
987 };
988 bool interface_not_for_body = object.config().support_interface_not_for_body;
989 layer_tools.extruders.push_back(get_next_extruder(interface_not_for_body ? extruder_interface - 1 : -1, all_extruders) + 1);
990 }
991 }
992 if (has_interface) layer_tools.extruders.push_back(extruder_interface);
993 if (has_support || has_interface) {
994 layer_tools.has_support = true;
995 layer_tools.wiping_extrusions().is_support_overriddable_and_mark(role, object);
996 }
997 }
998
999 // Belt brim apron bands own their layers outright: they print below the
1000 // object's first layer, so no object or support layer claims an extruder there
1001 // and process_layer() would bail out at "Nothing to extrude". Claim the
1002 // object's outer wall filament, in the same raw 1-based domain the loops above
1003 // push. Deliberately not layer_tools.has_object, which drives skirt marking
1004 // and wiping overrides.
1005 if (! object.belt_brim_prologue().empty()) {
1006 // 1-based, same domain the object/support pushes above use; reindexed to 0-based
1007 // with the rest of the list later.
1008 const unsigned int brim_filament = object.belt_brim_filament();
1009 for (const BeltBrimBand &band : object.belt_brim_prologue()) {
1010 if (band.fills.empty())
1011 continue;
1012 LayerTools &layer_tools = this->tools_for_layer(band.print_z);
1013 layer_tools.extruders.push_back(brim_filament);
1014 }
1015 }
1016
1017 // Coincident brim bands (belt_brim_by_layer) print ON an object layer rather than
1018 // below it, but that layer can produce no InstanceVisit in process_layer - a
1019 // zero-extrusion lead-in slice with no coinciding support - and the band would then
1020 // be silently dropped. Register the brim filament on every layer that carries a
1021 // coincident band, in the same 1-based domain as the prologue push above, so a brim
1022 // pass always exists there.
1023 if (object.has_belt_brim()) {
1024 const unsigned int brim_filament = object.belt_brim_filament();
1025 const auto &by_layer = object.belt_brim_by_layer();
1026 const size_t n = std::min(by_layer.size(), object.layers().size());
1027 for (size_t i = 0; i < n; ++ i) {
1028 if (by_layer[i].empty())
1029 continue;
1030 LayerTools &layer_tools = this->tools_for_layer(object.layers()[i]->print_z);
1031 layer_tools.extruders.push_back(brim_filament);
1032 }
1033 }
1034
1035 for (auto& layer : m_layer_tools) {
1036 // Sort and remove duplicates
1037 sort_remove_duplicates(layer.extruders);
1038
1039 // make sure that there are some tools for each object layer (e.g. tall wiping object will result in empty extruders vector)
1040 if (layer.extruders.empty() && layer.has_object)
1041 layer.extruders.emplace_back(0); // 0="dontcare" extruder - it will be taken care of in reorder_extruders
1042 }
1043}
#define EPSILON
Definition ColorUtils.cpp:64
const LayerTools & tools_for_layer(coordf_t print_z) const
Definition ToolOrdering.cpp:3318
const std::vector< unsigned int > & all_extruders() const
Definition ToolOrdering.hpp:306
std::map< unsigned int, std::map< const PrintObject *, std::vector< size_t > > > m_mixed_object_layers
Definition ToolOrdering.hpp:403
std::map< unsigned int, std::map< LayerTools::MixedSubLayerGroup::VolumeKey, std::vector< size_t > > > m_gradient_volume_layers
Definition ToolOrdering.hpp:416
std::map< const PrintObject *, std::vector< size_t > > m_object_all_layer_indices
Definition ToolOrdering.hpp:408
zaa_minimize_perimeter_height PrintRegionConfig
Definition PrintConfig.hpp:2183
std::vector< unsigned int > parse_mixed_components(const std::string &str)
Definition FilamentMixer.cpp:172
static std::vector< T > get_flush_volumes_matrix(const std::vector< T > &fv_matrix, size_t extruder_id=-1, size_t nozzle_nums=1)
Definition PrintConfig.hpp:2685
ExtrusionRole
Definition ExtrusionEntity.hpp:26
@ erSupportMaterialInterface
Definition ExtrusionEntity.hpp:42
@ erSupportMaterial
Definition ExtrusionEntity.hpp:41
@ erSupportTransition
Definition ExtrusionEntity.hpp:43
static unsigned int region_filament(const ExtrusionEntityCollection &extrusions, const PrintRegionConfig &config)
Definition ToolOrdering.cpp:144
std::vector< ExtrusionEntity * > ExtrusionEntitiesPtr
Definition ExtrusionEntity.hpp:172
zaa_minimize_perimeter_height flush_volumes_matrix((ConfigOptionFloats, flush_volumes_vector))((ConfigOptionFloat
auto cast(const BoundingBoxBase< Tin > &b)
Definition BoundingBox.hpp:299

References Slic3r::append(), Slic3r::PrintRegion::config(), EPSILON, Slic3r::PrintRegion::gradient_volume_id(), layer_tools(), m_gradient_volume_layers, m_layer_tools, m_mixed_object_layers, m_object_all_layer_indices, m_print_config_ptr, Slic3r::parse_mixed_components(), Slic3r::PrintRegionConfig, Slic3r::region_filament(), tools_for_layer(), and Slic3r::ObjectID::valid().

Referenced by ToolOrdering(), and ToolOrdering().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ empty()

bool Slic3r::ToolOrdering::empty ( ) const
inline
319{ return m_layer_tools.empty(); }

References m_layer_tools.

Referenced by ToolOrdering(), generate_first_layer_tool_order(), generate_first_layer_tool_order(), and Slic3r::OozePrevention::pre_toolchange().

Here is the caller graph for this function:

◆ end()

std::vector< LayerTools >::const_iterator Slic3r::ToolOrdering::end ( ) const
inline
318{ return m_layer_tools.end(); }

References m_layer_tools.

◆ enforce_mixed_component_order()

void Slic3r::ToolOrdering::enforce_mixed_component_order ( )
private
2742{
2743 for (LayerTools &lt : m_layer_tools) {
2744 if (lt.mixed_sub_layer_groups.empty())
2745 continue;
2746
2747 // Build a set of extruders present in lt.extruders for fast lookup.
2748 std::set<unsigned int> ext_set(lt.extruders.begin(), lt.extruders.end());
2749
2750 // 1. Build DAG from mixed group constraints.
2751 // For each group [c0, c1, c2, ...], add edges c0->c1, c1->c2, ...
2752 // Only between components that are both present in lt.extruders.
2753 // Use an edge set to avoid duplicate edges inflating in-degree.
2754 std::map<unsigned int, std::vector<unsigned int>> adj;
2755 std::map<unsigned int, int> in_degree;
2756 std::set<std::pair<unsigned int, unsigned int>> edge_set;
2757
2758 for (unsigned int ext : lt.extruders)
2759 in_degree[ext] = 0;
2760
2761 for (const auto &grp : lt.mixed_sub_layer_groups) {
2762 for (size_t i = 0; i + 1 < grp.components_0based.size(); ++i) {
2763 unsigned int a = grp.components_0based[i];
2764 unsigned int b = grp.components_0based[i + 1];
2765 if (!ext_set.count(a) || !ext_set.count(b))
2766 continue;
2767 if (edge_set.insert({a, b}).second) {
2768 adj[a].push_back(b);
2769 in_degree[b] += 1;
2770 }
2771 }
2772 }
2773
2774 // 2. Record original position (from flush optimizer) as priority.
2775 std::map<unsigned int, size_t> orig_pos;
2776 for (size_t i = 0; i < lt.extruders.size(); ++i)
2777 orig_pos[lt.extruders[i]] = i;
2778
2779 // 3. Kahn's topological sort with priority queue (prefer original position).
2780 auto cmp = [&orig_pos](unsigned int lhs, unsigned int rhs) {
2781 return orig_pos[lhs] > orig_pos[rhs]; // min-heap by orig_pos
2782 };
2783 std::priority_queue<unsigned int, std::vector<unsigned int>, decltype(cmp)> pq(cmp);
2784
2785 for (unsigned int ext : lt.extruders) {
2786 if (in_degree[ext] == 0)
2787 pq.push(ext);
2788 }
2789
2790 std::vector<unsigned int> ordered;
2791 ordered.reserve(lt.extruders.size());
2792 while (!pq.empty()) {
2793 unsigned int ext = pq.top();
2794 pq.pop();
2795 ordered.push_back(ext);
2796 if (auto it = adj.find(ext); it != adj.end()) {
2797 for (unsigned int next : it->second) {
2798 if (--in_degree[next] == 0)
2799 pq.push(next);
2800 }
2801 }
2802 }
2803
2804 // Safety: if topological sort didn't produce all elements, keep original order.
2805 if (ordered.size() != lt.extruders.size())
2806 ordered = lt.extruders;
2807
2808 // 4. Verify: every mixed group's component order is preserved as subsequence.
2809 for (const auto &grp : lt.mixed_sub_layer_groups) {
2810 size_t prev_pos = 0;
2811 bool valid = true;
2812 for (unsigned int c : grp.components_0based) {
2813 if (!ext_set.count(c))
2814 continue;
2815 auto it = std::find(ordered.begin() + prev_pos, ordered.end(), c);
2816 if (it == ordered.end()) { valid = false; break; }
2817 prev_pos = (it - ordered.begin()) + 1;
2818 }
2819 assert(valid && "enforce_mixed_component_order: mixed group subsequence violated");
2820 (void)valid;
2821 }
2822
2823 lt.extruders = ordered;
2824 }
2825}

References enforce_mixed_component_order(), and m_layer_tools.

Referenced by enforce_mixed_component_order(), sort_and_build_data(), and sort_and_build_data().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ fill_wipe_tower_partitions()

void Slic3r::ToolOrdering::fill_wipe_tower_partitions ( const PrintConfig & config,
coordf_t object_bottom_z,
coordf_t max_layer_height )
private
1047{
1048 if (m_layer_tools.empty())
1049 return;
1050
1051 // Count the minimum number of tool changes per layer.
1052 size_t last_extruder = size_t(-1);
1053 for (LayerTools &lt : m_layer_tools) {
1054 lt.wipe_tower_partitions = lt.extruders.size();
1055 if (! lt.extruders.empty()) {
1056 if (last_extruder == size_t(-1) || last_extruder == lt.extruders.front())
1057 // The first extruder on this layer is equal to the current one, no need to do an initial tool change.
1058 -- lt.wipe_tower_partitions;
1059 last_extruder = lt.extruders.back();
1060 }
1061 }
1062
1063 // Propagate the wipe tower partitions down to support the upper partitions by the lower partitions.
1064 for (int i = int(m_layer_tools.size()) - 2; i >= 0; -- i)
1065 m_layer_tools[i].wipe_tower_partitions = std::max(m_layer_tools[i + 1].wipe_tower_partitions, m_layer_tools[i].wipe_tower_partitions);
1066
1067
1068 int wrapping_layer_nums = config.wrapping_detection_layers;
1069 for (size_t i = 0; i < wrapping_layer_nums; ++i) {
1070 if (i >= m_layer_tools.size())
1071 break;
1072 LayerTools &lt = m_layer_tools[i];
1073 lt.has_wipe_tower = config.enable_wrapping_detection;
1074 }
1075
1076 //FIXME this is a hack to get the ball rolling.
1077 // The `print_z < object_bottom_z` clause reads "below the object" as "raft
1078 // gap". On a belt printer that is wrong: the brim apron legitimately prints
1079 // below the object's first layer, and treating those layers as raft would put a
1080 // wipe tower at negative Z. A belt printer never prints the classic
1081 // prime tower (Print::has_wipe_tower()), so simply drop the clause there.
1082 //
1083 // Gate on config.belt_printer, NOT on has_belt_brim: every layer below the
1084 // object bottom on a belt printer is legitimately a sub-object stream - brim
1085 // apron, belt support printed below Z0, or the object's own lead-in - and none of
1086 // them is ever raft, because Print::validate() rejects raft_layers>0 on a belt
1087 // printer outright. Narrowing this to has_belt_brim would reclassify
1088 // belt-support-below-floor layers as raft on brim-less belt prints and reintroduce
1089 // the negative-Z wipe tower, so the broad belt_printer gate is correct.
1090 const bool belt_no_raft_gap = config.belt_printer.value;
1091 for (LayerTools &lt : m_layer_tools)
1092 lt.has_wipe_tower |= ((lt.has_object || lt.has_support) && (config.timelapse_type == TimelapseType::tlSmooth || lt.wipe_tower_partitions > 0))
1093 || (! belt_no_raft_gap && lt.print_z < object_bottom_z + EPSILON);
1094
1095 // Test for a raft, insert additional wipe tower layer to fill in the raft separation gap.
1096 // Skipped on belt printers for the same reason as the clause above: layers
1097 // below the object are brim apron, not raft.
1098 for (size_t i = 0; ! belt_no_raft_gap && i + 1 < m_layer_tools.size(); ++ i) {
1099 const LayerTools &lt = m_layer_tools[i];
1100 const LayerTools &lt_next = m_layer_tools[i + 1];
1101 if (lt.print_z < object_bottom_z + EPSILON && lt_next.print_z >= object_bottom_z + EPSILON) {
1102 // lt is the last raft layer. Find the 1st object layer.
1103 size_t j = i + 1;
1104 for (; j < m_layer_tools.size() && ! m_layer_tools[j].has_wipe_tower; ++ j);
1105 if (j < m_layer_tools.size()) {
1106 const LayerTools &lt_object = m_layer_tools[j];
1107 coordf_t gap = lt_object.print_z - lt.print_z;
1108 assert(gap > 0.f);
1109 if (gap > max_layer_height + EPSILON) {
1110 // Insert one additional wipe tower layer between lh.print_z and lt_object.print_z.
1111 LayerTools lt_new(0.5f * (lt.print_z + lt_object.print_z));
1112 // Find the 1st layer above lt_new.
1113 for (j = i + 1; j < m_layer_tools.size() && m_layer_tools[j].print_z < lt_new.print_z - EPSILON; ++ j);
1114 if (std::abs(m_layer_tools[j].print_z - lt_new.print_z) < EPSILON) {
1115 m_layer_tools[j].has_wipe_tower = true;
1116 } else {
1117 LayerTools &lt_extra = *m_layer_tools.insert(m_layer_tools.begin() + j, lt_new);
1118 //LayerTools &lt_prev = m_layer_tools[j];
1119 LayerTools &lt_next = m_layer_tools[j + 1];
1120 assert(! m_layer_tools[j - 1].extruders.empty() && ! lt_next.extruders.empty());
1121 // FIXME: Following assert tripped when running combine_infill.t. I decided to comment it out for now.
1122 // If it is a bug, it's likely not critical, because this code is unchanged for a long time. It might
1123 // still be worth looking into it more and decide if it is a bug or an obsolete assert.
1124 //assert(lt_prev.extruders.back() == lt_next.extruders.front());
1125 lt_extra.has_wipe_tower = true;
1126 lt_extra.extruders.push_back(lt_next.extruders.front());
1127 lt_extra.wipe_tower_partitions = lt_next.wipe_tower_partitions;
1128 }
1129 }
1130 }
1131 break;
1132 }
1133 }
1134
1135 // Ensure wipe tower vertical continuity:
1136 //
1137 // (1) Any existing LayerTools sandwiched between two has_wipe_tower layers must itself be a
1138 // wipe-tower layer. The LayerTools entry already exists, but it has neither object nor
1139 // support geometry (has_object == false && has_support == false), so the marking pass
1140 // above leaves has_wipe_tower == false. Happens e.g. when one object is fully floating
1141 // above another and the support_top_z_distance / support_bottom_z_distance gap leaves an
1142 // interior layer with no object and no support (e.g. B top z=20.4, A first layer z=20.8,
1143 // the z=20.6 LayerTools entry exists but stays unmarked).
1144 //
1145 // (2) When two adjacent has_wipe_tower layers are farther apart than max_layer_height and no
1146 // LayerTools entry exists between them, insert virtual wipe-tower-only layers to bridge
1147 // the gap. Happens with raft: BambuStudio's raft contact layer can be thicker than
1148 // max_layer_height (e.g. raft base top z=0.2, raft contact top z=0.5 — gap 0.3 > 0.28),
1149 // and there is no LayerTools entry between those two z values.
1150 //
1151 // wipe_tower_partitions has already been max-propagated downward above, so partition counts
1152 // on the filled-in / inserted layers stay consistent.
1153 {
1154 int first_wt_idx = -1;
1155 int last_wt_idx = -1;
1156 for (int i = 0; i < (int)m_layer_tools.size(); ++i)
1158 if (first_wt_idx < 0) first_wt_idx = i;
1159 last_wt_idx = i;
1160 }
1161 for (int i = first_wt_idx + 1; i < last_wt_idx; ++i) {
1162 LayerTools &lt = m_layer_tools[i];
1163 lt.has_wipe_tower = true;
1164 // GCode::process_layer emits wipe-tower G-code inside `for (extruder_id : layer_tools.extruders)`.
1165 // An empty extruders vector here would silently skip wipe tower output, leaving the tower
1166 // physically floating. Seed from the nearest non-empty neighbor so the loop actually runs.
1167 if (lt.extruders.empty()) {
1168 unsigned int seed_extruder = 0;
1169 bool found_seed = false;
1170 for (int j = i - 1; j >= 0; --j)
1171 if (!m_layer_tools[j].extruders.empty()) {
1172 seed_extruder = m_layer_tools[j].extruders.back();
1173 found_seed = true;
1174 break;
1175 }
1176 if (!found_seed)
1177 for (int j = i + 1; j < (int)m_layer_tools.size(); ++j)
1178 if (!m_layer_tools[j].extruders.empty()) {
1179 seed_extruder = m_layer_tools[j].extruders.front();
1180 found_seed = true;
1181 break;
1182 }
1183 if (found_seed)
1184 lt.extruders.push_back(seed_extruder);
1185 }
1186 }
1187
1188 // Walk adjacent has_wipe_tower pairs and split oversized gaps. Re-evaluate the same i
1189 // after each insertion so very large gaps get split into multiple layers.
1190 for (int i = 0; i + 1 < (int)m_layer_tools.size(); ) {
1191 LayerTools &lt = m_layer_tools[i];
1192 LayerTools &lt_next = m_layer_tools[i + 1];
1193 if (!lt.has_wipe_tower || !lt_next.has_wipe_tower) {
1194 ++i;
1195 continue;
1196 }
1197 coordf_t gap = lt_next.print_z - lt.print_z;
1198 if (gap <= max_layer_height + EPSILON) {
1199 ++i;
1200 continue;
1201 }
1202 LayerTools lt_new(0.5 * (lt.print_z + lt_next.print_z));
1203 lt_new.has_wipe_tower = true;
1204 if (!lt_next.extruders.empty())
1205 lt_new.extruders.push_back(lt_next.extruders.front());
1206 else if (!lt.extruders.empty())
1207 lt_new.extruders.push_back(lt.extruders.back());
1208 lt_new.wipe_tower_partitions = lt_next.wipe_tower_partitions;
1209 m_layer_tools.insert(m_layer_tools.begin() + i + 1, lt_new);
1210 }
1211 }
1212
1213 // If the model contains empty layers (such as https://github.com/prusa3d/Slic3r/issues/1266), there might be layers
1214 // that were not marked as has_wipe_tower, even when they should have been. This produces a crash with soluble supports
1215 // and maybe other problems. We will therefore go through layer_tools and detect and fix this.
1216 // So, if there is a non-object layer starting with different extruder than the last one ended with (or containing more than one extruder),
1217 // we'll mark it with has_wipe tower.
1218 for (unsigned int i=0; i+1<m_layer_tools.size(); ++i) {
1219 LayerTools& lt = m_layer_tools[i];
1220 LayerTools& lt_next = m_layer_tools[i+1];
1221 if (lt.extruders.empty() || lt_next.extruders.empty())
1222 break;
1223 if (!lt_next.has_wipe_tower && (lt_next.extruders.front() != lt.extruders.back() || lt_next.extruders.size() > 1))
1224 lt_next.has_wipe_tower = true;
1225 // We should also check that the next wipe tower layer is no further than max_layer_height:
1226 unsigned int j = i+1;
1227 double last_wipe_tower_print_z = lt_next.print_z;
1228 while (++j < m_layer_tools.size()-1 && !m_layer_tools[j].has_wipe_tower)
1229 if (m_layer_tools[j+1].print_z - last_wipe_tower_print_z > max_layer_height + EPSILON) {
1230 if (!config.enable_wrapping_detection)
1231 m_layer_tools[j].has_wipe_tower = true;
1232 last_wipe_tower_print_z = m_layer_tools[j].print_z;
1233 }
1234 }
1235
1236 // Calculate the wipe_tower_layer_height values.
1237 coordf_t wipe_tower_print_z_last = 0.;
1238 for (LayerTools &lt : m_layer_tools)
1239 if (lt.has_wipe_tower) {
1240 lt.wipe_tower_layer_height = lt.print_z - wipe_tower_print_z_last;
1241 wipe_tower_print_z_last = lt.print_z;
1242 }
1243}
bool has_wipe_tower() const
Definition ToolOrdering.hpp:321
unsigned int last_extruder() const
Definition ToolOrdering.hpp:303
@ tlSmooth
Definition PrintConfig.hpp:470

References EPSILON, Slic3r::LayerTools::extruders, fill_wipe_tower_partitions(), Slic3r::LayerTools::has_wipe_tower, has_wipe_tower(), last_extruder(), m_layer_tools, Slic3r::LayerTools::print_z, Slic3r::tlSmooth, and Slic3r::LayerTools::wipe_tower_partitions.

Referenced by fill_wipe_tower_partitions(), sort_and_build_data(), and sort_and_build_data().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ first_extruder()

unsigned int Slic3r::ToolOrdering::first_extruder ( ) const
inline

References m_first_printing_extruder.

Referenced by ToolOrdering(), ToolOrdering(), ToolOrdering(), Slic3r::GCode::_do_export(), Slic3r::CLI::run(), sort_and_build_data(), and sort_and_build_data().

Here is the caller graph for this function:

◆ front()

const LayerTools & Slic3r::ToolOrdering::front ( ) const
inline
315{ return m_layer_tools.front(); }

References m_layer_tools.

Referenced by sort_and_build_data().

Here is the caller graph for this function:

◆ generate_first_layer_tool_order() [1/2]

std::vector< unsigned int > Slic3r::ToolOrdering::generate_first_layer_tool_order ( const Print & print)
private
638{
639 std::vector<unsigned int> tool_order;
640 int initial_extruder_id = -1;
641 std::map<int, double> min_areas_per_extruder;
642
643 for (auto object : print.objects()) {
644 const Layer* target_layer = nullptr;
645 for(auto layer : object->layers()){
646 for(auto layerm : layer->regions()){
647 for(auto& expoly : layerm->raw_slices){
648 if (!offset_ex(expoly, -0.2 * scale_(print.config().initial_layer_line_width)).empty()) {
649 target_layer = layer;
650 break;
651 }
652 }
653 if(target_layer)
654 break;
655 }
656 if(target_layer)
657 break;
658 }
659
660 if(!target_layer)
661 return tool_order;
662
663 for (auto layerm : target_layer->regions()) {
664 int extruder_id = layerm->region().config().option("outer_wall_filament_id")->getInt();
665
666 for (auto expoly : layerm->raw_slices) {
667 const double nozzle_diameter = print.config().nozzle_diameter.get_at(0);
668 const coordf_t initial_layer_line_width = print.config().get_abs_value("initial_layer_line_width", nozzle_diameter);
669
670 if (offset_ex(expoly, -0.2 * scale_(initial_layer_line_width)).empty())
671 continue;
672
673 double contour_area = expoly.contour.area();
674 auto iter = min_areas_per_extruder.find(extruder_id);
675 if (iter == min_areas_per_extruder.end()) {
676 min_areas_per_extruder.insert({ extruder_id, contour_area });
677 }
678 else {
679 if (contour_area < min_areas_per_extruder.at(extruder_id)) {
680 min_areas_per_extruder[extruder_id] = contour_area;
681 }
682 }
683 }
684 }
685 }
686
687 double max_minimal_area = 0.;
688 for (auto ape : min_areas_per_extruder) {
689 auto iter = tool_order.begin();
690 for (; iter != tool_order.end(); iter++) {
691 if (min_areas_per_extruder.at(*iter) < min_areas_per_extruder.at(ape.first))
692 break;
693 }
694
695 tool_order.insert(iter, ape.first);
696 }
697
699
700 return tool_order;
701}
#define scale_(val)
Definition libslic3r.h:106
zaa_minimize_perimeter_height nozzle_diameter((ConfigOptionBool, reduce_infill_retraction))((ConfigOptionBool
static void apply_first_layer_order(const DynamicPrintConfig *config, std::vector< unsigned int > &tool_order)
Definition ToolOrdering.cpp:618
Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta, JoinType joinType, double miterLimit)
Definition ClipperUtils.cpp:605

References Slic3r::apply_first_layer_order(), Slic3r::Print::config(), empty(), m_print_full_config, Slic3r::nozzle_diameter(), Slic3r::Print::objects(), Slic3r::offset_ex(), Slic3r::Layer::regions(), and scale_.

Referenced by ToolOrdering(), and ToolOrdering().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ generate_first_layer_tool_order() [2/2]

std::vector< unsigned int > Slic3r::ToolOrdering::generate_first_layer_tool_order ( const PrintObject & object)
private
704{
705 std::vector<unsigned int> tool_order;
706 int initial_extruder_id = -1;
707 std::map<int, double> min_areas_per_extruder;
708 const Layer* target_layer = nullptr;
709 for(auto layer : object.layers()){
710 for(auto layerm : layer->regions()){
711 for(auto& expoly : layerm->raw_slices){
712 if (!offset_ex(expoly, -0.2 * scale_(object.config().line_width)).empty()) {
713 target_layer = layer;
714 break;
715 }
716 }
717 if(target_layer)
718 break;
719 }
720 if(target_layer)
721 break;
722 }
723
724 if(!target_layer)
725 return tool_order;
726
727 for (auto layerm : target_layer->regions()) {
728 int extruder_id = layerm->region().config().option("outer_wall_filament_id")->getInt();
729 for (auto expoly : layerm->raw_slices) {
730 const double nozzle_diameter = object.print()->config().nozzle_diameter.get_at(0);
731 const coordf_t line_width = object.config().get_abs_value("line_width", nozzle_diameter);
732
733 if (offset_ex(expoly, -0.2 * scale_(line_width)).empty())
734 continue;
735
736 double contour_area = expoly.contour.area();
737 auto iter = min_areas_per_extruder.find(extruder_id);
738 if (iter == min_areas_per_extruder.end()) {
739 min_areas_per_extruder.insert({ extruder_id, contour_area });
740 }
741 else {
742 if (contour_area < min_areas_per_extruder.at(extruder_id)) {
743 min_areas_per_extruder[extruder_id] = contour_area;
744 }
745 }
746 }
747 }
748
749 double max_minimal_area = 0.;
750 for (auto ape : min_areas_per_extruder) {
751 auto iter = tool_order.begin();
752 for (; iter != tool_order.end(); iter++) {
753 if (min_areas_per_extruder.at(*iter) < min_areas_per_extruder.at(ape.first))
754 break;
755 }
756
757 tool_order.insert(iter, ape.first);
758 }
759
761
762 return tool_order;
763}

References Slic3r::apply_first_layer_order(), empty(), m_print_full_config, Slic3r::nozzle_diameter(), Slic3r::offset_ex(), Slic3r::Layer::regions(), and scale_.

Here is the call graph for this function:

◆ get_filament_change_stats()

FilamentChangeStats Slic3r::ToolOrdering::get_filament_change_stats ( FilamentChangeMode mode)
1875{
1876 switch (mode)
1877 {
1884 default:
1885 break;
1886 }
1888}

References get_filament_change_stats(), m_stats_by_multi_extruder_best, m_stats_by_multi_extruder_curr, m_stats_by_single_extruder, MultiExtBest, MultiExtCurr, and SingleExt.

Referenced by Slic3r::GCode::_do_export(), and get_filament_change_stats().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ get_layered_nozzle_group_result()

const MultiNozzleUtils::LayeredNozzleGroupResult & Slic3r::ToolOrdering::get_layered_nozzle_group_result ( ) const
inline
328{ return m_nozzle_group_result; }

References m_nozzle_group_result.

◆ get_most_used_extruder()

int Slic3r::ToolOrdering::get_most_used_extruder ( ) const
inline
323{ return most_used_extruder; }

References most_used_extruder.

Referenced by Slic3r::GCode::process_layers().

Here is the caller graph for this function:

◆ get_nozzle_status()

const MultiNozzleUtils::NozzleStatusRecorder & Slic3r::ToolOrdering::get_nozzle_status ( ) const
inline
334{ return m_nozzle_status; }
MultiNozzleUtils::NozzleStatusRecorder m_nozzle_status
Definition ToolOrdering.hpp:432

References m_nozzle_status.

◆ get_recommended_filament_maps()

MultiNozzleUtils::LayeredNozzleGroupResult Slic3r::ToolOrdering::get_recommended_filament_maps ( const std::vector< std::vector< unsigned int > > & layer_filaments,
const Print * print,
const FilamentMapMode mode,
const std::vector< std::set< int > > & physical_unprintables,
const std::vector< std::set< int > > & geometric_unprintables,
const std::map< int, std::set< NozzleVolumeType > > & unprintable_volumes = {},
const std::unordered_map< int, int > & nozzle_status = {} )
static
1753{
1754 using namespace MultiNozzleUtils;
1755
1756 if (!print || layer_filaments.empty())
1757 return LayeredNozzleGroupResult();
1758
1759 const auto& print_config = print->config();
1760 size_t filament_nums = print_config.filament_colour.values.size();
1761 size_t extruder_nums = print_config.nozzle_diameter.values.size();
1762 auto used_filaments = collect_sorted_used_filaments(layer_filaments);
1763 bool has_multiple_nozzle = std::any_of(print_config.extruder_max_nozzle_count.values.begin(), print_config.extruder_max_nozzle_count.values.end(),
1764 [](int v) { return v > 1; });
1765 bool has_multiple_extruder = extruder_nums > 1;
1766
1767 auto nozzle_list = build_default_nozzle_list(print_config, extruder_nums);
1768
1769 // Manual mode: build directly from the user's filament->extruder map.
1770 if (mode == FilamentMapMode::fmmManual && !has_multiple_nozzle) {
1771 auto manual_filament_map = print_config.filament_map.values;
1772 std::transform(manual_filament_map.begin(), manual_filament_map.end(), manual_filament_map.begin(), [](int v) { return v - 1; });
1773 auto result = LayeredNozzleGroupResult::create(manual_filament_map, nozzle_list, used_filaments);
1774 return result ? *result : LayeredNozzleGroupResult();
1775 }
1776
1777 // Fully-manual mode: build the nozzle-granular result from the config nozzle map.
1779 auto manual_filament_map = print_config.filament_map.values;
1780 std::transform(manual_filament_map.begin(), manual_filament_map.end(), manual_filament_map.begin(), [](int v) { return v - 1; });
1781 float diameter = print_config.nozzle_diameter.values.empty() ? 0.4f : (float)print_config.nozzle_diameter.values.front();
1782 // Orca: create() indexes the volume/nozzle maps per used filament with no bounds check, so
1783 // pass them only when a producer sized them to the filament count (mis-sized maps can
1784 // arrive from stale projects or CLI runs until the per-filament synthesis lands there).
1785 // Without valid maps the fully-manual request cannot be honoured; return the empty result,
1786 // the same failure an unsatisfiable create() yields.
1787 std::optional<LayeredNozzleGroupResult> nozzle_result;
1788 if (print_config.filament_volume_map.values.size() == filament_nums &&
1789 print_config.filament_nozzle_map.values.size() == filament_nums)
1790 nozzle_result = LayeredNozzleGroupResult::create(used_filaments, manual_filament_map, print_config.filament_volume_map.values, print_config.filament_nozzle_map.values, get_extruder_nozzle_stats(print_config.extruder_nozzle_stats.values), diameter);
1791 if (!nozzle_result)
1792 BOOST_LOG_TRIVIAL(error) << "Failed to build nozzle group result from filament nozzle map!";
1793 return nozzle_result ? *nozzle_result : LayeredNozzleGroupResult();
1794 }
1795
1796 int master_extruder_id = print_config.master_extruder_id.value - 1;
1797 std::vector<int> ret(filament_nums, master_extruder_id);
1798
1799 // Non-BBL multi-extruder printers do not support filament grouping: filament id == extruder id.
1800 if (has_multiple_extruder && !print->is_BBL_printer()) {
1801 for (size_t i = 0; i < filament_nums && i < extruder_nums; i++)
1802 ret[i] = (int)i;
1803 auto result_opt = LayeredNozzleGroupResult::create(ret, nozzle_list, used_filaments);
1804 return result_opt ? *result_opt : LayeredNozzleGroupResult();
1805 }
1806
1807 if (has_multiple_extruder || has_multiple_nozzle) {
1808 auto context = build_filament_group_context(print, layer_filaments, physical_unprintables, geometric_unprintables, unprintable_volumes, mode, nozzle_status);
1809
1810 // other_layers_seq custom-sequence lambda (1-based layer/extruder). Only threaded for the
1811 // single-nozzle-per-extruder engine.
1812 std::vector<LayerPrintSequence> other_layers_seqs = get_other_layers_print_sequence(print_config.other_layers_print_sequence_nums.value, print_config.other_layers_print_sequence.values);
1813 auto get_custom_seq = [other_layers_seqs](int layer_idx, std::vector<int>& out_seq) -> bool {
1814 for (size_t idx = other_layers_seqs.size() - 1; idx != size_t(-1); --idx) {
1815 const auto& other_layers_seq = other_layers_seqs[idx];
1816 if (layer_idx + 1 >= other_layers_seq.first.first && layer_idx + 1 <= other_layers_seq.first.second) {
1817 out_seq = other_layers_seq.second;
1818 return true;
1819 }
1820 }
1821 return false;
1822 };
1823
1824 if (has_multiple_nozzle && mode == FilamentMapMode::fmmManual) {
1825 auto manual_filament_map = print_config.filament_map.values;
1826 std::transform(manual_filament_map.begin(), manual_filament_map.end(), manual_filament_map.begin(), [](int v) { return v - 1; });
1827 ret = calc_filament_group_for_manual_multi_nozzle(manual_filament_map, context);
1828 } else if (has_multiple_nozzle && mode == FilamentMapMode::fmmAutoForMatch) {
1830 } else {
1831 // TPU: keep the dedicated TPU split for single-nozzle-per-extruder printers.
1832 auto tpu_filaments = get_filament_by_type(used_filaments, &print_config, "TPU");
1833 if (!has_multiple_nozzle && !tpu_filaments.empty()) {
1834 ret = std::vector<int>(context.group_info.total_filament_num, context.machine_info.master_extruder_id);
1835 for (size_t fidx = 0; fidx < (size_t)context.group_info.total_filament_num; ++fidx)
1836 ret[fidx] = tpu_filaments.count((int)fidx) ? context.machine_info.master_extruder_id : (1 - context.machine_info.master_extruder_id);
1837 } else {
1838 FilamentGroup fg(context);
1839 if (!has_multiple_nozzle)
1840 fg.get_custom_seq = get_custom_seq;
1841 ret = fg.calc_filament_group();
1842 // Flush-mode auto grouping: restore the master-extruder preference
1843 // (optimize_group_for_master_extruder). Match mode assigns by AMS colour and never had
1844 // it. Kept out of the FilamentGroup engine so the test harness is unaffected.
1845 if (context.group_info.mode == FGMode::FlushMode)
1846 ret = apply_master_extruder_preference(context, used_filaments, ret);
1847 }
1848 }
1849
1850 if (has_multiple_nozzle) {
1851 auto result_opt = LayeredNozzleGroupResult::create(ret, context.nozzle_info.nozzle_list, used_filaments);
1852 if (!result_opt)
1853 return LayeredNozzleGroupResult();
1854 auto result = *result_opt;
1855 if (mode == FilamentMapMode::fmmManual) {
1856 // Manual grouping must reproduce the user's filament->extruder map exactly; a
1857 // deviation means the requested assignment cannot be satisfied by the nozzle
1858 // inventory, which must surface as a slicing error instead of silently regrouping.
1859 auto result_map = result.get_extruder_map();
1860 for (auto fid : used_filaments) {
1861 if (result_map[fid] != print_config.filament_map.values[fid] - 1) {
1862 throw Slic3r::RuntimeError(_L("Group error in manual mode. Please check nozzle count or regroup."));
1863 }
1864 }
1865 }
1866 return result;
1867 }
1868 }
1869
1870 auto result_opt = LayeredNozzleGroupResult::create(ret, nozzle_list, used_filaments);
1871 return result_opt ? *result_opt : LayeredNozzleGroupResult();
1872}
#define _L(s)
Definition OBJ.cpp:34
zaa_minimize_perimeter_height master_extruder_id((ConfigOptionStrings, printer_extruder_variant))((ConfigOptionBool
std::vector< std::map< NozzleVolumeType, int > > get_extruder_nozzle_stats(const std::vector< std::string > &strs)
Definition PrintConfig.cpp:940
std::vector< LayerPrintSequence > get_other_layers_print_sequence(int sequence_nums, const std::vector< int > &sequence)
Definition ParameterUtils.cpp:11
static std::set< int > get_filament_by_type(const std::vector< unsigned int > &used_filaments, const PrintConfig *print_config, const std::string &type)
Definition ToolOrdering.cpp:74
std::vector< int > calc_filament_group_for_manual_multi_nozzle(const std::vector< int > &filament_map_manual, const FilamentGroupContext &ctx)
Definition FilamentGroup.cpp:1495
std::vector< unsigned int > collect_sorted_used_filaments(const std::vector< std::vector< unsigned int > > &layer_filaments)
Definition FilamentGroup.cpp:563
static std::vector< MultiNozzleUtils::NozzleInfo > build_default_nozzle_list(const PrintConfig &print_config, size_t extruder_nums)
Definition ToolOrdering.cpp:1892
static std::vector< int > apply_master_extruder_preference(const FilamentGroupContext &ctx, const std::vector< unsigned int > &used_filaments, std::vector< int > nozzle_ret)
Definition ToolOrdering.cpp:1716
@ FlushMode
Definition FilamentGroup.hpp:36
@ fmmAutoForMatch
Definition PrintConfig.hpp:634
@ fmmNozzleManual
Definition PrintConfig.hpp:636
@ fmmManual
Definition PrintConfig.hpp:635
std::vector< int > calc_filament_group_for_match_multi_nozzle(const FilamentGroupContext &ctx)
Definition FilamentGroup.cpp:1350

References _L, Slic3r::apply_master_extruder_preference(), Slic3r::build_default_nozzle_list(), Slic3r::build_filament_group_context(), Slic3r::FilamentGroup::calc_filament_group(), Slic3r::calc_filament_group_for_manual_multi_nozzle(), Slic3r::calc_filament_group_for_match_multi_nozzle(), Slic3r::collect_sorted_used_filaments(), Slic3r::Print::config(), Slic3r::Print::empty(), Slic3r::FlushMode, Slic3r::fmmAutoForMatch, Slic3r::fmmManual, Slic3r::fmmNozzleManual, Slic3r::FilamentGroup::get_custom_seq, Slic3r::get_extruder_nozzle_stats(), Slic3r::get_filament_by_type(), Slic3r::get_other_layers_print_sequence(), get_recommended_filament_maps(), Slic3r::Print::is_BBL_printer(), and Slic3r::master_extruder_id().

Referenced by get_recommended_filament_maps(), and Slic3r::plan_filament_mapping_and_order_by_combo_ranges().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ handle_dontcare_extruder() [1/2]

void Slic3r::ToolOrdering::handle_dontcare_extruder ( const std::vector< unsigned int > & first_layer_tool_order)
255{
256 const PrintConfig* print_config = m_print_config_ptr;
257 if (!print_config && m_print_object_ptr)
258 print_config = &m_print_object_ptr->print()->config();
259
260 if(m_layer_tools.empty() || tool_order_layer0.empty())
261 return;
262
263 // Reorder the extruders of first layer
264 {
265 LayerTools& lt = m_layer_tools[0];
266 std::vector<unsigned int> layer0_extruders = lt.extruders;
267 lt.extruders.clear();
268 for (unsigned int extruder_id : tool_order_layer0) {
269 auto iter = std::find(layer0_extruders.begin(), layer0_extruders.end(), extruder_id);
270 if (iter != layer0_extruders.end()) {
271 lt.extruders.push_back(extruder_id);
272 *iter = (unsigned int)-1;
273 }
274 }
275
276 for (unsigned int extruder_id : layer0_extruders) {
277 if (extruder_id == 0)
278 continue;
279
280 if (extruder_id != (unsigned int)-1)
281 lt.extruders.push_back(extruder_id);
282 }
283
284 // all extruders are zero
285 if (lt.extruders.empty()) {
286 lt.extruders.push_back(tool_order_layer0[0]);
287 }
288 }
289
290 int last_extruder_id = m_layer_tools[0].extruders.back();
291 for (int i = 1; i < m_layer_tools.size(); i++) {
292 LayerTools& lt = m_layer_tools[i];
293
294 // Extruders in lt.extruders are already sorted.
295
296 if (lt.extruders.empty())
297 continue;
298 if (lt.extruders.size() == 1 && lt.extruders.front() == 0)
299 lt.extruders.front() = last_extruder_id;
300 else {
301 if (lt.extruders.front() == 0)
302 // Pop the "don't care" extruder, the "don't care" region will be merged with the next one.
303 lt.extruders.erase(lt.extruders.begin());
304
305 if (print_config == nullptr
306 || print_config->toolchange_ordering == ToolChangeOrderingType::Default)
307 {
308 // Reorder the extruders to start with the last one.
309 for (size_t i = 1; i < lt.extruders.size(); ++i) {
310 if (lt.extruders[i] == last_extruder_id) {
311 // Move the last extruder to the front.
312 std::rotate(
313 lt.extruders.begin(),
314 lt.extruders.begin() + i,
315 lt.extruders.begin() + i + 1
316 );
317 break;
318 }
319 }
320 }
321 }
322 last_extruder_id = lt.extruders.back();
323 }
324
325 // Reindex the extruders, so they are zero based, not 1 based.
326 for (LayerTools& lt : m_layer_tools){
327 for (unsigned int& extruder_id : lt.extruders) {
328 assert(extruder_id > 0);
329 --extruder_id;
330 }
331 }
332}
@ Default
Definition PrintConfig.hpp:492

References Slic3r::Default, Slic3r::LayerTools::extruders, m_layer_tools, m_print_config_ptr, and m_print_object_ptr.

Referenced by ToolOrdering(), and ToolOrdering().

Here is the caller graph for this function:

◆ handle_dontcare_extruder() [2/2]

void Slic3r::ToolOrdering::handle_dontcare_extruder ( unsigned int first_extruder)
335{
336 const PrintConfig* print_config = m_print_config_ptr;
337 if (!print_config && m_print_object_ptr)
338 print_config = &m_print_object_ptr->print()->config();
339
340 if(m_layer_tools.empty())
341 return;
342 if(last_extruder_id == (unsigned int)-1){
343 // The initial print extruder has not been decided yet.
344 // Initialize the last_extruder_id with the first non-zero extruder id used for the print.
345 last_extruder_id = 0;
346 for (size_t i = 0; i < m_layer_tools.size() && last_extruder_id == 0; ++ i) {
347 const LayerTools &lt = m_layer_tools[i];
348 for (unsigned int extruder_id : lt.extruders)
349 if (extruder_id > 0) {
350 last_extruder_id = extruder_id;
351 break;
352 }
353 }
354 if (last_extruder_id == 0)
355 // Nothing to extrude.
356 return;
357 }else{
358 // 1 based idx
359 ++ last_extruder_id;
360 }
361
362 for (LayerTools &lt : m_layer_tools) {
363 // Extruders in lt.extruders are already sorted.
364
365 if (lt.extruders.empty())
366 continue;
367 if (lt.extruders.size() == 1 && lt.extruders.front() == 0)
368 lt.extruders.front() = last_extruder_id;
369 else {
370 if (lt.extruders.front() == 0)
371 // Pop the "don't care" extruder, the "don't care" region will be merged with the next one.
372 lt.extruders.erase(lt.extruders.begin());
373
374 if (print_config == nullptr
375 || print_config->toolchange_ordering == ToolChangeOrderingType::Default)
376 {
377 // Reorder the extruders to start with the last one.
378 for (size_t i = 1; i < lt.extruders.size(); ++i) {
379 if (lt.extruders[i] == last_extruder_id) {
380 // Move the last extruder to the front.
381 std::rotate(
382 lt.extruders.begin(),
383 lt.extruders.begin() + i,
384 lt.extruders.begin() + i + 1
385 );
386 break;
387 }
388 }
389 }
390
391 if (lt == m_layer_tools[0]) {
392 // On first layer with wipe tower, prefer a soluble extruder
393 // at the beginning, so it is not wiped on the first layer.
394 if (print_config && print_config->enable_prime_tower) {
395 for (size_t i = 0; i<lt.extruders.size(); ++i)
396 if (print_config->filament_soluble.get_at(lt.extruders[i]-1)) { // 1-based...
397 std::swap(lt.extruders[i], lt.extruders.front());
398 break;
399 }
400 }
401
402 // Then, if we specified the tool order, apply it now
404 }
405
406 }
407 last_extruder_id = lt.extruders.back();
408 }
409
410 // Reindex the extruders, so they are zero based, not 1 based.
411 for (LayerTools &lt : m_layer_tools){
412 for (unsigned int &extruder_id : lt.extruders) {
413 assert(extruder_id > 0);
414 -- extruder_id;
415 }
416 }
417}

References Slic3r::apply_first_layer_order(), Slic3r::Default, Slic3r::LayerTools::extruders, m_layer_tools, m_print_config_ptr, m_print_full_config, and m_print_object_ptr.

Here is the call graph for this function:

◆ has_non_support_filament()

bool Slic3r::ToolOrdering::has_non_support_filament ( const PrintConfig & config)
1395 {
1396 for (const unsigned int &filament : m_all_printing_extruders) {
1397 if (!config.filament_is_support.get_at(filament)) {
1398 return true;
1399 }
1400 }
1401
1402 return false;
1403}

References has_non_support_filament(), and m_all_printing_extruders.

Referenced by cal_non_support_filaments(), and has_non_support_filament().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ has_wipe_tower()

bool Slic3r::ToolOrdering::has_wipe_tower ( ) const
inline
321{ return ! m_layer_tools.empty() && m_first_printing_extruder != (unsigned int)-1 && m_layer_tools.front().has_wipe_tower; }

References m_first_printing_extruder, and m_layer_tools.

Referenced by Slic3r::GCode::_do_export(), and fill_wipe_tower_partitions().

Here is the caller graph for this function:

◆ initialize_layers()

void Slic3r::ToolOrdering::initialize_layers ( std::vector< coordf_t > & zs)
private
766{
768 // Merge numerically very close Z values.
769 for (size_t i = 0; i < zs.size();) {
770 // Find the last layer with roughly the same print_z.
771 size_t j = i + 1;
772 coordf_t zmax = zs[i] + EPSILON;
773 for (; j < zs.size() && zs[j] <= zmax; ++ j) ;
774 // Assign an average print_z to the set of layers with nearly equal print_z.
775 m_layer_tools.emplace_back(LayerTools(0.5 * (zs[i] + zs[j-1])));
776 i = j;
777 }
778}

References EPSILON, m_layer_tools, and Slic3r::sort_remove_duplicates().

Referenced by ToolOrdering(), and ToolOrdering().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ insert_wipe_tower_extruder()

bool Slic3r::ToolOrdering::insert_wipe_tower_extruder ( )
private
420{
421 if (!m_print_config_ptr || !m_print_config_ptr->enable_prime_tower)
422 return false;
423 // Belt mode has no classic wipe tower; the dedicated wipe tower filament
424 // must not inject extra toolchanges into the purge prism planning.
425 if (m_print_config_ptr->belt_printer)
426 return false;
427 if (m_print_config_ptr->wipe_tower_filament == 0)
428 return false;
429
430 bool changed = false;
431 const unsigned int wipe_extruder = (unsigned int)(m_print_config_ptr->wipe_tower_filament - 1);
432 for (LayerTools &lt : m_layer_tools) {
433 if (lt.wipe_tower_partitions > 0) {
434 if (std::find(lt.extruders.begin(), lt.extruders.end(), wipe_extruder) == lt.extruders.end()) {
435 lt.extruders.emplace_back(wipe_extruder);
436 changed = true;
437 }
438 }
439 }
440 return changed;
441}

References m_layer_tools, and m_print_config_ptr.

Referenced by sort_and_build_data(), and sort_and_build_data().

Here is the caller graph for this function:

◆ is_last_extrusion_layer()

bool Slic3r::ToolOrdering::is_last_extrusion_layer ( coordf_t print_z,
unsigned int extruder_id ) const
3663{
3664 auto it = m_last_layer_per_extruder.find(extruder_id);
3665 if (it == m_last_layer_per_extruder.end())
3666 return true;
3667 return print_z >= it->second - EPSILON;
3668}

References EPSILON, is_last_extrusion_layer(), and m_last_layer_per_extruder.

Referenced by is_last_extrusion_layer().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ last_extruder()

unsigned int Slic3r::ToolOrdering::last_extruder ( ) const
inline
303{ return m_last_printing_extruder; }

References m_last_printing_extruder.

Referenced by Slic3r::GCode::_do_export(), and fill_wipe_tower_partitions().

Here is the caller graph for this function:

◆ layer_tools()

std::vector< LayerTools > & Slic3r::ToolOrdering::layer_tools ( )
inline
320{ return m_layer_tools; }

References m_layer_tools.

Referenced by Slic3r::GCode::_do_export(), cal_max_additional_fan(), cal_most_used_extruder(), and collect_extruders().

Here is the caller graph for this function:

◆ mark_skirt_layers()

void Slic3r::ToolOrdering::mark_skirt_layers ( const PrintConfig & config,
coordf_t max_layer_height )
private
3208{
3209 if (m_layer_tools.empty())
3210 return;
3211
3212 if (m_layer_tools.front().extruders.empty()) {
3213 // Empty first layer, no skirt will be printed.
3214 //FIXME throw an exception?
3215 return;
3216 }
3217
3218 size_t i = 0;
3219 for (;;) {
3220 m_layer_tools[i].has_skirt = true;
3221 size_t j = i + 1;
3222 for (; j < m_layer_tools.size() && ! m_layer_tools[j].has_object; ++ j);
3223 // i and j are two successive layers printing an object.
3224 if (j == m_layer_tools.size())
3225 // Don't print skirt above the last object layer.
3226 break;
3227 // Mark some printing intermediate layers as having skirt.
3228 double last_z = m_layer_tools[i].print_z;
3229 for (size_t k = i + 1; k < j; ++ k) {
3230 if (m_layer_tools[k + 1].print_z - last_z > max_layer_height + EPSILON) {
3231 // Layer k is the last one not violating the maximum layer height.
3232 // Don't extrude skirt on empty layers.
3233 while (m_layer_tools[k].extruders.empty())
3234 -- k;
3235 if (m_layer_tools[k].has_skirt) {
3236 // Skirt cannot be generated due to empty layers, there would be a missing layer in the skirt.
3237 //FIXME throw an exception?
3238 break;
3239 }
3240 m_layer_tools[k].has_skirt = true;
3241 last_z = m_layer_tools[k].print_z;
3242 }
3243 }
3244 i = j;
3245 }
3246}
bool has_skirt(const DynamicPrintConfig &cfg)
Definition PrintConfig.cpp:14405

References EPSILON, Slic3r::has_skirt(), m_layer_tools, and mark_skirt_layers().

Referenced by ToolOrdering(), ToolOrdering(), and mark_skirt_layers().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ reorder_extruders_for_minimum_flush_volume()

void Slic3r::ToolOrdering::reorder_extruders_for_minimum_flush_volume ( bool reorder_first_layer)
private
2850{
2851 const PrintConfig* print_config = m_print_config_ptr;
2852 if (!print_config && m_print_object_ptr) {
2853 print_config = &(m_print_object_ptr->print()->config());
2854 }
2855
2856 if (!print_config || m_layer_tools.empty())
2857 return;
2858
2859 const unsigned int number_of_extruders = (unsigned int)(print_config->filament_colour.values.size() + EPSILON);
2860
2861 using FlushMatrix = std::vector<std::vector<float>>;
2862 size_t nozzle_nums = print_config->nozzle_diameter.values.size();
2863 const auto wipe_tower_type = m_print->wipe_tower_type();
2864
2865 std::vector<FlushMatrix> nozzle_flush_mtx;
2866 for (size_t nozzle_id = 0; nozzle_id < nozzle_nums; ++nozzle_id) {
2867 std::vector<float> flush_matrix(cast<float>(get_flush_volumes_matrix(print_config->flush_volumes_matrix.values, nozzle_id, nozzle_nums)));
2868 std::vector<std::vector<float>> wipe_volumes;
2869 if ((print_config->purge_in_prime_tower && print_config->single_extruder_multi_material) || wipe_tower_type == WipeTowerType::Type1) {
2870 for (unsigned int i = 0; i < number_of_extruders; ++i)
2871 wipe_volumes.push_back(std::vector<float>(flush_matrix.begin() + i * number_of_extruders, flush_matrix.begin() + (i + 1) * number_of_extruders));
2872 } else {
2873 // populate wipe_volumes with prime_volume
2874 for (unsigned int i = 0; i < number_of_extruders; ++i)
2875 wipe_volumes.push_back(std::vector<float>(number_of_extruders, print_config->prime_volume));
2876 }
2877 nozzle_flush_mtx.emplace_back(wipe_volumes);
2878 }
2879
2880 // Fast purge mode uses flush_multiplier_fast; Default is inert.
2881 auto flush_multiplies = (print_config->prime_volume_mode == PrimeVolumeMode::pvmFast) ? print_config->flush_multiplier_fast.values
2882 : print_config->flush_multiplier.values;
2883 flush_multiplies.resize(nozzle_nums, 1);
2884 for (size_t nozzle_id = 0; nozzle_id < nozzle_nums; ++nozzle_id) {
2885 for (auto& vec : nozzle_flush_mtx[nozzle_id]) {
2886 for (auto& v : vec)
2887 v *= flush_multiplies[nozzle_id];
2888 }
2889 }
2890
2891 std::vector<int>filament_maps(number_of_extruders, 0);
2893
2894 std::vector<std::vector<unsigned int>> layer_filaments;
2895 for (auto& lt : m_layer_tools) {
2896 layer_filaments.emplace_back(lt.extruders);
2897 }
2898
2899 std::vector<unsigned int> used_filaments = collect_sorted_used_filaments(layer_filaments);
2900
2901 std::vector<std::set<int>>geometric_unprintables = m_print->get_geometric_unprintable_filaments();
2902
2903 // Unprintable sets are keyed by filament id, but a mixed-color slot is virtual: what actually
2904 // reaches the nozzle are its components. Expand the slot to those components so a geometric
2905 // restriction is applied to the filaments really being printed. No-op without mixed filaments.
2906 {
2907 const auto &is_mixed = m_print->config().filament_is_mixed.values;
2908 const auto &comp_strs = m_print->config().filament_mixed_components.values;
2909 if (has_any_mixed_filament(is_mixed))
2910 expand_mixed_slots_in_unprintables(geometric_unprintables, is_mixed, comp_strs);
2911 }
2912
2913 std::vector<std::set<int>>physical_unprintables = m_print->get_physical_unprintable_filaments(used_filaments);
2914 auto filament_unprintable_volumes = m_print->get_filament_unprintable_flow(used_filaments);
2915
2916 filament_maps = m_print->get_filament_maps();
2917 map_mode = m_print->get_filament_map_mode();
2918
2919 // Grouping now yields a nozzle-aware LayeredNozzleGroupResult; the
2920 // extruder-level filament_maps that feeds the ordering/stats below is derived from it.
2921 MultiNozzleUtils::LayeredNozzleGroupResult grouping_result;
2922
2923 // The custom-sequence machinery is built before the grouping decision so both the static reorder
2924 // and the dynamic per-layer plan can share it. Pure local setup (no dependency on the
2925 // grouping result), so hoisting it above the branch does not change the static path's output.
2926 std::vector<std::vector<unsigned int>>filament_sequences;
2927 std::vector<unsigned int>filament_lists(number_of_extruders);
2928 std::iota(filament_lists.begin(), filament_lists.end(), 0);
2929
2930 std::vector<LayerPrintSequence> other_layers_seqs;
2931 const ConfigOptionInts* other_layers_print_sequence_op = print_config->option<ConfigOptionInts>("other_layers_print_sequence");
2932 const ConfigOptionInt* other_layers_print_sequence_nums_op = print_config->option<ConfigOptionInt>("other_layers_print_sequence_nums");
2933 if (other_layers_print_sequence_op && other_layers_print_sequence_nums_op) {
2934 const std::vector<int>& print_sequence = other_layers_print_sequence_op->values;
2935 int sequence_nums = other_layers_print_sequence_nums_op->value;
2936 other_layers_seqs = get_other_layers_print_sequence(sequence_nums, print_sequence);
2937 }
2938
2939 std::vector<unsigned int>first_layer_filaments;
2940 if (!m_layer_tools.empty())
2941 first_layer_filaments = m_layer_tools[0].extruders;
2942
2943 const bool use_cyclic_ordering =
2944 (print_config->toolchange_ordering == ToolChangeOrderingType::Cyclic);
2945
2946 // By default the first layer keeps its adhesion-optimized order (and any custom first layer
2947 // sequence); the cyclic sequence is only forced onto it when the user opts in.
2948 const bool cyclic_first_layer = use_cyclic_ordering && print_config->toolchange_cyclic_first_layer.value;
2949
2950 // Optional user defined cyclic sequence, given as 1-based filament numbers ("3,2,1,4"). Filaments
2951 // missing from it keep their ascending order after the listed ones, so a partial or bogus entry
2952 // still yields the default cyclic order.
2953 const std::vector<unsigned int> cyclic_order =
2954 use_cyclic_ordering ? parse_cyclic_order(print_config->toolchange_cyclic_order.value, number_of_extruders)
2955 : std::vector<unsigned int>();
2956
2957 // Reorder a layer's filaments (0-based) for cyclic ordering: ascending by default, or following the
2958 // user defined sequence when one was given. Filaments absent from the sequence keep ascending order
2959 // after the listed ones.
2960 auto apply_cyclic_order = [&cyclic_order](std::vector<unsigned int>& filaments) {
2961 std::sort(filaments.begin(), filaments.end());
2962 if (!cyclic_order.empty())
2963 std::stable_sort(filaments.begin(), filaments.end(), [&cyclic_order](unsigned int lhs, unsigned int rhs) {
2964 auto rank = [&cyclic_order](unsigned int filament) {
2965 return size_t(std::find(cyclic_order.begin(), cyclic_order.end(), filament) - cyclic_order.begin());
2966 };
2967 return rank(lhs) < rank(rhs);
2968 });
2969 };
2970
2971 // other_layers_seq: the layer_idx and extruder_idx are base on 1
2972 auto get_custom_seq = [&other_layers_seqs, &reorder_first_layer, &first_layer_filaments, &layer_filaments, use_cyclic_ordering, cyclic_first_layer, &apply_cyclic_order](int layer_idx, std::vector<int>& out_seq) -> bool {
2973 if (!reorder_first_layer && layer_idx == 0) {
2974 // The first layer tool order is already decided (adhesion-optimized, plus any custom first
2975 // layer sequence). Only override it with the cyclic sequence when the user opted in.
2976 std::vector<unsigned int> ordered = first_layer_filaments;
2977 if (cyclic_first_layer)
2978 apply_cyclic_order(ordered);
2979 out_seq.resize(ordered.size());
2980 std::transform(ordered.begin(), ordered.end(), out_seq.begin(), [](auto item) {return int(item) + 1; });
2981 return true;
2982 }
2983 for (size_t idx = other_layers_seqs.size() - 1; idx != size_t(-1); --idx) {
2984 const auto& other_layers_seq = other_layers_seqs[idx];
2985 if (layer_idx + 1 >= other_layers_seq.first.first && layer_idx + 1 <= other_layers_seq.first.second) {
2986 out_seq = other_layers_seq.second;
2987 return true;
2988 }
2989 }
2990
2991 // Skip the first layer here (layer_idx == 0 only reaches this point on the reorder_first_layer
2992 // path) unless the user asked for cyclic order on it, so it keeps the default flush ordering.
2993 if (use_cyclic_ordering && layer_idx >= 0 && (layer_idx != 0 || cyclic_first_layer)
2994 && size_t(layer_idx) < layer_filaments.size()) {
2995 std::vector<unsigned int> ordered = layer_filaments[size_t(layer_idx)];
2996 apply_cyclic_order(ordered);
2997 out_seq.resize(ordered.size());
2998 std::transform(ordered.begin(), ordered.end(), out_seq.begin(), [](auto item) { return int(item) + 1; });
2999 return true;
3000 }
3001
3002 return false;
3003 };
3004
3005 // Dynamic (per-layer filament-selector) regroup. is_dynamic_group_reorder()
3006 // gates on enable_filament_dynamic_map (unset on every current profile), so this is closed for the
3007 // whole shipping fleet AND H2C static mode — the static branch below is the only one they take, so
3008 // their g-code is byte-identical. Only an H2C profile that enables the selector opens this branch.
3009 const bool dynamic_reorder = m_print && m_print->is_dynamic_group_reorder();
3010 // Orca: there is no is_sequential_print() helper, so the not-sequential check is mirrored with
3011 // the same predicate the static by-object gate below uses. Sequential prints (with more than
3012 // one object) publish and write back from the by-object branch in Print::process instead of
3013 // from each per-object ordering.
3014 const bool not_sequential = print_config->print_sequence != PrintSequence::ByObject ||
3015 (m_print && m_print->objects().size() == 1);
3016
3017 if (dynamic_reorder) {
3018 // Build the grouping context, plan per-combo-range nozzle maps + filament orders, then wrap the
3019 // per-layer maps in a selector (4-arg create) result — which sets support_dynamic_nozzle_map and
3020 // lights the GCode per-layer hotend/nozzle placeholders. filament_sequences is produced here, so
3021 // the static reorder below is skipped for this branch.
3022 auto grouping_context = build_filament_group_context(m_print, layer_filaments, physical_unprintables, geometric_unprintables, filament_unprintable_volumes, FilamentMapMode::fmmAutoForFlush,
3023 m_initial_nozzle_status.get_nozzle_filament_map());
3024 // The time estimator's per-extruder print times are global and do not apply to per-range
3025 // grouping.
3026 grouping_context.speed_info.group_with_time = false;
3027
3029 auto dynamic_plan_res = plan_filament_mapping_and_order_by_combo_ranges(m_print, grouping_context, get_custom_seq, FilamentMapMode::fmmAutoForFlush,
3030 physical_unprintables, geometric_unprintables, filament_unprintable_volumes, &m_nozzle_status);
3031
3032 std::vector<std::vector<int>> nozzle_map_per_layer;
3033 for (auto& res : dynamic_plan_res) {
3034 filament_sequences.emplace_back(cast<unsigned int>(res.fil_order));
3035 nozzle_map_per_layer.emplace_back(res.fil_nozzle_match);
3036 }
3037
3038 auto result = MultiNozzleUtils::LayeredNozzleGroupResult::create(nozzle_map_per_layer, grouping_context.nozzle_info.nozzle_list, used_filaments, filament_sequences);
3039 grouping_result = result ? *result : MultiNozzleUtils::LayeredNozzleGroupResult();
3040
3041 // Derive the extruder-level map for the stats path; the write-back resolves the
3042 // per-variant slots from the full grouping result itself.
3043 std::vector<int> derived_maps = grouping_result.get_extruder_map(false); // 1-based
3044 if (!derived_maps.empty()) {
3045 filament_maps = derived_maps;
3046 // A sequential per-object plan must not write its own map: the objects' plans are
3047 // stitched print-wide afterwards and written back once from there.
3048 if (not_sequential)
3049 m_print->update_to_config_by_nozzle_group_result(grouping_result);
3050 }
3051 std::transform(filament_maps.begin(), filament_maps.end(), filament_maps.begin(), [](int value) { return value - 1; });
3052 }
3053 // only check and map in sequence mode, in by object mode, we check the map in print.cpp
3054 else if (print_config->print_sequence != PrintSequence::ByObject || m_print->objects().size() == 1) {
3055 grouping_result = ToolOrdering::get_recommended_filament_maps(layer_filaments, m_print, map_mode, physical_unprintables, geometric_unprintables, filament_unprintable_volumes);
3056 std::vector<int> derived_maps = grouping_result.get_extruder_map(false); // 1-based extruder map
3057
3058 if (map_mode < FilamentMapMode::fmmManual) {
3059 if (derived_maps.empty())
3060 return;
3061 filament_maps = derived_maps;
3062 } else if (!derived_maps.empty()) {
3063 // Manual modes: the result mirrors the user's config map; adopt it for consistency.
3064 filament_maps = derived_maps;
3065 }
3066 // Write the maps back for every mode: used filaments adopt the engine's extruder/nozzle
3067 // choice, unused ones keep their config assignment. In manual modes the extruder map
3068 // mirrors the user's map (a deviation throws in get_recommended_filament_maps).
3069 if (!derived_maps.empty()) {
3070 // Orca: the config maps are the merge base; fall back to a synthesized base when no
3071 // producer sized them to the filament count (CLI runs until the per-filament
3072 // synthesis lands there), where indexing per filament would run out of bounds.
3073 std::vector<int> base_filament_map = print_config->filament_map.values;
3074 if (base_filament_map.size() != derived_maps.size())
3075 base_filament_map.assign(derived_maps.size(), 1);
3076 std::vector<int> base_volume_map = print_config->filament_volume_map.values;
3077 if (base_volume_map.size() != derived_maps.size())
3078 base_volume_map.assign(derived_maps.size(), (int)nvtStandard);
3079 m_print->update_filament_maps_to_config(FilamentGroupUtils::update_used_filament_values(base_filament_map, derived_maps, used_filaments),
3080 FilamentGroupUtils::update_used_filament_values(base_volume_map, grouping_result.get_volume_map(), used_filaments),
3081 grouping_result.get_nozzle_map());
3082 }
3083 std::transform(filament_maps.begin(), filament_maps.end(), filament_maps.begin(), [](int value) { return value - 1; });
3084
3085 if (m_print->is_BBL_printer())
3086 check_filament_printable_after_group(used_filaments, filament_maps, print_config);
3087 }
3088 else {
3089 // by-object: grouping was decided in Print.cpp; just wrap the (0-based) config map.
3090 std::transform(filament_maps.begin(), filament_maps.end(), filament_maps.begin(), [](int value) {return value - 1; });
3091 grouping_result = build_group_result_from_map(*print_config, filament_maps, used_filaments);
3092 }
3093
3094 // The grouping result comes from the nozzle-centric engine. For single-nozzle-per-extruder printers
3095 // (incl. H2D) each filament resolves to nozzle_id == extruder_id, so GCode's static hotend/nozzle
3096 // placeholders are unchanged; H2C/A2L resolve to a nozzle-granular result (dynamic mode
3097 // resolves per-layer). GCode consumes this via Print::get_layered_nozzle_group_result().
3098 m_nozzle_group_result = grouping_result;
3099 // Orca: the ToolOrdering member is stored unconditionally, but the Print-level store is gated
3100 // behind the not-sequential check hoisted above.
3101 if (m_print != nullptr && not_sequential)
3102 m_print->set_nozzle_group_result(std::make_shared<MultiNozzleUtils::LayeredNozzleGroupResult>(m_nozzle_group_result));
3103
3104 auto maps_without_group = filament_maps;
3105 for (auto& item : maps_without_group)
3106 item = 0;
3107
3108 // The dynamic branch produced filament_sequences itself (per-layer selector plan); only the static
3109 // path needs the extruder-map flush reorder here.
3110 if (!dynamic_reorder) {
3112 filament_lists,
3113 m_print->is_BBL_printer() ? filament_maps : maps_without_group, // non-bbl printers do not support filament group yet
3114 layer_filaments,
3115 nozzle_flush_mtx,
3116 get_custom_seq,
3117 &filament_sequences
3118 );
3119 }
3120
3121 // The three-mode flush-stat caches are now computed from the nozzle-aware grouping result via the
3122 // nozzle-aware calc_filament_change_info_by_toolorder. Stats are GUI-only (surfaced by
3123 // get_filament_change_stats for the mode comparison); they never feed g-code, so this block is
3124 // byte-inert. For single-nozzle-per-extruder printers (H2D/X1/...) nozzle_id == extruder_id, so
3125 // every cached value equals the extruder-level stats.
3126 auto curr_flush_info = calc_filament_change_info_by_toolorder(print_config, grouping_result, nozzle_flush_mtx, filament_sequences);
3127 if (nozzle_nums <= 1)
3128 m_stats_by_single_extruder = curr_flush_info;
3129 else {
3130 m_stats_by_multi_extruder_curr = curr_flush_info;
3131 if (map_mode == fmmAutoForFlush)
3132 m_stats_by_multi_extruder_best = curr_flush_info;
3133 }
3134
3135 // in multi extruder mode, collect data under the other modes (for the GUI mode comparison)
3136 if (nozzle_nums > 1) {
3137 // always calculate the info as if a single extruder were used
3138 {
3139 std::vector<std::vector<unsigned int>> single_extruder_sequences;
3141 filament_lists,
3142 maps_without_group,
3143 layer_filaments,
3144 nozzle_flush_mtx,
3145 get_custom_seq,
3146 &single_extruder_sequences
3147 );
3148 // One logical nozzle (extruder 0, nozzle 0); every filament resolves to it.
3149 // diameter/volume_type are unused by the stat calc.
3150 MultiNozzleUtils::NozzleInfo single_nozzle;
3151 single_nozzle.volume_type = NozzleVolumeType::nvtStandard;
3152 single_nozzle.extruder_id = 0;
3153 single_nozzle.group_id = 0;
3154 auto single_result = MultiNozzleUtils::LayeredNozzleGroupResult::create(maps_without_group, {single_nozzle}, used_filaments);
3155 if (single_result)
3156 m_stats_by_single_extruder = calc_filament_change_info_by_toolorder(print_config, *single_result, nozzle_flush_mtx, single_extruder_sequences);
3157 }
3158 // if not already in best-for-flush mode, also calculate the info under best-for-flush grouping
3159 if (map_mode != fmmAutoForFlush)
3160 {
3161 std::vector<std::vector<unsigned int>> best_sequences;
3162 if (dynamic_reorder) {
3163 // When the filament selector is active the "best" plan
3164 // is the per-combo-range dynamic regroup, computed over a *copy* of the initial nozzle
3165 // status so it cannot perturb the chosen m_nozzle_status / primary result. NOTE:
3166 // is_dynamic_group_reorder() implies filament_map_mode == fmmAutoForFlush, contradicting
3167 // this map_mode != fmmAutoForFlush guard, so this sub-branch is unreachable under the
3168 // current predicate. It is provably inert.
3169 auto best_context = build_filament_group_context(m_print, layer_filaments, physical_unprintables, geometric_unprintables, filament_unprintable_volumes, FilamentMapMode::fmmAutoForFlush,
3170 m_initial_nozzle_status.get_nozzle_filament_map());
3171 best_context.speed_info.group_with_time = false;
3172 MultiNozzleUtils::NozzleStatusRecorder best_nozzle_status = m_initial_nozzle_status;
3174 physical_unprintables, geometric_unprintables, filament_unprintable_volumes, &best_nozzle_status);
3175 std::vector<std::vector<int>> best_nozzle_map_per_layer;
3176 for (auto& res : best_plan) {
3177 best_sequences.emplace_back(cast<unsigned int>(res.fil_order));
3178 best_nozzle_map_per_layer.emplace_back(res.fil_nozzle_match);
3179 }
3180 auto best_result = MultiNozzleUtils::LayeredNozzleGroupResult::create(best_nozzle_map_per_layer, best_context.nozzle_info.nozzle_list, used_filaments, best_sequences);
3181 if (best_result)
3182 m_stats_by_multi_extruder_best = calc_filament_change_info_by_toolorder(print_config, *best_result, nozzle_flush_mtx, best_sequences);
3183 }
3184 else {
3185 // Best-for-flush grouping (nozzle-aware result). The extruder-level map fed to the flush
3186 // reorder is derived exactly as before, so best_sequences (and the flush weight) are
3187 // identical; only flush_filament_change_count is now charged per physical nozzle.
3188 auto best_group_result = get_recommended_filament_maps(layer_filaments, m_print, fmmAutoForFlush, physical_unprintables, geometric_unprintables, filament_unprintable_volumes);
3189 std::vector<int> best_maps = best_group_result.get_extruder_map();
3191 filament_lists,
3192 best_maps,
3193 layer_filaments,
3194 nozzle_flush_mtx,
3195 get_custom_seq,
3196 &best_sequences
3197 );
3198 m_stats_by_multi_extruder_best = calc_filament_change_info_by_toolorder(print_config, best_group_result, nozzle_flush_mtx, best_sequences);
3199 }
3200 }
3201 }
3202
3203 for (size_t i = 0; i < filament_sequences.size(); ++i)
3204 m_layer_tools[i].extruders = std::move(filament_sequences[i]);
3205}
MultiNozzleUtils::NozzleStatusRecorder m_initial_nozzle_status
Definition ToolOrdering.hpp:431
static MultiNozzleUtils::LayeredNozzleGroupResult get_recommended_filament_maps(const std::vector< std::vector< unsigned int > > &layer_filaments, const Print *print, const FilamentMapMode mode, const std::vector< std::set< int > > &physical_unprintables, const std::vector< std::set< int > > &geometric_unprintables, const std::map< int, std::set< NozzleVolumeType > > &unprintable_volumes={}, const std::unordered_map< int, int > &nozzle_status={})
Definition ToolOrdering.cpp:1752
std::vector< int > update_used_filament_values(const std::vector< int > &old_values, const std::vector< int > &new_values, const std::vector< unsigned int > &used_filaments)
Definition FilamentGroupUtils.cpp:346
bool check_filament_printable_after_group(const std::vector< unsigned int > &used_filaments, const std::vector< int > &filament_maps, const PrintConfig *print_config)
Definition ToolOrdering.cpp:102
@ nvtStandard
Definition PrintConfig.hpp:619
bool has_any_mixed_filament(const std::vector< unsigned char > &is_mixed)
Definition FilamentMixer.cpp:486
zaa_minimize_perimeter_height print_sequence((ConfigOptionEnum< PrintOrder >, print_order))((ConfigOptionInts
std::vector< unsigned int > parse_cyclic_order(const std::string &str, unsigned int number_of_extruders)
Definition ToolOrdering.cpp:2828
static FilamentChangeStats calc_filament_change_info_by_toolorder(const PrintConfig *config, const MultiNozzleUtils::LayeredNozzleGroupResult &group_result, const std::vector< FlushMatrix > &flush_matrix, const std::vector< std::vector< unsigned int > > &layer_sequences)
Definition ToolOrdering.cpp:202
void expand_mixed_slots_in_unprintables(std::vector< std::set< int > > &unprintables, const std::vector< unsigned char > &is_mixed, const std::vector< std::string > &comp_strs)
Definition FilamentMixer.cpp:595
std::vector< std::vector< float > > FlushMatrix
Definition ToolOrderUtils.hpp:17
@ pvmFast
Definition PrintConfig.hpp:659
@ ByObject
Definition PrintConfig.hpp:312
ConfigOptionIntsTempl< false > ConfigOptionInts
Definition Config.hpp:1154
@ Type1
Definition PrintConfig.hpp:114
@ Cyclic
Definition PrintConfig.hpp:493
int reorder_filaments_for_minimum_flush_volume(const std::vector< unsigned int > &filament_lists, const std::vector< int > &filament_maps, const std::vector< std::vector< unsigned int > > &layer_filaments, const std::vector< FlushMatrix > &flush_matrix, std::optional< std::function< bool(int, std::vector< int > &)> > get_custom_seq, std::vector< std::vector< unsigned int > > *filament_sequences, const std::unordered_map< int, int > &nozzle_status)
Definition ToolOrderUtils.cpp:1143
FilamentMapMode
Definition PrintConfig.hpp:632
static std::vector< FilamentPlanRes > plan_filament_mapping_and_order_by_combo_ranges(Print *print, const FilamentGroupContext &ctx, const std::function< bool(int, std::vector< int > &)> get_custom_seq, const FilamentMapMode mode, const std::vector< std::set< int > > &physical_unprintables, const std::vector< std::set< int > > &geometric_unprintables, const std::map< int, std::set< NozzleVolumeType > > &unprintable_volumes, MultiNozzleUtils::NozzleStatusRecorder *io_nozzle_status)
Definition ToolOrdering.cpp:2097
zaa_minimize_perimeter_height flush_multiplier((ConfigOptionEnum< PrimeVolumeMode >, prime_volume_mode))((ConfigOptionFloats
zaa_minimize_perimeter_height wipe_tower_type((ConfigOptionBool, purge_in_prime_tower))((ConfigOptionBool
static MultiNozzleUtils::LayeredNozzleGroupResult build_group_result_from_map(const PrintConfig &print_config, const std::vector< int > &filament_map_0based, const std::vector< unsigned int > &used_filaments)
Definition ToolOrdering.cpp:1917

References Slic3r::cast(), Slic3r::collect_sorted_used_filaments(), Slic3r::Cyclic, EPSILON, Slic3r::expand_mixed_slots_in_unprintables(), Slic3r::fmmAutoForFlush, Slic3r::get_flush_volumes_matrix(), Slic3r::get_other_layers_print_sequence(), Slic3r::has_any_mixed_filament(), m_layer_tools, m_print, m_print_config_ptr, m_print_object_ptr, Slic3r::parse_cyclic_order(), Slic3r::print_sequence(), Slic3r::pvmFast, reorder_extruders_for_minimum_flush_volume(), Slic3r::Type1, Slic3r::ConfigOptionVector< int >::values, and Slic3r::wipe_tower_type().

Referenced by reorder_extruders_for_minimum_flush_volume(), sort_and_build_data(), and sort_and_build_data().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ resolve_mixed_filaments()

void Slic3r::ToolOrdering::resolve_mixed_filaments ( const PrintConfig & config)
private
2247{
2248 const auto &is_mixed = config.filament_is_mixed.values;
2249 const auto &comp_strs = config.filament_mixed_components.values;
2250 const auto &ratio_strs = config.filament_mixed_sublayer_ratios.values;
2251
2252 // Capture mixed slots that actually appear on layers before they are expanded to
2253 // physical components. Assigned-but-unused mixed slots never enter layer_tools.
2254 m_used_mixed_filaments.clear();
2255 if (has_any_mixed_filament(is_mixed)) {
2256 std::set<unsigned int> used;
2257 for (const LayerTools &lt : m_layer_tools)
2258 for (unsigned int ext : lt.extruders)
2259 if (ext < is_mixed.size() && is_mixed[ext])
2260 used.insert(ext);
2261 m_used_mixed_filaments.assign(used.begin(), used.end());
2262 }
2263
2264 if (!has_any_mixed_filament(is_mixed))
2265 return;
2266
2267 const bool sublayer_enabled = config.enable_mixed_color_sublayer.value;
2268
2269 struct SlotInfo {
2270 std::vector<unsigned int> components; // 1-based
2271 std::vector<double> ratios;
2272 std::vector<long long> accum; // deficit accumulator (integer, unit: 1e-6 mm)
2273 };
2274 std::vector<SlotInfo> slots(is_mixed.size());
2275 for (size_t i = 0; i < is_mixed.size(); ++i) {
2276 if (!is_mixed[i])
2277 continue;
2278 slots[i].components = parse_mixed_components(i < comp_strs.size() ? comp_strs[i] : "");
2279 if (slots[i].components.size() < 2) {
2280 slots[i].components.clear();
2281 continue;
2282 }
2283 for (unsigned int cid : slots[i].components) {
2284 unsigned int idx0 = cid - 1;
2285 if (idx0 >= is_mixed.size() || (idx0 < is_mixed.size() && is_mixed[idx0])) {
2286 slots[i].components.clear();
2287 break;
2288 }
2289 }
2290 if (slots[i].components.empty())
2291 continue;
2292 slots[i].ratios = parse_mixed_ratios(
2293 i < ratio_strs.size() ? ratio_strs[i] : "", slots[i].components.size());
2294 if (!sublayer_enabled) {
2295 for (double &r : slots[i].ratios)
2297 double sum = 0;
2298 for (double r : slots[i].ratios) sum += r;
2299 if (sum > 0)
2300 for (double &r : slots[i].ratios) r /= sum;
2301 }
2302 slots[i].accum.assign(slots[i].components.size(), 0LL);
2303 }
2304
2305 // Parse gradient settings per slot
2306 const auto &gradient_flags = config.filament_mixed_gradient.values;
2307 const auto &gradient_range_strs = config.filament_mixed_gradient_range.values;
2308 const auto &gradient_curve_strs = config.filament_mixed_gradient_curve.values;
2309 struct GradientInfo {
2310 double start = 0.10;
2311 double end_val = 0.90;
2312 GradientCurve curve; // empty -> use linear (start, end_val); non-empty wins
2313 };
2314 std::vector<bool> is_gradient(is_mixed.size(), false);
2315 std::vector<GradientInfo> gradient_info(is_mixed.size());
2316 for (size_t i = 0; i < is_mixed.size(); ++i) {
2317 if (!is_mixed[i] || slots[i].components.size() != 2)
2318 continue;
2319 if (i >= gradient_flags.size() || !gradient_flags[i])
2320 continue;
2321 is_gradient[i] = true;
2322 if (i < gradient_range_strs.size() && !gradient_range_strs[i].empty()) {
2323 CNumericLocalesSetter c_locale_setter;
2324 float v0 = 0, v1 = 0;
2325 if (std::sscanf(gradient_range_strs[i].c_str(), "%f,%f", &v0, &v1) == 2 &&
2326 v0 > 0 && v0 < 1.0 && v1 > 0 && v1 < 1.0) {
2327 gradient_info[i].start = v0;
2328 gradient_info[i].end_val = v1;
2329 }
2330 }
2331 if (i < gradient_curve_strs.size() && !gradient_curve_strs[i].empty())
2332 gradient_info[i].curve = parse_gradient_curve(gradient_curve_strs[i]);
2333 }
2334
2335 // Pass 1: identify continuous runs for each gradient slot (Per-Run).
2336 // A "run" is a maximal sequence of consecutive layers where the slot appears.
2337 struct GradientRunInfo {
2338 std::vector<size_t> run_lengths;
2339 int current_run = -1;
2340 size_t current_idx = 0;
2341 bool prev_appeared = false;
2342 bool last_absent_was_relevant = false;
2343 };
2344 std::map<unsigned int, GradientRunInfo> gradient_runs;
2345 for (size_t i = 0; i < is_mixed.size(); ++i)
2346 if (is_gradient[i]) gradient_runs[static_cast<unsigned int>(i)] = {};
2347
2348 // Build per-slot sets of all layer indices where any slot-owning object has a
2349 // layer. Used by gradient run detection (a gap is real only if the slot is
2350 // absent at a layer belonging to one of its own objects) and by calc_slot_lh
2351 // to keep prev_relevant_z_for_slot current even when a slot skips many layers.
2352 std::map<unsigned int, std::set<size_t>> slot_relevant_layers;
2353 for (auto &[slot_idx, obj_map] : m_mixed_object_layers) {
2354 for (auto &[obj, _] : obj_map) {
2355 auto it = m_object_all_layer_indices.find(obj);
2356 if (it != m_object_all_layer_indices.end())
2357 slot_relevant_layers[slot_idx].insert(it->second.begin(), it->second.end());
2358 }
2359 }
2360
2361 if (!gradient_runs.empty()) {
2362 for (size_t li = 0; li < m_layer_tools.size(); ++li) {
2363 if (li == 0) continue;
2364 const auto &lt = m_layer_tools[li];
2365 for (auto &[slot, run] : gradient_runs) {
2366 bool here = std::find(lt.extruders.begin(), lt.extruders.end(), slot) != lt.extruders.end();
2367 if (here) {
2368 bool real_gap = false;
2369 if (!run.prev_appeared && !run.run_lengths.empty()) {
2370 real_gap = run.last_absent_was_relevant;
2371 }
2372 if (run.run_lengths.empty() || real_gap)
2373 run.run_lengths.push_back(0);
2374 run.run_lengths.back()++;
2375 run.last_absent_was_relevant = false;
2376 } else if (!run.run_lengths.empty()) {
2377 auto rel_it = slot_relevant_layers.find(slot);
2378 if (rel_it != slot_relevant_layers.end() && rel_it->second.count(li))
2379 run.last_absent_was_relevant = true;
2380 }
2381 run.prev_appeared = here;
2382 }
2383 }
2384 for (auto &[slot, run] : gradient_runs) {
2385 run.current_run = -1;
2386 run.current_idx = 0;
2387 run.prev_appeared = false;
2388 run.last_absent_was_relevant = false;
2389 }
2390 }
2391
2392 // Per-object gradient: pre-compute per-object runs (respecting Z gaps within each object).
2393 struct PerObjRunState {
2394 std::vector<size_t> run_start_offsets; // index into layer_indices where each run starts
2395 std::vector<size_t> run_lengths;
2396 int current_run = -1;
2397 size_t current_idx = 0;
2398 };
2399
2400 // Detect whether a gap between two consecutive gradient-slot appearances is a
2401 // real run break. A gap is real only if the object has its own layer inside the
2402 // gap that does NOT use the gradient slot (i.e. the slot was genuinely absent).
2403 // Uses lower_bound to skip global indices that don't belong to the object.
2404 auto has_real_gap = [](size_t prev_idx, size_t cur_idx,
2405 const std::set<size_t>& obj_set,
2406 const std::set<size_t>& slot_set) -> bool {
2407 for (auto it = obj_set.lower_bound(prev_idx + 1);
2408 it != obj_set.end() && *it < cur_idx; ++it) {
2409 if (!slot_set.count(*it))
2410 return true;
2411 }
2412 return false;
2413 };
2414
2415 // Segment a sorted list of layer indices into runs, using has_real_gap to decide
2416 // where to break. Shared by the per-object and per-volume paths below.
2417 auto segment_runs = [&](const std::vector<size_t>& layer_indices,
2418 const std::set<size_t>& obj_set,
2419 const std::set<size_t>& slot_set) -> PerObjRunState {
2420 PerObjRunState st;
2421 for (size_t i = 0; i < layer_indices.size(); ++i) {
2422 bool new_run = (i == 0) ||
2423 has_real_gap(layer_indices[i - 1], layer_indices[i], obj_set, slot_set);
2424 if (new_run) {
2425 st.run_start_offsets.push_back(i);
2426 st.run_lengths.push_back(0);
2427 }
2428 st.run_lengths.back()++;
2429 }
2430 return st;
2431 };
2432
2433 std::map<unsigned int, std::map<const PrintObject*, PerObjRunState>> per_obj_runs;
2434 for (auto &[slot, obj_map] : m_mixed_object_layers) {
2435 if (slot >= is_gradient.size() || !is_gradient[slot])
2436 continue;
2437 for (auto &[obj, layer_indices] : obj_map) {
2438 sort_remove_duplicates(layer_indices);
2439 // Erase layer 0 — this mutation is also relied upon by the Pass 2 binary_search below.
2440 if (!layer_indices.empty() && layer_indices.front() == 0)
2441 layer_indices.erase(layer_indices.begin());
2442
2443 const auto &all_obj_layers = m_object_all_layer_indices[obj];
2444 std::set<size_t> all_obj_set(all_obj_layers.begin(), all_obj_layers.end());
2445 std::set<size_t> grad_set(layer_indices.begin(), layer_indices.end());
2446
2447 per_obj_runs[slot][obj] = segment_runs(layer_indices, all_obj_set, grad_set);
2448 }
2449 }
2450
2451 // Per-volume gradient: mirror the per-object run-segmentation logic above for
2452 // m_gradient_volume_layers. When per_part_gradient is off (or no qualifying volume exists),
2453 // m_gradient_volume_layers is empty and per_vol_runs ends up empty too — so all subsequent
2454 // checks of `per_vol_runs.find(slot) != end()` will fail and the legacy per-object path
2455 // remains the only path taken.
2456 using VolumeKey = LayerTools::MixedSubLayerGroup::VolumeKey;
2457 std::map<unsigned int, std::map<VolumeKey, PerObjRunState>> per_vol_runs;
2458 for (auto &[slot, vol_map] : m_gradient_volume_layers) {
2459 if (slot >= is_gradient.size() || !is_gradient[slot])
2460 continue;
2461 for (auto &[vkey, layer_indices] : vol_map) {
2462 sort_remove_duplicates(layer_indices);
2463 if (!layer_indices.empty() && layer_indices.front() == 0)
2464 layer_indices.erase(layer_indices.begin());
2465
2466 const auto &all_obj_layers = m_object_all_layer_indices[vkey.obj];
2467 std::set<size_t> all_obj_set(all_obj_layers.begin(), all_obj_layers.end());
2468 std::set<size_t> vol_grad_set(layer_indices.begin(), layer_indices.end());
2469
2470 per_vol_runs[slot][vkey] = segment_runs(layer_indices, all_obj_set, vol_grad_set);
2471 }
2472 }
2473 // Pass 2: resolve per layer
2474 coordf_t prev_print_z = 0.;
2475 // Track last print_z per mixed slot so that layer height is computed from the
2476 // slot's own previous appearance, not from a global Z that may include layers
2477 // belonging only to other objects with different layer heights.
2478 std::map<unsigned int, coordf_t> prev_print_z_for_slot;
2479 // Track the last Z where a slot-owning object had ANY layer (regardless of
2480 // whether the slot was present). Used to detect genuine gaps: if the slot was
2481 // absent but its owner objects had layers, prev_relevant_z advances while
2482 // prev_print_z_for_slot stays stale. Taking the max of both gives correct lh.
2483 std::map<unsigned int, coordf_t> prev_relevant_z_for_slot;
2484
2485 // Compute the effective layer height for a mixed slot by choosing the best
2486 // reference Z among: (1) the slot's own last Z, (2) the last Z where the
2487 // slot's owning object had any layer, (3) the global previous Z as fallback
2488 // when the slot appears for the first time.
2489 auto calc_slot_lh = [&](unsigned int ext, coordf_t print_z) -> double {
2490 auto slot_pz_it = prev_print_z_for_slot.find(ext);
2491 auto rel_pz_it = prev_relevant_z_for_slot.find(ext);
2492 coordf_t base_z = prev_print_z;
2493 if (slot_pz_it != prev_print_z_for_slot.end()) {
2494 base_z = slot_pz_it->second;
2495 if (rel_pz_it != prev_relevant_z_for_slot.end())
2496 base_z = std::max(base_z, rel_pz_it->second);
2497 }
2498 double lh = print_z - base_z;
2499 return (lh > 0.) ? lh : 0.2; // 0.2mm safety fallback; should not trigger in normal operation
2500 };
2501
2502 for (LayerTools &lt : m_layer_tools) {
2503 size_t layer_idx = static_cast<size_t>(&lt - m_layer_tools.data());
2504
2505 // Update gradient run state (skip first layer to match counting).
2506 if (layer_idx > 0) {
2507 for (auto &[slot, run] : gradient_runs) {
2508 bool here = std::find(lt.extruders.begin(), lt.extruders.end(), slot) != lt.extruders.end();
2509 if (here) {
2510 if (!run.prev_appeared) {
2511 if (run.last_absent_was_relevant || run.current_run < 0) {
2512 run.current_run++;
2513 run.current_idx = 0;
2514 }
2515 }
2516 run.last_absent_was_relevant = false;
2517 } else {
2518 auto rel_it = slot_relevant_layers.find(slot);
2519 if (rel_it != slot_relevant_layers.end() && rel_it->second.count(layer_idx))
2520 run.last_absent_was_relevant = true;
2521 }
2522 run.prev_appeared = here;
2523 }
2524 }
2525
2526 std::vector<unsigned int> new_extruders;
2527 for (unsigned int ext : lt.extruders) {
2528 if (ext >= slots.size() || slots[ext].components.empty()) {
2529 new_extruders.push_back(ext);
2530 continue;
2531 }
2532 auto &s = slots[ext];
2533
2534 // Skip sublayer splitting for the first layer to preserve bed adhesion.
2535 if (sublayer_enabled && layer_idx > 0) {
2536 double lh = calc_slot_lh(ext, lt.print_z);
2537 size_t n = s.components.size();
2538
2539 std::vector<double> sub_heights;
2540 bool gradient_last_no_split = false;
2541 unsigned int gradient_last_dominant_0b = 0;
2542 if (is_gradient[ext] && n == 2) {
2543 auto gr_it = gradient_runs.find(ext);
2544 if (gr_it != gradient_runs.end() && gr_it->second.current_run >= 0 &&
2545 static_cast<size_t>(gr_it->second.current_run) < gr_it->second.run_lengths.size()) {
2546 auto &run = gr_it->second;
2547 size_t N = run.run_lengths[run.current_run];
2548 size_t idx = run.current_idx++;
2549 double t = (N > 0) ? (2.0 * idx + 1.0) / (2.0 * N) : 0.5;
2550 // Custom curve wins over linear range when present; OFF path stays bit-identical.
2551 double r1 = gradient_info[ext].curve.empty()
2552 ? (gradient_info[ext].start + (gradient_info[ext].end_val - gradient_info[ext].start) * t)
2553 : sample_gradient_curve(gradient_info[ext].curve, t);
2554 double r2 = 1.0 - r1;
2555 sub_heights.push_back(r1 * lh);
2556 sub_heights.push_back(r2 * lh);
2557 // The sublayer split path sorts components by physical ID ascending;
2558 // the higher-ID component ends up on top (visible surface). If the
2559 // gradient's dominant component has the lower physical ID, splitting
2560 // would put the non-dominant color on the visible top surface. In
2561 // that case, skip the split and print this final run-layer as pure
2562 // dominant color to preserve the gradient appearance.
2563 if (idx == N - 1) {
2564 // When r1 == r2 (exactly 50/50), component[0] is treated as dominant.
2565 size_t dominant = (r1 >= r2) ? 0 : 1;
2566 unsigned int dom_0b = s.components[dominant] - 1;
2567 unsigned int oth_0b = s.components[1 - dominant] - 1;
2568 if (dom_0b < oth_0b) {
2569 gradient_last_no_split = true;
2570 gradient_last_dominant_0b = dom_0b;
2571 }
2572 }
2573 } else {
2574 for (double r : s.ratios)
2575 sub_heights.push_back(r * lh);
2576 }
2577 } else {
2578 for (double r : s.ratios)
2579 sub_heights.push_back(r * lh);
2580 }
2581
2582 // Per-part gradient: when this slot has any qualifying volume, the global
2583 // no-split short-circuit must NOT bypass MixedSubLayerGroup creation — each
2584 // volume needs its own no-split decision in GCode.cpp (a per-volume "last
2585 // run-layer" can occur on a different layer index than the per-object one). We
2586 // still keep the per-object short-circuit when per_vol_runs[ext] is empty, which
2587 // covers the legacy path bit-identically.
2588 bool per_vol_active_for_slot = per_vol_runs.find(ext) != per_vol_runs.end()
2589 && !per_vol_runs[ext].empty();
2590
2591 if (gradient_last_no_split && !per_vol_active_for_slot) {
2592 lt.mixed_filament_resolution[ext] = gradient_last_dominant_0b;
2593 new_extruders.push_back(gradient_last_dominant_0b);
2594 prev_print_z_for_slot[ext] = lt.print_z;
2595 continue;
2596 }
2597
2598 LayerTools::MixedSubLayerGroup grp;
2599 grp.mixed_slot_0based = ext;
2600 grp.layer_height = lh;
2601 grp.is_gradient = is_gradient[ext];
2602 for (size_t k = 0; k < s.components.size(); ++k) {
2603 unsigned int comp_0based = s.components[k] - 1;
2604 grp.components_0based.push_back(comp_0based);
2605 }
2606 grp.sub_heights = sub_heights;
2607
2608 // Write gradient metadata (run-aware). Both per_object_gradient and
2609 // per_volume_gradient are populated independently from their own run-state
2610 // machines; the GCode emitter chooses per-region:
2611 // - tagged region (gradient_volume_id valid) -> per_volume_gradient[{obj, vol}]
2612 // - untagged region (modifier / painted / etc.) -> per_object_gradient[obj]
2613 // Populating both keeps the per-object run state correct even when per-volume
2614 // takes over for the same (slot, obj), and lets untagged geometry (which is
2615 // never split per-volume) keep its per-object gradient ratios.
2616 if (grp.is_gradient) {
2617 auto vol_runs_slot_it = per_vol_runs.find(ext);
2618 if (vol_runs_slot_it != per_vol_runs.end()) {
2619 auto vol_slot_it = m_gradient_volume_layers.find(ext);
2620 for (auto &[vkey, st] : vol_runs_slot_it->second) {
2621 auto &layer_indices = vol_slot_it->second[vkey];
2622 if (!std::binary_search(layer_indices.begin(), layer_indices.end(), layer_idx))
2623 continue;
2624 if (st.current_run < 0 ||
2625 st.current_idx >= st.run_lengths[st.current_run]) {
2626 st.current_run++;
2627 st.current_idx = 0;
2628 }
2629 size_t run_N = st.run_lengths[st.current_run];
2630 size_t run_idx = st.current_idx++;
2631 grp.per_volume_gradient[vkey] = {
2632 run_N,
2633 run_idx,
2634 gradient_info[ext].start,
2635 gradient_info[ext].end_val,
2636 gradient_info[ext].curve,
2637 };
2638 }
2639 }
2640
2641 auto runs_slot_it = per_obj_runs.find(ext);
2642 if (runs_slot_it != per_obj_runs.end()) {
2643 auto slot_it = m_mixed_object_layers.find(ext);
2644 for (auto &[obj, st] : runs_slot_it->second) {
2645 auto &layer_indices = slot_it->second[obj];
2646 if (!std::binary_search(layer_indices.begin(), layer_indices.end(), layer_idx))
2647 continue;
2648 if (st.current_run < 0 ||
2649 st.current_idx >= st.run_lengths[st.current_run]) {
2650 st.current_run++;
2651 st.current_idx = 0;
2652 }
2653 size_t run_N = st.run_lengths[st.current_run];
2654 size_t run_idx = st.current_idx++;
2655 grp.per_object_gradient[obj] = {
2656 run_N,
2657 run_idx,
2658 gradient_info[ext].start,
2659 gradient_info[ext].end_val,
2660 gradient_info[ext].curve,
2661 };
2662 }
2663 }
2664 }
2665
2666 if (grp.components_0based.size() > 1) {
2667 unsigned int first_comp_0based = s.components[0] - 1;
2668 std::vector<size_t> idx(grp.components_0based.size());
2669 std::iota(idx.begin(), idx.end(), 0);
2670 std::sort(idx.begin(), idx.end(), [&](size_t a, size_t b) {
2671 return grp.components_0based[a] < grp.components_0based[b];
2672 });
2673 std::vector<unsigned int> sorted_comps;
2674 std::vector<double> sorted_heights;
2675 for (size_t i : idx) {
2676 sorted_comps.push_back(grp.components_0based[i]);
2677 sorted_heights.push_back(grp.sub_heights[i]);
2678 }
2679 grp.components_0based = std::move(sorted_comps);
2680 grp.sub_heights = std::move(sorted_heights);
2681 if (grp.is_gradient) {
2682 for (size_t i = 0; i < grp.components_0based.size(); ++i) {
2683 if (grp.components_0based[i] == first_comp_0based) {
2684 grp.gradient_first_sorted_idx = static_cast<int>(i);
2685 break;
2686 }
2687 }
2688 }
2689 }
2690
2691 for (unsigned int comp : grp.components_0based)
2692 new_extruders.push_back(comp);
2693 lt.mixed_sub_layer_groups.push_back(std::move(grp));
2694 prev_print_z_for_slot[ext] = lt.print_z;
2695 } else {
2696 // Deficit Round-Robin: pick one component per layer.
2697 // Weight by layer height so volume ratios stay accurate
2698 // even with adaptive layer heights.
2699 double lh = calc_slot_lh(ext, lt.print_z);
2700 long long lh_i = std::llround(lh * 1e6);
2701
2702 // For 2-component gradient on the first layer, use the gradient's
2703 // starting ratio instead of the configured mixing ratio so the
2704 // selected filament matches the gradient's "from" end.
2705 // Only affects the first layer; when sublayer splitting is enabled
2706 // (required for gradient), layers 1+ take the sublayer path and
2707 // do not touch the DRR accumulator.
2708 if (layer_idx == 0 && is_gradient[ext] && s.components.size() == 2) {
2709 double r0 = gradient_info[ext].start;
2710 s.accum[0] += std::llround(r0 * lh_i);
2711 s.accum[1] += std::llround((1.0 - r0) * lh_i);
2712 } else {
2713 for (size_t k = 0; k < s.ratios.size(); ++k)
2714 s.accum[k] += std::llround(s.ratios[k] * lh_i);
2715 }
2716 size_t sel = 0;
2717 for (size_t k = 1; k < s.accum.size(); ++k)
2718 if (s.accum[k] > s.accum[sel])
2719 sel = k;
2720 s.accum[sel] -= lh_i;
2721 unsigned int resolved = s.components[sel] - 1;
2722 lt.mixed_filament_resolution[ext] = resolved;
2723 new_extruders.push_back(resolved);
2724 prev_print_z_for_slot[ext] = lt.print_z;
2725 }
2726 }
2727 lt.extruders = new_extruders;
2728 sort_remove_duplicates(lt.extruders);
2729
2730 // Update prev_relevant_z: for each slot that has relevant-layer tracking,
2731 // advance if the current layer belongs to a slot-owning object.
2732 for (auto &[slot, rel_set] : slot_relevant_layers) {
2733 if (rel_set.count(layer_idx))
2734 prev_relevant_z_for_slot[slot] = lt.print_z;
2735 }
2736
2737 prev_print_z = lt.print_z;
2738 }
2739}
#define _(s)
Definition 3mf.cpp:306
std::vector< unsigned int > m_used_mixed_filaments
Definition ToolOrdering.hpp:397
AppActionRunResult run(const std::string &key, const std::string &param={})
Definition NativeCommands.cpp:668
@ N
Definition CalibrationWizardSavePage.cpp:124
GradientCurve parse_gradient_curve(const std::string &s)
Definition FilamentMixer.cpp:269
static double snap_to_simple_fraction(double r, int max_denom=10)
Definition ToolOrdering.cpp:2228
double sample_gradient_curve(const GradientCurve &c, double t)
Definition FilamentMixer.cpp:357
std::vector< double > parse_mixed_ratios(const std::string &str, size_t n_components)
Definition FilamentMixer.cpp:404

References _, Slic3r::LayerTools::MixedSubLayerGroup::components_0based, Slic3r::LayerTools::MixedSubLayerGroup::gradient_first_sorted_idx, Slic3r::has_any_mixed_filament(), Slic3r::LayerTools::MixedSubLayerGroup::is_gradient, Slic3r::LayerTools::MixedSubLayerGroup::layer_height, m_gradient_volume_layers, m_layer_tools, m_mixed_object_layers, m_object_all_layer_indices, m_used_mixed_filaments, Slic3r::LayerTools::MixedSubLayerGroup::mixed_slot_0based, Slic3r::parse_gradient_curve(), Slic3r::parse_mixed_components(), Slic3r::parse_mixed_ratios(), Slic3r::LayerTools::MixedSubLayerGroup::per_object_gradient, Slic3r::LayerTools::MixedSubLayerGroup::per_volume_gradient, resolve_mixed_filaments(), Slic3r::sample_gradient_curve(), Slic3r::snap_to_simple_fraction(), Slic3r::sort_remove_duplicates(), and Slic3r::LayerTools::MixedSubLayerGroup::sub_heights.

Referenced by resolve_mixed_filaments(), sort_and_build_data(), and sort_and_build_data().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ set_nozzle_status()

void Slic3r::ToolOrdering::set_nozzle_status ( const MultiNozzleUtils::NozzleStatusRecorder & status)
inline

◆ sort_and_build_data() [1/2]

void Slic3r::ToolOrdering::sort_and_build_data ( const Print & print,
unsigned int first_extruder,
bool prime_multi_material = false )
444{
445 // if first extruder is -1, we can decide the first layer tool order before doing reorder function
446 // so we shouldn't reorder first layer in reorder function
447 bool reorder_first_layer = (first_extruder != (unsigned int)(-1));
448 this->resolve_mixed_filaments(print.config());
451 m_sorted = true;
452
453 double max_layer_height = 0.;
454 double object_bottom_z = 0.;
455 for (const auto& object : print.objects()) {
456 for (const Layer* layer : object->layers()) {
457 if (layer->has_extrusions()) {
458 object_bottom_z = layer->print_z - layer->height;
459 break;
460 }
461 }
462 max_layer_height = std::max(max_layer_height, object->config().layer_height.value);
463 }
464
465 max_layer_height = calc_max_layer_height(print.config(), max_layer_height);
466
467 this->fill_wipe_tower_partitions(print.config(), object_bottom_z, max_layer_height);
468 if (this->insert_wipe_tower_extruder()) {
470 // Orca reorders a second time here (BBS has no such path); re-enforce so the
471 // mixed sub-layer component order survives the extra pass.
473 this->fill_wipe_tower_partitions(print.config(), object_bottom_z, max_layer_height);
474 }
475
476 this->collect_extruder_statistics(prime_multi_material);
477}
bool m_sorted
Definition ToolOrdering.hpp:421
void resolve_mixed_filaments(const PrintConfig &config)
Definition ToolOrdering.cpp:2246
void enforce_mixed_component_order()
Definition ToolOrdering.cpp:2741
void fill_wipe_tower_partitions(const PrintConfig &config, coordf_t object_bottom_z, coordf_t max_layer_height)
Definition ToolOrdering.cpp:1046
void reorder_extruders_for_minimum_flush_volume(bool reorder_first_layer)
Definition ToolOrdering.cpp:2849
bool insert_wipe_tower_extruder()
Definition ToolOrdering.cpp:419

References Slic3r::calc_max_layer_height(), collect_extruder_statistics(), Slic3r::Print::config(), enforce_mixed_component_order(), fill_wipe_tower_partitions(), first_extruder(), insert_wipe_tower_extruder(), m_sorted, Slic3r::Print::objects(), reorder_extruders_for_minimum_flush_volume(), and resolve_mixed_filaments().

Here is the call graph for this function:

◆ sort_and_build_data() [2/2]

void Slic3r::ToolOrdering::sort_and_build_data ( const PrintObject & object,
unsigned int first_extruder,
bool prime_multi_material = false )
480{
481 // if first extruder is -1, we can decide the first layer tool order before doing reorder function
482 // so we shouldn't reorder first layer in reorder function
483 bool reorder_first_layer = (first_extruder != (unsigned int)(-1));
484 this->resolve_mixed_filaments(object.print()->config());
487 m_sorted = true;
488
489 double max_layer_height = calc_max_layer_height(object.print()->config(), object.config().layer_height);
490
491 this->fill_wipe_tower_partitions(object.print()->config(), object.layers().front()->print_z - object.layers().front()->height, max_layer_height);
492 if (this->insert_wipe_tower_extruder()) {
494 // Orca reorders a second time here (BBS has no such path); re-enforce so the
495 // mixed sub-layer component order survives the extra pass.
497 this->fill_wipe_tower_partitions(object.print()->config(), object.layers().front()->print_z - object.layers().front()->height, max_layer_height);
498 }
499
500 this->collect_extruder_statistics(prime_multi_material);
501}
const LayerTools & front() const
Definition ToolOrdering.hpp:315

References Slic3r::calc_max_layer_height(), collect_extruder_statistics(), enforce_mixed_component_order(), fill_wipe_tower_partitions(), first_extruder(), front(), insert_wipe_tower_extruder(), Slic3r::layer_height(), m_sorted, reorder_extruders_for_minimum_flush_volume(), and resolve_mixed_filaments().

Referenced by Slic3r::GCode::_do_export().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ tools_for_layer() [1/2]

LayerTools & Slic3r::ToolOrdering::tools_for_layer ( coordf_t print_z)
inline
313{ return const_cast<LayerTools&>(std::as_const(*this).tools_for_layer(print_z)); }

◆ tools_for_layer() [2/2]

const LayerTools & Slic3r::ToolOrdering::tools_for_layer ( coordf_t print_z) const
3319{
3320 auto it_layer_tools = std::lower_bound(m_layer_tools.begin(), m_layer_tools.end(), LayerTools(print_z - EPSILON));
3321 assert(it_layer_tools != m_layer_tools.end());
3322 coordf_t dist_min = std::abs(it_layer_tools->print_z - print_z);
3323 for (++ it_layer_tools; it_layer_tools != m_layer_tools.end(); ++ it_layer_tools) {
3324 coordf_t d = std::abs(it_layer_tools->print_z - print_z);
3325 if (d >= dist_min)
3326 break;
3327 dist_min = d;
3328 }
3329 -- it_layer_tools;
3330 assert(dist_min < EPSILON);
3331 return *it_layer_tools;
3332}

References EPSILON, m_layer_tools, and tools_for_layer().

Referenced by collect_extruders(), Slic3r::GCode::process_layers(), and tools_for_layer().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ used_mixed_filaments()

const std::vector< unsigned int > & Slic3r::ToolOrdering::used_mixed_filaments ( ) const
inline
309{ return m_used_mixed_filaments; }

References m_used_mixed_filaments.

Member Data Documentation

◆ m_all_printing_extruders

std::vector<unsigned int> Slic3r::ToolOrdering::m_all_printing_extruders
private

◆ m_first_printing_extruder

unsigned int Slic3r::ToolOrdering::m_first_printing_extruder = (unsigned int)-1
private

◆ m_gradient_volume_layers

std::map<unsigned int, std::map<LayerTools::MixedSubLayerGroup::VolumeKey, std::vector<size_t> > > Slic3r::ToolOrdering::m_gradient_volume_layers
private

◆ m_initial_nozzle_status

MultiNozzleUtils::NozzleStatusRecorder Slic3r::ToolOrdering::m_initial_nozzle_status
private

Referenced by set_nozzle_status().

◆ m_last_layer_per_extruder

std::map<unsigned int, coordf_t> Slic3r::ToolOrdering::m_last_layer_per_extruder
private

◆ m_last_printing_extruder

unsigned int Slic3r::ToolOrdering::m_last_printing_extruder = (unsigned int)-1
private

◆ m_layer_tools

◆ m_mixed_object_layers

std::map<unsigned int, std::map<const PrintObject*, std::vector<size_t> > > Slic3r::ToolOrdering::m_mixed_object_layers
private

◆ m_nozzle_group_result

MultiNozzleUtils::LayeredNozzleGroupResult Slic3r::ToolOrdering::m_nozzle_group_result
private

◆ m_nozzle_status

MultiNozzleUtils::NozzleStatusRecorder Slic3r::ToolOrdering::m_nozzle_status
private

◆ m_object_all_layer_indices

std::map<const PrintObject*, std::vector<size_t> > Slic3r::ToolOrdering::m_object_all_layer_indices
private

◆ m_print

Print* Slic3r::ToolOrdering::m_print
private

◆ m_print_config_ptr

◆ m_print_full_config

◆ m_print_object_ptr

const PrintObject* Slic3r::ToolOrdering::m_print_object_ptr = nullptr
private

◆ m_sorted

bool Slic3r::ToolOrdering::m_sorted = false
private

◆ m_stats_by_multi_extruder_best

FilamentChangeStats Slic3r::ToolOrdering::m_stats_by_multi_extruder_best
private

Referenced by clear(), and get_filament_change_stats().

◆ m_stats_by_multi_extruder_curr

FilamentChangeStats Slic3r::ToolOrdering::m_stats_by_multi_extruder_curr
private

Referenced by clear(), and get_filament_change_stats().

◆ m_stats_by_single_extruder

FilamentChangeStats Slic3r::ToolOrdering::m_stats_by_single_extruder
private

Referenced by clear(), and get_filament_change_stats().

◆ m_used_mixed_filaments

std::vector<unsigned int> Slic3r::ToolOrdering::m_used_mixed_filaments
private

◆ most_used_extruder

int Slic3r::ToolOrdering::most_used_extruder
private

The documentation for this class was generated from the following files: