OrcaSlicer latest
OrcaSlicer
Loading...
Searching...
No Matches
Slic3r::MultiNozzleUtils Namespace Reference

Classes

struct  NozzleInfo
struct  NozzleGroupInfo
struct  FilamentChangeTimeParams
class  NozzleGroupResultBase
 Abstract base for a nozzle-grouping result. More...
class  LayeredNozzleGroupResult
 Layer-aware nozzle-grouping result. Used by the back-end slicing code; supports per-layer nozzle mapping. More...
class  StaticNozzleGroupResult
 Layer-less nozzle-grouping result for the device side (static nozzle mapping only). Reconstructed from a loaded gcode.3mf together with the filament/nozzle change sequences. More...
class  NozzleStatusRecorder
struct  FilamentChangeSimResult

Functions

std::vector< NozzleInfo > build_nozzle_list (std::vector< NozzleGroupInfo > nozzle_groups)
std::vector< NozzleInfo > build_nozzle_list (double diameter, const std::vector< int > &filament_nozzle_map, const std::vector< int > &filament_volume_map, const std::vector< int > &filament_map)
void normalize_nozzle_map_per_layer (std::vector< std::vector< int > > &layer_filament_nozzle_maps, const std::vector< std::vector< unsigned int > > &layer_filaments)
static bool has_filament_mapped_to_multiple_nozzles (const std::vector< std::vector< int > > &layer_filament_nozzle_maps, const std::vector< unsigned int > &used_filaments)
FilamentChangeSimResult simulate_filament_change_time (const std::vector< int > &logical_filaments, const std::vector< NozzleInfo > &nozzle_list, const std::vector< int > &filament_change_seq, const std::vector< int > &nozzle_change_seq, const std::vector< int > &group_of_filament, const FilamentChangeTimeParams &time_params, const std::vector< bool > &ams_preload_enabled, bool calc_sliced_time)
std::vector< NozzleInfo > load_nozzle_infos_with_compatibility (const std::vector< NozzleInfo > &nozzle_infos, const std::vector< FilamentInfo > &filament_infos, const std::vector< int > &filament_map, const std::vector< NozzleVolumeType > &extruder_volume_types, const std::vector< double > &nozzle_diameter)

Class Documentation

◆ Slic3r::MultiNozzleUtils::FilamentChangeTimeParams

struct Slic3r::MultiNozzleUtils::FilamentChangeTimeParams
Class Members
float selector_load_time {0.0f}
float selector_unload_time {0.0f}
float standard_load_time {0.0f}
float standard_unload_time {0.0f}

◆ Slic3r::MultiNozzleUtils::FilamentChangeSimResult

struct Slic3r::MultiNozzleUtils::FilamentChangeSimResult
Class Members
double actual_time = 0.0
double sliced_time = 0.0

Function Documentation

◆ build_nozzle_list() [1/2]

