diff --git a/src/libslic3r/GCode/WipeTower2.cpp b/src/libslic3r/GCode/WipeTower2.cpp index 34e4b6146f..ee0f9c375a 100644 --- a/src/libslic3r/GCode/WipeTower2.cpp +++ b/src/libslic3r/GCode/WipeTower2.cpp @@ -2130,31 +2130,68 @@ std::pair WipeTower2::get_wipe_tower_cone_base(double width, dou } // Static method to extract wipe_volumes[from][to] from the configuration. -std::vector> WipeTower2::extract_wipe_volumes(const PrintConfig& config) +// Takes a ConfigBase so the GUI's wipe tower size estimate can pass the plate's +// DynamicPrintConfig directly instead of materializing a full PrintConfig per call. +std::vector> WipeTower2::extract_wipe_volumes(const ConfigBase& config) { - // Get wiping matrix to get number of extruders and convert vector to vector: - std::vector wiping_matrix(cast(config.flush_volumes_matrix.values)); - auto scale = config.flush_multiplier.get_at(0); + // flush_volumes_matrix holds one filaments x filaments block per nozzle (written by + // PresetBundle::update_multi_material_filament_presets), so the filament count is + // sqrt(size / nozzles). One tower serves every nozzle and the filament to nozzle assignment is + // only decided later by ToolOrdering, so fold the blocks with std::max: the depth reserved here + // has to cover the worst nozzle. With a single nozzle the fold has one term. + const std::vector &raw_matrix = config.option("flush_volumes_matrix")->values; + const auto *nozzle_diameter = config.option("nozzle_diameter"); + size_t nozzle_nums = (nozzle_diameter == nullptr || nozzle_diameter->values.empty()) ? 1 : nozzle_diameter->values.size(); + unsigned int number_of_extruders = (unsigned int)(sqrt(raw_matrix.size() / nozzle_nums) + EPSILON); + if (size_t(number_of_extruders) * number_of_extruders * nozzle_nums != raw_matrix.size()) { + // Saved for a different nozzle count (older project, or the printer was just switched): + // fall back to reading the whole option as one block, as this did before. + nozzle_nums = 1; + number_of_extruders = (unsigned int)(sqrt(raw_matrix.size()) + EPSILON); + } // The values shall only be used when SEMM is enabled. The purging for other printers // is determined by filament_minimal_purge_on_wipe_tower. - if (! config.purge_in_prime_tower.value || ! config.single_extruder_multi_material.value) - std::fill(wiping_matrix.begin(), wiping_matrix.end(), 0.f); + const bool purge = config.option("purge_in_prime_tower")->value + && config.option("single_extruder_multi_material")->value; - // Extract purging volumes for each extruder pair: - std::vector> wipe_volumes; - const unsigned int number_of_extruders = (unsigned int)(sqrt(wiping_matrix.size())+EPSILON); - for (size_t i = 0; i(wiping_matrix.begin()+i*number_of_extruders, wiping_matrix.begin()+(i+1)*number_of_extruders)); + // Extract purging volumes for each extruder pair, each nozzle's block scaled by its own multiplier: + std::vector> wipe_volumes(number_of_extruders, std::vector(number_of_extruders, 0.f)); + if (purge) { + const auto *multiplier = config.option("flush_multiplier"); + for (size_t nozzle_id = 0; nozzle_id < nozzle_nums; ++nozzle_id) { + const std::vector block = get_flush_volumes_matrix(raw_matrix, nozzle_id, nozzle_nums); + const double scale = multiplier->get_at(nozzle_id); + for (unsigned int i = 0; i(wipe_volumes[i][j], float(block[size_t(i) * number_of_extruders + j]) * scale); + } + } // Also include filament_minimal_purge_on_wipe_tower. This is needed for the preview. + const auto *minimal_purge = config.option("filament_minimal_purge_on_wipe_tower"); for (unsigned int i = 0; i(wipe_volumes[i][j] * scale, config.filament_minimal_purge_on_wipe_tower.get_at(j)); + wipe_volumes[i][j] = std::max(wipe_volumes[i][j], minimal_purge->get_at(j)); return wipe_volumes; } +float WipeTower2::estimate_semm_flush_volume(const ConfigBase& config, size_t filaments_cnt) +{ + const std::vector> wipe_volumes = extract_wipe_volumes(config); + if (wipe_volumes.empty()) // an empty flush matrix would make the average below 0/0 + return 0.f; + float maximum = 0.f; + for (const std::vector &v : wipe_volumes) + maximum += *std::max_element(v.begin(), v.end()); + maximum = maximum * filaments_cnt / wipe_volumes.size(); + + // Orca: it's overshooting a bit, so let's reduce it a bit + maximum *= 0.6; + return maximum; +} + static float get_wipe_depth(float volume, float layer_height, float perimeter_width, float extra_flow, float extra_spacing, float width) { float length_to_extrude = (volume_to_length(volume, perimeter_width, layer_height)) / extra_flow; diff --git a/src/libslic3r/GCode/WipeTower2.hpp b/src/libslic3r/GCode/WipeTower2.hpp index ca9e73bb28..5b1a474b5d 100644 --- a/src/libslic3r/GCode/WipeTower2.hpp +++ b/src/libslic3r/GCode/WipeTower2.hpp @@ -17,6 +17,7 @@ namespace Slic3r class WipeTowerWriter2; class PrintRegionConfig; +class ConfigBase; class WipeTower2 { @@ -26,7 +27,10 @@ public: // in WipeTowerIntegration::append_tcr2 does not strip it. static const std::string wait_for_temp_tag() { return ";_WAIT_FOR_TEMP_ON_WIPE_TOWER"; } static std::pair get_wipe_tower_cone_base(double width, double height, double depth, double angle_deg); - static std::vector> extract_wipe_volumes(const PrintConfig& config); + static std::vector> extract_wipe_volumes(const ConfigBase& config); + // Estimated total flush volume of a SEMM print with the given number of filaments, + // used to reserve wipe tower space before the tower is generated. + static float estimate_semm_flush_volume(const ConfigBase& config, size_t filaments_cnt); // Construct ToolChangeResult from current state of WipeTower2 and WipeTowerWriter2. diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 6a34cc31ff..1af28255ee 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -3944,6 +3944,12 @@ const WipeTowerData &Print::wipe_tower_data(size_t filaments_cnt) const double volume = wipe_volume * filament_depth_count; if (m_config.nozzle_diameter.values.size() == 2) volume += filament_change_volume * (int) (filaments_cnt / 2); + // Sizing should take into account currently set wiping volumes. + // For a long time, the initial preview would just use 900/width per toolchange (15mm on a 60mm wide tower) + // and it worked well enough. Let's try to do slightly better by accounting for the purging volumes. + const bool semm_flush = m_config.purge_in_prime_tower && m_config.single_extruder_multi_material; + if (semm_flush) volume = WipeTower2::estimate_semm_flush_volume(m_config, filaments_cnt); + if (m_config.wipe_tower_wall_type.value == WipeTowerWallType::wtwRib) { double depth = std::sqrt(volume / layer_height * extra_spacing); if (need_wipe_tower || filaments_cnt > 1) { @@ -3955,30 +3961,16 @@ const WipeTowerData &Print::wipe_tower_data(size_t filaments_cnt) const } } else { - double width = m_config.prime_tower_width; - if (m_config.purge_in_prime_tower && m_config.single_extruder_multi_material) { - // Calculating depth should take into account currently set wiping volumes. - // For a long time, the initial preview would just use 900/width per toolchange (15mm on a 60mm wide tower) - // and it worked well enough. Let's try to do slightly better by accounting for the purging volumes. - std::vector> wipe_volumes = WipeTower2::extract_wipe_volumes(m_config); - std::vector max_wipe_volumes; - for (const std::vector &v : wipe_volumes) - max_wipe_volumes.emplace_back(*std::max_element(v.begin(), v.end())); - float maximum = std::accumulate(max_wipe_volumes.begin(), max_wipe_volumes.end(), 0.f); - maximum = maximum * filaments_cnt / max_wipe_volumes.size(); - - // Orca: it's overshooting a bit, so let's reduce it a bit - maximum *= 0.6; - const_cast(this)->m_wipe_tower_data.depth = maximum / (layer_height * width); - } else { - double depth = volume / (layer_height * width) * extra_spacing; - if (need_wipe_tower || m_wipe_tower_data.depth > EPSILON) { + double width = m_config.prime_tower_width; + double depth = volume / (layer_height * width); + // The flush volumes already hold the spacing between wipes. + if (!semm_flush) depth *= extra_spacing; + if (need_wipe_tower || depth > EPSILON) { float min_wipe_tower_depth = WipeTower::get_limit_depth_by_height(max_height); depth = std::max((double) min_wipe_tower_depth, depth); } const_cast(this)->m_wipe_tower_data.depth = depth; - } - const_cast(this)->m_wipe_tower_data.brim_width = m_config.prime_tower_brim_width; + const_cast(this)->m_wipe_tower_data.brim_width = m_config.prime_tower_brim_width; } if (m_config.prime_tower_brim_width < 0) const_cast(this)->m_wipe_tower_data.brim_width = WipeTower::get_auto_brim_by_height(max_height); } diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 45c0d87791..27fe44a867 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2871,6 +2871,9 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re } if (wt && (need_wipe_tower || filaments_count > 1) && !wxGetApp().plater()->only_gcode_mode() && !wxGetApp().plater()->is_gcode_3mf()) { + // The tower size estimate reads printer- and filament-scope keys, which the print preset + // does not carry; built once here rather than per plate. + const DynamicPrintConfig full_config = wxGetApp().preset_bundle->full_config(); for (int plate_id = 0; plate_id < n_plates; plate_id++) { // If print ByObject and there is only one object in the plate, the wipe tower is allowed to be generated. PartPlate* part_plate = ppl.get_plate(plate_id); @@ -2895,9 +2898,8 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re if (part_plate->get_objects_on_this_plate().empty()) continue; float brim_width = print->wipe_tower_data(filaments_count).brim_width; - const DynamicPrintConfig &print_cfg = wxGetApp().preset_bundle->prints.get_edited_preset().config; int nozzle_nums = wxGetApp().preset_bundle->get_printer_extruder_count(); - Vec3d wipe_tower_size = ppl.get_plate(plate_id)->estimate_wipe_tower_size(print_cfg, w, v, nozzle_nums, 0, false, dynamic_cast(dconfig.option("enable_wrapping_detection"))->value); + Vec3d wipe_tower_size = ppl.get_plate(plate_id)->estimate_wipe_tower_size(full_config, w, v, nozzle_nums, 0, false, dynamic_cast(dconfig.option("enable_wrapping_detection"))->value); // The stored position is already clamped onto the bed, by // set_default_wipe_tower_pos_for_plate and again on every drag. diff --git a/src/slic3r/GUI/PartPlate.cpp b/src/slic3r/GUI/PartPlate.cpp index 3ab764aaf3..910c761c06 100644 --- a/src/slic3r/GUI/PartPlate.cpp +++ b/src/slic3r/GUI/PartPlate.cpp @@ -2262,6 +2262,12 @@ Vec3d PartPlate::estimate_wipe_tower_size(const DynamicPrintConfig & config, con } double volume = wipe_volume * (extruder_count == 2 ? plate_extruder_size : (plate_extruder_size - 1)); if (extruder_count == 2) volume += filament_change_volume * (int) (plate_extruder_size / 2); + // Read from the passed plate config — m_print may not have been applied yet + // (fresh plates, CLI), in which case its PrintConfig still holds defaults. + const auto *purge_opt = config.option("purge_in_prime_tower"); + const auto *semm_opt = config.option("single_extruder_multi_material"); + const bool semm_flush = purge_opt && purge_opt->value && semm_opt && semm_opt->value; + if (semm_flush) volume = WipeTower2::estimate_semm_flush_volume(config, plate_extruder_size); if (use_rib_wall) { depth = std::sqrt(volume / layer_height * extra_spacing); if (need_wipe_tower || plate_extruder_size > 1) { @@ -2274,7 +2280,9 @@ Vec3d PartPlate::estimate_wipe_tower_size(const DynamicPrintConfig & config, con } } else { - depth = volume/ (layer_height * w) *extra_spacing; + depth = volume / (layer_height * w); + // The flush volumes already hold the spacing between wipes. + if (!semm_flush) depth *= extra_spacing; if (need_wipe_tower || depth > EPSILON) { float min_wipe_tower_depth = WipeTower::get_limit_depth_by_height(max_height); depth = std::max((double)min_wipe_tower_depth, depth); @@ -4380,22 +4388,21 @@ void PartPlateList::set_default_wipe_tower_pos_for_plate(int plate_idx, bool ini f_volume_maps = wxGetApp().preset_bundle->get_default_nozzle_volume_types_for_filaments(filament_maps); } DynamicPrintConfig full_config = wxGetApp().preset_bundle->full_config(false, filament_maps, f_volume_maps); - const DynamicPrintConfig &print_cfg = wxGetApp().preset_bundle->prints.get_edited_preset().config; - float w = dynamic_cast(print_cfg.option("prime_tower_width"))->value; + float w = dynamic_cast(full_config.option("prime_tower_width"))->value; float v = dynamic_cast(full_config.option("prime_volume"))->value; bool enable_wrapping = false; const ConfigOptionBool *wrapping_opt = dynamic_cast(full_config.option("enable_wrapping_detection")); if (wrapping_opt) enable_wrapping = wrapping_opt->value; int nozzle_nums = wxGetApp().preset_bundle->get_printer_extruder_count(); - Vec3d wipe_tower_size = part_plate->estimate_wipe_tower_size(print_cfg, w, v, nozzle_nums, init_pos ? 2 : 0, false, enable_wrapping); + Vec3d wipe_tower_size = part_plate->estimate_wipe_tower_size(full_config, w, v, nozzle_nums, init_pos ? 2 : 0, false, enable_wrapping); if (!init_pos && (is_approx(wipe_tower_size(0), 0.0) || is_approx(wipe_tower_size(1), 0.0))) { - wipe_tower_size = part_plate->estimate_wipe_tower_size(print_cfg, w, v, nozzle_nums, 2, false, enable_wrapping); + wipe_tower_size = part_plate->estimate_wipe_tower_size(full_config, w, v, nozzle_nums, 2, false, enable_wrapping); } // Compute brim-aware margin: brim extends outward from tower position float brim_width = 0.f; - const ConfigOptionFloat *brim_opt = print_cfg.option("prime_tower_brim_width"); + const ConfigOptionFloat *brim_opt = full_config.option("prime_tower_brim_width"); if (brim_opt) { brim_width = brim_opt->value; if (brim_width < 0) brim_width = WipeTower::get_auto_brim_by_height((float) wipe_tower_size.z());