std::vector< NozzleInfo > Slic3r::MultiNozzleUtils::build_nozzle_list ( double diameter,
const std::vector< int > & filament_nozzle_map,
const std::vector< int > & filament_volume_map,
const std::vector< int > & filament_map )
44{
45 std::string diameter_str = format_diameter_to_str(diameter);
46 std::map<int, std::vector<int>> nozzle_to_filaments;
47 for(size_t idx = 0; idx < filament_nozzle_map.size(); ++idx){
48 int nozzle_id = filament_nozzle_map[idx];
49 nozzle_to_filaments[nozzle_id].emplace_back(static_cast<int>(idx));
50 }
51 std::vector<NozzleInfo> ret;
52 for(auto& elem : nozzle_to_filaments){
53 int nozzle_id = elem.first;
54 auto& filaments = elem.second;
55 NozzleInfo info;
56 info.diameter = diameter_str;
57 info.group_id = nozzle_id;
58 info.extruder_id = filament_map[filaments.front()];
59 info.volume_type = NozzleVolumeType(filament_volume_map[filaments.front()]);
60 ret.emplace_back(std::move(info));
61 }
62 return ret;
63}
NozzleVolumeType
Definition PrintConfig.hpp:618
zaa_minimize_perimeter_height filament_nozzle_map((ConfigOptionInts, filament_map_2))((ConfigOptionStrings
zaa_minimize_perimeter_height filament_map((ConfigOptionInts, filament_volume_map))((ConfigOptionInts
std::string format_diameter_to_str(double diameter, int precision)
Definition utils.cpp:1739

References Slic3r::MultiNozzleUtils::NozzleInfo::diameter, Slic3r::MultiNozzleUtils::NozzleInfo::extruder_id, Slic3r::filament_map(), Slic3r::filament_nozzle_map(), Slic3r::format_diameter_to_str(), Slic3r::MultiNozzleUtils::NozzleInfo::group_id, and Slic3r::MultiNozzleUtils::NozzleInfo::volume_type.

Here is the call graph for this function:

◆ build_nozzle_list() [2/2]

std::vector< NozzleInfo > Slic3r::MultiNozzleUtils::build_nozzle_list ( std::vector< NozzleGroupInfo > nozzle_groups)
26{
27 std::vector<NozzleInfo> ret;
28 std::sort(nozzle_groups.begin(), nozzle_groups.end());
29 int nozzle_id = 0;
30 for (auto& group : nozzle_groups) {
31 for (int i = 0; i < group.nozzle_count; ++i) {
32 NozzleInfo tmp;
33 tmp.diameter = group.diameter;
34 tmp.extruder_id = group.extruder_id;
35 tmp.volume_type = group.volume_type;
36 tmp.group_id = nozzle_id++;
37 ret.emplace_back(std::move(tmp));
38 }
39 }
40 return ret;
41}
Definition MultiNozzleUtils.hpp:31
int extruder_id
Definition MultiNozzleUtils.hpp:34
int group_id
Definition MultiNozzleUtils.hpp:35
std::string diameter
Definition MultiNozzleUtils.hpp:32
NozzleVolumeType volume_type
Definition MultiNozzleUtils.hpp:33

References Slic3r::MultiNozzleUtils::NozzleInfo::diameter, Slic3r::MultiNozzleUtils::NozzleInfo::extruder_id, Slic3r::MultiNozzleUtils::NozzleInfo::group_id, and Slic3r::MultiNozzleUtils::NozzleInfo::volume_type.

Referenced by Slic3r::MultiNozzleUtils::LayeredNozzleGroupResult::create().

Here is the caller graph for this function:

◆ has_filament_mapped_to_multiple_nozzles()

bool Slic3r::MultiNozzleUtils::has_filament_mapped_to_multiple_nozzles ( const std::vector< std::vector< int > > & layer_filament_nozzle_maps,
const std::vector< unsigned int > & used_filaments )
static
116{
117 if (layer_filament_nozzle_maps.empty() || used_filaments.empty())
118 return false;
119
120 for (auto filament_id_u : used_filaments) {
121 int filament_id = static_cast<int>(filament_id_u);
122 std::set<int> nozzle_ids;
123
124 for (size_t layer_id = 0; layer_id < layer_filament_nozzle_maps.size(); ++layer_id) {
125 const auto &map = layer_filament_nozzle_maps[layer_id];
126 if (filament_id < 0 || filament_id >= static_cast<int>(map.size()))
127 continue;
128
129 int nozzle_id = map[filament_id];
130 if (nozzle_id < 0)
131 continue;
132
133 nozzle_ids.insert(nozzle_id);
134 if (nozzle_ids.size() > 1)
135 return true;
136 }
137 }
138
139 return false;
140}

Referenced by Slic3r::MultiNozzleUtils::LayeredNozzleGroupResult::create().

Here is the caller graph for this function:

◆ load_nozzle_infos_with_compatibility()

std::vector< NozzleInfo > Slic3r::MultiNozzleUtils::load_nozzle_infos_with_compatibility ( const std::vector< NozzleInfo > & nozzle_infos,
const std::vector< FilamentInfo > & filament_infos,
const std::vector< int > & filament_map,
const std::vector< NozzleVolumeType > & extruder_volume_types,
const std::vector< double > & nozzle_diameter )
907{
908 bool has_nozzle_info = !nozzle_infos.empty();
909 bool has_valid_filament_info = !filament_infos.empty() && std::all_of(filament_infos.begin(), filament_infos.end(), [](const FilamentInfo& info){
910 return info.group_id.size() == 1;
911 });
912
913 if(!has_nozzle_info && !has_valid_filament_info){
914 BOOST_LOG_TRIVIAL(warning)<<__FUNCTION__ << ": building nozzle list from filament map and volume types";
915
916 // Backward compatibility for older gcode.3mf:
917 // - nozzle_diameter is always present and its size defines extruder count.
918 // - filament_map may be missing; treat it as [0, 0, ...] for each extruder.
919 // - extruder_volume_types may be missing; treat it as all Standard.
920 const size_t extruder_count = nozzle_diameter.size();
921
922 std::vector<NozzleVolumeType> volume_types_fixed = extruder_volume_types;
923 volume_types_fixed.resize(extruder_count, NozzleVolumeType::nvtStandard);
924
925 std::vector<NozzleInfo> result;
926 result.reserve(extruder_count);
927 for (size_t extruder_id = 0; extruder_id < extruder_count; ++extruder_id) {
928 NozzleInfo info;
930 info.group_id = static_cast<int>(extruder_id);
931 info.extruder_id = static_cast<int>(extruder_id);
932 info.volume_type = volume_types_fixed[extruder_id];
933 result.emplace_back(std::move(info));
934 }
935 return result;
936 }
937
938 if(!has_nozzle_info){
939 BOOST_LOG_TRIVIAL(info)<<__FUNCTION__ << ": building nozzle list from filament info";
940 std::map<int, NozzleInfo> nozzle_map; // group_id -> NozzleInfo
941 for(auto& filament : filament_infos){
942 int group_id = filament.group_id.front();
943 if(group_id < 0 || nozzle_map.find(group_id) != nozzle_map.end()){
944 continue;
945 }
946
947 auto volume_type_str_to_enum = ConfigOptionEnum<NozzleVolumeType>::get_enum_values();
948
949 NozzleInfo info;
950 info.diameter = format_diameter_to_str(filament.nozzle_diameter);
951 info.group_id = group_id;
952 // Orca: bounds-check filament_map[filament.id] so a malformed 3mf (filament id
953 // beyond the map) degrades to extruder 0 instead of dereferencing out of range.
954 info.extruder_id = (filament.id >= 0 && filament.id < static_cast<int>(filament_map.size()))
955 ? filament_map[filament.id] - 1
956 : 0; // to 0-based
957
958 if (volume_type_str_to_enum.count(filament.nozzle_volume_type))
959 info.volume_type = NozzleVolumeType(volume_type_str_to_enum.at(filament.nozzle_volume_type));
960 else {
961 info.volume_type = NozzleVolumeType::nvtStandard;
962 }
963
964 nozzle_map[group_id] = std::move(info);
965 }
966
967 std::vector<NozzleInfo> ret;
968 for(auto& elem : nozzle_map){
969 ret.emplace_back(elem.second);
970 }
971 return ret;
972 }
973
974 auto result = nozzle_infos;
975 std::sort(result.begin(), result.end());
976 BOOST_LOG_TRIVIAL(info)<<__FUNCTION__ << ": using new 3mf format with " << result.size() << " nozzle infos.";
977 return result;
978}
static const t_config_enum_values & get_enum_values()
zaa_minimize_perimeter_height nozzle_diameter((ConfigOptionBool, reduce_infill_retraction))((ConfigOptionBool
@ nvtStandard
Definition PrintConfig.hpp:619
uint32_t group_id(std::vector< Bridge > &bridges, uint32_t src_id)
Definition LayerRegion.cpp:214
Definition FilamentGroupUtils.hpp:34

References Slic3r::MultiNozzleUtils::NozzleInfo::diameter, Slic3r::MultiNozzleUtils::NozzleInfo::extruder_id, Slic3r::filament_map(), Slic3r::format_diameter_to_str(), Slic3r::ConfigOptionEnum< T >::get_enum_values(), Slic3r::group_id(), Slic3r::MultiNozzleUtils::NozzleInfo::group_id, Slic3r::nozzle_diameter(), Slic3r::nvtStandard, and Slic3r::MultiNozzleUtils::NozzleInfo::volume_type.

Referenced by Slic3r::GUI::PartPlateList::load_from_3mf_structure().

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

◆ normalize_nozzle_map_per_layer()

void Slic3r::MultiNozzleUtils::normalize_nozzle_map_per_layer ( std::vector< std::vector< int > > & layer_filament_nozzle_maps,
const std::vector< std::vector< unsigned int > > & layer_filaments )
67{
68 if (layer_filament_nozzle_maps.empty())
69 return;
70
71 const int total_layers = static_cast<int>(layer_filament_nozzle_maps.size());
72 int filament_count = 0;
73 for (const auto &layer_map : layer_filament_nozzle_maps)
74 filament_count = std::max(filament_count, static_cast<int>(layer_map.size()));
75
76 auto layer_uses_filament = [](const std::vector<unsigned int> &filaments, int filament_id) {
77 return std::find(filaments.begin(), filaments.end(), static_cast<unsigned int>(filament_id)) != filaments.end();
78 };
79
80 std::vector<int> last_used_nozzle(filament_count, -1);
81 std::unordered_map<int, int> first_used_nozzle;
82 std::unordered_map<int, int> first_used_layer;
83
84 // Forward pass: layers that extrude a filament define its nozzle; layers that don't inherit
85 // the nozzle it last used (carry-forward), remembering the first-ever nozzle for the back-fill.
86 for (int layer_id = 0; layer_id < total_layers; ++layer_id) {
87 auto &layer_map = layer_filament_nozzle_maps[layer_id];
88 const auto &used = layer_id < static_cast<int>(layer_filaments.size()) ? layer_filaments[layer_id] : std::vector<unsigned int>();
89
90 for (int filament_id = 0; filament_id < static_cast<int>(layer_map.size()); ++filament_id) {
91 if (layer_uses_filament(used, filament_id)) {
92 last_used_nozzle[filament_id] = layer_map[filament_id];
93 if (first_used_nozzle.count(filament_id) == 0) {
94 first_used_nozzle[filament_id] = layer_map[filament_id];
95 first_used_layer[filament_id] = layer_id;
96 }
97 } else if (last_used_nozzle[filament_id] >= 0) {
98 layer_map[filament_id] = last_used_nozzle[filament_id];
99 }
100 }
101 }
102
103 // Back-fill pass: layers before a filament's first use inherit the first nozzle it ever uses.
104 for (int layer_id = 0; layer_id < total_layers; ++layer_id) {
105 auto &layer_map = layer_filament_nozzle_maps[layer_id];
106 for (int filament_id = 0; filament_id < static_cast<int>(layer_map.size()); ++filament_id) {
107 if (first_used_layer.count(filament_id) != 0 && layer_id < first_used_layer[filament_id])
108 layer_map[filament_id] = first_used_nozzle[filament_id];
109 }
110 }
111}

Referenced by Slic3r::ToolOrdering::build_sequential_group_result().

Here is the caller graph for this function:

◆ simulate_filament_change_time()

FilamentChangeSimResult Slic3r::MultiNozzleUtils::simulate_filament_change_time ( const std::vector< int > & logical_filaments,
const std::vector< NozzleInfo > & nozzle_list,
const std::vector< int > & filament_change_seq,
const std::vector< int > & nozzle_change_seq,
const std::vector< int > & group_of_filament,
const FilamentChangeTimeParams & time_params,
const std::vector< bool > & ams_preload_enabled,
bool calc_sliced_time )
476{
478 if (logical_filaments.empty() || nozzle_list.empty() || filament_change_seq.empty() || nozzle_change_seq.empty())
479 return result;
480
481 // Re-map the parameter semantics:
482 // standard = AMS -> selector -> extruder (full path), selector = selector -> extruder (short path)
483 // so AMS -> selector = standard - selector
484 const float load_ams_to_selector = time_params.standard_load_time - time_params.selector_load_time;
485 const float unload_ams_to_selector = time_params.standard_unload_time - time_params.selector_unload_time;
486 const float load_selector_to_ext = time_params.selector_load_time;
487 const float unload_ext_to_selector = time_params.selector_unload_time;
488
489 // nozzle_id -> extruder_id
490 std::unordered_map<int, int> nozzle_to_extruder;
491 nozzle_to_extruder.reserve(nozzle_list.size());
492 for (const auto& nozzle : nozzle_list)
493 nozzle_to_extruder[nozzle.group_id] = nozzle.extruder_id;
494
495 // filament_id -> AMS group
496 std::unordered_map<int, int> filament_to_group;
497 filament_to_group.reserve(logical_filaments.size());
498 for (size_t i = 0; i < logical_filaments.size(); ++i)
499 filament_to_group[logical_filaments[i]] = group_of_filament[i];
500
501 const auto get_group = [&](int filament_id) -> int {
502 auto it = filament_to_group.find(filament_id);
503 return it != filament_to_group.end() ? it->second : -1;
504 };
505
506 const auto is_preload_enabled = [&](int group_id) -> bool {
507 if (group_id < 0 || group_id >= static_cast<int>(ams_preload_enabled.size()))
508 return false;
509 return ams_preload_enabled[group_id];
510 };
511
512 // Filament location states
513 enum class Location { IN_AMS, IN_SELECTOR, IN_EXTRUDER };
514 std::unordered_map<int, Location> filament_location; // filament_id -> current location
515 std::unordered_map<int, int> filament_extruder; // filament_id -> extruder it sits in (only valid when IN_EXTRUDER)
516 std::unordered_map<int, int> extruder_filament; // extruder_id -> currently loaded filament
517 // group_id -> filaments currently occupying that AMS channel (IN_SELECTOR or IN_EXTRUDER)
518 std::unordered_map<int, std::unordered_set<int>> ams_group_occupied;
519
520 filament_location.reserve(logical_filaments.size());
521 filament_extruder.reserve(logical_filaments.size());
522
523 // Initial state: every filament is in the AMS, every extruder is empty
524 for (int f : logical_filaments)
525 filament_location[f] = Location::IN_AMS;
526
527 // Slicer-estimate simulator: use NozzleStatusRecorder to track what each nozzle/extruder holds during slicing
528 NozzleStatusRecorder sliced_recorder;
529
530 const size_t seq_len = std::min(filament_change_seq.size(), nozzle_change_seq.size());
531 double actual_time = 0.0;
532 double sliced_time = 0.0;
533
534 for (size_t i = 0; i < seq_len; ++i) {
535 int B = filament_change_seq[i];
536 int nozzle_id = nozzle_change_seq[i];
537
538 auto nozzle_iter = nozzle_to_extruder.find(nozzle_id);
539 if (nozzle_iter == nozzle_to_extruder.end()) continue;
540
541 int E = nozzle_iter->second; // target extruder
542
543 // Step 0: compute the slicer-estimated time
544 // Slicer estimate: simulate the slicer's view (no selector awareness);
545 // count a load/unload when nozzle_in_extruder_change || filament_in_nozzle_change
546 if (calc_sliced_time) {
547 int old_nozzle_in_E = sliced_recorder.get_nozzle_in_extruder(E);
548 int old_filament_in_nozzle = sliced_recorder.get_filament_in_nozzle(nozzle_id);
549 int old_filament_in_ext = sliced_recorder.get_filament_in_nozzle(old_nozzle_in_E);
550
551 bool nozzle_change = (old_nozzle_in_E != nozzle_id);
552 bool filament_change = (old_filament_in_nozzle != B);
553
554 if (nozzle_change || filament_change) {
555 if (old_filament_in_ext != -1)
556 sliced_time += time_params.standard_unload_time;
557 sliced_time += time_params.standard_load_time;
558 }
559 sliced_recorder.set_nozzle_status(nozzle_id, B, E);
560 }
561
562 // Step 1: find the filament A currently loaded in the target extruder E
563 int A = -1;
564 {
565 auto it = extruder_filament.find(E);
566 if (it != extruder_filament.end())
567 A = it->second;
568 }
569
570 int group_B = get_group(B);
571 int group_A = (A != -1) ? get_group(A) : -1;
572
573 // Step 2: clear B's AMS-channel occupancy
574 auto group_it = ams_group_occupied.find(group_B);
575 if (group_it != ams_group_occupied.end()) {
576 for (int X : group_it->second) {
577 if (X == B) continue;
578 // X shares B's AMS channel, retreat it to the AMS to make way
579 Location loc_X = filament_location[X];
580 if (loc_X == Location::IN_EXTRUDER) {
581 actual_time += unload_ext_to_selector + unload_ams_to_selector;
582 int E2 = filament_extruder[X];
583 extruder_filament.erase(E2);
584 filament_extruder.erase(X);
585 } else if (loc_X == Location::IN_SELECTOR) {
586 actual_time += unload_ams_to_selector;
587 }
588 filament_location[X] = Location::IN_AMS;
589 }
590 group_it->second.clear();
591 }
592
593 // Step 3: A exits E (while A is still in the extruder)
594 // Step 3.5: pre-load B (in parallel with Step 3)
595 // actual time = max(Step 3, Step 3.5)
596 bool step3_executed = false;
597 float step3_time = 0.0f;
598 if (A != -1 && A != B && filament_location[A] == Location::IN_EXTRUDER) {
599 if (is_preload_enabled(group_A) && group_A != group_B) {
600 step3_time = unload_ext_to_selector;
601 filament_location[A] = Location::IN_SELECTOR;
602 } else {
603 step3_time = unload_ext_to_selector + unload_ams_to_selector;
604 filament_location[A] = Location::IN_AMS;
605 ams_group_occupied[group_A].erase(A);
606 }
607 extruder_filament.erase(E);
608 filament_extruder.erase(A);
609 step3_executed = true;
610 }
611
612 float step3_5_time = 0.0f;
613 if (step3_executed &&
614 filament_location[B] == Location::IN_AMS &&
615 group_A != group_B &&
616 is_preload_enabled(group_B)) {
617 step3_5_time = load_ams_to_selector;
618 filament_location[B] = Location::IN_SELECTOR;
619 ams_group_occupied[group_B].insert(B);
620 }
621
622 actual_time += std::max(step3_time, step3_5_time);
623
624 // Step 4: push B into E
625 // Step 6: pre-load the next filament C (in parallel with Step 4)
626 // actual time = max(Step 4, Step 6)
627 float step4_time = 0.0f;
628 Location loc_B = filament_location[B];
629 if (loc_B == Location::IN_AMS) {
630 step4_time = load_ams_to_selector + load_selector_to_ext;
631 } else if (loc_B == Location::IN_SELECTOR) {
632 step4_time = load_selector_to_ext;
633 }
634
635 // Step 5: update state
636 extruder_filament[E] = B;
637 filament_location[B] = Location::IN_EXTRUDER;
638 filament_extruder[B] = E;
639 ams_group_occupied[group_B].insert(B);
640
641 float step6_time = 0.0f;
642 if (i + 1 < seq_len) {
643 int C = filament_change_seq[i + 1];
644 int group_C = get_group(C);
645 if (filament_location[C] == Location::IN_AMS &&
646 group_C != group_B &&
647 is_preload_enabled(group_C) &&
648 ams_group_occupied[group_C].empty()) {
649 step6_time = load_ams_to_selector;
650 filament_location[C] = Location::IN_SELECTOR;
651 ams_group_occupied[group_C].insert(C);
652 }
653 }
654
655 actual_time += std::max(step4_time, step6_time);
656 }
657
658 result.actual_time = actual_time;
659 result.sliced_time = sliced_time;
660 return result;
661}
Definition MultiNozzleUtils.hpp:228
float standard_load_time
Definition MultiNozzleUtils.hpp:90
float selector_load_time
Definition MultiNozzleUtils.hpp:88
float selector_unload_time
Definition MultiNozzleUtils.hpp:89
float standard_unload_time
Definition MultiNozzleUtils.hpp:91
double actual_time
Definition MultiNozzleUtils.hpp:254
double sliced_time
Definition MultiNozzleUtils.hpp:255
Definition MultiNozzleUtils.hpp:253
static double f(double x, double z_sin, double z_cos, bool vertical, bool flip)
Definition FillGyroid.cpp:124
@ E
Definition libslic3r.h:144
@ X
Definition libslic3r.h:141
bool empty(const BoundingBoxBase< PointType, PointsType > &bb)
Definition BoundingBox.hpp:273

References Slic3r::MultiNozzleUtils::FilamentChangeSimResult::actual_time, Slic3r::E, Slic3r::empty(), Slic3r::f(), Slic3r::MultiNozzleUtils::NozzleStatusRecorder::get_filament_in_nozzle(), Slic3r::MultiNozzleUtils::NozzleStatusRecorder::get_nozzle_in_extruder(), Slic3r::group_id(), Slic3r::MultiNozzleUtils::FilamentChangeTimeParams::selector_load_time, Slic3r::MultiNozzleUtils::FilamentChangeTimeParams::selector_unload_time, Slic3r::MultiNozzleUtils::NozzleStatusRecorder::set_nozzle_status(), Slic3r::MultiNozzleUtils::FilamentChangeSimResult::sliced_time, Slic3r::MultiNozzleUtils::FilamentChangeTimeParams::standard_load_time, Slic3r::MultiNozzleUtils::FilamentChangeTimeParams::standard_unload_time, and Slic3r::X.

Referenced by Slic3r::calc_change_time_for_group(), and Slic3r::GUI::SelectMachineDialog::get_filament_change_gap_time().

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